Python图像处理:批量添加水印的优雅实现与进阶技巧

1. 简介

在日常图像处理中,为图片添加水印是一项常见任务。有多种方法和工具可供选择,而今天我们将专注于使用Python语言结合PIL库批量添加水印。

需要注意的是,所选用的图片格式不应为JPG或JPEG,因为这两种格式的图片不支持透明度设置。

2. PIL库概述

先前的文章已经详细介绍过PIL库,这里不再赘述。

  • PIL是Python的图像处理库,支持多种文件格式。
  • PIL提供强大的图像和图形处理功能,包括缩放、裁剪、叠加以及添加线条、文字等操作。
  • 安装PIL库可使用以下命令:
python 复制代码
pip install Pillow

3. PIL库中涉及的类

模块或类 说明
image模块 用于图像处理
ImageDraw 2D图像对象
ImageFont 字体存储
ImageEnhance 图像增强

4. 实现原理

本文的主要目标是批量为某个文件夹下的图片添加水印,实现原理如下:

  • 设置水印内容;
  • 使用Image对象的open()方法打开原始图片;
  • 使用Image对象的new()方法创建存储水印图片的对象;
  • 使用ImageDraw.Draw对象的text()方法绘制水印文字;
  • 使用ImageEnhance中Brightness的enhance()方法设置水印透明度。

5. 实现过程

5.1 原始图片

设定原始图片的存储目录,例如:

python 复制代码
F:\python_study\image\image01

5.2 导入相关模块

导入所需的PIL模块或类:

python 复制代码
from PIL imort Image, ImageDraw, ImageFont, ImageEnhance
import os

5.3 初始化数据

通过用户手动输入相关信息,如图片存储路径、水印文字、水印位置、水印透明度等:

python 复制代码
class WatermarkText():
    def __init__(self):
        super(WatermarkText, self).__init__()
        self.image_path = input('图片路径:')
        self.watermark_text = input('水印文字:')
        self.position_flag = int(input('水印位置(1:左上角,2:左下角,3:右上角,4:右下角,5:居中):'))
        self.opacity = float(input('水印透明度(0---1之间的1位小数):'))

5.4 水印字体设置

选择系统字体库中的字体:

python 复制代码
self.font = ImageFont.truetype("cambriab.ttf", size=35)

5.5 打开原始图片并创建存储对象

打开原始图片并转换为RGBA:

python 复制代码
image = Image.open(img).convert('RGBA')

创建绘制对象:

python 复制代码
new_img = Image.new('RGBA', image.size, (255, 255, 255, 0))
image_draw = ImageDraw.Draw(new_img)

5.6 计算图片和水印的大小

计算图片大小:

python 复制代码
w, h = image.size

计算文字大小:

python 复制代码
w1 = self.font.getsize(self.watermark_text)[0]
h1 = self.font.getsize(self.watermark_text)[1]

5.7 选择性设置水印文字

通过if语句实现:

python 复制代码
if self.position_flag == 1:  # 左上角
    location = (0, 0)
elif self.position_flag == 2:  # 左下角
    location = (0, h - h1)
elif self.position_flag == 3:  # 右上角
    location = (w - w1, 0)
elif self.position_flag == 4:  # 右下角
    location = (w - w1, h - h1)
elif self.position_flag == 5:  # 居中
    location = (h/2, h/2)

5.8 绘制文字并设置透明度

绘制文字:

python 复制代码
image_draw.text(location, self.watermark_text, font=self.font, fill="blue")

设置透明度:

python 复制代码
transparent = new_img.split()[3]
transparent = ImageEnhance.Brightness(transparent).enhance(self.opacity)
new_img.putalpha(transparent)

Image.alpha_composite(image, new_img).save(img)

5.9 遍历获取图片文件并调用绘制方法

python 复制代码
watermark_text = WatermarkText()
try:
    file_list = os.listdir(watermark_text.image_path)
    for i in range(0, len(file_list)):
        filepath = os.path.join(watermark_text.image_path, file_list[i])
        if os.path.isfile(filepath):
            filetype = os.path.splitext(filepath)[1]
            if filetype == '.png':
                watermark_text.add_text_watermark(filepath)
            else:
                print("图片格式有误,请使用png格式图片")
    print('批量添加水印完成')
except:
    print('输入的文件路径有误,请检查~~')

6. 完整源码

python 复制代码
from PIL import

 Image, ImageDraw, ImageFont, ImageEnhance
