当前位置: 首页 > news >正文

避坑指南:Labelme标注的JSON转YOLO格式时,坐标归一化和多人处理怎么写代码?

Labelme转YOLO格式实战:坐标归一化与多人标注处理详解

当你从Labelme的JSON标注切换到YOLO格式时,最令人头疼的莫过于坐标归一化和多人标注处理。作为计算机视觉开发者,我曾在这个转换过程中踩过无数坑——从坐标计算错误到多人标注合并混乱,再到文件路径处理的各种陷阱。本文将用实战代码带你彻底解决这些问题。

1. 理解YOLO关键点格式的核心逻辑

YOLO格式的关键点标注远比简单的物体检测复杂。官方文档中明确要求每个对象实例占据一行,包含以下信息:

<class-index> <x> <y> <width> <height> <px1> <py1> <px2> <py2> ... <pxn> <pyn>

关键差异点

  • DIM=2格式:仅包含关键点的x,y坐标
  • DIM=3格式:每个关键点追加可见性标志(0=不可见,1=遮挡,2=可见)

实际项目中,我推荐使用DIM=3格式,因为它能更好地处理遮挡情况。以下是两种格式的对比示例:

格式类型示例数据适用场景
DIM=20 0.5 0.5 0.3 0.4 0.4 0.6 0.5 0.7简单场景,所有关键点可见
DIM=30 0.5 0.5 0.3 0.4 0.4 0.6 2 0.5 0.7 1复杂场景,处理遮挡和不可见点

2. 解析Labelme JSON文件结构

Labelme生成的JSON文件包含完整的标注信息,但结构较为复杂。以下是一个典型的结构示例:

{ "version": "5.0.1", "flags": {}, "shapes": [ { "label": "person", "points": [[x1,y1], [x2,y2]], "shape_type": "rectangle" }, { "label": "keypoint", "points": [[x,y]], "shape_type": "point" } ], "imagePath": "image.jpg", "imageHeight": 1080, "imageWidth": 1920 }

处理要点

  1. 先提取图像尺寸(imageWidth/imageHeight),这是归一化的基准
  2. 区分"person"标注(边界框)和关键点标注
  3. 注意points字段的嵌套结构差异(矩形框是二维列表,关键点是一维列表)

我在实际项目中封装了一个解析函数:

