基于Python+OpenCV实现图片批量缩放与加水印功能
作者:yuanpan
在日常工作中,经常会遇到这样的图片处理需求:
- 商品图片上传前需要统一尺寸。
- 活动照片需要批量压缩后再发送。
- 资料截图需要加上公司名或项目名水印。
- 文件夹里有几十张、几百张图片,不适合一张张手工处理。
这些任务非常适合用 Python 自动化完成。本文使用 opencv-python 库,带你从图片读取、保存、缩放、添加文字水印开始,逐步完成一个“批量缩放并加水印”的完整小项目。
文章适合刚接触图像处理的 Python 初学者阅读,不要求你提前掌握复杂的图像算法。
1. OpenCV 是什么
OpenCV 是一个常用的计算机视觉和图像处理库,可以完成图片读取、图片保存、尺寸变换、颜色处理、绘图、视频处理、人脸检测、目标检测等任务。
在 Python 中,我们通常安装的是 opencv-python:
pip install opencv-python
安装完成后,可以在 Python 中导入:
import cv2 print(cv2.__version__)
如果能正常打印版本号,说明安装成功。
2. 图片读取与保存
OpenCV 读取图片使用 cv2.imread(),保存图片使用 cv2.imwrite()。
import cv2
image = cv2.imread("input.jpg")
if image is None:
raise FileNotFoundError("图片读取失败,请检查文件路径")
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 = 1000 scale = 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.35
watermarked = cv2.addWeighted(overlay, alpha, output, 1 - alpha, 0)
这里的 alpha 表示水印透明度。值越大,水印越明显;值越小,水印越淡。
6. 批量处理整个文件夹图片
批量处理的基本思路是:
- 遍历输入文件夹。
- 找到所有图片文件。
- 读取每张图片。
- 缩放图片。
- 添加水印。
- 保存到输出文件夹。
常见图片后缀包括:
IMAGE_EXTENSIONS = {".jpg", ".jpeg", ".png", ".bmp", ".webp"}
遍历文件夹可以使用 pathlib:
from pathlib import Path
input_dir = Path("input_images")
for file_path in input_dir.iterdir():
if file_path.suffix.lower() in IMAGE_EXTENSIONS:
print(file_path)
7. 中文路径兼容处理
很多初学者会遇到一个问题:图片路径里有中文时,cv2.imread() 可能读取失败,cv2.imwrite() 也可能保存失败。
为了更稳定地兼容中文路径,可以使用 numpy.fromfile() + cv2.imdecode() 读取图片,用 cv2.imencode() + tofile() 保存图片。
7.1 兼容中文路径读取
import cv2
import numpy as np
def imread_unicode(file_path):
data = np.fromfile(str(file_path), dtype=np.uint8)
image = cv2.imdecode(data, cv2.IMREAD_COLOR)
return image
7.2 兼容中文路径保存
def imwrite_unicode(file_path, image):
ext = file_path.suffix
success, encoded_image = cv2.imencode(ext, image)
if not success:
return False
encoded_image.tofile(str(file_path))
return True
这两个函数在 Windows 中文目录、中文文件名场景下非常实用。
8. 完整项目代码
下面是完整可运行代码。建议保存为 batch_resize_watermark.py。
项目目录示例:
image_project/ ├─ batch_resize_watermark.py ├─ input_images/ │ ├─ 示例图片1.jpg │ ├─ 示例图片2.png │ └─ 示例图片3.webp └─ output_images/
完整代码:
from pathlib import Path
import cv2
import numpy as np
IMAGE_EXTENSIONS = {".jpg", ".jpeg", ".png", ".bmp", ".webp"}
def imread_unicode(file_path: Path):
"""兼容中文路径的图片读取。"""
data = np.fromfile(str(file_path), dtype=np.uint8)
image = cv2.imdecode(data, cv2.IMREAD_COLOR)
return image
def imwrite_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)
if not success:
return False
encoded_image.tofile(str(file_path))
return True
def resize_keep_ratio(image, max_width: int = 1200):
"""按最大宽度等比例缩放图片。"""
height, width = image.shape[:2]
if width <= max_width:
return image.copy()
scale = max_width / width
target_height = int(height * scale)
resized = cv2.resize(
image,
(max_width, target_height),
interpolation=cv2.INTER_AREA
)
return resized
def add_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)
return watermarked
def process_image(
input_path: Path,
output_path: Path,
watermark_text: str,
max_width: int
) -> bool:
"""处理单张图片:读取、缩放、加水印、保存。"""
image = imread_unicode(input_path)
if image is None:
print(f"读取失败: {input_path}")
return False
resized = resize_keep_ratio(image, max_width=max_width)
watermarked = add_text_watermark(resized, watermark_text)
success = imwrite_unicode(output_path, watermarked)
if not success:
print(f"保存失败: {output_path}")
return False
return True
def batch_process_images(
input_dir: Path,
output_dir: Path,
watermark_text: str = "Python OpenCV",
max_width: int = 1200
) -> None:
"""批量处理文件夹中的图片。"""
if not input_dir.exists():
raise FileNotFoundError(f"输入文件夹不存在: {input_dir}")
output_dir.mkdir(parents=True, exist_ok=True)
image_files = [
file_path
for file_path in input_dir.iterdir()
if file_path.is_file() and file_path.suffix.lower() in IMAGE_EXTENSIONS
]
if not image_files:
print(f"没有找到可处理的图片: {input_dir}")
return
success_count = 0
for input_path in image_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
)
if success:
success_count += 1
print(f"处理完成: {input_path.name}")
print(f"批量处理结束,成功 {success_count}/{len(image_files)} 张")
print(f"输出目录: {output_dir.resolve()}")
def main():
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_images
9. 效果演示说明
运行前,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 + margin
10.4 如何递归处理子文件夹
当前代码只处理输入目录第一层图片。如果要递归处理子文件夹,可以把:
input_dir.iterdir()
改成:
input_dir.rglob("*")
同时输出路径可以根据相对路径生成,避免不同子目录下的同名图片互相覆盖。
总结
本文完成了一个适合图像处理初学者的 OpenCV 实战项目:批量缩放文件夹图片,并添加半透明文字水印。这个项目虽然不复杂,但覆盖了图片自动化处理中的几个核心能力:
- 使用
opencv-python读取和保存图片。 - 使用
cv2.resize()等比例缩放图片。 - 使用
cv2.putText()添加文字水印。 - 使用
cv2.addWeighted()实现半透明效果。 - 使用
pathlib批量遍历文件夹。 - 使用
imdecode和imencode兼容中文路径。
掌握这些基础之后,你可以继续扩展更多功能,例如图片压缩、格式转换、添加 Logo 水印、递归处理子目录、生成处理日志等,把它变成真正适合日常工作的图片批处理工具。
以上就是基于Python+OpenCV实现图片批量缩放与加水印功能的详细内容,更多关于Python OpenCV图片批量缩放与加水印的资料请关注脚本之家其它相关文章!
