Labelme转YOLO格式实战坐标归一化与多人标注处理详解当你从Labelme的JSON标注切换到YOLO格式时最令人头疼的莫过于坐标归一化和多人标注处理。作为计算机视觉开发者我曾在这个转换过程中踩过无数坑——从坐标计算错误到多人标注合并混乱再到文件路径处理的各种陷阱。本文将用实战代码带你彻底解决这些问题。1. 理解YOLO关键点格式的核心逻辑YOLO格式的关键点标注远比简单的物体检测复杂。官方文档中明确要求每个对象实例占据一行包含以下信息class-index x y width height px1 py1 px2 py2 ... pxn pyn关键差异点DIM2格式仅包含关键点的x,y坐标DIM3格式每个关键点追加可见性标志0不可见1遮挡2可见实际项目中我推荐使用DIM3格式因为它能更好地处理遮挡情况。以下是两种格式的对比示例格式类型示例数据适用场景DIM20 0.5 0.5 0.3 0.4 0.4 0.6 0.5 0.7简单场景所有关键点可见DIM30 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 }处理要点先提取图像尺寸imageWidth/imageHeight这是归一化的基准区分person标注边界框和关键点标注注意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, keypoints3. 多人标注处理的关键技术当一张图片中有多个人时需要将各自的关键点与对应的人体边界框正确关联。这是转换过程中最容易出错的部分。解决方案为每个person创建独立的数据结构根据空间距离将关键点分配到最近的人体框处理可能的关键点共享情况如握手场景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常见陷阱忘记取绝对值导致宽度/高度为负值使用错误的图像尺寸有些JSON文件可能不包含imageWidth/imageHeight归一化前未验证坐标是否超出图像边界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))增强鲁棒性的技巧添加图像尺寸回退机制处理关键点缺失情况验证归一化后的值是否在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)使用字符串格式化代替roundf{x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}7. 高级技巧处理部分可见关键点对于DIM3格式可以精细处理关键点可见性。以下是根据实际情况判断可见性的逻辑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%。特别是在监控场景中人体经常被家具部分遮挡精确标注可见性至关重要。