外观设计专利图像标准化处理程序

一、 程序核心功能

该程序用于自动化处理外观设计专利申请中的系列附图,解决专利局对视图尺寸、分辨率、格式及各视图比例一致性的审查要求:

  • 300 DPI 分辨率统一化:将输入图像的物理尺寸计算基准统一锁定为 300 DPI,并在保存时将 300 DPI 元数据写入 JPG 文件。
  • 同组视图等比例联动缩放:采用两遍扫描算法。自动识别同目录下一套视图中的最大宽与最大高,一旦超限即计算统一缩放比 $final_scale$,确保主视图、后视图、俯视图等所有视图按完全相同的比例缩放。
  • 尺寸安全阈值控制:
    • 宽度限制:$\ge 15.0\text{ cm} \implies$ 统一压缩至最大宽度 $14.9\text{ cm}$。
    • 高度限制:$\ge 22.0\text{ cm} \implies$ 统一压缩至最大高度 $21.9\text{ cm}$。
  • 输入宽容与强制 JPG 输出:
    • 支持输入格式:PNG、JPG、JPEG、BMP、TIFF、WEBP、GIF 等。
    • 强制输出格式:高清晰度 .jpg(质量 95、无色度抽样损失)。
  • 透明通道自动纯白合成:若输入带有透明通道(如 PNG/WEBP 的 Alpha 通道),自动以纯白底色 (255, 255, 255) 填充,杜绝转为 JPG 时产生黑底。
  • 非覆盖式目录镜像导出:递归遍历一级及多级子目录,在指定的目标路径下完整复刻原文件夹层级,保护原图不被篡改。

二、 核心转换与计算逻辑

1. 物理尺寸与像素换算(基于 300 DPI)

$$W_{\text{cm}} = \frac{W_{\text{px}}}{300} \times 2.54 \quad , \quad H_{\text{cm}} = \frac{H_{\text{px}}}{300} \times 2.54$$

2. 组缩放比例计算流程

步骤 评估对象 判断条件 计算公式
阶段 1(宽度遍历) 组内初始最大宽度 $W_{\max}$ $W_{\max} \ge 15.0\text{ cm}$ $scale_w = \frac{14.9}{W_{\max}}$(未超限则为 1.0)
阶段 2(高度遍历) 阶段 1 缩放后的最大高度 $H_{\max}'$ $H_{\max}’ \ge 22.0\text{ cm}$ $scale_h = \frac{21.9}{H_{\max}’}$(未超限则为 1.0)
阶段 3(综合执行) 全组所有图片 $final_scale < 1.0$ $final_scale = scale_w \times scale_h$

三、 环境依赖与配置

1. Python 环境

  • Python 3.7+
  • 依赖库:Pillow(内置库 os、pathlib 无需安装)

2. 安装命令

运行该程序只需要安装 1 个第三方库:Pillow(Python 的图像处理库)。

代码中用到的 os 和 pathlib 属于 Python 内置的标准库,无需单独安装。

在终端(Terminal)或命令提示符(CMD/PowerShell)中执行:

Bash

1
pip install pillow

如果使用国内网络遇到下载较慢,可使用清华镜像源加速安装:

Bash

1
pip install pillow -i https://pypi.tuna.tsinghua.edu.cn/simple

四、 使用操作流程

  1. 组织文件目录:将每个外观设计产品的成套视图(4~10 张不等)单独存放在一个子文件夹内,支持在一个总根目录下放置多个产品的子文件夹。

  2. 修改脚本配置路径:在脚本末尾的 if __name__ == '__main__': 区域填入实际路径:

    Python

    1
    2
    3
    4
    5
    
    # 输入源根目录(包含各专利产品子文件夹)
    INPUT_DIR = r"D:\Raw_Images"
    
    # 输出目标根目录(自动新建并镜像结构)
    OUTPUT_DIR = r"D:\Processed_JPG"
    
  3. 运行脚本:在命令行中执行 python <脚本名>.py。

  4. 验证结果:控制台会逐行打印每个子目录的缩放比例及每张图转换前后的像素和物理尺寸(cm)。

