照片处理工具代码
作为一名摄影爱好者兼Python开发者,我开发了一套自动化图片处理工具,现已开源分享。这些工具能帮助摄影师快速完成常见后期处理工作,特别适合需要批量处理图片的场景。

工具清单与适用场景
1. 基础处理
拼接.py
- 功能:将多张图片拼接为全景图或网格排列图
- 特色:智能识别相似边缘自动对齐
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
115
116import os
from PIL import Image
supported_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp']
def find_image_file(filename):
"""查找实际存在的图片文件(支持无扩展名或错误扩展名)"""
# 如果输入包含扩展名
if os.path.splitext(filename)[1]:
if os.path.isfile(filename):
return filename
# 尝试纠正大小写
base = os.path.splitext(filename)[0]
for ext in supported_extensions:
if os.path.isfile(base + ext.lower()):
return base + ext.lower()
# 无扩展名的情况
else:
for ext in supported_extensions:
path = filename + ext
if os.path.isfile(path):
return path
return None
def main():
images = []
original_filenames = []
max_width = 0
print("""照片拼接工具
==========================
1. 输入图片名称
2. 输入 'done' 完成输入
3. 输入 'exit' 退出程序
""")
while True:
user_input = input("请输入图片名称 > ").strip()
# 退出检测
if user_input.lower() == 'exit':
print("已退出程序")
return
# 完成输入检测
if not user_input or user_input.lower() == 'done':
if not images:
print("错误:至少需要输入一张有效图片")
continue
break
# 查找文件
filepath = find_image_file(user_input)
if not filepath:
print(f" × 未找到文件: {user_input}(支持格式:{', '.join(supported_extensions)})")
continue
# 加载图片
try:
img = Image.open(filepath)
img = img.convert('RGB')
images.append(img)
original_filenames.append(os.path.basename(filepath))
max_width = max(max_width, img.width)
print(f" √ 已加载: {filepath} ({img.width} x {img.height})")
except Exception as e:
print(f" × 无法处理文件 {filepath}: {str(e)}")
continue
# 调整图片尺寸
resized_images = []
total_height = 0
print("\n正在调整图片尺寸...")
for img in images:
w_percent = max_width / img.width
h_size = int(img.height * w_percent)
resized_img = img.resize((max_width, h_size), Image.LANCZOS)
resized_images.append(resized_img)
total_height += h_size
print(f" → 调整 {img.width} x {img.height} 到 {max_width} x {h_size}")
# 拼接图片
combined_img = Image.new('RGB', (max_width, total_height))
y_offset = 0
info_data = []
print("\n开始拼接图片...")
for i, img in enumerate(resized_images):
combined_img.paste(img, (0, y_offset))
info_data.append({
'filename': original_filenames[i],
'y_start': y_offset,
'height': img.height
})
print(f" ✓ 已拼接: {original_filenames[i]} (起始位置: {y_offset}, 高度: {img.height})")
y_offset += img.height
# 保存结果
combined_img.save('combined.jpg')
with open('combined_info.txt', 'w') as f:
for entry in info_data:
f.write(f"{entry['filename']},{entry['y_start']},{entry['height']}\n")
print(f"""\n操作完成!
生成文件:combined.jpg(尺寸:{max_width}x{total_height})
combined_info.txt(包含 {len(info_data)} 条记录)""")
if __name__ == "__main__":
main()
print("处理完成!按回车键退出...")
aaa = input()
拆分.py
- 功能:将单张大图按指定行列拆分为网格小图
- 应用场景:制作拼图素材、社交媒体九宫格图片
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
106import os
from PIL import Image
import warnings
warnings.filterwarnings("ignore")
def main():
# 自动检测文件路径
combined_img_path = 'combined.jpg'
info_file_path = 'combined_info.txt'
print("""\n照片拆分工具(自动模式)
==========================
检测当前目录下的:
- 拼接文件:combined.jpg
- 信息文件:combined_info.txt
""")
# 验证文件存在
if not os.path.exists(combined_img_path):
print(f"错误:找不到拼接文件 {combined_img_path}")
return
if not os.path.exists(info_file_path):
print(f"错误:找不到信息文件 {info_file_path}")
return
try:
# 加载拼接图片
combined_img = Image.open(combined_img_path)
img_width = combined_img.width
print(f"√ 已加载拼接图片 ({img_width}x{combined_img.height})")
except Exception as e:
print(f"无法打开拼接图片: {e}")
return
try:
# 读取信息文件
with open(info_file_path) as f:
lines = f.readlines()
print(f"√ 已加载信息文件(包含 {len(lines)} 条记录)")
except Exception as e:
print(f"无法打开信息文件: {e}")
return
success_count = 0
error_count = 0
print("\n开始拆分图片...")
for line_num, line in enumerate(lines, 1):
parts = line.strip().split(',')
if len(parts) != 3:
print(f"× 第{line_num}行格式错误:{line.strip()}")
error_count += 1
continue
filename, y_start, height = parts
try:
y = int(y_start)
h = int(height)
except ValueError:
print(f"× 第{line_num}行数值错误:{line.strip()}")
error_count += 1
continue
# 验证坐标有效性
if y < 0 or h <= 0:
print(f"× 第{line_num}行数值无效(y={y}, h={h})")
error_count += 1
continue
if y + h > combined_img.height:
print(f"× 第{line_num}行越界:{y + h} > 图片总高度 {combined_img.height}")
error_count += 1
continue
# 创建输出目录(如果需要)
# Create a single directory
path = "拆分图片"
if not os.path.exists(path):
os.makedirs(path)
# 执行裁剪
try:
crop_area = (0, y, img_width, y + h)
cropped = combined_img.crop(crop_area)
cropped.save('拆分图片//' + filename)
print(f"√ 已保存:{filename} ({img_width}x{h})")
success_count += 1
except Exception as e:
print(f"× 保存 {filename} 失败:{str(e)}")
error_count += 1
print(f"""\n操作完成!
成功拆分:{success_count} 张
失败记录:{error_count} 条""")
if __name__ == "__main__":
main()
print("处理完成!按回车键退出...")
aaa = input()
2. 版权保护
添加水印.py
- 功能:添加半透明文字水印
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
109from PIL import Image
import os
import glob
# from rich.progress import Progress
def calculate_brightness(img):
"""计算图片区域的平均亮度"""
pixels = img.convert("L").getdata()
return sum(pixels) / len(pixels)
def add_watermark(original_path, i, ii):
# 创建输出目录
original_dir = os.path.dirname(original_path)
output_dir = os.path.join(original_dir, "添加水印")
os.makedirs(output_dir, exist_ok=True) # 自动创建目录
# 生成输出路径
file_name = os.path.basename(original_path)
output_path = os.path.join(output_dir, f"{os.path.splitext(file_name)[0]}_水印{os.path.splitext(file_name)[1]}")
# 水印路径配置
WATERMARKS = {
"light": r"C:\Users\yyq09\Pictures\水印 - 黑.png", # 亮背景用黑水印
"dark": r"C:\Users\yyq09\Pictures\水印 - 白.png" # 暗背景用白水印
}
# 打开原始图片
with Image.open(original_path) as img:
original_format = img.format # 保留原始格式信息
# 转换格式并保留Alpha通道
original = img.convert("RGBA")
width, height = original.size
# 计算底部区域亮度(取高度5%的区域)
crop_height = max(50, int(height * 0.1))
bottom_region = original.crop((width * 0.45, height - crop_height, width * 0.55, height))
brightness = calculate_brightness(bottom_region)
# 选择合适的水印
watermark_path = WATERMARKS["light"] if brightness > 128 * 1.2 else WATERMARKS["dark"]
try:
with Image.open(watermark_path) as watermark:
# 调整水印尺寸(最大宽度为原图的70%)
wm_width, wm_height = watermark.size
if width > height:
max_width = int(width * 0.1)
else:
max_width = int(width * 0.17)
scaling_factor = min(max_width / wm_width, 1.0)
new_size = (
int(wm_width * scaling_factor),
int(wm_height * scaling_factor)
)
watermark = watermark.resize(new_size, Image.LANCZOS).convert("RGBA")
# 计算水印位置
x = (width - new_size[0]) // 2
y = height - int(new_size[1] * 1.2) # 底部保留2%边距
# 创建透明层合并水印
composite = Image.new("RGBA", original.size)
composite.paste(watermark, (x, y))
result = Image.alpha_composite(original, composite)
# 构建保存参数
save_params = img.info.copy()
# 保存结果(保持原始格式)
# output_path = f"{os.path.splitext(original_path)[0]}_添加水印{os.path.splitext(original_path)[1]}"
# 修改保存部分
result.convert(img.mode).save(
output_path,
format=original_format,
**save_params
)
print(f"已处理 {i}/{ii}: {os.path.basename(original_path)}")
except FileNotFoundError:
print(f"水印文件不存在:{watermark_path}")
except Exception as e:
print(f"处理失败:{original_path} - {str(e)}")
if __name__ == "__main__":
# 支持的图片格式
extensions = ["jpg", "jpeg", "png", "bmp", "webp"]
# # 并行处理(提升大图处理速度)
# with Pool() as pool:
# tasks = []
# for ext in extensions:
# tasks.extend(glob.glob(f"*.{ext}"))
# pool.map(add_watermark, tasks)
i = 1
for ext in extensions:
for file in glob.glob(f"*.{ext}"):
add_watermark(file, i, len(glob.glob(f"*.{ext}")))
i += 1
print("处理完成!按回车键退出...")
input()添加水印和拍摄信息.py
- 高级功能:在基础水印上叠加EXIF信息
- 自动读取:相机型号、光圈快门、GPS位置(需原图包含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
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203from PIL import Image, ImageDraw, ImageFont, ImageOps
import os
import glob
# from multiprocessing import Pool
from PIL.ExifTags import TAGS
import piexif
from rich.progress import Progress
# 新增字体路径配置(微软雅黑)
FONT_PATH = "C:/Windows/Fonts/msyh.ttc" # Windows系统字体路径
def get_exif_data(img):
"""获取EXIF信息并解析关键参数"""
exif_data = {}
try:
if hasattr(img, '_getexif'):
exif_info = img._getexif()
if exif_info:
for tag, value in exif_info.items():
decoded = TAGS.get(tag, tag)
exif_data[decoded] = value
# 使用piexif补充读取(兼容更多格式)
exif_dict = piexif.load(img.info.get('exif', b''))
for ifd in exif_dict:
for tag in exif_dict[ifd]:
tag_name = piexif.TAGS[ifd][tag]["name"]
exif_data[tag_name] = exif_dict[ifd][tag]
except:
pass
# 参数解析逻辑
def parse_param(param):
if isinstance(param, tuple):
return float(param[0]) / float(param[1])
elif isinstance(param, int):
return float(param)
return None
# 焦距处理
focal_length = exif_data.get('FocalLength', exif_data.get('FocalLengthIn35mmFilm'))
focal = f"{int(parse_param(focal_length))}mm" if focal_length else '--mm'
# 光圈处理
aperture = exif_data.get('FNumber', exif_data.get('ApertureValue'))
f_number = f"f/{parse_param(aperture):.1f}" if aperture else 'f/--'
# 快门速度处理
exposure = exif_data.get('ExposureTime')
if exposure:
if isinstance(exposure, tuple):
val = parse_param(exposure)
shutter = f"1/{int(1 / val)}s" if val < 1 else f"{val:.0f}s"
else:
shutter = f"{exposure}s"
else:
shutter = '--s'
# ISO处理
iso = exif_data.get('ISOSpeedRatings', exif_data.get('PhotographicSensitivity'))
iso_str = f"ISO{iso}" if iso else 'ISO---'
return f"{focal} {f_number} {shutter} {iso_str}"
def calculate_brightness(img):
"""计算图片区域的平均亮度"""
pixels = img.convert("L").getdata()
return sum(pixels) / len(pixels)
def add_watermark(original_path, i, ii):
# 创建输出目录
original_dir = os.path.dirname(original_path)
output_dir = os.path.join(original_dir, "添加水印和拍摄信息")
os.makedirs(output_dir, exist_ok=True) # 自动创建目录
# 生成输出路径
file_name = os.path.basename(original_path)
output_path = os.path.join(output_dir,
f"{os.path.splitext(file_name)[0]}_水印和拍摄信息{os.path.splitext(file_name)[1]}")
# 水印路径配置
WATERMARKS = {
"light": r"C:\Users\yyq09\Pictures\水印 - 黑.png", # 亮背景用黑水印
"dark": r"C:\Users\yyq09\Pictures\水印 - 白.png" # 暗背景用白水印
}
# 打开原始图片
with Image.open(original_path) as img:
original_format = img.format # 保留原始格式信息
# 转换格式并保留Alpha通道
original = img.convert("RGBA")
width, height = original.size
# 计算底部区域亮度(取高度5%的区域)
crop_height = max(50, int(height * 0.1))
bottom_region = original.crop((width * 0.45, height - crop_height, width * 0.55, height))
brightness = calculate_brightness(bottom_region)
# 选择合适的水印
watermark_path = WATERMARKS["light"] if brightness > 128 * 1.2 else WATERMARKS["dark"]
try:
with Image.open(watermark_path) as watermark:
# 调整水印尺寸(最大宽度为原图的70%)
wm_width, wm_height = watermark.size
if width > height:
max_width = int(width * 0.1)
FONT_RATIO = 0.25 # 文字大小与水印高度的比例
TEXT_MARGIN = 17 # 水印与文字间距
down_length = 1.5 # 底边距离
else:
max_width = int(width * 0.17)
FONT_RATIO = 0.25 # 文字大小与水印高度的比例
TEXT_MARGIN = 20 # 水印与文字间距
down_length = 1.5 # 底边距离
scaling_factor = min(max_width / wm_width, 1.0)
new_size = (
int(wm_width * scaling_factor),
int(wm_height * scaling_factor)
)
watermark = watermark.resize(new_size, Image.LANCZOS).convert("RGBA")
# 计算水印位置
x = (width - new_size[0]) // 2
y = height - int(new_size[1] * down_length) # 底部保留边距
# 创建透明层合并水印
composite = Image.new("RGBA", original.size)
composite.paste(watermark, (x, y))
result = Image.alpha_composite(original, composite)
# 在合成水印后添加文字信息
exif_text = get_exif_data(img)
draw = ImageDraw.Draw(result)
# 自动选择字体大小(水印高度的40%)
text_size = int(new_size[1] * FONT_RATIO)
try:
# 尝试加载微软雅黑字体
font = ImageFont.truetype(FONT_PATH, text_size)
except Exception as e:
print(f"字体加载失败,使用默认字体:{str(e)}")
font = ImageFont.load_default(text_size)
# 计算文字位置(水印下方10像素)
text_y = y + new_size[1] + TEXT_MARGIN
text_width = font.getlength(exif_text)
text_x = (width - text_width) // 2
# 自动选择文字颜色(基于水印区域亮度)
text_color = 'white' if calculate_brightness(bottom_region) < 128 * 1.2 else 'black'
# 添加文字阴影增强可读性
shadow_color = 'white' if text_color == 'white' else 'black'
for offset in [(-1, -1), (-1, 1), (1, -1), (1, 1)]:
draw.text((text_x + offset[0], text_y + offset[1]),
exif_text, font=font, fill=shadow_color)
# 绘制主文字
draw.text((text_x, text_y), exif_text, font=font, fill=text_color)
# 构建保存参数
save_params = img.info.copy()
# 保存结果(保持原始格式)
result.convert(img.mode).save(
output_path,
format=original_format,
**save_params
)
print(f"已处理 {i}/{ii}: {os.path.basename(original_path)}")
except FileNotFoundError:
print(f"水印文件不存在:{watermark_path}")
except Exception as e:
print(f"处理失败:{original_path} - {str(e)}")
if __name__ == "__main__":
# 支持的图片格式
extensions = ["jpg", "jpeg", "png", "bmp", "webp"]
# # 并行处理(提升大图处理速度)
# with Pool() as pool:
# tasks = []
# for ext in extensions:
# tasks.extend(glob.glob(f"*.{ext}"))
# pool.map(add_watermark, tasks)
i = 1
for ext in extensions:
for file in glob.glob(f"*.{ext}"):
add_watermark(file, i, len(glob.glob(f"*.{ext}")))
i += 1
print("处理完成!按回车键退出...")
input()
3. 艺术化处理
添加高斯背景和拍摄信息.py
- 二合一功能:同时实现背景模糊+参数展示
- 特色布局:
- 底部10%区域显示拍摄参数
- 参数面板半透明渐变效果
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371from PIL import Image, ImageFilter, ImageDraw, ImageFont
import piexif
import os
from rich.progress import Progress
# Configuration constants
WATERMARKS = {
"light": r"C:\Users\yyq09\Pictures\水印 - 黑.png",
"dark": r"C:\Users\yyq09\Pictures\水印 - 白.png"
}
FONT_PATH = "C:/Windows/Fonts/msyh.ttc"
OUTPUT_DIRS = {
"background": "高斯背景",
"final": "信息模糊水印处理"
}
BLUR_RADIUS = 69
BRIGHTNESS_THRESHOLD = 128 * 1.2
def apply_drop_shadow(
background: Image.Image,
img: Image.Image,
position: tuple[int, int]
) -> None:
shadow_radius = int(min(img.size) * 0.015)
shadow_opacity = 255
"""为图片添加阴影效果
Args:
background: 背景画布对象
img: 原始图片对象
position: 图片粘贴位置 (x, y)
shadow_radius: 阴影扩散半径(默认10像素)
shadow_opacity: 阴影透明度(0-255,默认80)
"""
x, y = position
width, height = img.size
# 创建阴影层
shadow_layer = Image.new("RGBA", (width + shadow_radius * 2, height + shadow_radius * 2))
shadow_draw = ImageDraw.Draw(shadow_layer)
# 绘制渐变阴影
for i in range(shadow_radius, 0, -1):
alpha = int(shadow_opacity * (shadow_radius / shadow_radius) ** 0.8)
shadow_draw.rounded_rectangle(
[(shadow_radius - i, shadow_radius - i),
(width + shadow_radius + i - 1, height + shadow_radius + i - 1)],
radius=int(min(width, height) * 0.03) + i,
fill=(0, 0, 0, alpha))
# 粘贴阴影到背景
background.paste(shadow_layer, (x - shadow_radius, y - shadow_radius), shadow_layer)
def add_rounded_corners(img: Image.Image, radius: int = 15) -> Image.Image:
"""为图片添加圆角效果
Args:
img: 原始图片对象
radius: 圆角半径(默认15像素)
Returns:
PIL.Image.Image: 带圆角的图片对象
"""
# 创建透明蒙版
mask = Image.new('L', img.size, 0)
draw = ImageDraw.Draw(mask)
# 绘制圆角矩形
draw.rounded_rectangle([(0, 0), img.size], radius, fill=255)
# 应用蒙版
if img.mode != 'RGBA':
img = img.convert('RGBA')
img.putalpha(mask)
return img
def process_image(image_path: str, index, ii) -> Image.Image:
"""为图片添加高斯模糊背景
Args:
image_path: 原始图片路径
index: 当前处理序号
Returns:
PIL.Image.Image: 处理后的图片对象
"""
with Image.open(image_path) as img:
original_format = img.format
img = img.convert("RGB") if img.mode != "RGB" else img
width, height = img.size
# 计算扩展尺寸
is_landscape = width > height
delta_x = calculate_padding(width, 0.03 if is_landscape else 0.05)
delta_y_top = calculate_padding(height, 0.05 if is_landscape else 0.03)
delta_y_bottom = calculate_padding(height, 0.12 if is_landscape else 0.08)
new_width = width + 2 * delta_x
new_height = height + delta_y_top + delta_y_bottom
# 创建背景画布
background = create_background(img, new_width, new_height, delta_x, delta_y_top, delta_y_bottom)
# 应用高斯模糊
blurred_background = background.filter(ImageFilter.GaussianBlur(radius=BLUR_RADIUS))
rounded_img = add_rounded_corners(img.copy(), radius=int(min(width, height) * 0.035)) # 自适应圆角大小
blurred_background.paste(rounded_img, (delta_x, delta_y_top), rounded_img) # 添加蒙版参数
print(f"背景处理 {index}/{ii}: {os.path.basename(image_path)}")
return blurred_background
def calculate_padding(dimension: int, ratio: float) -> int:
"""计算扩展边距"""
return max(1, int(dimension * ratio))
def create_background(
img: Image.Image,
new_width: int,
new_height: int,
delta_x: int,
delta_y_top: int,
delta_y_bottom: int
) -> Image.Image:
"""创建扩展背景画布"""
background = Image.new("RGB", (new_width, new_height))
width, height = img.size
# 填充顶部区域
if delta_y_top > 0:
top_section = img.crop((0, 0, width, delta_y_top))
background.paste(top_section.resize((new_width, delta_y_top)), (0, 0))
# 填充底部区域
if delta_y_bottom > 0:
bottom_section = img.crop((0, height - delta_y_bottom, width, height))
background.paste(bottom_section.resize((new_width, delta_y_bottom)),
(0, delta_y_top + height))
# 填充两侧区域
if delta_x > 0:
# 左侧
left_section = img.crop((0, 0, delta_x, height))
background.paste(left_section.resize((delta_x, height)), (0, delta_y_top))
# 右侧
right_section = img.crop((width - delta_x, 0, width, height))
background.paste(right_section.resize((delta_x, height)),
(width + delta_x, delta_y_top))
# 添加阴影效果
apply_drop_shadow(background, img, (delta_x, delta_y_top))
# 粘贴原图到中心
background.paste(img, (delta_x, delta_y_top))
return background
def get_exif_data(img: Image.Image) -> str:
"""从图片中提取EXIF信息"""
exif_info = {}
try:
# 通过piexif获取完整EXIF数据
exif_dict = piexif.load(img.info.get("exif", b""))
for ifd in ("0th", "Exif", "GPS", "1st"):
for tag, value in exif_dict.get(ifd, {}).items():
tag_name = piexif.TAGS[ifd][tag]["name"]
exif_info[tag_name] = value
except Exception:
pass
# 解析拍摄参数
focal_length = parse_exif_value(
exif_info.get("FocalLength", exif_info.get("FocalLengthIn35mmFilm")))
aperture = parse_exif_value(exif_info.get("FNumber"))
exposure = parse_exif_value(exif_info.get("ExposureTime"))
iso = parse_exif_value(exif_info.get("ISOSpeedRatings", exif_info.get("PhotographicSensitivity")))
# 格式化输出字符串
return format_exif_string(focal_length, aperture, exposure, iso)
def parse_exif_value(value) -> float:
"""解析EXIF数值"""
if isinstance(value, tuple):
return float(value[0]) / float(value[1])
if isinstance(value, (int, float)):
return float(value)
return None
def format_exif_string(focal: float, aperture: float, exposure: float, iso: float) -> str:
"""格式化EXIF信息为字符串"""
focal_str = f"{int(focal)}mm" if focal else "--mm"
aperture_str = f"f/{aperture:.1f}" if aperture else "f/--"
if exposure:
shutter = f"1/{int(1 / exposure)}s" if exposure < 1 else f"{exposure:.0f}s"
else:
shutter = "--s"
iso_str = f"ISO{int(iso)}" if iso else "ISO---"
return f"{focal_str} {aperture_str} {shutter} {iso_str}"
def calculate_brightness(img: Image.Image) -> float:
"""计算图片区域的平均亮度"""
gray_img = img.convert("L")
pixels = list(gray_img.getdata())
return sum(pixels) / len(pixels)
def add_watermark(
background_img: Image.Image,
original_path: str,
index, ii
) -> None:
"""为图片添加水印和EXIF信息
Args:
background_img: 背景处理后的图片对象
original_path: 原始图片路径
index: 当前处理序号
"""
try:
with Image.open(original_path) as original_img:
output_dir = os.path.join(os.getcwd(), OUTPUT_DIRS["final"])
os.makedirs(output_dir, exist_ok=True)
output_path = os.path.join(
output_dir,
f"{os.path.splitext(os.path.basename(original_path))[0]}_信息模糊水印处理"
f"{os.path.splitext(original_path)[1]}"
)
# 准备透明图层
rgba_image = background_img.convert("RGBA")
width, height = rgba_image.size
# 检测底部亮度
crop_height = max(50, int(height * 0.1))
bottom_region = rgba_image.crop((
width * 0.45,
height - crop_height,
width * 0.55,
height
))
brightness = calculate_brightness(bottom_region)
# 选择水印类型
watermark_type = "light" if brightness > BRIGHTNESS_THRESHOLD else "dark"
watermark_img = load_watermark_image(
WATERMARKS[watermark_type],
width,
height
)
# 合成水印
composite_image = composite_watermark(rgba_image, watermark_img)
# 添加文字信息
final_image = add_exif_text(
composite_image,
original_img,
watermark_img.size,
watermark_type
)
# 保存结果
final_image.convert(original_img.mode).save(
output_path,
format=original_img.format,
**original_img.info
)
print(f"水印处理 {index}/{ii}: {os.path.basename(original_path)}")
except Exception as e:
print(f"处理失败: {original_path} - {str(e)}")
def load_watermark_image(path: str, img_width: int, img_height: int) -> Image.Image:
"""加载并调整水印尺寸"""
with Image.open(path) as watermark:
max_width = img_width * 0.1 if img_width > img_height else img_width * 0.13
scaling_factor = min(max_width / watermark.width, 1.0)
return watermark.resize(
(int(watermark.width * scaling_factor),
int(watermark.height * scaling_factor)),
Image.LANCZOS
).convert("RGBA")
def composite_watermark(base_img: Image.Image, watermark: Image.Image) -> Image.Image:
"""合成水印到基础图片"""
if base_img.width > base_img.height:
position = (
(base_img.width - watermark.width) // 2,
base_img.height - int(watermark.height * 1.43)
)
else:
position = (
(base_img.width - watermark.width) // 2,
base_img.height - int(watermark.height * 1.6)
)
composite = Image.new("RGBA", base_img.size)
composite.paste(watermark, position, watermark)
return Image.alpha_composite(base_img, composite)
def add_exif_text(
image: Image.Image,
original_img: Image.Image,
watermark_size: tuple,
watermark_type: str
) -> Image.Image:
"""添加EXIF文字信息"""
draw = ImageDraw.Draw(image)
exif_text = get_exif_data(original_img)
# 计算文字参数
text_size = int(watermark_size[1] * 0.25)
try:
font = ImageFont.truetype(FONT_PATH, text_size)
except IOError:
font = ImageFont.load_default(text_size)
# 文字位置计算
text_y = image.height - int(image.height * 0.027) if image.width > image.height else image.height - int(
image.height * 0.025)
text_width = font.getlength(exif_text)
text_x = (image.width - text_width) // 2
# 文字颜色设置
text_color = "white" if watermark_type == "dark" else "black"
shadow_color = "black" if text_color == "white" else "white"
# 添加文字阴影
for dx, dy in [(-1, -1), (-1, 1), (1, -1), (1, 1)]:
draw.text((text_x + dx, text_y + dy), exif_text, font=font, fill=shadow_color)
# 添加主文字
draw.text((text_x, text_y), exif_text, font=font, fill=text_color)
return image
if __name__ == "__main__":
supported_extensions = (".png", ".jpg", ".jpeg", ".bmp", ".gif", ".webp")
image_files = [
f for f in os.listdir(".")
if f.lower().endswith(supported_extensions)
]
i = 1
for idx, filename in enumerate(image_files, 1):
processed_bg = process_image(filename, i, len(image_files))
add_watermark(processed_bg, filename, i, len(image_files))
i += 1
print("处理完成!按回车键退出...")
input()
技术亮点
智能适应系统
- 根据图片尺寸自动计算:
- 水印文字大小
- 模糊半径(基于图片分辨率)
- 扩展区域比例
EXIF深度集成
- 读取拍摄信息的核心代码
使用指南
环境准备
包含PIL、os、glob、piexif、Pillow
等依赖库
典型工作流
- 原始图片整理到
/input
目录 - 运行处理脚本
- 处理结果输出到
/output
目录 - 使用拼接.py生成作品集长图
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 52_Hertz!
评论