計算機(jī)視覺模型效果不佳,你可能是被相機(jī)的Exif信息坑了
點(diǎn)擊上方“小白學(xué)視覺”,選擇加"星標(biāo)"或“置頂”
重磅干貨,第一時間送達(dá)
為何別人用得好好的人臉識別、目標(biāo)檢測開源模型,到了初學(xué)者手中,效果卻慘不忍睹?其中原因可能很多,有時候這個原因很“愚蠢”。
最近一位Medium上的博主Adam Geitgey給初學(xué)者指出了一個極其簡單而又容易忽視的關(guān)鍵點(diǎn):相機(jī)的Exif信息。

在開發(fā)和使用計算機(jī)視覺(CV)模型的過程中,由于NumPy、TensorFlow和電腦上的圖片查看器在處理Exif上存在著差異,讓這個問題變得十分隱秘。
Adam是一位知名的機(jī)器學(xué)習(xí)課程博主,他的博客內(nèi)容非常實(shí)用,幾乎篇篇都能收獲上千贊,足見其受歡迎的程度。

他在最新的文章中指出了CV模型在處理Exif存在的缺失,以及補(bǔ)救方法,下面是他文章的主要內(nèi)容。
普通智能手機(jī)或者相機(jī)拍照時,如果手持方向發(fā)生變化,內(nèi)部的重力感應(yīng)器件會告訴設(shè)備,照片究竟哪個邊是向上的。
當(dāng)我們在手機(jī)、相機(jī)或者電腦的Photoshop軟件上查看照片時,完全沒有問題,就像這樣:

然而眼見并非為實(shí),實(shí)際圖像的像素數(shù)據(jù)不會旋轉(zhuǎn)。這是由于圖像傳感器是對連續(xù)的像素信息流進(jìn)行逐行讀取,因此你無論縱向和橫向握持相機(jī),圖像都是按照一個方向進(jìn)行存儲。

那么拍照設(shè)備和電腦為什么就能按照正確的方向顯示圖片呢?這是因?yàn)檎掌镞€保存著一組元數(shù)據(jù),稱之為Exif,即可交換圖像文件格式(Exchangeable image file format)。
Exif中包含著照片的像素數(shù)、焦距、光圈等信息,其中還有一個方向(Orientation)的數(shù)據(jù)。

上圖中Orientation一項(xiàng)的參數(shù)是Rotate 90 CW,意思是圖像在顯示前需要順時針旋轉(zhuǎn)90度。如果圖片查看程序沒有執(zhí)行此操作,你就只能擰著脖子看了。

Exif原先是用在TIFF圖像格式上,后來才加入到JPEG圖像格式中,而圖像數(shù)據(jù)集中的圖片大多是JPEG格式。
一些程序?yàn)榱吮3窒蚝蠹嫒菪裕粫ソ馕鯡xif數(shù)據(jù)。大多數(shù)用于處理圖像數(shù)據(jù)的Python庫(如NumPy、SciPy,TensorFlow,Keras等)就是這樣的。
這意味著當(dāng)你使用這些工具導(dǎo)入圖像時,都將獲得原始的未旋轉(zhuǎn)圖像數(shù)據(jù)。如果把這些側(cè)躺著或上下顛倒的圖像輸入到CV模型中,會得到錯誤的檢測結(jié)果。
這個問題看起來很愚蠢,似乎初學(xué)者會犯這樣的低級錯誤。但事實(shí)并非如此!甚至連Google云上的視覺API Demo也無法正確處理Exif方向問題:

如果我們把圖像旋轉(zhuǎn)到正確的方向再上傳,檢測的結(jié)果與上圖相比將完全改變:

當(dāng)我們在電腦上查看圖片時完全沒問題,但是一用到模型中就不正常。因此很難發(fā)現(xiàn)問題的所在。

這也導(dǎo)致一些開發(fā)者在Github上提問,抱怨他們正在使用的開源項(xiàng)目已損壞,或是模型不夠準(zhǔn)確。但是實(shí)際上問題要簡單得多,只是圖片的方向錯了!
解決以上問題的方法就是,在導(dǎo)入圖像時檢查它們的Exif數(shù)據(jù),在必要時旋轉(zhuǎn)圖像。Adam已經(jīng)寫好了一段代碼:
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)加入以上代碼后,就可以正確地將圖像導(dǎo)入Keras或TensorFlow了。
如果覺得麻煩,Adam還把上面的代碼打包好了,在GitHub上這個項(xiàng)目叫做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()
交流群
歡迎加入公眾號讀者群一起和同行交流,目前有SLAM、三維視覺、傳感器、自動駕駛、計算攝影、檢測、分割、識別、醫(yī)學(xué)影像、GAN、算法競賽等微信群(以后會逐漸細(xì)分),請掃描下面微信號加群,備注:”昵稱+學(xué)校/公司+研究方向“,例如:”張三 + 上海交大 + 視覺SLAM“。請按照格式備注,否則不予通過。添加成功后會根據(jù)研究方向邀請進(jìn)入相關(guān)微信群。請勿在群內(nèi)發(fā)送廣告,否則會請出群,謝謝理解~