五、 注意事项与使用规范

  • 同组视图目录隔离原则:
    • 必须保证一个子目录下仅存放属于同一款产品的成套视图。程序是以“单个子文件夹”为最小单位计算最高/最宽并执行同比例联动的。若把不同产品的图片混在同一目录下,会导致尺寸较小的产品被强行按大尺寸产品的比例同步压缩。
  • 同名文件覆盖风险:
    • 若同一子目录下同时存在 view1.png 和 view1.tif,转换后输出文件名均会变为 view1.jpg,后读取的文件会覆盖先读取的文件。处理前请确保同一子目录内文件名去重。
  • Windows 路径书写规范:
    • 配置文件路径时,字符串前请保留 r 前缀(如 r"D:\folder\sub"),避免反斜杠 \ 引起转义字符解析错误。
  • 插值算法保证清晰度:
    • 程序内部使用 Image.Resampling.LANCZOS 高阶抗锯齿插值,对于专利附图中的细线条、标注数字有极好的保真效果,无需额外进行锐化处理。

六、完整源代码

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
import os
from pathlib import Path
from PIL import Image

# 支持读取的常见图片格式(不限制输入类型)
IMAGE_EXTS = {'.jpg', '.jpeg', '.png', '.bmp', '.tif', '.tiff', '.webp', '.jfif', '.gif'}

# 外观设计专利规范常量
TARGET_DPI = 300          # 统一输出分辨率 (DPI)
CM_PER_INCH = 2.54        # 1英寸 = 2.54厘米

MAX_W_CM = 15.0           # 宽度上限阈值
TARGET_W_CM = 14.9        # 宽度超限后的目标宽度
MAX_H_CM = 22.0           # 高度上限阈值
TARGET_H_CM = 21.9        # 高度超限后的目标高度


def get_image_info(img_path: Path):
    """读取图片像素尺寸,并基于 300 DPI 计算物理尺寸 (厘米)"""
    with Image.open(img_path) as img:
        w_px, h_px = img.size
        # 在 300 DPI 下:厘米 = (像素 / 300) * 2.54
        w_cm = (w_px / TARGET_DPI) * CM_PER_INCH
        h_cm = (h_px / TARGET_DPI) * CM_PER_INCH
        return {
            'path': img_path,
            'stem': img_path.stem,      # 文件名(不含后缀)
            'filename': img_path.name,  # 原完整文件名
            'w_px': w_px,
            'h_px': h_px,
            'w_cm': w_cm,
            'h_cm': h_cm
        }


def convert_to_rgb(img: Image.Image) -> Image.Image:
    """将任意色彩模式的图像转换为适用于 JPG 的 RGB 模式,透明区域填充为纯白色"""
    if img.mode in ('RGBA', 'LA') or (img.mode == 'P' and 'transparency' in img.info):
        # 转换为 RGBA 提取透明通道
        rgba_img = img.convert('RGBA')
        # 创建纯白底图
        white_bg = Image.new('RGB', rgba_img.size, (255, 255, 255))
        # 按照 Alpha 遮罩将图像贴到白底上
        white_bg.paste(rgba_img, mask=rgba_img.split()[3])
        return white_bg
    elif img.mode != 'RGB':
        return img.convert('RGB')
    return img


