為什麼我的 CV 模型不好用?沒想到原因竟如此簡單……
選自Medium
作者:Adam Geitgey
機器之心編譯
參與:Panda
計算機視覺模型表現不佳的原因有很多,比如架構設計缺陷、資料集代表性不足、超引數選擇失誤等。但有一個很簡單的原因卻常常被人們忽略:影像的方向。機器學習專家 Adam Geitgey 近日釋出了一篇文章探討了這一簡單卻又讓很多人頭痛的問題,並分享了他為解決這一問題編寫的自動影像旋轉程式。
我寫過很多有關計算機視覺和機器學習專案的內容,比如目標識別系統和人臉識別專案。我有一個開源的 Python 人臉識別軟體庫,算得上是 GitHub 上最受歡迎的十大機器學習庫之一。這也意味著我常常收到關於 Python 和計算機視覺方面的新人提問。
以我的經驗,有一個技術問題比其它任何問題都更容易讓人受挫——倒不是複雜的理論問題或昂貴 GPU 的問題。人們基本上沒意識到,幾乎所有人都是以側向方式將影像載入記憶體的,而計算機在檢測側向影像中的目標或人臉時的能力可沒那麼出色。
數位相機如何自動旋轉影像
當你在拍攝照片時,相機會感知你向哪邊傾斜。當你在另一個程式中檢視照片時,它們會以正確的方向顯示。
但棘手的問題在於, 你的相機實際上並沒有在儲存到磁碟中的檔案中旋轉影像資料。因為數位相機中的影像感測器是逐行讀取的,最終彙整合連續的畫素資訊流。這能讓相機更輕鬆地儲存畫素資料,因為不管相機的姿勢如何,畫素資料總是以同樣的順序儲存的。
實際上,照片能否以正確的方向顯示完全取決於影像檢視器應用。相機在儲存影像資料的同時還會儲存有關每張圖片的後設資料——相機設定、位置資料以及理所應當的相機的旋轉角度。影像檢視器應當使用這種資訊來正確地顯示影像。
影像後設資料最常見的格式是 Exif(Exchangeable image file forma「可交換影像檔案格式」的縮寫)。Exif 格式的後設資料放在相機儲存的 jpeg 檔案中。你不能直接從影像本身讀到這種 Exif 資料,但可以使用任何知道如何讀取這一資料的程式進行讀取。
下面是使用 Exiftool 讀取的上面的鵝照片的 Exif 後設資料:
注意 Orientation(方向)這個資料元素。它能指示影像檢視器程式,在螢幕上顯示影像之前將圖順時針旋轉 90 度。如果程式忘記這麼做,影像就會側向顯示。
為什麼這讓很多 Python 計算機視覺應用表現不佳?
Exif 後設資料並非 jpeg 檔案格式的原生部分。在 TIFF 檔案格式使用了這種後設資料之後,jpeg 檔案格式才加入這種後設資料。其保持了與老一代影像檢視器的後向相容性,但這也意味著某些程式根本沒有費心去解析 Exif 資料。
numpy、scipy、TensorFlow、Keras 等大多數用於處理影像資料的 Python 庫都將自己視為研究通用資料陣列的人的科學工具。所以它們不在乎消費者層面的問題,比如「影像自動旋轉」——即使現在的所有相機拍照需要這種操作。
這差不多意味著,你用任意 Python 庫載入影像時,都會得到未經旋轉的原始影像資料。現在猜猜看,當你將側向的或倒向的影像輸入人臉識別或目標檢測模型會怎樣?因為你提供了錯誤的資料,檢測器會提示失敗。
你可能認為這個問題僅限於新手或學生寫的 Python 指令碼,但事實並非如此。即使谷歌的旗艦級 Vision API 演示也沒能正確地處理 Exif 方向:
谷歌的 Vision API 演示無法旋轉標準的手機拍攝的縱向影像。
儘管谷歌的視覺技術能成功地檢測出側向影像中存在一些動物,但它僅提供了一個不具體的「Animal(動物)」標籤。這是因為模型檢測側向的鵝要比檢測正向的鵝要困難得多。如果在輸入之前先正確地旋轉一下,則谷歌 Vision API 會得到如下的結果:
當影像方向正確時,谷歌的檢測結果要具體得多——不僅能正確給出「Goose(鵝)」標籤,而且置信度分數要高得多,這就好多了。
如果你能如本演示中的那樣看到影像是側向的,那麼這個問題要明顯得多。但問題就在於你一般看不到。如今計算機上的一般程式都會以正確旋轉後的形式顯示影像,而不是按照它實際在磁碟上儲存的側向資料的形式。所以當你想了解你的模型不能起效的原因而檢視影像時,影像檢視器會以正確的方向顯示,讓你無從瞭解你的模型效果差的原因。
Mac 上的 Finder 總是顯示應用了 Exif 旋轉後的影像,這樣就沒法看到檔案中的影像資料實際上是側向的。
這不可避免地導致人們在 GitHub 上報告問題,說他們使用的開源專案根本不行或模型不夠準確。但事情的本質非常簡單——他們輸入了側向甚至顛倒的影像!
解決這個問題
解決方案是,每當你用 Python 程式載入影像時,都執行一次 Exif 方向後設資料檢查,並在有需要時進行旋轉。做起來很簡單,不過在網上很難找到能為所有方向正確執行旋轉的示例程式碼。
下面是為任意影像應用正確的方向後再將其載入 numpy 陣列的程式碼:
import PIL.Image
import PIL.ImageOps
import numpy as np
def exif_transpose(img):
if not img:
return img
exif_orientation_tag = 274
# Check for EXIF data (only present on some files)
if hasattr(img, "_getexif") and isinstance(img._getexif(), dict) and exif_orientation_tag in img._getexif():
exif_data = img._getexif()
orientation = exif_data[exif_orientation_tag]
# Handle EXIF Orientation
if orientation == 1:
# Normal image - nothing to do!
pass
elif orientation == 2:
# Mirrored left to right
img = img.transpose(PIL.Image.FLIP_LEFT_RIGHT)
elif orientation == 3:
# Rotated 180 degrees
img = img.rotate(180)
elif orientation == 4:
# Mirrored top to bottom
img = img.rotate(180).transpose(PIL.Image.FLIP_LEFT_RIGHT)
elif orientation == 5:
# Mirrored along top-left diagonal
img = img.rotate(-90, expand=True).transpose(PIL.Image.FLIP_LEFT_RIGHT)
elif orientation == 6:
# Rotated 90 degrees
img = img.rotate(-90, expand=True)
elif orientation == 7:
# Mirrored along top-right diagonal
img = img.rotate(90, expand=True).transpose(PIL.Image.FLIP_LEFT_RIGHT)
elif orientation == 8:
# Rotated 270 degrees
img = img.rotate(90, expand=True)
return img
def load_image_file(file, mode='RGB'):
# Load the image with PIL
img = PIL.Image.open(file)
if hasattr(PIL.ImageOps, 'exif_transpose'):
# Very recent versions of PIL can do exit transpose internally
img = PIL.ImageOps.exif_transpose(img)
else:
# Otherwise, do the exif transpose ourselves
img = exif_transpose(img)
img = img.convert(mode)
return np.array(img)
之後,你可以將這個影像資料陣列傳遞給需要的所有標準 Python 機器學習庫,比如 Keras 和 TensorFlow。
因為這個問題很常見,所以我將其製作成了一個 pip 庫,名為 image_to_numpy,你可以這樣安裝它:
pip3 install image_to_numpy
你可以在任何 Python 程式中使用它來實現正確的影像載入,比如:
import matplotlib.pyplot as plt
import image_to_numpy# Load your image file
img = image_to_numpy.load_image_file("my_file.jpg")# Show it on the screen (or whatever you want to do)
plt.imshow(img)
plt.show()
更多資訊可檢視自述檔案。
原文連結:https://medium.com/@ageitgey/the-dumb-reason-your-fancy-computer-vision-app-isnt-working-exif-orientation-73166c7d39da
https://www.toutiao.com/i6747513990475153924/
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69946223/viewspace-2660342/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- springboot + rabbitmq 做智慧家居,我也沒想到會這麼簡單Spring BootMQ
- 我也沒想到 Springboot + Flowable 開發工作流會這麼簡單Spring Boot
- 如何在Word中快速製作組織結構圖?沒想到可以如此簡單
- 為什麼React Native如此受歡迎的7個原因React Native
- Python為什麼發展這麼快速?原因很簡單!Python
- 為什麼我如此討厭scrums? - RedditScrum
- 悲劇的我啊,為什麼如此悲劇
- 沒想到,這麼簡單的執行緒池用法,深藏這麼多坑!執行緒
- Redis為什麼是單執行緒?為什麼有如此高的效能?Redis執行緒
- 一個簡單的字串,為什麼 Redis 要設計的如此特別字串Redis
- 為什麼擴散diffution模型如此強大? - Reddit模型
- 為什麼寫爬蟲用Python語言?原因很簡單!爬蟲Python
- 真是沒想到 Springboot + Flowable 工作流開發會這麼簡單Spring Boot
- 2020年我的幾個沒想到
- 良心貼!沒想到 Google 排名第一的程式語言,可以這麼簡單!Go
- 為什麼學完Web前端後薪資如此之高?原因如下Web前端
- 沒想到吧!Google 排名第一的程式語言,為什麼會這麼火?Go
- RTS簡史——他們為什麼如此熱愛RTS
- 為什麼我沒有收到贈送的流量
- 今天的IT如此複雜,其背後原因是什麼?
- Python 為什麼如此設計?Python
- Linux系統為什麼沒有病毒?原因出乎意料!Linux
- 為什麼Web前端薪資如此高呢?總結了這4個原因Web前端
- 萬萬沒想到,我的煉丹爐玩壞了
- IPV6改造?華為雲如此簡單
- 為什麼Kubernetes的儲存如此艱難?
- 為什麼魂系列的敘事如此迷人?
- 為什麼Web3如此重要?Web
- 為什麼 Dapr 如此令人興奮
- 沒有什麼,開發ASP.NET時隨便寫寫,想到什麼寫什麼ASP.NET
- Go 世界如此簡單!Go
- 小程式中遇到observer函式引起的記憶體洩漏咋整?沒想到這麼簡單Server函式記憶體
- 為什麼python比c更簡單Python
- 單例模式就是如此簡單單例模式
- SQL:我為什麼慢你心裡沒數嗎?SQL
- 面阿里P7,竟問這麼簡單的題目?阿里
- 為什麼我們說區塊鏈沒有那麼容易?區塊鏈
- 我整理了 50 多個簡歷致命問題,知道為什麼投簡歷沒回復了!