import os

class WatermarkText():
    def __init__(self):
        super(WatermarkText, self).__init__()
        self.image_path = input('图片路径:')
        self.watermark_text = input('水印文字:')
        self.position_flag = int(input('水印位置(1:左上角,2:左下角,3:右上角,4:右下角,5:居中):'))
        self.opacity = float(input('水印透明度(0---1之间的1位小数):'))

        # 设置字体
        self.font = ImageFont.truetype("cambriab.ttf", size=35)

    # 文字水印
    def add_text_watermark(self, img):
        global location
        image = Image.open(img).convert('RGBA') 
        new_img = Image.new('RGBA', image.size, (255, 255, 255, 0)) 
        image_draw = ImageDraw.Draw(new_img) 
        w, h = image.size  # 图片大小
        w1 = self.font.getsize(self.watermark_text)[0]  # 字体宽度
        h1 = self.font.getsize(self.watermark_text)[1]  # 字体高度

        # 设置水印文字位置
        if self.position_flag == 1:  # 左上角
            location = (0, 0)
        elif self.position_flag == 2:  # 左下角
            location = (0, h - h1)
        elif self.position_flag == 3:  # 右上角
            location = (w - w1, 0)
        elif self.position_flag == 4:  # 右下角
            location = (w - w1, h - h1)
        elif self.position_flag == 5:  # 居中
            location = (h/2, h/2)
        # 绘制文字
        image_draw.text(location, self.watermark_text, font=self.font, fill="blue")

        # 设置透明度
        transparent = new_img.split()[3]
        transparent = ImageEnhance.Brightness(transparent).enhance(self.opacity)
        new_img.putalpha(transparent)

        Image.alpha_composite(image, new_img).save(img)

if __name__ == "__main__":
    watermark_text = WatermarkText()
    try:
        file_list = os.listdir(watermark_text.image_path) 
        for i in range(0, len(file_list)): 
            filepath = os.path.join(watermark_text.image_path, file_list[i])
            if os.path.isfile(filepath): 
                filetype = os.path.splitext(filepath)[1] 
                if filetype == '.png': 
                    watermark_text.add_text_watermark(filepath) 
                else:
                    print("图片格式有误,请使用png格式图片")
        print('批量添加水印完成')
    except:
        print('输入的文件路径有误,请检查~~')

7. 效果展示

运行过程:

python 复制代码
D:\Python37\python.exe F:/python_study/python_project/watermark_text.py
图片路径:F:\python_study\image\image01
水印文字:
水印位置(1:左上角,2:左下角,3:右上角,4:右下角,5:居中):1
水印透明度(0---1之间的1位小数):0.5
F:/python_study/python_project/watermark_text.py:32: DeprecationWarning: getsize is deprecated and will be removed in Pillow 10 (2023-07-01). Use getbbox or getlength instead.
  w1 = self.font.getsize(self.watermark_text)[0]  # 获取字体宽度
F:/python_study/python_project/watermark_text.py:33: DeprecationWarning: getsize is deprecated and will be removed in Pillow 10 (2023-07-01). Use getbbox or getlength instead.
  h1 = self.font.getsize(self.watermark_text)[1]  # 获取字体高度
批量添加水印完成

8. 改进与建议

8.1 参数输入方式优化

在初始化数据的部分,我们可以考虑通过命令行参数或配置文件的方式输入相关信息,以提高用户体验。例如使用argparse库来解析命令行参数。

python 复制代码
import argparse

class WatermarkText():
    def __init__(self):
        parser = argparse.ArgumentParser(description='Add watermark to images.')
        parser.add_argument('--image_path', type=str, help='Path to the image directory.')
        parser.add_argument('--watermark_text', type=str, help='Text for watermark.')
        parser.add_argument('--position_flag', type=int, help='Position flag for watermark (1: top-left, 2: bottom-left, 3: top-right, 4: bottom-right, 5: center).')
        parser.add_argument('--opacity', type=float, help='Opacity for watermark (0-1 with 1 decimal place).')
        
        args = parser.parse_args()

        self.image_path = args.image_path or input('Image path: ')
        self.watermark_text = args.watermark_text or input('Watermark text: ')
        self.position_flag = args.position_flag or int(input('Watermark position (1: top-left, 2: bottom-left, 3: top-right, 4: bottom-right, 5: center): '))
        self.opacity = args.opacity or float(input('Watermark opacity (0-1 with 1 decimal place): '))

