狡猾的机器视觉应用程序无法正常工作的愚蠢原因:EXIF中的方向

我写了很多关于计算机视觉和机器学习项目的文章,例如对象识别系统人脸识别项目 。 我还有一个开源的Python 人脸识别库 ,该以某种方式使其成为Github上最流行十大机器学习库 。 所有这些都导致Python和机器视觉的新手问我很多问题。



从经验来看,有一个特定的技术问题通常会使人们感到困惑。 不,这不是一个困难的理论问题,也不是昂贵的GPU的问题。 事实是,几乎每个人都不知道将图像加载到旋转的内存中。 而且计算机无法很好地检测物体或识别旋转图像中的人脸。

数码相机如何自动旋转图像


拍摄照片时,相机会记录手机的位置,以便在另一个程序中以正确的方向显示照片:



但是相机实际上并不旋转文件内的像素数据。 由于数码相机中的图像传感器是作为连续的像素信息流逐行读取的,因此相机更容易始终以相同顺序存储像素数据,而不管手机的实际位置如何。



这是用于查看程序的问题-在屏幕上显示图片之前正确旋转图片。 除了图像本身的数据外,相机还保存元数据-镜头设置,位置数据,当然还有相机的旋转角度。 观众应使用此信息正确显示。

最常见的图像元数据格式称为EXIF (可交换图像文件格式的缩写)。 EXIF元数据嵌入在每个jpeg文件中。 您无法在屏幕上看到它们,但是任何知道在哪里查看的程序都可以读取它们。

这是来自exiftool工具的鹅的JPEG图像中的EXIF元数据:



看到“方向”元素了吗? 他告诉观看者,在屏幕上显示之前,图片应顺时针旋转90度。 如果程序忘记执行此操作,则图像将在侧面!



为什么这会破坏Python中如此众多的机器视觉应用程序?


EXIF元数据最初不是JPEG格式的一部分。 后来,他们借鉴了TIFF格式的想法将它们引入。 为了向后兼容,此元数据是可选的,并且某些程序不费力地解析它。

大多数Python图像库(例如numpy,scipy,TensorFlow,Keras等)都将自己视为科学的工具,供认真使用共享数据集的人使用。 他们并不关心消费者级别的问题,例如自动图像旋转,尽管这对于现代相机拍摄的世界上几乎所有照片都是必需的。

这意味着在使用几乎所有Python库处理图像时,您都无需旋转即可获取原始图像数据。 当您尝试以侧面或上下颠倒的方式在面部或物体检测模型中上传照片时,您会猜出会发生什么? 检测器不会触发,因为您提供了错误的数据。

您可能会认为问题仅出现在初学者和学生的程序中,但事实并非如此! 甚至Google旗舰版Vision API演示版也无法正确处理EXIF方向:


演示Google Vision API不知道如何旋转从标准手机拍摄的纵向图像

尽管Google Vision可以识别出侧面的某些动物,但会用通用标签“动物”标记它们,因为与垂直鹅相比,机器视觉模型更难识别其侧面的鹅。 如果在将图像提交给模型之前正确旋转图像,则结果如下:



通过正确的方向,Google可以检测出具有更具体鹅标记和更高置信度指标的鸟类。 好多了!

当您清楚地看到图像在侧面时 ,这是一个非常明显的问题,如本演示中所示。 但这就是一切变得阴险的地方-通常您看不到它! 您计算机上的所有普通程序将以正确的方向显示图像,而不是实际在磁盘上的存储方式。 因此,当您尝试查看图像以查看模型为何不起作用时,它将正确显示,并且您将无法理解模型为何不起作用!


Mac上的Finder始终显示从EXIF正确旋转的照片。 无法看到图像实际上存储在其侧面

这不可避免地导致在Github上产生大量公开票:人们抱怨开源项目已损坏,模型也不十分准确。 但是问题要简单得多-他们只是将旋转或倒置的照片馈送到入口!

改正


解决方案是,每次将图像加载到Python程序中时,都应检查EXIF方向元数据并在必要时旋转图像。 这很容易做到,但是在Internet上很难找到适合所有方向的代码示例。

这是将任何图像以正确方向加载到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。

由于该问题无处不在,因此我将此功能发布为一个名为image_to_numpy的pip库。 您可以按以下方式安装它:

  pip3安装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() 

有关更多详细信息,请参见自述文件

好好享受

Source: https://habr.com/ru/post/zh-CN471112/


All Articles