def process_image_group(input_group_dir: Path, output_group_dir: Path):
    """处理单个子目录下的同组外观设计图片"""
    image_files = [
        f for f in input_group_dir.iterdir()
        if f.is_file() and f.suffix.lower() in IMAGE_EXTS
    ]

    if not image_files:
        return

    # 1. 收集该目录下所有图片在 300 DPI 下的尺寸信息
    images_info = []
    for f in image_files:
        try:
            images_info.append(get_image_info(f))
        except Exception as e:
            print(f"⚠️ 无法读取文件 {f.name},已跳过: {e}")

    if not images_info:
        return

    # ---------------- 第一次遍历:宽度检查 ----------------
    max_w_cm = max(info['w_cm'] for info in images_info)
    scale_w = 1.0
    if max_w_cm >= MAX_W_CM:
        scale_w = TARGET_W_CM / max_w_cm

    # ---------------- 第二次遍历:高度检查 ----------------
    max_h_cm_after_w = max(info['h_cm'] * scale_w for info in images_info)
    scale_h = 1.0
    if max_h_cm_after_w >= MAX_H_CM:
        scale_h = TARGET_H_CM / max_h_cm_after_w

    # 最终综合缩放比例
    final_scale = scale_w * scale_h

    # 确保目标输出目录存在
    output_group_dir.mkdir(parents=True, exist_ok=True)

    print(f"\n📂 正在处理目录: {input_group_dir}")
    print(f"   - 图片数量: {len(images_info)} 张")
    print(f"   - 原始最大尺寸 (300 DPI下): {max_w_cm:.2f}cm × {max(i['h_cm'] for i in images_info):.2f}cm")
    if final_scale < 1.0:
        print(f"   - 需等比缩小,最终缩放比例: {final_scale * 100:.2f}%")
    else:
        print(f"   - 尺寸均在规范内 (≤15cm × ≤22cm),保持原尺寸并转为 300 DPI JPG")

    # ---------------- 执行转换、转RGB并强制另存为 JPG ----------------
    for info in images_info:
        target_w_px = max(1, int(round(info['w_px'] * final_scale)))
        target_h_px = max(1, int(round(info['h_px'] * final_scale)))

        # 强制将输出文件后缀设为 .jpg
        target_path = output_group_dir / f"{info['stem']}.jpg"

        with Image.open(info['path']) as img:
            # 1. 尺寸调整(高质量 Lanczos 抗锯齿插值)
            if target_w_px != info['w_px'] or target_h_px != info['h_px']:
                processed_img = img.resize((target_w_px, target_h_px), Image.Resampling.LANCZOS)
            else:
                processed_img = img.copy()

            # 2. 格式与透明通道处理(转为标准 RGB 白底)
            processed_img = convert_to_rgb(processed_img)

            # 3. 保存为高质量 300 DPI 的 JPEG 图像
            processed_img.save(
                target_path,
                format='JPEG',
                dpi=(TARGET_DPI, TARGET_DPI),
                quality=95,        # 保持极高清晰度
                subsampling=0       # 保持色彩无色度抽样损失
            )

        out_w_cm = (target_w_px / TARGET_DPI) * CM_PER_INCH
        out_h_cm = (target_h_px / TARGET_DPI) * CM_PER_INCH
        print(f"   ✓ {info['filename']} -> {target_path.name} ({target_w_px}x{target_h_px}px, {out_w_cm:.2f}cm × {out_h_cm:.2f}cm)")


def batch_process(input_root_dir: str, output_root_dir: str):
    """递归遍历输入根目录下的所有子目录,并将结果镜像保存到输出根目录"""
    in_root = Path(input_root_dir).resolve()
    out_root = Path(output_root_dir).resolve()

    if not in_root.exists():
        print(f"❌ 输入目录不存在: {in_root}")
        return

    if in_root == out_root:
        print("❌ 输出目录不能与输入目录相同,请指定一个独立的输出目录!")
        return

    print("=" * 60)
    print(f"输入源根目录: {in_root}")
    print(f"输出目标目录: {out_root}")
    print("=" * 60)

    for current_dir, _, _ in os.walk(in_root):
        curr_in_path = Path(current_dir)
        rel_path = curr_in_path.relative_to(in_root)
        curr_out_path = out_root / rel_path

        process_image_group(curr_in_path, curr_out_path)

    print("\n🎉 全部处理完成!所有图片已标准化转为 300 DPI 的 JPG 格式。")


if __name__ == '__main__':
    # ------------------ 请在此处配置路径 ------------------
    # 1. 存放原始图片的根目录
    INPUT_DIR = r"D:\Images_Raw"

    # 2. 输出目录(会自动镜像创建相同结构的子文件夹)
    OUTPUT_DIR = r"D:\Images_Export_JPG"
    # -----------------------------------------------------

    batch_process(INPUT_DIR, OUTPUT_DIR)