8.2 异常处理改进

在处理异常的部分,我们可以更具体地捕获异常类型,并提供更友好的提示信息。

python 复制代码
try:
    # existing code...
except FileNotFoundError:
    print('Error: The specified image directory does not exist.')
except PermissionError:
    print('Error: Permission denied to access the specified image directory.')
except Exception as e:
    print(f'An unexpected error occurred: {e}')

8.3 代码结构优化

可以考虑将一些功能模块化,提高代码的可读性和维护性。例如,将文字水印的添加功能独立成一个方法。

python 复制代码
class WatermarkText():
    # existing code...

    def add_text_watermark(self, img):
        # existing code...

8.4 日志记录

考虑在程序中添加日志记录,记录关键步骤和出错信息,以便于排查问题。

python 复制代码
import logging

logging.basicConfig(level=logging.INFO)

class WatermarkText():
    # existing code...

    def add_text_watermark(self, img):
        try:
            # existing code...
            logging.info(f'Successfully added watermark to {img}')
        except Exception as e:
            logging.error(f'Error adding watermark to {img}: {e}')

8.5 扩展功能

在程序中可以考虑添加更多功能,比如支持不同的水印颜色、字体大小等选项,以使程序更加灵活。

这些改进和建议将有助于提高程序的稳定性、易用性和可维护性。

当然,我们将继续改进和完善你的代码。在这一部分,我们会考虑一些进一步的优化和改进。

9. 优化图片格式检查

在处理图片文件时,可以优化检查图片格式的方式。使用os.path.splitext得到的文件扩展名可能包含大写字母,为了确保匹配,可以将文件扩展名转换为小写。

python 复制代码
if filetype.lower() == '.png':
    watermark_text.add_text_watermark(filepath)
else:
    print("Error: Image format is not supported. Please use PNG format.")

10. 增加用户交互性

可以考虑在程序中增加更多用户交互性,比如在成功添加水印后询问用户是否继续添加水印。

python 复制代码
while True:
    try:
        # existing code...

        print('Watermark added successfully.')
        
        another = input('Do you want to add watermark to another image? (yes/no): ').lower()
        if another != 'yes':
            break
    except Exception as e:
        logging.error(f'Error: {e}')

这样,用户可以选择是否继续添加水印,提高程序的交互性。

11. 多线程处理

如果你需要处理大量图片,可以考虑使用多线程来加速处理过程。这可以通过concurrent.futures模块实现。

python 复制代码
from concurrent.futures import ThreadPoolExecutor

# existing code...

if __name__ == "__main__":
    watermark_text = WatermarkText()
    try:
        file_list = os.listdir(watermark_text.image_path) 

        with ThreadPoolExecutor() as executor:
            executor.map(watermark_text.add_text_watermark, [os.path.join(watermark_text.image_path, file) for file in file_list])

        print('Batch watermarking completed.')
    except Exception as e:
        logging.error(f'Error: {e}')

这将允许同时处理多个图片,提高处理速度。

12. 其他优化建议

  • 考虑支持更多图片格式,而不仅限于PNG。你可以使用Pillow库中的Image.register_open()方法注册其他格式的图片打开器。
  • 如果水印文字较长,可以考虑自动调整文字大小,以适应图片。
相关推荐
鬼火儿2 小时前
SpringBoot】Spring Boot 项目的打包配置
java·后端
cr7xin3 小时前
缓存三大问题及解决方案
redis·后端·缓存
间彧4 小时前
Kubernetes的Pod与Docker Compose中的服务在概念上有何异同?
后端
间彧4 小时前
从开发到生产,如何将Docker Compose项目平滑迁移到Kubernetes?
后端
间彧4 小时前
如何结合CI/CD流水线自动选择正确的Docker Compose配置?
后端
间彧4 小时前
在多环境(开发、测试、生产)下,如何管理不同的Docker Compose配置?
后端
间彧4 小时前
如何为Docker Compose中的服务配置健康检查,确保服务真正可用?
后端
间彧4 小时前
Docker Compose和Kubernetes在编排服务时有哪些核心区别?
后端
间彧4 小时前
如何在实际项目中集成Arthas Tunnel Server实现Kubernetes集群的远程诊断?
后端
brzhang5 小时前
读懂 MiniMax Agent 的设计逻辑,然后我复刻了一个MiniMax Agent
前端·后端·架构