def parse_labelme_json(json_path): with open(json_path) as f: data = json.load(f) image_size = (data['imageWidth'], data['imageHeight']) persons = [] keypoints = [] for shape in data['shapes']: if shape['label'] == 'person': persons.append(shape['points']) else: keypoints.append({ 'label': shape['label'], 'point': shape['points'][0] # 提取单点坐标 }) return image_size, persons, keypoints

3. 多人标注处理的关键技术

当一张图片中有多个人时,需要将各自的关键点与对应的人体边界框正确关联。这是转换过程中最容易出错的部分。

解决方案

  1. 为每个person创建独立的数据结构
  2. 根据空间距离将关键点分配到最近的人体框
  3. 处理可能的关键点共享情况(如握手场景)
def assign_keypoints_to_persons(persons, keypoints): person_instances = [] for i, person_box in enumerate(persons): # 计算人体框中心点 x_center = (person_box[0][0] + person_box[1][0]) / 2 y_center = (person_box[0][1] + person_box[1][1]) / 2 # 收集属于此人的关键点 assigned_kps = [] for kp in keypoints: kp_x, kp_y = kp['point'] # 简单示例:使用中心点距离判断 distance = ((kp_x - x_center)**2 + (kp_y - y_center)**2)**0.5 if distance < 200: # 阈值根据实际情况调整 assigned_kps.append(kp) person_instances.append({ 'box': person_box, 'keypoints': assigned_kps }) return person_instances

提示:更精确的做法是计算关键点到人体框的IoU,但简单距离判断在大多数情况下已经足够。

4. 坐标归一化的数学原理与实现

归一化是格式转换的核心,需要将绝对像素坐标转换为0-1之间的相对值。计算公式如下:

normalized_x = absolute_x / image_width normalized_y = absolute_y / image_height

对于边界框,需要额外计算中心点和宽高:

def normalize_coordinates(box, keypoints, img_width, img_height): # 处理边界框 x1, y1 = box[0] x2, y2 = box[1] x_center = (x1 + x2) / 2 / img_width y_center = (y1 + y2) / 2 / img_height width = abs(x1 - x2) / img_width height = abs(y1 - y2) / img_height # 处理关键点 normalized_kps = [] for kp in keypoints: x, y = kp['point'] normalized_kps.extend([ x / img_width, y / img_height, 2 # 默认可见 ]) return [x_center, y_center, width, height] + normalized_kps

常见陷阱

  1. 忘记取绝对值导致宽度/高度为负值
  2. 使用错误的图像尺寸(有些JSON文件可能不包含imageWidth/imageHeight)
  3. 归一化前未验证坐标是否超出图像边界

5. 完整转换流程与异常处理

结合上述技术点,下面是完整的转换流程:

def convert_labelme_to_yolo(json_path, output_dir): # 1. 解析原始JSON img_size, persons, keypoints = parse_labelme_json(json_path) img_width, img_height = img_size # 2. 处理多人情况 person_instances = assign_keypoints_to_persons(persons, keypoints) # 3. 准备输出内容 yolo_lines = [] for person in person_instances: normalized = normalize_coordinates( person['box'], person['keypoints'], img_width, img_height ) yolo_line = ' '.join(map(str, [0] + normalized)) # 0是person类别 yolo_lines.append(yolo_line) # 4. 写入文件 output_path = os.path.join(output_dir, os.path.basename(json_path).replace('.json', '.txt')) with open(output_path, 'w') as f: f.write('\n'.join(yolo_lines))

增强鲁棒性的技巧

  1. 添加图像尺寸回退机制
  2. 处理关键点缺失情况
  3. 验证归一化后的值是否在0-1范围内
# 图像尺寸回退示例 if 'imageWidth' not in data: img = Image.open(os.path.join(base_dir, data['imagePath'])) img_width, img_height = img.size else: img_width = data['imageWidth'] img_height = data['imageHeight']

6. 实战中的典型问题与解决方案

问题1:生成的TXT文件与图像不对应

解决方案

  • 使用一致的命名规则(如image.jpg对应image.txt)
  • 在代码中添加验证步骤:
image_stem = Path(data['imagePath']).stem label_stem = Path(output_path).stem assert image_stem == label_stem, "文件名不匹配"

问题2:多人标注时关键点分配错误

解决方案

  • 实现更精确的分配算法
  • 添加人工验证步骤
  • 在JSON中添加分组ID信息

问题3:坐标归一化后精度丢失

解决方案

  • 增加小数位数(如round(x, 6))
  • 使用字符串格式化代替round:
f"{x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}"

7. 高级技巧:处理部分可见关键点

对于DIM=3格式,可以精细处理关键点可见性。以下是根据实际情况判断可见性的逻辑:

def determine_visibility(kp, img_width, img_height): x, y = kp['point'] # 超出图像边界 if x < 0 or x >= img_width or y < 0 or y >= img_height: return 0 # 其他业务逻辑判断 # ... return 2 # 默认可见

在最近���一个跌倒检测项目中,我发现正确处理关键点可见性能使模型性能提升约15%。特别是在监控场景中,人体经常被家具部分遮挡,精确标注可见性至关重要。

http://www.jsqmd.com/news/874958/

相关文章:

  • PXE安装麒麟Kylin后,我用这个脚本搞定了软件源、远程桌面和sudo免密
  • 用Python+OpenCV复现DWT-DCT-SVD图像水印:从原理到代码的保姆级实战
  • CANN 推理缓存:相同输入的秒级响应实战
  • ESP32嵌入式AI语音助手安全加固实战指南
  • Windows设备管理器报‘代码43’导致HDMI无输出?保姆级排查与修复指南(附原理)
  • 别再让WSL2吃光你的C盘!手把手教你迁移到D盘并优化内存配置(Windows10/11通用)
  • 别再只会用LSB了:聊聊DWT小波变换水印在Python里的实战(附代码避坑)
  • 保姆级教程:用Python复现CDSM融合算法,在NuScenes上跑通3D目标检测
  • CANN 精度调优:INT8 量化误差分析与混合精度策略实战
  • 别再手动处理表格了!用PyQt6的QTableWidget右键菜单实现高效数据编辑(支持复制粘贴到Excel)
  • K230目标检测实战:手把手教你用Labelme标注数据并一键转成VOC格式(附Python脚本)
  • 盯盯拍Mini2固件v3.5.2.35导致SD卡识别失败的技术解析
  • 保姆级教程:在Ubuntu 22.04上从源码编译COLMAP 3.9(含6个常见Bug解决方案)
  • 移动端事件相机实时手势识别:TFLite加速与功耗优化实践
  • 告别手动标注!用SAM+Python脚本,5分钟批量生成你的专属分割数据集
  • Oracle EBS 把 SAP 的利润中心作为独立段放进 Oracle EBS 的 COA,本质是用 EBS“科目即多维索引” 的弹性域架构,模拟 SAP“利润中心 = 独立核算维度”
  • AI系统误差传播建模:从仿真数据生成到高效参数估计的完整方案
  • 中小企业AI落地实战:从能力配置到生态嵌入的五步导航图
  • ARCADE:用AR交互评估弥合CV模型指标与感知的鸿沟
  • 鸿蒙electron跨端框架PC想法卡片实战:把零散灵感做成能继续展开的卡片流
  • 材料机器学习实战:从成分、结构到工艺的特征工程全解析
  • 从《炉石传说》猜卡组到垃圾邮件过滤:用Python手把手实现贝叶斯更新(附代码)
  • 【AI Agent法律应用实战指南】:20年律所技术总监亲授3大落地场景与5个避坑红线
  • OpenClaw 源码解析(一):项目总览与源码阅读路线
  • 对话雷军:造车是十年之功 小米要放平心态
  • 计算机视觉如何让外骨骼机器人实现预见式步态辅助控制
  • Arm CPU指针认证安全:PACMAN攻击与防御实践
  • 保险智能体部署失败率高达73%?揭秘头部险企AI Agent上线前必须完成的3个合规校验步骤
  • 在 Oracle EBS R12 / Cloud EBS 里,怎么新建一个利润中心段(用来承接 SAP 利润中心)
  • .NET Framework 4.7.2 TLS 1.3 兼容性故障排查与修复