二、系统架构与技术选型
一、方案概述
本工具旨在解决瓶装产品图像数据管理中存在的命名不规范、标注文件(JSON)与图片名称不匹配、手动操作效率低等问题。通过图形化界面,用户可以自定义命名规则、校验图片数量、克隆式重命名图片并同步更新对应的 JSON 标注文件,从而建立一套可复用、可扩展的图像资产管理流程。
核心价值:
- 保留原始数据不变,通过克隆生成规范化的副本。
- 自动维护 JSON 文件与图片之间的关联,避免人工修改出错。
- 高度可配置的命名模板,适应不同项目和品种的命名习惯。
二、系统架构与技术选型
| 层级 | 技术/方案 |
|---|---|
| 前端 GUI | PyQt5(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 等子文件夹或直接包含图片)。
校验规则(可自定义):
| 子文件夹 | 期望图片数量(默认) | 是否强制 |
|---|---|---|
| side1 | 4 | 是 |
| side2 | 4 | 是 |
| base | 1 | 是 |
| top | 任意(或0) | 否 |
| 其他自定义 | 用户配置 | 用户指定 |
输出:通过/失败列表,失败原因(如缺少图片、多余图片),并提供给 GUI 日志区域。
3.2 命名规则引擎
默认模板:{品种}_{视角}_{瓶号}_{序号:02d}
支持的占位符:
{品种}– 用户输入或从 JSON 中提取(例如b-1中的品种标识){视角}– 子文件夹名(side1 / side2 / base / top){瓶号}– 文件夹名(B001){序号}– 按数字顺序递增,可设置起始值、步长、填充长度(如 01){日期}– 当前日期或 JSON 中的日期{序列}– 本批处理序号(1~99)
自定义配置方式:在 GUI 中提供编辑框,支持实时预览,并将配置保存到 config.json。
3.3 克隆与重命名执行
原则:不修改原始文件,在目标根目录下重建相同的文件夹结构,并将重命名后的图片写入。
执行流程:
- 根据源路径和目标根目录,构建对应的目标瓶号文件夹路径。
- 对于每个视角文件夹(side1 等),按文件名字母顺序排序(或按用户指定的排序规则),生成序号。
- 根据命名规则生成新文件名,使用
shutil.copy2(src, dst)复制图片并同时重命名。 - 记录新旧文件路径映射表(内存中),用于后续 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,二者匹配。
因此我们采用 “文件名主体匹配” 作为基础策略:
- 对于瓶号文件夹下的每一个
.json文件,提取其不带扩展名的文件名主体base_name。 - 在同一个瓶号文件夹(包括所有子文件夹 side1, side2, base 等)中寻找与
base_name匹配的图片文件(扩展名为 .png/.jpg 等)。- 如果找到唯一图片,即可确定该 JSON 对应那张图片。
- 重命名后,根据新旧文件名映射表,将 JSON 中的
imagePath更新为新图片文件名。 - 可选:同时也将 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,可通过增加配置文件明确关系。
五、扩展功能说明
批量创建文件夹
- 用户输入瓶号列表(支持
B001-B100格式),选择模板(side1/side2/base 数量),一键生成空目录结构。
- 用户输入瓶号列表(支持
硬盘占用分析
- 递归统计各瓶号文件夹的图片数量、总大小、平均大小,展示为柱状图(可使用 matplotlib 或简易 QTableWidget)。
历史记录与操作日志
- 每次重命名操作记录:时间、源路径、目标路径、处理的瓶号数量、成功/失败数、总耗时。保存为
history.log。
- 每次重命名操作记录:时间、源路径、目标路径、处理的瓶号数量、成功/失败数、总耗时。保存为
多线程执行
- 拷贝图片和更新 JSON 使用
QThread+ 信号槽更新进度条,避免界面冻结。
- 拷贝图片和更新 JSON 使用
六、性能与异常处理
| 关注点 | 实现措施 |
|---|---|
| 大量文件处理 | 使用生成器遍历文件,一次只加载一个 JSON 到内存;拷贝采用缓冲流。 |
| 路径兼容性 | 使用 os.path.join 和 pathlib.Path,同时支持 Windows 和 Linux。 |
| 错误恢复 | 如果某瓶号处理失败,记录错误,继续处理下一个,不中断整个任务。 |
| 校验不通过 | 提供“强制继续”选项,但默认禁止并展示错误详情。 |
| 耗时统计 | 在任务开始和结束时记录 time.time(),结束时输出总耗时。 |
七、总结
本方案通过预校验 → 命名规则引擎 → 克隆重命名 → JSON 主体匹配同步四个核心步骤,解决了批量重命名图片并维护标注关联的问题。其中 JSON 匹配采用基于文件名主体的自动对应策略,无需人工干预,且保证原数据安全。GUI 设计强调了可配置性与实时反馈,扩展功能为未来管理需求提供了良好基础。
下一步:可依据此方案进行模块拆分,制定开发计划(例如先实现控制台版本验证匹配逻辑,再封装 GUI)。
评论