如何使用PIL保留JPEG元数据?

3

我正在使用PIL库调整图像大小以自动化我的工作中其他软件的输入,这些软件需要这些元数据。

图像已正确调整大小,但丢失了其中包含的所有元数据。

我使用以下代码进行调整大小。

from PIL import Image
filename = r'input.jpg'
ratio=0.2
im = Image.open(filename)
out = im.resize([int(ratio * s) for s in im.size], Image.ANTIALIAS)
out.save("out.jpg", format=im.format, optimize=True)

我可以使用以下方法将所有图像元数据保存到字典中:

im.__dict__

像pyexif、pyexif2等库返回Null值。我检查im.dict中的exif标签,发现该标签为空。

有没有一种方法将这个字典添加到我的调整大小的图像中?

图片下载链接:图片

非常感谢。

编辑:im.dict的结果(该字典中包含一个XML代码,用于向我们公司使用的软件传递元数据)。我们可以在3个标签info-->comment、app-->com和applist1中看到XML脚本。

{'im': None,
'mode': 'RGB',
'_size': (4096, 2304),
'palette': None,
'info': {'jfif': 258,
     'jfif_version': (1, 2),
         'jfif_unit': 0, 'jfif_density': (1, 1),
         'comment': b'<?xml version="1.0" encoding="utf-8"?>\n<image time="09:04:10.271222" date="2020.06.15" acq_index="7666">\n\t<Position time="20200615T090410.834" received="2020-Jun-15 09:04:10.267082" extrapolated="false" age="4" transponder_id="0">\n\t\t<Coords long="-40.9273182" lat="-22.7837963"/>\n\t\t<Depth altitude="5.14" depth="85.57"/>\n\t\t<Direction pitch="-3.09" roll="0.03" yaw="180.02"/>\n\t</Position>\n\t<acquisition>\n\t\t<exposure>5000</exposure>\n\t\t<digital_gain>1.19</digital_gain>\n\t\t<analog_gain>6</analog_gain>\n\t\t<sensor_gain>4</sensor_gain>\n\t\t<aperture>1.4</aperture>\n\t\t<focus>498</focus>\n\t\t<name>ColorCamera</name>\n\t\t<camera_session_name>start_1</camera_session_name>\n\t\t<camera_sub_session_name/>\n\t\t<focus_enc>3945</focus_enc>\n\t\t<width>4096</width>\n\t\t<height>2304</height>\n\t\t<seq_slot>0</seq_slot>\n\t\t<dequeue_time>2020-06-15T09:04:10.887848</dequeue_time>\n\t</acquisition>\n\t<errors/>\n\t<versions>\n\t\t<software>0.968s4</software>\n\t\t<fpga>0x02d1</f                    pga>\n\t\t<pic>210</pic>\n\t\t<serial_number>191</serial_number>\n\t</versions>\n\t<ntp>\n\t\t<ntpq>*192.168.99.100                   1 u   69  128  377    0.196   -3.329   0.521</ntpq>\n\t\t<state>within_limits</state>\n\t\t<sync_level>excellent_sync</sync_level>\n\t</ntp>\n\t<pps/>\n</image>\n'}, 'category': 0, 'readonly': 1, 'pyaccess': None, '_exif': None, '_min_frame': 0, 'custom_mimetype': None, 'tile': [('jpeg', (0, 0, 4096, 2304), 0, ('RGB', ''))], 
'decoderconfig': (),
'decodermaxblock': 65536,
'fp': <_io.BufferedReader name='input.jpg'>,
'filename': 'input.jpg',
'_exclusive_fp': True, 'bits': 8,
'layers': 3,
'layer': [(1, 2, 2, 0), (2, 1, 1, 1), (3, 1, 1, 1)],
'huffman_dc': {},
'huffman_ac': {},
'quantization': {0: array('B', [3, 2, 2, 3, 2, 2, 3, 3, 3, 3, 4, 3, 3, 4, 5, 8, 5, 5, 4, 4, 5, 10, 7, 7, 6, 8, 12, 10, 12, 12, 11, 10, 11, 11, 13, 14, 18, 16, 13, 14, 17, 14, 11, 11, 16, 22, 16, 17, 19, 20, 21, 21, 21, 12, 15, 23, 24, 22, 20, 24, 18, 20, 21, 20]), 
                 1: array('B', [3, 4, 4, 5                    , 4, 5, 9, 5, 5, 9, 20, 13, 11, 13, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20])}, 

'app': {'APP0': b'JFIF\x00\x01\x02\x00\x00\x01\x00\x01\x00\x00', 
        'COM': b'<?xml version="1.0" encoding="utf-8"?>\n<image time="09:04:10.271222" date="2020.06.15" acq_index="7666">\n\t<Position time="20200615T090410.834" received="2020-Jun-15 09:04:10.267082" extrapolated="false" age="4" transponder_id="0">\n\t\t<Coords long="-40.9273182" lat="-22.7837963"/>\n\t\t<Depth altitude="5.14" depth="85.57"/>\n\t\t<Direction pitch="-3.09" roll="0.03" yaw="180.02"/>\n\t</Position>\n\t<acquisition>\n\t\t<exposure>5000</exposure>\n\t\t<digital_gain>1.19</digital_gain>\n\t\t<analog_gain>6</analog_gain>\n\t\t<sensor_gain>4</sensor_gain>\n\t\t<aperture>1.4</aperture>\n\t\t<focus>498</focus>\n\t\t<name>ColorCamera</name>\n\t\t<camera_session_name>start_1</camera_session_name>\n\t\t<camera_sub_session_name/>\n\t\t<focus_enc>3945</focus_enc>\n\t\t<width>4096</width>\n\t\t<height>2304</height>\n\t\t<seq_slot>0</seq_slot>\n\t\t<dequeue_time>2020-06-15T09:04:10.887848</dequeue_time>\n\t</acquisition>\n\t<errors/>\n\t<versions>\n\t\t<software>0.968s4</software>\n\t\t<fpga>0x02d1</fpga>\               n\t\t<pic>210</pic>\n\t\t<serial_number>191</serial_number>\n\t</versions>\n\t<ntp>\n\t\t<ntpq>*192.168.99.100                   1 u   69  128  377    0.196   -3.329   0.521</ntpq>\n\t\t<state>within_limits</state>\n\t\t<sync_level>excellent_sync</sync_level>\n\t</ntp>\n\t<pps/>\n</image>\n'},

'applist': [('APP0', b'JFIF\x00\x01\x02\x00\x00\x01\x00\x01\x00\x00'), ('COM', b'<?xml version="1.0" encoding="utf-8"?>\n<image time="09:04:10.271222" date="2020.06.15" acq_index="7666">\n\t<Position time="20200615T090410.834" received="2020-Jun-15 09:04:10.267082" extrapolated="false" age="4" transponder_id="0">\n\t\t<Coords long="-40.9273182" lat="-22.7837963"/>\n\t\t<Depth altitude="5.14" depth="85.57"/>\n\t\t<Direction pitch="-3.09" roll="0.03" yaw="180.02"/>\n\t</Position>\n\t<acquisition>\n\t\t<exposure>5000</exposure>\n\t\t<digital_gain>1.19</digital_gain>\n\t\t<analog_gain>6</analog_gain>\n\t\t<sensor_gain>4</sensor_gain>\n\t\t<aperture>1.4</aperture>\n\t\t<focus>498</focus>\n\t\t<name>ColorCamera</name>\n\t\t<camera_session_name>start_1</camera_session_name>\n\t\t<camera_sub_session_name/>\n\t\t<focus_enc>3945</focus_enc>\n\t\t<width>4096</width>\n\t\t<height>2304</height>\n\t\t<seq_slot>0</seq_slot>\n\t\t<dequeue_time>2020-06-15T09:04:10.887848</dequeue_time>\n\t</acquisition>\n\t<errors/>\n\t<versions>\n\t\t<software>0.968s4</software>\n\t\t<fpga>0x02d1</fpga>\n\t\t<pic>210</pic>\n\t\t<serial_number>191</serial_number>\n\t</versions>\n\t<ntp>\n\t\t<ntpq>*192.168.99.100                   1 u   69  128  377    0.196   -3.329   0.521</ntpq>\n\t\t<state>within_limits</state>\n\t\t<sync_level>excellent_sync</sync_level>\n\t</ntp>\n\t<pps/>\n</image>\n')],

'icclist': []}

