批量加水印:Python + OpenCV 一次给一文件夹图盖章
一、背景:给图片加水印,别一张张手动来
要把一批图片(作品集、产品图、活动照)都打上水印,Photoshop 一张张打开、拖文字、调位置、导出——十张就烦,一百张能让人崩溃,而且每张位置还可能不齐。这种"对一批文件做同一个确定性操作"的活,最适合写成脚本:指定文件夹、指定水印内容和位置,一条命令全部处理完,结果一致、可重复。
本文用 Python + OpenCV 做一个批量加水印工具:遍历输入目录的图片,在每张右下角叠上半透明水印文字,输出到另一个目录。讲清 OpenCV 怎么读图、怎么画半透明文字、怎么批量遍历。具体 API 以官方文档为准,思路是重点。
二、为什么用 OpenCV 而不是 Pillow
加水印 Pillow 也能做,OpenCV 的优势在"图像就是 numpy 数组",能做更灵活的合成、后续要加缩放/滤波也顺。本文选 OpenCV,一来覆盖常见计算机视觉场景(顺便熟悉 cv2),二来半透明叠加用数组运算很直观。
核心就两步:读图为 BGR 数组 → 在数组上画文字/叠加 → 写回文件。OpenCV 读进来的是 numpy 数组,所有操作都是对数组算,理解这一点后面都好懂。
三、单张加水印:半透明文字
import cv2
def add_watermark(img_path, out_path, text="© demo"):
img = cv2.imread(img_path)
h, w = img.shape[:2]
# 水印位置:右下角留边
pos = (int(w * 0.78), int(h * 0.92))
# 先画不透明底字,再做半透明叠加
cv2.putText(img, text, pos, cv2.FONT_HERSHEY_SIMPLEX,
1.2, (255, 255, 255), 2, cv2.LINE_AA)
cv2.imwrite(out_path, img)
cv2.imread 读成 numpy 数组(注意是 BGR 顺序,不是 RGB);putText 在图上直接画文字,pos 用图片宽高比例算,保证大图小图位置一致;FONT_HERSHEY_SIMPLEX 是内置字体,1.2 字号、(255,255,255) 白色、2 线宽;LINE_AA 抗锯齿让字边缘平滑。画完 imwrite 写回。这里画的是不透明白字,最简单可用。
四、半透明:让水印不挡画面
纯不透明字在某些图上很刺眼。更优雅的是半透明水印——字带 alpha,叠在原图上若隐若现。OpenCV 做半透明要用"alpha 合成",对一片区域混合:
import numpy as np
def add_watermark_alpha(img, text, alpha=0.5):
overlay = img.copy()
cv2.putText(overlay, text, (int(img.shape[1]*0.78), int(img.shape[0]*0.92)),
cv2.FONT_HERSHEY_SIMPLEX, 1.2, (255,255,255), 2, cv2.LINE_AA)
# 原图 * (1-alpha) + 文字层 * alpha
return cv2.addWeighted(overlay, alpha, img, 1 - alpha, 0)
思路是:复制一份图,在上面画白字得到 overlay;再用 addWeighted 把 overlay 按 alpha、img 按 1-alpha 加权相加,得到"原图淡、白字按透明度浮现"的效果。alpha=0.5 即半透明,调小更淡、调大更显。这种合成比直接 putText 观感好很多,是水印的常用做法。
五、批量遍历整个文件夹
单张函数有了,批量就是遍历目录、调函数、结果写另一目录:
import os, glob
def batch(in_dir, out_dir, text):
os.makedirs(out_dir, exist_ok=True)
for path in glob.glob(os.path.join(in_dir, "*.[jp][pn]g")):
name = os.path.basename(path)
img = cv2.imread(path)
if img is None: # 读失败的跳过,不中断整体
print("跳过:", name); continue
out = add_watermark_alpha(img, text)
cv2.imwrite(os.path.join(out_dir, name), out)
print("处理完成")
glob 匹配 jpg/png,逐张读、处理、写到 out_dir。两点保护:第一,os.makedirs(..., exist_ok=True) 目标目录不存在就建;第二,读图返回 None(文件损坏或非图片)时 continue 跳过、打印提示,不让一张坏图中断整个批处理。out_dir 和 in_dir 分开,符合"保留原始、产出新的"原则,不会覆盖原图。
六、几个注意点
第一,中文水印可能变方块。OpenCV 的 putText 用的是内置位图字体,不含中文字形,画中文会显示成方块或乱码。要打中文水印,得用支持中文的字体文件(如把文字先用 Pillow/FreeType 渲染成图再叠加),或加载 TTF 字体。本文用纯英文/符号水印规避,需要中文请换字体方案,具体以所选库文档为准。
第二,BGR 顺序坑。OpenCV 读图是 BGR,(255,255,255) 在你以为是白、其实是蓝红通道对调的"白"(巧合白色不受影响),但指定彩色(如红色 (0,0,255))时要记住是 BGR,写 (0,0,255) 才是红。和 matplotlib/PIL 的 RGB 搞混是常见 bug。
第三,输出格式与质量。imwrite 写 jpg 默认有压缩,大量处理可能累积劣化;要保质量可指定 cv2.imwrite(out, img, [cv2.IMWRITE_JPEG_QUALITY, 95]),或输出 png 无损。按你对画质和体积的要求选。
第四,尺寸与字号。字号用固定 1.2 在不同分辨率图上视觉大小不一。更稳的是按图片短边比例算字号(font_scale = h / 720 之类),保证大图小图水印占比一致,别小图水印占半屏、大图小到看不见。
第五,别覆盖原图。再次强调 out_dir 单独放,万一水印参数不满意,原图还在能重跑。批量操作覆盖原始文件是高危动作,务必输出到新目录。
七、小结
批量加水印 = 单张叠加函数 + 目录遍历。单张用 cv2.imread 读成数组、putText 画字、addWeighted 做半透明、imwrite 写出;批量用 glob 遍历、结果写独立 out_dir、读失败跳过不中断。核心坑:OpenCV 内置字体不支持中文(要中文换字体方案)、读图是 BGR 顺序、字号按图比例算才一致、输出到新目录别覆盖原图。具体 API 参数以官方文档为准。
- 点赞
- 收藏
- 关注作者
评论(0)