告别手动重标:基于Python脚本的Labelme数据集增强与JSON同步更新实战
1. 为什么我们需要自动化处理Labelme标注数据做计算机视觉项目的朋友都知道数据标注是个体力活。特别是使用Labelme这类工具进行语义分割标注时每张图片都要手动勾勒物体轮廓工作量巨大。更让人头疼的是当我们对原始图片进行数据增强比如镜像翻转、旋转时对应的JSON标注文件却不会自动更新。我最近就遇到了这个痛点。项目需要增加数据多样性打算对标注好的数据集做90°、180°、270°旋转增强。按照传统做法每旋转一张图片就得重新标注一次相当于工作量直接翻三倍这种重复劳动不仅效率低下还容易出错。好在Python脚本可以帮我们解决这个问题。通过编写自动化脚本我们可以在对图片进行几何变换的同时智能地更新对应的JSON标注文件。这样既保证了数据增强的效果又避免了重复标注的麻烦。下面我就分享下这个实战经验手把手教你如何用Python脚本实现Labelme数据集的智能增强。2. 环境准备与核心思路2.1 基础环境配置在开始之前我们需要准备好开发环境。这个方案主要依赖以下几个Python库Pillow用于图像处理glob用于文件路径匹配json用于JSON文件读写os用于系统路径操作math用于旋转计算安装命令很简单pip install pillow其他库都是Python标准库无需额外安装。建议使用Python 3.6版本我在3.8环境下测试通过。2.2 核心解决思路整个方案的核心逻辑其实很清晰遍历原始图片文件夹获取所有待处理的图片对每张图片执行几何变换镜像、旋转等同时读取对应的JSON标注文件根据相同的变换规则更新JSON中的标注点坐标保存变换后的图片和更新后的JSON文件关键在于第四步——如何准确计算变换后的标注点坐标。对于镜像翻转我们需要找到中轴线然后对称翻转所有点坐标对于旋转则需要使用三角函数进行坐标变换。3. 实战代码解析与常见坑点3.1 文件路径处理的正确姿势原始代码中第一个大坑就是文件路径处理。很多人在运行脚本时发现输出为空就是因为路径匹配出了问题。错误示范img_list glob.glob(path * file_format)这种写法有两个问题如果路径中包含特殊字符如下划线_通配符*可能匹配失败直接用拼接路径在不同操作系统下可能不兼容正确做法是使用os.path.joinimg_list glob.glob(os.path.join(path, *.jpg))同样输出文件路径也应该这样处理full_path os.path.join(save_path, LR file_name)3.2 镜像翻转的坐标计算对于左右镜像翻转我们需要先找到图片的中轴线然后将所有标注点对称翻转。具体算法如下获取图片宽度width计算中轴mid_width width / 2对于每个标注点(temp_x, temp_y)计算该点到中轴的距离dis新x坐标 中轴坐标 ± 距离根据原位置决定加减y坐标保持不变代码实现if temp_x mid_width: dis temp_x - mid_width new_x mid_width - dis elif temp_x mid_width: dis mid_width - temp_x new_x mid_width dis else: new_x temp_x new_y temp_y3.3 旋转操作的坐标变换旋转操作相对复杂些需要用到三角函数计算。核心公式是旋转矩阵新x (x - mid_x)*cosθ - (y - mid_y)*sinθ mid_x 新y (x - mid_x)*sinθ (y - mid_y)*cosθ mid_yPython实现new_x (temp_x - mid_width) * math.cos(math.radians(angel)) - (temp_y - mid_height) * math.sin(math.radians(angel)) mid_width new_y (temp_x - mid_width) * math.sin(math.radians(angel)) (temp_y - mid_height) * math.cos(math.radians(angel)) mid_height注意这里角度需要转换为弧度且旋转是绕图片中心点进行的。4. 完整代码实现与使用指南4.1 完整增强脚本下面是整合了所有功能的完整代码包含了左右镜像、上下镜像以及旋转增强from PIL import Image import os import glob import json import base64 import math # 配置参数 path your_dataset_path # 原始数据集路径 save_path your_output_path # 输出路径 file_format .jpg # 图片格式 replace_format .json # JSON文件格式 # 文件名前缀 LR lr_ # 左右镜像 TB tb_ # 上下镜像 R90 r90_ # 旋转90度 R180 r180_ # 旋转180度 R270 r270_ # 旋转270度 # 获取图片列表 img_list glob.glob(os.path.join(path, f*{file_format})) def mirror_lr(): 左右镜像增强 print(开始左右镜像处理...) for img_path in img_list: json_path img_path.replace(file_format, replace_format) if not os.path.exists(json_path): continue with open(json_path, encodingutf-8) as f: setting json.load(f) # 计算中轴并更新坐标 width setting[imageWidth] mid_width width / 2 for shape in setting[shapes]: for point in shape[points]: temp_x point[0] if temp_x mid_width: point[0] mid_width - (temp_x - mid_width) elif temp_x mid_width: point[0] mid_width (mid_width - temp_x) # 保存结果 file_name setting[imagePath] setting[imagePath] LR file_name img_save_path os.path.join(save_path, LR file_name) json_save_path img_save_path.replace(file_format, replace_format) # 处理图片 Image.open(img_path).transpose(Image.FLIP_LEFT_RIGHT).save(img_save_path) with open(img_save_path, rb) as f: setting[imageData] base64.b64encode(f.read()).decode() # 保存JSON with open(json_save_path, w, encodingutf-8) as f: json.dump(setting, f) def rotate(angle, prefix): 旋转增强 print(f开始旋转{angle}度处理...) rad math.radians(angle) cos_val math.cos(rad) sin_val math.sin(rad) for img_path in img_list: json_path img_path.replace(file_format, replace_format) if not os.path.exists(json_path): continue with open(json_path, encodingutf-8) as f: setting json.load(f) # 计算中心点并更新坐标 width setting[imageWidth] height setting[imageHeight] mid_width width / 2 mid_height height / 2 for shape in setting[shapes]: for point in shape[points]: x, y point[0] - mid_width, point[1] - mid_height point[0] x * cos_val - y * sin_val mid_width point[1] x * sin_val y * cos_val mid_height # 保存结果 file_name setting[imagePath] setting[imagePath] prefix file_name img_save_path os.path.join(save_path, prefix file_name) json_save_path img_save_path.replace(file_format, replace_format) # 处理图片 if angle 90: Image.open(img_path).transpose(Image.ROTATE_270).save(img_save_path) elif angle 180: Image.open(img_path).transpose(Image.ROTATE_180).save(img_save_path) elif angle 270: Image.open(img_path).transpose(Image.ROTATE_90).save(img_save_path) with open(img_save_path, rb) as f: setting[imageData] base64.b64encode(f.read()).decode() with open(json_save_path, w, encodingutf-8) as f: json.dump(setting, f) # 执行增强 mirror_lr() rotate(90, R90) rotate(180, R180) rotate(270, R270)4.2 使用说明修改脚本开头的路径配置path原始数据集路径save_path增强后数据保存路径file_format图片格式如.jpg、.png根据需要注释/取消注释最后的增强函数调用运行脚本后增强后的图片和JSON会自动保存到输出路径可以使用Labelme打开生成的JSON文件检查标注是否正确5. 实际应用中的优化建议5.1 批量处理与进度显示当处理大量图片时建议添加进度显示功能。可以这样修改total len(img_list) for idx, img_path in enumerate(img_list): print(f处理进度: {idx1}/{total} ({((idx1)/total)*100:.1f}%)) # 剩余处理代码...5.2 异常处理与日志记录为了增强脚本的健壮性应该添加异常处理try: with open(json_path, encodingutf-8) as f: setting json.load(f) except Exception as e: print(f处理{json_path}时出错: {str(e)}) continue5.3 多进程加速对于超大规模数据集可以使用多进程加速from multiprocessing import Pool def process_image(img_path): # 把原来的处理逻辑封装到这里 pass if __name__ __main__: with Pool(4) as p: # 4个进程 p.map(process_image, img_list)这个脚本我已经在实际项目中多次使用效果非常稳定。特别是在处理几千张图片的数据集时节省了数百小时的人工标注时间。最大的收获不仅是效率提升更重要的是保证了数据增强后标注的一致性——这是手动重标很难做到的。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2464866.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!