请参考 https://dev59.com/fm445IYBdhLWcg3wpLx_ 以读取图像的exif信息。很抱歉,我不知道如何编写exif。 - Tarik
这个回答解决了你的问题吗?使用PIL调整大小(创建缩略图)时保留图像的exif数据 - Mike Scotty
这个解决方案不起作用,因为每次获取exif数据时,结果都是空值。我只能使用im.__dict__来查看数据。 - Vinicius Nogueira
3个回答

3

您可以通过 exif 关键字参数,将从原始文件中提取的元数据添加到调整大小后的文件中。如果您将代码修改为以下内容:

from PIL import Image

filename = r'input.jpg'
ratio = 0.2

im = Image.open(filename)
EXIF = im.getexif()

out = im.resize([int(ratio * s) for s in im.size], Image.ANTIALIAS)
out.save("out.jpg", format=im.format, optimize=True, exif=EXIF)

元数据现在应该被转移到新图像。

我尝试了但没有成功。当我使用任何方法获取exif元数据时都返回null。 我只能通过im.__dict__看到元数据。 - Vinicius Nogueira
1
@ViniciusNogueira 这很奇怪。我正在使用一个test JPG图像,当我缩小尺寸并重新导入out.jpg图像时,我能够使用im.getexif()检索元数据。例如,我也可以使用Gnome Image Viewer查看元数据。您能否尝试打印我提供的相同初始测试图像的元数据? - panadestein
你的图片很好。我的图片格式有问题(没有exif标签,它是另一种元数据,但我还不知道如何处理)。我已经上传了,你能试试吗?https://1drv.ms/u/s!Av58o7S5NWNXjP4QCPuyZ2PzJGlonQ?e=tgRohl我的结果是filename = r'input.jpg'im = Image.open(filename)EXIF = im.getexif()print(EXIF) {} - Vinicius Nogueira
使用gdalinfo查看输入结果输出结果,但我无法在out.jpg中看到元数据COMMENT。 - Vinicius Nogueira
@ViniciusNogueira 我已经尝试使用Python和Gnome Image Viewer查看了你在OneDrive中分享的原始图像,但它没有任何元数据,因此无法传输。 - panadestein
我知道它没有Exif元数据,但是它有一个包含XML的注释作为元数据(我使用im.__dict__更新了帖子中的结果)。使用GDAL工具查看结果(GDAL_INFO_INPUT),与im.__dict__相同。 - Vinicius Nogueira

