Python + OpenCV 实战:图片批量缩放与加水印教程
Python + OpenCV 实战:图片批量缩放与加水印教程
在日常工作中,经常会遇到这样的图片处理需求:
- 商品图片上传前需要统一尺寸。
- 活动照片需要批量压缩后再发送。
- 资料截图需要加上公司名或项目名水印。
- 文件夹里有几十张、几百张图片,不适合一张张手工处理。
这些任务非常适合用 Python 自动化完成。本文使用opencv-python库,带你从图片读取、保存、缩放、添加文字水印开始,逐步完成一个“批量缩放并加水印”的完整小项目。
文章适合刚接触图像处理的 Python 初学者阅读,不要求你提前掌握复杂的图像算法。
1. OpenCV 是什么
OpenCV 是一个常用的计算机视觉和图像处理库,可以完成图片读取、图片保存、尺寸变换、颜色处理、绘图、视频处理、人脸检测、目标检测等任务。
在 Python 中,我们通常安装的是opencv-python:
pipinstallopencv-python安装完成后,可以在 Python 中导入:
importcv2print(cv2.__version__)如果能正常打印版本号,说明安装成功。
2. 图片读取与保存
OpenCV 读取图片使用cv2.imread(),保存图片使用cv2.imwrite()。
importcv2 image=cv2.imread("input.jpg")ifimageisNone:raiseFileNotFoundError("图片读取失败,请检查文件路径")cv2.imwrite("output.jpg",image)需要注意的是,OpenCV 读取到的图片是一个 NumPy 数组。它的形状通常是:
(高度, 宽度, 通道数)例如:
print(image.shape)输出可能是:
(1080, 1920, 3)这表示图片高度是 1080,宽度是 1920,通道数是 3。
3. 图片缩放
图片缩放使用cv2.resize()。
3.1 按指定宽高缩放
resized=cv2.resize(image,(800,600))cv2.imwrite("resized.jpg",resized)注意:cv2.resize()的尺寸参数顺序是(宽度, 高度),不是(高度, 宽度)。
3.2 按比例缩放
如果希望图片按 50% 缩小:
resized=cv2.resize(image,None,fx=0.5,fy=0.5)其中:
fx表示宽度缩放比例。fy表示高度缩放比例。
3.3 按目标宽度等比例缩放
实际项目中,更常见的是限制图片宽度,同时保持原始宽高比:
height,width=image.shape[:2]target_width=1000scale=target_width/width target_height=int(height*scale)resized=cv2.resize(image,(target_width,target_height))这样不会把图片拉伸变形。
4. 添加文字水印
OpenCV 可以使用cv2.putText()在图片上绘制文字。
watermarked=image.copy()cv2.putText(watermarked,text="Python OpenCV",org=(30,60),fontFace=cv2.FONT_HERSHEY_SIMPLEX,fontScale=1.2,color=(255,255,255),thickness=2,lineType=cv2.LINE_AA)cv2.imwrite("watermarked.jpg",watermarked)参数说明:
text:水印文字。org:文字左下角坐标,格式是(x, y)。fontFace:字体类型。fontScale:字体大小。color:文字颜色,OpenCV 使用 BGR 顺序,不是 RGB。thickness:文字粗细。lineType:抗锯齿方式,cv2.LINE_AA会让文字边缘更平滑。
5. 半透明文字水印
直接把文字画到图片上会比较生硬。更常见的做法是使用一张透明图层,再和原图混合。
overlay=image.copy()output=image.copy()cv2.putText(overlay,"Python OpenCV",(30,60),cv2.FONT_HERSHEY_SIMPLEX,1.2,(255,255,255),2,cv2.LINE_AA)alpha=0.35watermarked=cv2.addWeighted(overlay,alpha,output,1-alpha,0)这里的alpha表示水印透明度。值越大,水印越明显;值越小,水印越淡。
6. 批量处理整个文件夹图片
批量处理的基本思路是:
- 遍历输入文件夹。
- 找到所有图片文件。
- 读取每张图片。
- 缩放图片。
- 添加水印。
- 保存到输出文件夹。
常见图片后缀包括:
IMAGE_EXTENSIONS={".jpg",".jpeg",".png",".bmp",".webp"}遍历文件夹可以使用pathlib:
frompathlibimportPath input_dir=Path("input_images")forfile_pathininput_dir.iterdir():iffile_path.suffix.lower()inIMAGE_EXTENSIONS:print(file_path)7. 中文路径兼容处理
很多初学者会遇到一个问题:图片路径里有中文时,cv2.imread()可能读取失败,cv2.imwrite()也可能保存失败。
为了更稳定地兼容中文路径,可以使用numpy.fromfile()+cv2.imdecode()读取图片,用cv2.imencode()+tofile()保存图片。
7.1 兼容中文路径读取
importcv2importnumpyasnpdefimread_unicode(file_path):data=np.fromfile(str(file_path),dtype=np.uint8)image=cv2.imdecode(data,cv2.IMREAD_COLOR)returnimage7.2 兼容中文路径保存
defimwrite_unicode(file_path,image):ext=file_path.suffix success,encoded_image=cv2.imencode(ext,image)ifnotsuccess:returnFalseencoded_image.tofile(str(file_path))returnTrue这两个函数在 Windows 中文目录、中文文件名场景下非常实用。
8. 完整项目代码
下面是完整可运行代码。建议保存为batch_resize_watermark.py。
项目目录示例:
image_project/ ├─ batch_resize_watermark.py ├─ input_images/ │ ├─ 示例图片1.jpg │ ├─ 示例图片2.png │ └─ 示例图片3.webp └─ output_images/完整代码:
frompathlibimportPathimportcv2importnumpyasnp IMAGE_EXTENSIONS={".jpg",".jpeg",".png",".bmp",".webp"}defimread_unicode(file_path:Path):"""兼容中文路径的图片读取。"""data=np.fromfile(str(file_path),dtype=np.uint8)image=cv2.imdecode(data,cv2.IMREAD_COLOR)returnimagedefimwrite_unicode(file_path:Path,image)->bool:"""兼容中文路径的图片保存。"""file_path.parent.mkdir(parents=True,exist_ok=True)ext=file_path.suffix success,encoded_image=cv2.imencode(ext,image)ifnotsuccess:returnFalseencoded_image.tofile(str(file_path))returnTruedefresize_keep_ratio(image,max_width:int=1200):"""按最大宽度等比例缩放图片。"""height,width=image.shape[:2]ifwidth<=max_width:returnimage.copy()scale=max_width/width target_height=int(height*scale)resized=cv2.resize(image,(max_width,target_height),interpolation=cv2.INTER_AREA)returnresizeddefadd_text_watermark(image,text:str,alpha:float=0.35,margin:int=30):"""在图片右下角添加半透明文字水印。"""output=image.copy()overlay=image.copy()height,width=image.shape[:2]font_face=cv2.FONT_HERSHEY_SIMPLEX font_scale=max(width/1200,0.7)thickness=max(int(width/600),1)text_size,baseline=cv2.getTextSize(text,font_face,font_scale,thickness)text_width,text_height=text_size x=max(width-text_width-margin,margin)y=max(height-margin,text_height+margin)# 先画一层深色阴影,提高浅色背景下的可读性cv2.putText(overlay,text,(x+2,y+2),font_face,font_scale,(0,0,0),thickness+1,cv2.LINE_AA)# 再画白色文字cv2.putText(overlay,text,(x,y),font_face,font_scale,(255,255,255),thickness,cv2.LINE_AA)watermarked=cv2.addWeighted(overlay,alpha,output,1-alpha,0)returnwatermarkeddefprocess_image(input_path:Path,output_path:Path,watermark_text:str,max_width:int)->bool:"""处理单张图片:读取、缩放、加水印、保存。"""image=imread_unicode(input_path)ifimageisNone:print(f"读取失败:{input_path}")returnFalseresized=resize_keep_ratio(image,max_width=max_width)watermarked=add_text_watermark(resized,watermark_text)success=imwrite_unicode(output_path,watermarked)ifnotsuccess:print(f"保存失败:{output_path}")returnFalsereturnTruedefbatch_process_images(input_dir:Path,output_dir:Path,watermark_text:str="Python OpenCV",max_width:int=1200)->None:"""批量处理文件夹中的图片。"""ifnotinput_dir.exists():raiseFileNotFoundError(f"输入文件夹不存在:{input_dir}")output_dir.mkdir(parents=True,exist_ok=True)image_files=[file_pathforfile_pathininput_dir.iterdir()iffile_path.is_file()andfile_path.suffix.lower()inIMAGE_EXTENSIONS]ifnotimage_files:print(f"没有找到可处理的图片:{input_dir}")returnsuccess_count=0forinput_pathinimage_files:output_path=output_dir/input_path.name success=process_image(input_path=input_path,output_path=output_path,watermark_text=watermark_text,max_width=max_width)ifsuccess:success_count+=1print(f"处理完成:{input_path.name}")print(f"批量处理结束,成功{success_count}/{len(image_files)}张")print(f"输出目录:{output_dir.resolve()}")defmain():input_dir=Path("input_images")output_dir=Path("output_images")batch_process_images(input_dir=input_dir,output_dir=output_dir,watermark_text="Python OpenCV",max_width=1200)if__name__=="__main__":main()运行脚本:
python batch_resize_watermark.py如果运行成功,终端会输出类似信息:
处理完成: 示例图片1.jpg 处理完成: 示例图片2.png 处理完成: 示例图片3.webp 批量处理结束,成功 3/3 张 输出目录: D:\image_project\output_images9. 效果演示说明
运行前,input_images文件夹中存放原始图片:
input_images/ ├─ 风景照片.jpg ├─ 商品主图.png └─ 活动现场.webp运行脚本后,output_images文件夹会生成同名图片:
output_images/ ├─ 风景照片.jpg ├─ 商品主图.png └─ 活动现场.webp处理后的图片会有两个变化:
- 如果原图宽度超过
max_width,会被等比例缩放到指定最大宽度,例如 1200 像素。 - 图片右下角会添加半透明文字水印,例如
Python OpenCV。
由于代码使用了中文路径兼容读写函数,即使文件名是风景照片.jpg、目录名是测试图片,也可以正常读取和保存。
10. 常见问题
10.1 为什么 OpenCV 的颜色是 BGR
OpenCV 默认使用 BGR 通道顺序,而很多其他库使用 RGB。比如白色在 OpenCV 中是:
(255,255,255)红色不是(255, 0, 0),而是:
(0,0,255)10.2 PNG 透明背景会保留吗
本文代码使用cv2.IMREAD_COLOR读取图片,会把图片读成三通道 BGR,透明通道不会保留。如果你需要保留透明背景,可以使用cv2.IMREAD_UNCHANGED读取,并额外处理 alpha 通道。
10.3 水印位置如何调整
代码中的水印位置由下面几行控制:
x=max(width-text_width-margin,margin)y=max(height-margin,text_height+margin)这是右下角位置。如果想放到左上角,可以改成:
x=margin y=text_height+margin10.4 如何递归处理子文件夹
当前代码只处理输入目录第一层图片。如果要递归处理子文件夹,可以把:
input_dir.iterdir()改成:
input_dir.rglob("*")同时输出路径可以根据相对路径生成,避免不同子目录下的同名图片互相覆盖。
总结
本文完成了一个适合图像处理初学者的 OpenCV 实战项目:批量缩放文件夹图片,并添加半透明文字水印。这个项目虽然不复杂,但覆盖了图片自动化处理中的几个核心能力:
- 使用
opencv-python读取和保存图片。 - 使用
cv2.resize()等比例缩放图片。 - 使用
cv2.putText()添加文字水印。 - 使用
cv2.addWeighted()实现半透明效果。 - 使用
pathlib批量遍历文件夹。 - 使用
imdecode和imencode兼容中文路径。
掌握这些基础之后,你可以继续扩展更多功能,例如图片压缩、格式转换、添加 Logo 水印、递归处理子目录、生成处理日志等,把它变成真正适合日常工作的图片批处理工具。
