商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > 基于Python+OpenCV实现图片批量缩放与加水印功能

基于Python+OpenCV实现图片批量缩放与加水印功能

  发布于2026-05-21 阅读(0)

扫一扫,手机访问

处理图片是很多工作中的高频需求,比如商品图要统一尺寸、活动照片需要批量压缩、资料截图得加上公司水印。如果文件夹里有几十上百张图片,手动操作简直让人头皮发麻。

这类重复性劳动,正是Python自动化脚本大显身手的地方。今天,我们就用opencv-python这个库,从零开始,一步步搭建一个能“批量缩放并添加水印”的实用工具。整个过程不涉及复杂的图像算法,非常适合Python初学者上手实践。

基于Python+OpenCV实现图片批量缩放与加水印功能

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数组。它的形状(shape)遵循(高度, 宽度, 通道数)的格式。比如一张1080p的彩色图片,打印出来的形状可能就是(1080, 1920, 3)

3. 图片缩放

改变图片尺寸,主要靠cv2.resize()函数。

3.1 按指定宽高缩放

直接指定目标宽度和高度即可,但要注意参数顺序是(宽度, 高度),别弄反了。

resized = cv2.resize(image, (800, 600))

3.2 按比例缩放

如果想等比例缩小一半,可以设置缩放因子:

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. 添加文字水印

使用cv2.putText()函数可以在图片上绘制文字。它的参数比较多,但都很好理解:

watermarked = image.copy()
cv2.putText(
    watermarked,
    text="Python OpenCV",      # 水印文字
    org=(30, 60),             # 文字左下角坐标(x, y)
    fontFace=cv2.FONT_HERSHEY_SIMPLEX, # 字体
    fontScale=1.2,            # 字体大小
    color=(255, 255, 255),    # 文字颜色(BGR顺序)
    thickness=2,              # 线条粗细
    lineType=cv2.LINE_AA      # 抗锯齿,让边缘更平滑
)

需要特别提醒的是,OpenCV默认使用BGR颜色通道,而不是常见的RGB。所以纯红色是(0, 0, 255),而不是(255, 0, 0)

5. 半透明文字水印

直接把不透明的文字画上去会显得很生硬。更优雅的做法是,先在一张 overlay(覆盖层)上画好文字,再通过图像混合,以一定的透明度叠加到原图上。

overlay = image.copy()
output = image.copy()
# 在overlay上绘制文字
cv2.putText(overlay, "Python OpenCV", (30, 60), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (255, 255, 255), 2, cv2.LINE_AA)
# 混合图像,alpha值控制水印透明度
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. 中文路径兼容处理

很多朋友在Windows环境下会遇到一个坑:当文件路径或名称包含中文时,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

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文件夹里放着你的原始图片。运行后,output_images文件夹会生成处理后的新图片。每张图片都会经历两个变化:

  1. 如果原图宽度超过了设定的max_width(比如1200像素),它会被等比例缩放到这个最大宽度。
  2. 图片的右下角会添加指定的半透明文字水印。

得益于中文路径兼容函数,即使你的文件夹或文件名全是中文,整个过程也能畅通无阻。

10. 常见问题

10.1 为什么 OpenCV 的颜色是 BGR

这是OpenCV的历史遗留设计。它默认使用BGR通道顺序,而许多其他库(如PIL、matplotlib)使用RGB。处理颜色时记住这个区别即可。

10.2 PNG 透明背景会保留吗

本文代码使用cv2.IMREAD_COLOR模式读取,会忽略透明通道(Alpha通道)。如果需要处理带透明背景的PNG,应使用cv2.IMREAD_UNCHANGED读取,并在后续步骤中妥善处理Alpha通道。

10.3 水印位置如何调整

水印位置由add_text_watermark函数中的坐标计算逻辑控制。默认是放在右下角。如果想放到左上角,可以修改坐标计算部分:

x = margin
y = text_height + margin

10.4 如何递归处理子文件夹

当前代码只处理输入目录的第一层。要递归处理所有子目录,可以将遍历部分从input_dir.iterdir()改为input_dir.rglob("*")。同时,生成输出路径时需要考虑原文件的相对路径,避免不同子目录下的同名文件相互覆盖。

总结

通过这个项目,我们串联起了OpenCV图像处理的几个核心环节:图片的读取与保存、等比例缩放、添加文字水印以及实现半透明效果。同时,也解决了实际开发中常见的中文路径问题和批量文件遍历需求。

掌握了这些基础,你就可以以此为核心,扩展出更多实用功能,比如批量格式转换、添加Logo图片水印、递归处理复杂目录结构、生成详细处理报告等,逐步打造出一个真正贴合自身工作流的自动化图片处理工具。

本文转载于:https://www.jb51.net/python/363522gn2.htm 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注