1
也许你可以使用wand而不是PIL,因为它会自动地将注释传递给下一个步骤:
#!/usr/bin/env python3

from wand.image import Image

with Image(filename='input.jpg') as img: 
    img.save(filename='result.jpg')

或者,这里有一个可能的解决方法。您可以像这样从input.jpg中提取注释并将其保存到名为comment.txt的文件中:

jhead -cs comment.txt input.jpg

您可以将该注释写入名为 result.jpg 的不同文件中,方法如下:
jhead -ci comment.txt result.jpg

我认为您可以使用Python subprocess模块,通过类似以下方式复制您的数据:

import subprocess

# Propagate JPEG comment forward from "input.jpg" to "result.jpg"
subprocess.run('jhead -cs - input.jpg | jhead -ci - result.jpg', shell=True)

0

谢谢大家。

@Mark Setchell非常感谢您,您使用wand和ImageMagick的解决方案很好。但是在我的工作中,并不是每个人都处理软件安装、编程等问题。因此,我决定只使用标准的Python库。

我使用了PIL制作了一个简单的解决方案。首先,我使用以下代码读取并调整图像大小:

from PIL import Image

raw_image = Image.open('input')
out = raw_image.resize([int(ratio * s) for s in raw_image.size], Image.ANTIALIAS)
out.save('out.jpg', format=raw_image.format, quality=100, optimize=True)

调整大小后保存图像,我只需获取输入图像的注释字段并插入到调整大小后的图像中。为此,我插入头部b'\xff\xfe',并使用字节串附加了输入图像的注释字段。

with open('out.jpg', 'r+b') as f:
    img = f.read()
    data = img[:2] + b'\xff\xfe\x04\xb5' + raw_image.app['COM'] + img[2:]
    f.seek(0)
    f.truncate()
    f.write(data)
    f.close()

我相信我可以大大改进这段代码,但首先我必须更多地学习编码和Python。此外,当我们使用多进程处理此代码时,可以大大减少处理时间。

感谢大家的帮助和讨论。

祝好, Vincius


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接