二、系统架构与技术选型

Article detail

学习笔记

2026/6/7 · 26 分钟阅读

二、系统架构与技术选型

一、方案概述

本工具旨在解决瓶装产品图像数据管理中存在的命名不规范、标注文件(JSON)与图片名称不匹配、手动操作效率低等问题。通过图形化界面,用户可以自定义命名规则、校验图片数量、克隆式重命名图片并同步更新对应的 JSON 标注文件,从而建立一套可复用、可扩展的图像资产管理流程。

核心价值

  • 保留原始数据不变,通过克隆生成规范化的副本。
  • 自动维护 JSON 文件与图片之间的关联,避免人工修改出错。
  • 高度可配置的命名模板,适应不同项目和品种的命名习惯。

二、系统架构与技术选型

层级技术/方案
前端 GUIPyQt5(QMainWindow, QTreeView, QTableWidget)
后端逻辑Python 3.10+,标准库 os, shutil, json, re, time
并发处理QThread(避免界面卡死)
配置存储JSON / YAML 配置文件
校验自定义规则引擎 + 正则表达式
文件操作shutil.copy2 保留元数据,os.path

三、核心功能模块设计

3.1 文件夹扫描与预校验模块

输入:用户选择的源瓶号根目录(例如 D:/bottles/
扫描逻辑:递归遍历根目录,识别 瓶号 文件夹(规则:目录名为瓶号,其下包含 side1/side2/base 等子文件夹或直接包含图片)。
校验规则(可自定义):

子文件夹期望图片数量(默认)是否强制
side14
side24
base1
top任意(或0)
其他自定义用户配置用户指定

输出:通过/失败列表,失败原因(如缺少图片、多余图片),并提供给 GUI 日志区域。

3.2 命名规则引擎

默认模板{品种}_{视角}_{瓶号}_{序号:02d}
支持的占位符

  • {品种} – 用户输入或从 JSON 中提取(例如 b-1 中的品种标识)
  • {视角} – 子文件夹名(side1 / side2 / base / top)
  • {瓶号} – 文件夹名(B001)
  • {序号} – 按数字顺序递增,可设置起始值、步长、填充长度(如 01)
  • {日期} – 当前日期或 JSON 中的日期
  • {序列} – 本批处理序号(1~99)

自定义配置方式:在 GUI 中提供编辑框,支持实时预览,并将配置保存到 config.json

3.3 克隆与重命名执行

原则:不修改原始文件,在目标根目录下重建相同的文件夹结构,并将重命名后的图片写入。

执行流程

  1. 根据源路径和目标根目录,构建对应的目标瓶号文件夹路径。
  2. 对于每个视角文件夹(side1 等),按文件名字母顺序排序(或按用户指定的排序规则),生成序号。
  3. 根据命名规则生成新文件名,使用 shutil.copy2(src, dst) 复制图片并同时重命名。
  4. 记录新旧文件路径映射表(内存中),用于后续 JSON 更新。

3.4 JSON 同步匹配逻辑(重点,详见第四节)

核心挑战:原始 JSON 文件中的 imagePath 指向旧图片文件名(例如 Image_20260601170215883.png),重命名后需要指向新图片文件名。由于一个瓶号文件夹可能包含多个 JSON 文件或多个图片,必须准确匹配。

解决方案:采用 “基于相对路径 + 文件名映射表” 策略,具体见下文。

3.5 GUI 设计(布局概述)

  • 左侧导航栏重命名 | 批量创建文件夹 | 硬盘占用分析 | 历史日志 | 设置
  • 右侧主区域(重命名页)
    • 源文件夹选择器 + 目标文件夹选择器
    • 品种、命名模板编辑框
    • 视角期望数量表格(可增删行)
    • 序号范围设置(如 1-20,30-40
    • 校验按钮 + 执行按钮
    • 进度条 + 日志输出(支持 QTextEdit 带颜色)
  • 状态栏:显示总耗时、成功/失败数量

四、JSON 匹配问题的详细解决方案

4.1 问题定义

原始 JSON 文件内容示例(节选):

{
  "imagePath": "Image_20260601170215883.png",
  "shapes": [ ... ]
}

该 JSON 文件位于瓶号文件夹的根目录(与 side1 等平级)。重命名后,图片从 Image_xxx.png 变为 品种_视角_瓶号_01.png但 JSON 文件中没有直接指明它对应的是 side1 中的第几张图。我们需要让程序知道:这个 JSON 原来是与哪张图片绑定的?

4.2 核心假设与匹配策略

由于原始数据来自标注工具(如 Labelme),通常 JSON 文件与图片同一目录文件名主体相同(除扩展名)。
观察用户提供的 JSON:"imagePath": "Image_20260601170215883.png",而 JSON 文件名是 Image_20260601170215883.json,二者匹配。

因此我们采用 “文件名主体匹配” 作为基础策略:

  1. 对于瓶号文件夹下的每一个 .json 文件,提取其不带扩展名的文件名主体 base_name
  2. 同一个瓶号文件夹(包括所有子文件夹 side1, side2, base 等)中寻找与 base_name 匹配的图片文件(扩展名为 .png/.jpg 等)。
    • 如果找到唯一图片,即可确定该 JSON 对应那张图片。
  3. 重命名后,根据新旧文件名映射表,将 JSON 中的 imagePath 更新为新图片文件名。
  4. 可选:同时也将 JSON 文件本身按规则重命名(例如 品种_视角_瓶号_01.json),并修改内部 imagePath 为相对路径(通常只保留文件名)。

4.3 处理复杂情况(多图片共享同一 JSON?)

  • 正常情况下,一个 JSON 只对应一个图片。若出现多个 JSON 与同一图片主体匹配,则按创建时间或用户选择处理,并报警。
  • 如果 JSON 文件位于子文件夹中(例如 side1 下也有 JSON),同样适用主体匹配策略,但需递归扫描。

4.4 映射表建立与更新流程

# 伪代码
mapping = {}  # key: 旧图片绝对路径, value: 新图片绝对路径
json_mapping = []  # list of (old_json_path, corresponding_img_old_path)

# 第一步:收集所有 JSON 文件,找到它们对应的原始图片
for json_path in find_all_json_files(bottle_dir):
    base = os.path.splitext(os.path.basename(json_path))[0]
    # 在瓶号目录下(含子文件夹)搜索 base.* 图片
    matched_img = find_image_by_basename(bottle_dir, base)
    if matched_img:
        json_mapping.append((json_path, matched_img))

# 第二步:执行图片重命名(克隆),生成 mapping
for old_img, new_img in generate_rename_plan(...):
    shutil.copy2(old_img, new_img)
    mapping[old_img] = new_img

# 第三步:更新 JSON
for json_path, old_img_path in json_mapping:
    if old_img_path in mapping:
        new_img_path = mapping[old_img_path]
        # 读取 JSON,修改 imagePath 为 new_img_path 的文件名(或相对路径)
        data = json.load(open(json_path))
        data['imagePath'] = os.path.basename(new_img_path)
        # 将更新后的 JSON 写入目标目录(与图片同目录或按规则放置)
        target_json_dir = os.path.dirname(new_img_path)  # 与图片同级
        new_json_name = os.path.splitext(os.path.basename(new_img_path))[0] + '.json'
        new_json_path = os.path.join(target_json_dir, new_json_name)
        with open(new_json_path, 'w') as f:
            json.dump(data, f, indent=2)

4.5 特殊情形与容错

情形处理方式
找不到与 JSON 主体匹配的图片在日志中警告,跳过该 JSON,不复制。
一个主体匹配到多个图片列出冲突文件,让用户手动选择,或按最近修改时间自动选择一个并记录。
图片重命名后,JSON 应与新图片同目录将更新后的 JSON 复制到新图片所在目录,保持二者在同一层,便于标注工具读取。
JSON 内部 shapes 使用了绝对路径默认只修改 imagePath,其他不变。如需修改 imageData(base64 图片数据),不做处理。
用户想自定义 JSON 命名规则提供配置项:{品种}_{视角}_{瓶号}_{序号}.json,但要注意 imagePath 内的文件名必须对应。

4.6 优势与可靠性

  • 不依赖额外 ID:仅利用已有文件名主体,对现有标注数据兼容性好。
  • 保持标注数据完整:所有 shapes、label 等关键信息完全保留。
  • 支持副本操作:原始 JSON 不会被修改,避免误操作风险。
  • 可扩展:如果未来需要支持多图片对应同一 JSON,可通过增加配置文件明确关系。

五、扩展功能说明

  1. 批量创建文件夹

    • 用户输入瓶号列表(支持 B001-B100 格式),选择模板(side1/side2/base 数量),一键生成空目录结构。
  2. 硬盘占用分析

    • 递归统计各瓶号文件夹的图片数量、总大小、平均大小,展示为柱状图(可使用 matplotlib 或简易 QTableWidget)。
  3. 历史记录与操作日志

    • 每次重命名操作记录:时间、源路径、目标路径、处理的瓶号数量、成功/失败数、总耗时。保存为 history.log
  4. 多线程执行

    • 拷贝图片和更新 JSON 使用 QThread + 信号槽更新进度条,避免界面冻结。

六、性能与异常处理

关注点实现措施
大量文件处理使用生成器遍历文件,一次只加载一个 JSON 到内存;拷贝采用缓冲流。
路径兼容性使用 os.path.joinpathlib.Path,同时支持 Windows 和 Linux。
错误恢复如果某瓶号处理失败,记录错误,继续处理下一个,不中断整个任务。
校验不通过提供“强制继续”选项,但默认禁止并展示错误详情。
耗时统计在任务开始和结束时记录 time.time(),结束时输出总耗时。

七、总结

本方案通过预校验 → 命名规则引擎 → 克隆重命名 → JSON 主体匹配同步四个核心步骤,解决了批量重命名图片并维护标注关联的问题。其中 JSON 匹配采用基于文件名主体的自动对应策略,无需人工干预,且保证原数据安全。GUI 设计强调了可配置性与实时反馈,扩展功能为未来管理需求提供了良好基础。

下一步:可依据此方案进行模块拆分,制定开发计划(例如先实现控制台版本验证匹配逻辑,再封装 GUI)。

评论

动作测试