本文最后更新于:2025年12月8日 下午

有时需要修改 JPEG 图像中的 exif 信息, 但是修改过的信息直接用 exifread 库读取报错,本文复现问题并记录解决方案。

简介

大疆的无人机拍摄的 jpeg 图像中自带复杂丰富的 exif 和 xmp 信息,我们可以用 python 进行读取 ,也可以编辑, 但是编辑过后 exifread 库无法顺利读取。

问题复现

此时是可以用 exifread 库读取信息的:

1
2
3
4
5
6
import exifread

image_path = 'ori.JPG'
with open(image_path, 'rb') as fileobj:
tags = exifread.process_file(fileobj, details=False)
print(tags)

修改 xmp 信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
from pyexiv2 import Image as xmpImage
from pyexiv2 import ImageData
import vvdutils as vv

# def phase_all_exif_info(image_path):
# img = xmpImage(image_path)
# total_info = dict()

# xmp_data = img.read_xmp()
# total_info.update(xmp_data)

# exif_info = img.read_exif()
# total_info.update(exif_info)

# return total_info

def signed_str_to_float(s):
try:
# 去除前后空格后直接转换
return float(s.strip())
except ValueError as e:
print(f"转换失败: 输入 '{s}' 不是有效的浮点数字符串")
return None


def phase_all_exif_info(image_path):
img = xmpImage(image_path)
total_info = dict()

xmp_data = img.read_xmp()
total_info.update(xmp_data)

exif_info = img.read_exif()
total_info.update(exif_info)

return total_info


'''
修改图像中EXIF和XMP数据, 原地操作,直接修改存储到当前图像
数据示例如下:
changes = { 'Xmp.drone-dji.AbsoluteAltitude': '+10000' }
'''
def modify_xmp_and_exif(image_path, xmp_changes=None, exif_changes=None):
"""
只修改 XMP 元数据,不操作像素数据
"""
try:
# 1. 打开图片读取元数据(仅加载元数据,不加载完整像素)
with xmpImage(image_path) as img:
# 如果提供了 EXIF 修改,应用它
if exif_changes:
img.modify_exif(exif_changes)
else:
img.modify_exif({})

# 如果提供了 XMP 修改,应用它
if xmp_changes:
img.modify_xmp(xmp_changes)

# 3. 获取修改后的完整元数据(包括 EXIF、XMP)
exif = img.read_exif()
xmp = img.read_xmp()

# 4. 用原始图片创建 ImageData 对象(承载像素和原始元数据)
with open(image_path, 'rb') as f:
img_data = ImageData(f.read())

# 5. 将修改后的元数据写入 ImageData(仅更新元数据区)
img_data.modify_exif(exif)
img_data.modify_xmp(xmp)

except FileNotFoundError:
print(f"错误:找不到文件 '{image_path}'")
except Exception as e:
print(f"处理图片时发生错误: {e}")


if __name__ == '__main__':
utm_zone = "WGS84 UTM 45N"
ariport_offset_lon = 1000 # cur_pos - airport_pos
ariport_offset_lat = 1000
ariport_offset_alt = 1000

image_file = 'ori.JPG'

exif_info = phase_all_exif_info(image_file)

lon = exif_info['Xmp.drone-dji.GpsLongitude']
lon = signed_str_to_float(lon)
lat = exif_info['Xmp.drone-dji.GpsLatitude']
lat = signed_str_to_float(lat)
alt = exif_info['Xmp.drone-dji.AbsoluteAltitude']
alt = signed_str_to_float(alt)
pt = [lon, lat]
point_image = vv.Point(pt[1], pt[0], utm_zone=utm_zone)
print(f'1:lon:{lon}, lat:{lat}')
point_image_x_utm = point_image.x_utm - ariport_offset_lon
point_image_y_utm = point_image.y_utm - ariport_offset_lat
new_point = vv.Point.from_xy_utm(utm_zone, point_image_x_utm, point_image_y_utm)

print(f'2:lon:{new_point.lon}, lat:{new_point.lat}')

update_lon = f'+{new_point.lon}' if new_point.lon > 0 else f'-{new_point.lon}'
update_lat = f'+{new_point.lat}' if new_point.lat > 0 else f'-{new_point.lat}'
changes = {
'Xmp.drone-dji.AbsoluteAltitude': '+10000',
'Xmp.drone-dji.GpsLongitude': update_lon,
'Xmp.drone-dji.GpsLatitude': update_lat
}
modify_xmp_and_exif(image_file, changes)
out_exif_info = phase_all_exif_info(image_file)
pass

修改前后:

1
2
1:lon:87.616024029, lat:44.600398776
2:lon:87.603330963344, lat:44.591463976635

可以在图像中看到修改成功。

但是此时用 exifread 执行相同代码却只能获取空字典。

问题定位

exifread 库的 jpeg.py 中的 find_jpeg_exif 函数里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

def find_jpeg_exif(fh: BinaryIO, data, fake_exif) -> tuple:
logger.debug("JPEG format recognized data[0:2]=0x%X%X", ord_(data[0]), ord_(data[1]))

base, fake_exif = _get_initial_base(fh, data, fake_exif)

# Big ugly patch to deal with APP2 (or other) data coming before APP1
fh.seek(0)
# in theory, this could be insufficient since 64K is the maximum size--gd
data = fh.read(base + 4000)

base = _get_base(base, data)

fh.seek(base + 12)
if ord_(data[2 + base]) == 0xFF and data[6 + base : 10 + base] == b"Exif":
# detected EXIF header
offset = fh.tell()
endian = fh.read(1)
# HACK TEST: endian = 'M'
elif ord_(data[2 + base]) == 0xFF and data[6 + base : 10 + base + 1] == b"Ducky":
# detected Ducky header.
logger.debug(
"EXIF-like header (normally 0xFF and code): 0x%X and %s",
ord_(data[2 + base]),
data[6 + base : 10 + base + 1],
)
offset = fh.tell()
endian = fh.read(1)
elif ord_(data[2 + base]) == 0xFF and data[6 + base : 10 + base + 1] == b"Adobe":
# detected APP14 (Adobe)
logger.debug(
"EXIF-like header (normally 0xFF and code): 0x%X and %s",
ord_(data[2 + base]),
data[6 + base : 10 + base + 1],
)
offset = fh.tell()
endian = fh.read(1)
else:
# no EXIF information
msg = "No EXIF header expected data[2+base]==0xFF and data[6+base:10+base]===Exif (or Duck)"
msg += "Did get 0x%X and %s" % (ord_(data[2 + base]), data[6 + base : 10 + base + 1])
raise InvalidExif(msg)
return offset, endian, fake_exif

data = fh.read(base + 4000) 不够获取修改过后的exif 信息, 无法成功解析图像中的 exif 数据。

解决方案

  • 使用 pyexiv2 库可以成功兼容新架构 exif 信息提取

  • 或者手动修改 4000 为 400000,这样会侵入原始 exifread 库, 不是很优雅

参考资料



文章链接:
https://www.zywvvd.com/notes/coding/python/exifread-bug/exifread-bug/


“觉得不错的话,给点打赏吧 ୧(๑•̀⌄•́๑)૭”

微信二维码

微信支付

支付宝二维码

支付宝支付

Python exifread 读取修改过的 jpeg 信息错误问题修复
https://www.zywvvd.com/notes/coding/python/exifread-bug/exifread-bug/
作者
Yiwei Zhang
发布于
2025年12月8日
许可协议