PIL踩坑记录:读取图片方向异常(yolov5标签错位)
奇怪的现象
今天用 YOLOv5 做项目时,对数据集的标记出现了奇怪的现象,在下述测试用例中可明显看到,标记框偏离了物体,故发文阐述原因和解决方法. ——©️ Sylvan Ding 转载需注明出处!
下载了一份数据集
UNIMIB2016
,使用 PIL 读取其中一张照片并标记 bbox.
from PIL import Imageimg_name = '20151127_120643.jpg'
img = Image.open(img_name)
img.show()
from PIL import ImageDraw
from PIL import ImageFontlabel_name = '20151127_120643.txt'
with open(label_name) as file:items = file.readlines()draw = ImageDraw.Draw(img)
for item in items:item = item.split()# convert str to intitem[1:] = list(map(int, item[1:]))# draw bboxdraw.rectangle([item[1], item[2], item[5], item[6]],outline="red", width=20)# add text about order of the pointsfor j, i in enumerate([1, 3, 5, 7]):j = j + 1draw.text([item[i], item[i + 1]], str(j))img.show()# items
# ['pane 2008 404 2713 404 2713 915 2008 915\n',
# 'pizzoccheri 1502 1104 2500 1104 2500 1984 1502 1984\n',
# 'arrosto 634 602 1193 602 1193 1371 634 1371\n',
# 'patate/pure 271 539 708 539 708 1408 271 1408\n']# the lash item, whose form is [cls, x1, y1, x2, y2, x3, y3, x4, y4]
# ['patate/pure', 271, 539, 708, 539, 708, 1408, 271, 1408]
bbox 正确,但整张图片好似被旋转了一样。接着使用 cv2 进行相同操作,并观察结果。
import cv2
from matplotlib import pyplot as pltimg = cv2.imread(img_name)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)for item in items:item = item.split()# convert str to intitem[1:] = list(map(int, item[1:]))# draw bboxcv2.rectangle( img,(item[1], item[2]),(item[5], item[6]),(0, 255, 0), 30)plt.imshow(img)
plt.show()
不难发现,
draw bbox
中使用的坐标均为[x1,y1], [x3, y3]
,即对角坐标(left-top、right-bottom),但PIL
和cv2
的结果不同。cv2
中 bbox 错位了,而PIL
中 text 发生了旋转.
😯 为什么会产生这种现象呢?从 EXIT 说起:
什么是EXIF?
EXIF(Exchangeable Image File)是"可交换图像文件"的缩写,当中包含了专门为数码相机的照片而定制的元数据,可以记录数码照片的拍摄参数、缩略图及其他属性信息。Exif 文件实际是JPEG文件的一种,遵从JPEG标准,只是在文件头信息中增加了有关拍摄信息的内容和索引图。
简单来说,Exif 信息就是由数码相机在拍摄过程中采集一系列的信息,然后把信息放置在我们熟知的 JPEG/TIFF 文件的头部,也就是说 Exif信息是镶嵌在 JPEG/TIFF 图像文件格式内的一组拍摄参数,它就好像是傻瓜相机的日期打印功能一样,只不过 Exif信息所记录的资讯更为详尽和完备。
EXIF Orientation tag
EXIF Orientation tag(EXIF方向参数)让你随便照像但都可以看到正确方向的照片而无需手动旋转(前提要图片浏览器支持,Windows 自带的不支持).
⭐️ 原因解释
造成第一目中“奇怪现象”的原因,我将其归纳为:在标记数据集时,不关注图像的 EXIF Orientation tag,而图像本身是含有 EXIF Orientation tag 的。在 PIL
读取图片时,创建了 Image
对象,其中存有 EXIF Orientation tag,根据该标记,不仅旋转了图像,还旋转了图像的参考系。而 cv2
读取图片时,直接生成 numpy.ndarray
数组,只根据 EXIF Orientation tag 对图像进行了旋转,但并没有旋转图像的参考系!
PIL
cv2
在绘制矩形框时,二者依赖的参考系不同,导致标记的错位。而 yolov5 使用的正是 cv2,故读取含 EXIF Orientation tag 的图片时,会造成 labels 和 图片的错位。
原因证实
PIL 的
PIL.ImageOps.exif_transpose(image)
方法,可以在读取图片后,清除Image
对象内的 EXIF Orientation tag,从而让坐标系不受该 tag 的影响.
from PIL import ImageOpsimg = Image.open(img_name)
img = ImageOps.exif_transpose(img)# draw bbox
...
可见,清除 EXIF 旋转信息后,PIL 所得结果和 cv2 结果一致!
⭐️ 解决方法
❤️ 当然,如果你有更好的方法欢迎在评论区留言告诉我哦!
from os import listdir
from PIL import Image
import numpy as npimg = './images/'
if __name__ == '__main__':for img_name in listdir(img_path):img = Image.open(img_path + img_name)img_rectified = Image.fromarray(np.asarray(img))img_rectified.save(img_path + img_name)
上述代码对
./images/
文件夹下所有图片进行了“修正”,核心是img_rectified = Image.fromarray(np.asarray(img))
,(1). 将img
转化为ndarray
,(2). 再将该ndarray
转化为Image
并保存.
(1)、(2) 等同于去除了图片所有的 EXIF 信息,这样图片就不会再发生“自动旋转”的现象了.
转载请注明出处:©️ Sylvan Ding
参考文献
- PIL thumbnail is rotating my image?
- 图片元信息Exif,给你详细讲讲
- EXIF 方向参数 Orientation