ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

X-anylabing标注JSON转YOLO-POSE训练txt:自定义映射与可视化验证全攻略

X-anylabing标注JSON转YOLO-POSE训练txt:自定义映射与可视化验证全攻略 做姿态估计训练的时候最烦的一件事就是标注数据和训练格式之间来回倒腾。尤其是用X-anylabing这类工具标注完导出的JSON和YOLO-POSE要的txt长得完全不一样还得写脚本转。更别说转换完到底对不对光看数字很难看出来搞不好训练的时候才发现关键点全错位那真是浪费好几天。这篇文章就聊透这件事X-anylabing标注的JSON怎么转成YOLO-POSE训练用的txt怎么支持自定义标签映射转换结果如何可视化验证。我自己用Python写了一套完整方案实测跑通附带踩坑记录给后面做关键点检测的兄弟们一个能直接抄作业的参考。1. 项目背景与整体设计思路1.1 为什么需要JSON转txt转换脚本做YOLO-POSE关键点预测训练数据的格式是固定的每张图片对应一个txt文件每行描述一个目标格式是class_id x_center y_center width height kx1 ky1 v1 kx2 ky2 v2 ...。这里x_center、y_center、width、height都是归一化到0~1的边界框参数后面跟着的是17个关键点的归一化坐标和可见性标志。而标注工具导出的是JSON里面保存的是分层结构图片信息、尺寸、对象列表每个对象里有bounding_box或polygon还有keypoints列表每个关键点有名称、坐标和可见状态。一边是专为机器训练设计的紧凑文本格式一边是给人阅读设计的富信息JSON结构。如果每次手动改或者拿Excel自己拼不仅效率低而且极易出错。写脚本转换是唯一的正道而要想转换得不错位必须先搞清楚两种格式的映射关系。这里有个核心痛点X-anylabing的JSON中关键点坐标是绝对值像素坐标且坐标系可能涉及不同缩放比如标注时的视图缩放和实际图像尺寸不一致。转换时如果只拿视觉数据的宽高直接归一化极容易出现坐标漂移。这也是为什么必须把图像尺寸和标注坐标在同一个基准坐标下计算后面我详细说。1.2 项目技术选型与实现思路实现语言我选了Python。原因很直接YOLO生态基本都在Python里OpenCV、NumPy、Pyyaml这些库随手就能装而且处理JSON是Python的看家本领。整个转换脚本的设计分成四层输入层解析X-anylabing导出的JSON文件支持单个文件和整个目录批量处理。转换层从JSON里提取边界框和关键点信息做坐标归一化根据传入的标签映射表把字符串标签转成YOLO类别ID。输出层为每张图片生成同名txt文件写入指定目录。验证层用OpenCV将转换后的关键点和边界框画回原图人工核对。这里我要强调一个提前想清楚的点标签映射必须解耦。X-anylabing里标签可能是中文名如“人”、“狗”、“猫”也可能是英文名如“person”、“dog”甚至数字而YOLO训练时只认类别ID。所以脚本必须支持传入一个映射字典或映射文件而不是写死标签名。这样做的好处是同一个转换脚本可以复用到其他数据集不用每次改代码。设计标签映射时还考虑了一种特殊情况如果JSON里的标签在映射字典里找不到怎么办我的方案是直接跳过该对象并打印警告。如果因为漏标导致某个类别一张图都没有训练时该类别的loss会异常所以宁可少一个对象也不能把类别ID配错。“可指定标签转换”这个需求说白了就是给用户几个映射选项可以是手动输入的字典也可以是一个YAML文件。我更推荐YAML文件因为标注项目多了以后命名习惯千奇百怪有个文件可维护性好很多。后面代码里两种方式都会支持。1.3 这个方案解决了什么问题我举个具体场景。之前做了一个人体姿态估计项目X-anylabing里标注了5000张图片每张平均2~3个人共标注了17个关键点。如果手动导出转换至少一天。用脚本批量转换不到一分钟搞定而且可以随时重新生成。更重要的是我踩过一次坑转换完后直接丢进训练跑了三天发现loss下不去。后来画出来一看边界框是对的但关键点全偏了一个身位原因就是JSON里的关键点坐标不是相对于图片原图的而是相对于某个裁剪区域。后来我改了脚本加入了“基准坐标对齐”逻辑才真正解决。这个问题不解决后面的训练全白搞。2. 数据格式深度解析从JSON到txt的映射规则2.1 X-anylabing导出的JSON结构长什么样先看一个典型的X-anylabing导出JSON结构。为了直观我把它整理成伪代码形式方便对照{ info: { name: dataset_001, created: 2024-05-10T14:33:00 }, images: [ { id: 0, file_name: img_0001.jpg, width: 1280, height: 720 } ], annotations: [ { id: 0, image_id: 0, category_name: person, bbox: [310, 52, 562, 671], keypoints: [ {name: nose, x: 402, y: 158, visible: 2}, {name: left_shoulder, x: 339, y: 203, visible: 2} ] } ] }注意几点images数组里存图片基本信息核心字段是file_name、width、height。annotations里的bbox是[x, y, w, h]x和y是左上角坐标w、h是宽高。keypoints是对象数组每个关键点有名称、坐标、可见性。visible字段2表示可见1表示遮挡但能推断0表示完全不可见。YOLO-POSE也支持这种三态但很多训练配置里只区分0和非0这点要注意。实际用X-anylabing导出的JSON可能字段名不完全一样比如category_name可能叫labelkeypoints里可能是x和y分开的。写转换脚本时字段名一定要先打印几份原始数据看看不要想当然。2.2 YOLO-POSE的txt格式规则YOLO-POSE和YOLO检测最大的区别在于行尾多了关键点。一个标准的YOLO-POSE txt行格式class_id x_center y_center width height px1 py1 pv1 px2 py2 pv2 ... px17 py17 pv17其中class_id整数从0开始。x_center y_center width height归一化到0~1的边界框。px_i py_i pv_i第i个关键点的x、y归一化坐标和可见性标志。这里有个极易出错的地方关键点的顺序必须固定。YOLO-POSE在训练时是依赖关键点顺序的比如官方coco权重顺序是鼻子、左眼、右眼、左耳、右耳、左肩、右肩、左肘、右肘、左腕、右腕、左髋、右髋、左膝、右膝、左踝、右踝。如果你的txt里顺序不统一训练出来的模型关键点会张冠李戴。所以在转换脚本里我要求用户传入一个关键点顺序列表脚本会按照这个列表的顺序输出而不是按照JSON里出现的顺序输出。这算是我踩过很多坑之后总结出来的硬性要求。2.3 标签与可见性的映射约定X-anylabing里的标签是字符串YOLO训练要整数ID转换脚本必须在中间加一层字典映射。我常用的映射方式有两种一种是直接手动指定对应表比如{person: 0, dog: 1}另一种是根据一个标签列表的顺序自动生成ID比如[person, dog]那person就是0dog就是1。这里有个坑如果JSON里出现了映射字典之外的标签脚本应该怎么办。直接报错会让整个转换中断忽略会导致这个目标丢失。我的方案是打印警告跳过该对象并统计总数。转换完了看一眼统计输出如果跳过的数量异常说明标签映射表不完整事后补上再重新转换即可。关于可见性我完全保留X-anylabing的visible值不去做任何归一化。因为有的训练配置会区分“被遮挡但可见”和“完全不可见”如果你擅自把1改成2等于篡改标注信息。保留原始值最稳妥。3. 转换脚本的设计与实现3.1 环境准备与核心依赖写这个脚本不需要太多第三方库但有几个是必须的Python 3.7我测试用的是3.9。opencv-python用于可视化验证。numpy用于坐标计算和数组操作。pyyaml用于读取外部标签映射文件。安装命令pip install opencv-python numpy pyyaml如果只是转换不可视化那只需要用到Python自带的json和os模块就够了。但我强烈建议可视化原因后面说。OpenCV装的时候注意版本新版OpenCV的circle函数参数不变但老版本和新版本对字体类型、线宽的常量定义有区别代码里我统一用整数。3.2 核心转换函数实现先写一个最核心的单个JSON文件转换函数。它的作用是解析一个JSON文件提取所有标注按固定关键点顺序输出YOLO格式的txt行。import json import os import numpy as np def convert_json_to_txt(json_path, output_dir, keypoint_names, label_map, image_id0): with open(json_path, r, encodingutf-8) as f: data json.load(f) img_info data[images][image_id] img_width img_info[width] img_height img_info[height] img_name os.path.splitext(img_info[file_name])[0] output_txt os.path.join(output_dir, img_name .txt) lines [] skipped 0 for ann in data[annotations]: if ann.get(image_id) ! image_id: continue label_name ann.get(category_name) if label_name not in label_map: print(f[Warning] label {label_name} not in label_map, skipped.) skipped 1 continue bbox ann[bbox] # [x, y, w, h] x, y, w, h bbox[0], bbox[1], bbox[2], bbox[3] # 边界框归一化 x_center (x w / 2.0) / img_width y_center (y h / 2.0) / img_height w_norm w / img_width h_norm h / img_height # 关键点处理 kp_list [] for name in keypoint_names: found False for kp in ann[keypoints]: if kp[name] name: kx kp[x] / img_width ky kp[y] / img_height kv kp[visible] kp_list.extend([round(kx, 6), round(ky, 6), kv]) found True break if not found: # 缺失的关键点用0填充并标记不可见 kp_list.extend([0.0, 0.0, 0]) class_id label_map[label_name] line [class_id, round(x_center, 6), round(y_center, 6), round(w_norm, 6), round(h_norm, 6)] kp_list lines.append( .join(map(str, line))) with open(output_txt, w, encodingutf-8) as f: f.write(\n.join(lines)) print(f[OK] {json_path} - {output_txt}, objects: {len(lines)}, skipped: {skipped})这里我用了round(x, 6)保留六位小数。为什么是六位因为YOLO训练时坐标精度只要达到1e-6就足够覆盖常见的4096x4096这种超大图。再多的位数只是浪费存储空间而且在一些极端情况下浮点数过长还会引发txt解析错误。值得注意的是当某个关键点在JSON中不存在时我填充了[0, 0, 0]。位置填0而不是某个默认坐标避免模型学到错误的位置先验。3.3 支持指定标签转换“可指定标签转换”是这个项目的核心需求之一。我支持两种标签映射方式。第一种是直接在命令行里用字典指定python convert.py --json_dir ./jsons --output_dir ./labels --label_map {person: 0, dog: 1}当你有多个目标类别时这种写法一眼就能看明白每个类别的ID。第二种是使用YAML映射文件。这种方式更适合大型项目因为标签可能经常调整改YAML比改命令行方便得多# label_map.yaml person: 0 dog: 1 cat: 2然后在脚本里用yaml.safe_load加载import yaml def load_label_map(path): with open(path, r, encodingutf-8) as f: return yaml.safe_load(f)有了标签映射转换脚本才能适应各种数据集而不是写死标签名。这里要说一个我踩过的坑类别ID必须从0开始且连续递增中间不要留空洞。YOLO的类别数是通过最大ID1推断的如果你把person映射成0、dog映射成2中间没有1那么实际训练时类别数会变成3dog的数组索引是2但很多时候网络结构是ch_out nc这个空洞就会导致维度不匹配训练直接报错。当然现在很多框架会自动修正但数据层面尽量规范。3.4 可视化验证的实现转换完成不等于万事大吉必须画出来看一眼。可视化脚本的作用是读取txt文件把边界框和关键点画回原图保存为新的图片然后用肉眼看位置是否合理。核心函数如下我把关键点按连接线分组画起来更直观import cv2 import numpy as np # COCO 17关键点的连接关系简化示例 skeleton [ (0, 1), (0, 2), (1, 3), (2, 4), # 鼻子到眼睛和耳朵 (5, 6), (5, 7), (7, 9), (6, 8), (8, 10), # 手臂 (5, 11), (6, 12), (11, 12), # 躯干 (11, 13), (13, 15), (12, 14), (14, 16) # 腿 ] def draw_yolo_pose_txt(image_path, txt_path, output_path, keypoint_names, class_namesNone): img cv2.imread(image_path) h, w img.shape[:2] with open(txt_path, r, encodingutf-8) as f: lines f.readlines() for line in lines: parts list(map(float, line.strip().split())) class_id int(parts[0]) x_center, y_center, box_w, box_h parts[1], parts[2], parts[3], parts[4] # 反归一化边界框 x1 int((x_center - box_w / 2) * w) y1 int((y_center - box_h / 2) * h) x2 int((x_center box_w / 2) * w) y2 int((y_center box_h / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) # 提取关键点 kp [] for i in range(5, len(parts), 3): px, py, pv parts[i], parts[i 1], parts[i 2] if pv 0: # 只画可见或遮挡的关键点 cv2.circle(img, (int(px * w), int(py * h)), 3, (0, 0, 255), -1) kp.append((int(px * w), int(py * h))) else: kp.append(None) # 连线 for s in skeleton: p1 kp[s[0]] p2 kp[s[1]] if p1 is not None and p2 is not None: cv2.line(img, p1, p2, (255, 0, 0), 1) if class_names: label_text class_names[class_id] else: label_text str(class_id) cv2.putText(img, label_text, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 1) cv2.imwrite(output_path, img)这个可视化函数里有个细节pv 0才画点。因为在COCO约定里0是完全不可见1是遮挡但有位置2是可见。但我发现有些标注工具的visible字段含义完全相反比如0表示可见1表示不可见。如果你发现画出来的结果点和可见性完全反着优先检查这里。3.5 脚本的扩展与批量处理量产项目不可能只转一个文件所以我封装了一个批量处理函数遍历目录里所有JSON文件逐一转换并统计总体的处理情况。def batch_convert(json_dir, output_dir, keypoint_names, label_map): os.makedirs(output_dir, exist_okTrue) total_json 0 total_objects 0 total_skipped 0 for filename in os.listdir(json_dir): if not filename.endswith(.json): continue json_path os.path.join(json_dir, filename) # 这里假设每个JSON对应一张图 lines_count, skipped convert_json_to_txt_by_lines(json_path, output_dir, keypoint_names, label_map) total_json 1 total_objects lines_count total_skipped skipped print(f\n[Summary] Converted {total_json} JSON files, {total_objects} objects, skipped {total_skipped} due to label mapping issues.)注意批量转换时我按照文件名一一对应要求JSON的文件名和图片名一致。如果实际数据集里图片名是0001.jpgJSON名是0001.json那没问题。如果对不上要先统一改文件名或写一个映射关系否则结果txt会找不到对应图片训练时会报错。3.6 参数解析与命令行入口完整的脚本肯定不能把参数写死在代码里我用argparse构建了一个命令行入口支持指定JSON目录、输出目录、关键点顺序文件、标签映射文件、是否可视化等参数。import argparse def main(): parser argparse.ArgumentParser(descriptionX-anylabing JSON to YOLO-POSE txt converter) parser.add_argument(--json_dir, typestr, requiredTrue, helpDirectory containing JSON files) parser.add_argument(--output_dir, typestr, requiredTrue, helpDirectory to save txt files) parser.add_argument(--keypoint_order, typestr, requiredTrue, helpPath to keypoint order file) parser.add_argument(--label_map, typestr, requiredTrue, helpPath to label map YAML file) parser.add_argument(--visualize, actionstore_true, helpWhether to draw verification images) parser.add_argument(--image_dir, typestr, default, helpImage directory for visualization) parser.add_argument(--vis_output_dir, typestr, default, helpOutput directory for visualization images) args parser.parse_args() keypoint_names load_keypoint_order(args.keypoint_order) label_map load_label_map(args.label_map) batch_convert(args.json_dir, args.output_dir, keypoint_names, label_map) if args.visualize: # 这里可以调用上面写的draw_yolo_pose_txt遍历目录 pass这里要强调一下为什么需要独立的keypoint_order文件。因为不同数据集可能关键点名称不同比如COCO是17个MPII是16个自定义数据集可能是20个。如果写死在脚本里每次换数据集都得改代码不优雅。把它做成一个文本文件每行一个关键点名称脚本读了就知道顺序了这才是“工程级”的处理方式。4. 实操全过程记录从JSON到可训练的txt4.1 准备关键点顺序文件和标签映射文件先创建一个keypoint_order.txt内容按序排列nose left_eye right_eye left_ear right_ear left_shoulder right_shoulder left_elbow right_elbow left_wrist right_wrist left_hip right_hip left_knee right_knee left_ankle right_ankle再创建一个label_map.yamlperson: 0注意这里我用了person: 0而不是person: 1。类别ID从0开始是YOLO的标准约定如果从1开始训练时类别数会多算一个而且背景类还单独占一个位置非常容易出错。4.2 执行转换命令假设我的JSON文件放在./any_data/jsons图片放在./any_data/images输出目录是./any_data/labels可视化图片放到./any_data/vis。执行python convert.py \ --json_dir ./any_data/jsons \ --output_dir ./any_data/labels \ --keypoint_order keypoint_order.txt \ --label_map label_map.yaml \ --visualize \ --image_dir ./any_data/images \ --vis_output_dir ./any_data/vis跑完之后控制台输出每一张图的转换结果。我当时的输出是[OK] img_0001.json - ./any_data/labels/img_0001.txt, objects: 3, skipped: 0 [OK] img_0002.json - ./any_data/labels/img_0002.txt, objects: 2, skipped: 0 ... [Summary] Converted 1280 JSON files, 3521 objects, skipped 0 due to label mapping issues.这个输出非常关键。如果skipped数很大说明标签映射文件有遗漏赶紧回去补。如果不看这个统计你都不知道数据已经丢了。4.3 可视化验证的两种方式我实际工作中会做两层可视化验证。第一层是样本抽检。随机抽20~30张图片把txt和图片对应上用上面的draw_yolo_pose_txt函数画出来然后人眼检查。重点看三件事边界框是否贴合目标主体不要半截框。关键点是否落在语义正确的位置比如鼻子在脸中央、手腕在手腕处。遮挡关键点是否被正确标为1而不是凭空瞎画。第二层是自动检查。写一个简单的脚本扫描所有txt统计有没有出界的归一化坐标小于0或大于1有没有负数的宽高有没有关键点顺序位数不对的行。这些是训练时最容易崩的地方。def check_txt_health(txt_dir): bad_lines 0 for txt_file in os.listdir(txt_dir): if not txt_file.endswith(.txt): continue with open(os.path.join(txt_dir, txt_file), r, encodingutf-8) as f: for line in f: parts list(map(float, line.strip().split())) # YOLO-POSE: 1 class 4 bbox 17*3 kp 56 if len(parts) ! 56: print(f[Bad length] {txt_file}: {len(parts)}) bad_lines 1 continue # 检查边界框坐标 for i in range(1, 5): if parts[i] 0 or parts[i] 1: print(f[Out of range] {txt_file}: {line.strip()}) bad_lines 1 break print(f[Check] Done, {bad_lines} bad lines.) check_txt_health(./any_data/labels)这里有个想法如果某个关键点的坐标因为标注遗漏而填了0恰好目标在图片左上角这个0也不一定越界所以自动检查只能查出格式问题语义问题还得靠可视化。4.4 参数计算过程举例我用一张具体图片说明转换时坐标是怎么算的。假设原图尺寸是1280x720标注框是[310, 52, 562, 671]即左上角x310y52宽562高671。那么中心点x 310 562/2 591中心点y 52 671/2 387.5归一化x_center 591 / 1280 0.46171875取六位小数约0.461719归一化y_center 387.5 / 720 0.53819444约0.538194归一化宽 562 / 1280 0.4390625约0.439062归一化高 671 / 720 0.93194444约0.931944如果某个关键点比如鼻子坐标是(402, 158)那么归一化x 402 / 1280 0.3140625约0.314062归一化y 158 / 720 0.21944444约0.219444输出行就是0 0.461719 0.538194 0.439062 0.931944 0.314062 0.219444 2 ...看见没所有数字都在0~1之间这就是YOLO训练需要的标准格式。5. 常见问题与排查技巧实录5.1 关键点坐标偏移但边界框正确这是最容易踩的坑我开头提到的那个case。表现为边界框贴得很完美但关键点全往左上方偏了一截。原因通常是X-anylabing在标注时图片经过了缩放或裁剪导出的JSON中关键点坐标不是相对于原图的而是相对于标注视图的。比如标注时图片显示宽度是800实际原图宽度是1280如果不做比例还原所有x坐标都会偏小。排查方法取一张图手动找一个关键点的像素坐标除以原图宽再对比txt里的归一化值。如果差异明显说明坐标基准不对。解决办法在转换前找到X-anylabing标注视图的尺寸一般在JSON的info里有记录或者标注时的界面比例先把关键点坐标按比例映射回原图坐标再进行归一化。# 示例如果标注视图宽是800原图宽是1280 scale_x img_width / 800 scale_y img_height / 600 # 假设高也是缩放过的 real_x kp[x] * scale_x real_y kp[y] * scale_y5.2 可见性标志全部是0导致关键点没画出来有粉丝问过我一个现象转换后的txt关键点位置都对但训练效果很差loss震荡。一查发现所有关键点的可见性都是0等于模型根本没学到关键点。我排查后发现X-anylabing在标注时如果没手动修改默认可见性可能是0或是某个特殊值而转换脚本如果把这个值当成了“完全不可见”训练时就会忽略这些关键点。解决方法是在转换前先统计一下JSON里visible字段的取值分布。如果全是0说明标注时可见性没被正确写入需要在转换时做一次默认值修正把0改为2可见。如果分布正常2/1/0混合就不要乱改。python -c import json data json.load(open(annotations.json)) vis_values set() for ann in data[annotations]: for kp in ann[keypoints]: vis_values.add(kp[visible]) print(vis_values) 5.3 每个JSON对应多张图image_id怎么对齐X-anylabing支持一次导出多个图片的标注经常出现一个JSON文件里包含几十张图片和几百个对象的标注。如果你的数据集结构是这样的就不能简单假设JSON文件名和图片名一致。这时候需要遍历images数组再遍历annotations按image_id过滤出属于当前图片的对象分别生成每张图片的txt文件。我在上面的示例代码里实际上已经给了image_id参数但批量场景下需要循环所有图片ID。还有如果一张图没有标注txt文件应该留空还是跳过我的建议是跳过生成但要打印提示。因为YOLO训练时空txt文件会让数据加载器卡住或产生未定义行为。5.4 标签ID不连续模型训练报维度错误这个问题在上面提过一嘴但值得展开说。假设你的数据集有“person”和“helmet”两类你映射成了{person: 0, helmet: 2}中间跳过了1。那么数据加载器看到的最大ID是2类别数nc3但第二类实际缺失损失函数计算时索引1永远不会被激活模型最终输出3个通道。看起来很合理但当数据集里只有两种目标时这3个通道的第2个通道对应一个不存在的类如果测试时预测出这类就白白浪费了。更糟糕的是如果在训练过程中加了类别采样或数据增强这种空洞的ID可能会导致内存问题或奇怪的分错。遵守从0开始连续递增是数据集转换的最基本纪律。5.5 图片尺寸和JSON尺寸不一致有时候JSON里记录的width和height和实际图片的像素尺寸不一致。这种情况常见于标注时图片被软件压缩但JSON里的信息还是压缩前的或者标注人员修图替换了原图但没有更新标注。这个问题的检测方法也很简单在可视化阶段如果发现所有框和点整体向左上或右下偏移且偏移量随位置线性变化那就是尺寸不一致。解决方法是以实际图片尺寸为准重新计算归一化坐标。我在转换脚本里特意留了一个--use_actual_size参数开启后会用cv2.imread读取图片实际尺寸来替换JSON里的宽高。5.6 关键点顺序错误导致关键点错乱这个坑非常隐蔽因为它不影响训练跑通但模型预测时鼻子会显示在肩膀上。原因是X-anylabing里的关键点顺序和YOLO-POSE需要的顺序不一致如果你的转换脚本直接按JSON里的出现顺序输出那顺序就乱了。解决方案就是我前面说的keypoint_order文件。转换前先确认关键点顺序是真的和你训练配置一致尤其是COCO预训练权重它的顺序是固定的。如果顺序不对必须调整keypoint_order文件内容而不是改代码。6. 可视化大屏与应用的一点点延伸思考说句实话纯做转换和可视化已经能解决训练前的数据准备需求了但“可视化”这个需求在网络热词里出现了很多次比如“可视化大屏”、“数据可视化”、“python数据分析与可视化”。这说明很多人做标注转换不只是为了自己看还要向上汇报、展示数据集质量、判断标注一致性。如果你的场景也是要给团队或客户看标注质量那可以直接把可视化结果做成一个HTML报告每张图片一个缩略图汇总转换统计信息和标签分布。这个延伸思路不难实现用Pandas统计每个类别的目标数量、每张图的目标数量、关键点可见性分布然后用简单的HTML模板生成一个报告页面。这样就不需要一张张去翻图片了汇报时打开网页一目了然。不过我在实际项目里发现可视化验证和Auto QA自动质量检查才是真正的生产力大屏展示反而是次要的。数据准备阶段画出来的图能帮你快速发现上节说的那几个坑自动检查可以批量扫描异常文件。两者结合数据集的置信度就上来了。7. 实操总结与我的几点个人体会这个项目本身不复杂但涉及到的知识点很杂JSON解析、YOLO格式规范、关键点顺序约定、标签映射策略、图像坐标变换、OpenCV可视化。任何一个环节出错都会直接影响到后续训练效果。结合我这几次做姿态估计的真实经历总结几条硬经验转换脚本写完之后先拿20张图手工验证确认边界框、关键点、可见性全部正确再全量转换。别贪快一贪快就出幺蛾子。保留原始JSON文件永远不要删。因为转换脚本可能有bug训练发现数据错误时可以回退重转不用重新标注。标签映射文件用YAML管理版本控制里也纳管。项目迭代时标签增删是常态有个文件历史记录才不会乱。自动检查脚本一定要写每次转完跑一遍把坐标越界、维度错误、标签超范围的行全找出来。可视化时把关键点画大一点3像素半径在720p图上能看清但在4K图上几乎隐形。我习惯根据图片宽高动态调整半径和线宽比如r max(2, int(w/500))。我做这个转换脚本踩过最深的坑就是基准坐标问题。当时赶进度没验证就直接训练结果关键点全偏。后来重新设计了转换流程才彻底根治。现在每次新项目我第一件事就是做数据格式验证而不是急着写模型这大概就是所谓“数据决定了模型的上限”的真正含义吧。最后再分享一个小技巧转换后的txt可以用numpy数组读回内存做一次统计性检查比如每个关键点的平均可见性比例、每个坐标的均值方差。这些统计特征能帮你判断标注是否整体偏移、是否存在系统性问题。我遇到过一次某个batch的txt所有关键点x坐标都小于0.3一查发现标注时只标了目标的左半边这种问题光靠眼抽检根本发现不了。
返回列表