ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

从labelme JSON到YOLO分割标签:完整转换指南与踩坑实录

从labelme JSON到YOLO分割标签:完整转换指南与踩坑实录 只要做过分割项目几乎都会被同一件事卡住labelme 标注完手里攒了一堆 JSON但训练脚本要的是 YOLO 那种 txt 标签。labelme2yolo 这个转换动作就是把 JSON 里的多边形坐标拆出来、归一化、写成分割数据集该有的样子。接下来我把我实际用下来的流程、脚本和踩过的坑完整梳理一遍。这篇文章适合正在做语义分割、实例分割的读者尤其是准备把数据喂给 YOLOv8-seg 这类模型的人内容覆盖格式原理解读、从零手写转换脚本、数据验证、常见报错排查以及医学影像里息肉分割这种特殊场景下的标注规范。不夸张地说转换这步要是做得糙后面模型性能直接打折而且很多时候问题还藏在看不见的地方。1. 为什么是 labelme YOLO 这套组合1.1 两种格式的本质差异先说 labelme 的产物。labelme 每标注一张图会生成一个同名 JSON 文件里面核心字段是shapes每一个 shape 至少包含三样东西label类别名、points像素坐标点列表、shape_type多边形、矩形、圆还是线。坐标是绝对像素坐标单位是像素而且顺序通常是标注时鼠标点击的顺序。再来看 YOLO 分割格式。拿 YOLOv8-seg 来说每个目标任务对应一个 txt 文件文件名和图片名一致里面每行是一个实例格式是class_id x1 y1 x2 y2 ... xn yn。这里 class_id 是从 0 开始的整数坐标全部归一化到 0 到 1 之间做法就是拿像素坐标除以图片宽和高。两种格式最关键的区别有三个第一类别从字符串变成整数需要一份类别名到 ID 的映射表第二坐标从绝对像素变成相对比例必须知道图片宽高否则算不出归一化坐标第三JSON 里还有大量跟训练无关的冗余字段比如imageData里编码的整张原图转换时应该把这些东西剥干净。有一个细节很多人第一次会做错YOLO 分割标签里的坐标并不要求按顺时针或者逆时针排列官方文档甚至建议不要特意去平滑顶点直接用标注的原始打点顺序。所以转换脚本里不要画蛇添足做顶点重排保持原样就行。另外提醒一句YOLO 的 segment 格式本质是实例分割。如果你的目标任务是像素级的纯语义分割也就是每个像素都需要一个类别标签、同一个类别的多个连通区域不需要区分那 YOLO 这套格式并不完全对口。实际工程里很多人嘴上说“语义分割”做的其实是“单类实例分割”比如息肉分割这类场景 YOLO-seg 反而非常顺手。真正要输出语义掩码图的项目还是得转成 VOC 的 PNG 掩码或者 COCO JSON 那一套别混着用。1.2 这套链路最适合什么场景结合最近搜索热度很高的几个词来看这套链路最典型的是医学影像里的息肉分割数据集制作。息肉这类目标往往边界不清晰、大小差异大人工标注时用 labelme 的多边形工具逐点描是最稳的。标注完以后要训练 YOLOv8-seg就必须把一堆 JSON 转成 txt。除了医学影像这套链路也适合工业缺陷检测、遥感目标分割、自动驾驶的物体轮廓标注等场景。共同特点是目标形态不规则用矩形框会引入大量背景噪声必须用多边形描述轮廓标注量不大不小几百到几千张图没到必须上专业标注平台的规模团队里已经有 labelme 的使用习惯不想再换工具。反过来讲如果你的项目是纯目标检测不在乎轮廓细节那直接用矩形标注转 YOLO 检测格式class_id cx cy w h会更简洁也别硬往分割格式上凑。分割格式标签体积大训练时多边形参与损失计算对标注质量要求更高不是所有场景都值得。1.3 工作流全景与核心决策点一条完整的数据准备链路大致是五步用 labelme 标注原始图片、转换脚本把 JSON 转成 YOLO 分割 txt、可视化抽查标注是否错位、按 train/val 切分数据集并把图片和标签对齐、最后把数据配置喂给 YOLO 训练。这五步里最容易翻车的是第三和第四步。很多人转换完直接开训结果训练几个 epoch 后 loss 异常回头检查才发现标签文件里坐标全跑偏或者 train 和 val 里混进了重复图片。所以我不建议跳过验证直接训练。核心决策点其实就一个转换这步用现成开源工具还是自己写脚本。我的经验是开源工具适合数据干净、格式统一、快速验证的场景但只要数据里有一点“野”的比如有人不小心标了 line 类型、某个 JSON 缺了 imageData、图片路径被移动过开源工具往往直接报错或者静默跳过你根本不知道哪些数据丢了。自己写脚本最大的好处是可控你可以把每一步日志打出来把所有异常样本集中到一个清单里回头统一处理。2. 转换前的数据准备与标注规范2.1 标注阶段必须养成的三个习惯转换脚本能救回一部分标注错误但救不了规范性问题。我在实际项目里吃过好几次亏总结下来三个习惯必须在标注阶段就定下来。第一个习惯是统一的文件命名。图片名和 JSON 名必须完全一致不要出现中文、空格、括号这类特殊字符。YOLO 训练管道的文件匹配逻辑非常简单粗暴它默认 labels 目录下找同名 txt而 labelme 生成 JSON 时用的就是当前打开图片的文件名。一旦你中途改过图片名JSON 和图片就可能对不上转换时要么找不到图要么标签张冠李戴。第二个习惯是锁定 shape_type。做分割任务原则上只用 polygon 一种类型。但很多人标注时图省事某些目标用矩形、某些用圆甚至误标成 line。转换脚本如果对每种类型都要兼容逻辑会变得很复杂。更麻烦的是不同人对同一目标的描述方式不一样后面做数据质量分析时根本没法统一标准。我建议在标注规范里直接写死一律用 polygon。第三个习惯是标签词汇完全一致。同一类目标有人标“polyp”有人标“Polyp”还有人标“polyp1”转换后就会被识别成三个类别训练时类别数量完全不对。这个检查最好放在标注阶段就做可以定期跑一段脚本扫描所有 JSON 里的 label 字段看看有没有脏标签。2.2 图片与 JSON 的组织方式标注时的目录结构直接决定转换脚本的复杂度。最推荐的做法是把图片和它对应的 JSON 放在同一个目录下并且在标注过程中不要移动目录。为什么这么强调因为 labelme 在保存 JSON 时imagePath字段记录的是打开图片时的相对路径。如果标注完成后你把整个目录挪了位置JSON 里的 imagePath 可能失效转换脚本要想找到原图就得自己去拼接路径。最稳妥的方案是转换前就把数据归拢成统一结构比如一个项目根目录下全部图片在一个文件夹、全部 JSON 在另一个文件夹然后用脚本根据文件名去匹配。还要注意imageData字段。默认情况下labelme 会把整张图片的 base64 编码写进 JSON导致一个只有几十 KB 的图片JSON 体积能到一两百 KB。批量标注几千张图磁盘开销很可观。如果是在命令行启动 labelme可以加上--nodata参数这样 JSON 里就不编码原图文件体积小很多但转换时你必须保证 imagePath 指向的图片真实存在。具体参数名不同 labelme 版本略有差异拿不准就labelme --help看一下。2.3 标注质量检查哪些样本需要返工很多人在标注完就开始转换这是不对的。我习惯先花一个小时做标注质量抽检重点看三类样本。第一类是超小目标。息肉在早期筛查图像里经常只占几十个像素标注的时候很容易随手点几个点就保存。如果多边形的归一化面积小于某个阈值比如千分之一这类样本在训练时贡献的梯度极小还容易学成噪声。我的建议是把这类样本单独筛出来要么重新仔细标注要么直接剔除。第二类是边界贴合度差的多边形。检查时可以写个脚本把多边形点数和围成区域的周长打出来。点数太少比如一个大病灶只标了三个点轮廓跟真实边界肯定偏差大点数特别多又密集比如一个平滑目标标了上千个点说明标注者没有用简化曲线后期训练时标签文件会很大加载也慢。第三类是交叉多边形。同一个目标的多个实例如果标注时两个 polygon 出现重叠训练时 YOLO 的损失计算会变得很古怪。labelme 本身不阻止这种操作转出来的标签也不会报错但会影响模型收敛。抽查时用多边形相交检测跑一遍把有重叠的样本挑出来人工复核。另一个容易忽略的点是标注完一定要保留原始标注数据不要图省事把所有 JSON 删掉只留转换后的 txt。因为后期如果发现某个类别需要合并、某个标签名需要修改直接改 JSON 重新转换比在 txt 上改容易得多。3. 实操从 labelme JSON 到 YOLO 分割 txt3.1 环境准备与依赖安装转换脚本不需要什么重型依赖一个 Python 环境加上 OpenCV 和 NumPy 就够了。OpenCV 用来读取图片尺寸和可视化检查NumPy 用来做数组运算。安装命令很简单pip install opencv-python numpy如果你打算先用开源工具跑通流程也可以直接装 labelme2yolopip install labelme2yolo需要注意版本兼容。Python 3.8 到 3.11 基本都能跑OpenCV 版本太新某些接口可能有小变化但核心函数imread、imdecode、polylines这些一直是稳定的。3.2 快速跑通先用现成的 labelme2yolo 工具开源工具适合第一次接触这个流程的时候用能帮你建立整体认知。以 nyans 的 labelme2yolo 为例最简单的命令是labelme2yolo --json_dir ./labelme_jsons --val_size 0.2 --test_size 0.1 --seg参数含义很直白json_dir指定 JSON 所在目录val_size和test_size控制验证集和测试集比例--seg表示输出分割格式。跑完之后会在当前目录生成一个 YOLO_DATASET 文件夹里面有 images、labels以及 train.txt、val.txt、test.txt 这些索引文件。但这类工具默认行为是固定的遇到 circle 或 rectangle 类型的 shape处理方式是按工具自身的规则来不一定符合你的预期。另外它对异常数据的处理通常很“安静”比如某个 JSON 里坐标越界它可能直接跳过也可能照写不误。所以我的建议是开源工具适合验证数据整体规模真正要训练模型之前还是得有一个自己完全掌控的转换脚本至少把异常样本全部暴露出来。3.3 手写转换脚本完全可控的实现方式下面这个脚本是我在实际项目里反复改过后的版本核心逻辑是遍历 JSON、读取图片宽高、按 shape_type 提取多边形、归一化坐标、过滤异常掩码、输出 txt 并复制原图。你可以直接复制改路径用。import json import base64 import glob import os import shutil import cv2 import numpy as np from pathlib import Path class LabelMe2YOLOSeg: def __init__(self, json_dir, images_dir, output_dir, min_points3, min_area_norm1e-5, circle_segments32): self.json_dir Path(json_dir) self.images_dir Path(images_dir) self.output_dir Path(output_dir) self.min_points min_points self.min_area_norm min_area_norm self.circle_segments circle_segments self.class_names [] def _read_json(self, path): with open(path, r, encodingutf-8) as f: return json.load(f) def _image_size_from_data(self, img_data_b64): raw base64.b64decode(img_data_b64) arr np.frombuffer(raw, np.uint8) img cv2.imdecode(arr, cv2.IMREAD_COLOR) if img is None: return None h, w img.shape[:2] return w, h def _image_size_from_file(self, img_path): img cv2.imread(str(img_path)) if img is None: return None h, w img.shape[:2] return w, h def _parse_polygon(self, shape, img_w, img_h): pts np.array(shape[points], dtypenp.float32) if len(pts) self.min_points: return None pts[:, 0] / img_w pts[:, 1] / img_h return pts def _parse_circle(self, shape, img_w, img_h): cx, cy shape[points][0] px, py shape[points][1] r np.hypot(px - cx, py - cy) theta np.linspace(0, 2 * np.pi, self.circle_segments, endpointFalse) x cx r * np.cos(theta) y cy r * np.sin(theta) pts np.stack([x, y], axis1).astype(np.float32) pts[:, 0] / img_w pts[:, 1] / img_h return pts def _polygon_area(self, pts): x pts[:, 0] y pts[:, 1] return 0.5 * np.abs(np.dot(x, np.roll(y, 1)) - np.dot(y, np.roll(x, 1))) def _collect_classes(self): classes set() for json_path in glob.glob(str(self.json_dir / *.json)): data self._read_json(json_path) for shape in data[shapes]: classes.add(shape[label]) self.class_names sorted(classes) def convert(self): self._collect_classes() images_out self.output_dir / images labels_out self.output_dir / labels images_out.mkdir(parentsTrue, exist_okTrue) labels_out.mkdir(parentsTrue, exist_okTrue) with open(self.output_dir / classes.txt, w, encodingutf-8) as f: for cls in self.class_names: f.write(cls \n) class_to_id {name: i for i, name in enumerate(self.class_names)} for json_path in glob.glob(str(self.json_dir / *.json)): data self._read_json(json_path) stem Path(json_path).stem img_w, img_h None, None if data.get(imageData): img_w, img_h self._image_size_from_data(data[imageData]) if img_w is None: rel_img data.get(imagePath, ) img_path self.images_dir / rel_img img_w, img_h self._image_size_from_file(img_path) if img_w is None: print(f[skip] cannot get image size: {json_path}) continue lines [] for shape in data[shapes]: if shape[label] not in class_to_id: continue if shape[shape_type] polygon: pts self._parse_polygon(shape, img_w, img_h) elif shape[shape_type] circle: pts self._parse_circle(shape, img_w, img_h) else: continue if pts is None: continue pts np.clip(pts, 0.0, 1.0) if self._polygon_area(pts) self.min_area_norm: continue line [str(class_to_id[shape[label]])] line [f{x:.6f} for x in pts.flatten()] lines.append( .join(line)) if lines: with open(labels_out / f{stem}.txt, w, encodingutf-8) as f: f.write(\n.join(lines) \n) if data.get(imageData): raw base64.b64decode(data[imageData]) arr np.frombuffer(raw, np.uint8) img cv2.imdecode(arr, cv2.IMREAD_COLOR) cv2.imwrite(str(images_out / f{stem}.jpg), img) else: rel_img data.get(imagePath, ) src self.images_dir / rel_img dst images_out / f{stem}{Path(rel_img).suffix} if src.exists(): shutil.copy(src, dst) else: print(f[warn] no valid mask: {json_path})脚本里有两个地方值得解释。第一个是imageSize的获取顺序。我优先用imageData的 base64 解码拿宽高这样即使原图不在也能转换如果imageData为空再根据imagePath去图片目录找文件。这里有个细节base64 字符串开头可能带data:image/jpeg;base64,这类前缀base64.b64decode需要先按逗号切一次脚本里没有显式处理但大多数 labelme 版本存的是纯 base64 串如果遇到解析失败可以改成data[imageData].split(,)[-1]再解码。第二个是min_area_norm过滤。这个值是归一化坐标系下的多边形面积100 万像素的图里一个 100x100 像素的小目标面积是 0.01算下来是万分之一。阈值设得太低一批难样本可能全部混进训练集设得太高小目标容易被误删。我通常先用 1e-5 跑一遍然后打印所有被过滤样本的原始面积分布再微调阈值。3.4 从圆形标注生成分割多边形前面说过建议统一用 polygon但现实中总有人用 circle 标了一部分或者某些特殊场景下圆形标注本身更高效。遇到这种情况脚本里的_parse_circle就是干这个的。原理很简单从 JSON 的 points 里取两个点第一个是圆心第二个是圆周上任意一点算两点欧氏距离得到半径然后在圆周上等角度采样 N 个点形成近似圆的多边形。N 取 32 时肉眼几乎看不出多边形和圆的区别训练也完全够用想精细一点可以取 64但标签文件会变大推理时点多也会增加一点计算量。这里要注意labelme 的 circle 类型points 只存圆心和边缘一个点而不是所有圆周坐标这是合理的因为圆本身是解析图形没必要存一堆冗余点。转换时采样点越多圆的逼近越精确但 32 和 64 的差异在分割任务里基本可以忽略。矩形标注如果想转成分割多边形更简单直接把四个点按顺序连起来就行。不过我个人还是建议矩形目标直接走检测格式矩形框没有轮廓信息强行转多边形得到的只是对角线上的四个点和真实目标边界相差很远。4. 数据集验证转换完不等于能直接训练4.1 可视化抽查把标签画回原图转换脚本跑完第一件事不是开训练而是可视化。把 txt 标签画回原图上人眼扫一遍能发现大量自动化脚本发现不了的问题。下面这段代码用来抽查单个标签很顺手import cv2 import numpy as np def draw_yolo_seg(label_path, img_path, classes, output_pathNone): img cv2.imread(img_path) h, w img.shape[:2] with open(label_path, r, encodingutf-8) as f: for line in f.read().strip().splitlines(): vals line.split() cls_id int(vals[0]) coords np.array(vals[1:], dtypenp.float32).reshape(-1, 2) coords_pixel (coords * np.array([w, h])).astype(np.int32) cv2.polylines(img, [coords_pixel], True, (0, 255, 0), 2) cv2.putText(img, classes[cls_id], tuple(coords_pixel[0] [5, 5]), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) if output_path: cv2.imwrite(output_path, img) else: cv2.imshow(check, img) cv2.waitKey(0)抽检时重点看三类问题一是标签轮廓和真实目标边界是否贴合尤其是归一化后有没有出现奇怪的拉伸二是多个目标重叠的区域是不是明显不对三是整张图上所有目标都缩成一个小点或者贴在图像角落说明坐标归一化时宽高顺序搞反了。宽高顺序搞反是非常典型的错误因为 OpenCV 读取形状是h, w而 YOLO 归一化要求除以的是w, h写成h, w就会出现标签整体偏移和变形。建议按照数据总量的 5% 到 10% 抽检均匀覆盖不同类别、不同大小目标不要只挑好看图看。4.2 train/val 划分与类别统计YOLO 训练时通常要求目录结构是images/train、images/val、labels/train、labels/val所以转换完还需要做一步划分。这里的核心原则是图片和标签必须成对出现只复制图片而不复制对应 txt 是最常见的翻车点。一个最简单的随机划分脚本import random, shutil from pathlib import Path def split_dataset(images_dir, labels_dir, out_dir, val_ratio0.2, seed42): random.seed(seed) images list(Path(images_dir).glob(*.*)) random.shuffle(images) val_n int(len(images) * val_ratio) val_imgs, train_imgs images[:val_n], images[val_n:] for split, items in [(train, train_imgs), (val, val_imgs)]: img_dst Path(out_dir) / split / images lbl_dst Path(out_dir) / split / labels img_dst.mkdir(parentsTrue, exist_okTrue) lbl_dst.mkdir(parentsTrue, exist_okTrue) for img in items: stem img.stem lbl Path(labels_dir) / f{stem}.txt if lbl.exists(): shutil.copy(img, img_dst / img.name) shutil.copy(lbl, lbl_dst / f{stem}.txt) else: print(f[warn] missing label: {img.name})跑完划分后建议做一次类别统计。统计每个类别在训练集和验证集里的实例数看看分布是否失衡。息肉分割这种场景经常出现的情况是样本里绝大多数都是正常图像真正带息肉的图像只有几百张如果不做类别平衡训练时模型很快会偏向预测“无目标”收敛后召回率非常难看。类别统计的脚本不复杂遍历所有 txt按行读取第一个数字累加就行。除了数量我还会统计每个实例的多边形平均点数如果某个类别的平均点数远高于其他类别说明标注精细度不一致这也是导致 mAP 波动的一个隐藏因素。4.3 给训练阶段留个后门先跑小批量数据准备好以后我先不急着全量训练。一般会用一个小学习率、小 batch 跑十几个 epoch目的有两个一是确认数据管道能正常加载不会在某个异常的 txt 上报错二是用验证集指标做个基准如果十几个 epoch 后 loss 一点不降大概率不是模型问题而是标签或者数据配置有问题。一个能用的 YOLOv8 分割训练命令大概是这样的yolo segment train datadata.yaml modelyolov8n-seg.pt epochs100 imgsz640 batch16data.yaml 里要注意path写绝对路径最省心names的顺序必须和转换脚本生成的classes.txt完全一致。最容易出错的就在这里转换脚本是按字母排序的类别列表而 data.yaml 里如果手写类别顺序和它不一致训练时类别标签就会整体错位模型学到的类别含义全乱了。我的建议是直接从转换脚本生成的classes.txt读取类别名程序化生成 data.yaml不要手敲。5. 常见问题与排查技巧实录5.1 常见转换错误速查表把过去项目里遇到的高频问题整理成一张表按症状、原因、解决办法来查。注意下面的内容都是实操里真实发生的不是理论推测。现象原因解决办法FileNotFoundError: image not foundimagePath 路径失效图片目录被移动或重命名转换前用脚本核对 JSON 数量和图片数量把图片统一归拢到一个目录再转换标签画出来后整体偏移或变形归一化时宽高写反把像素坐标除以了(h, w)而不是(w, h)检查img.shape[:2]的取值顺序OpenCV 返回的是(高, 宽)某个 JSON 转换完 txt 是空的shapes 里全是 line 或 point 类型或者所有多边形面积都小于过滤阈值打印异常 JSON 文件名回源头看标注工具里的 shape_type训练时报IndexError: index N is out of boundsdata.yaml 里的 names 数量比 txt 中出现的最大类别 ID 小用脚本统计 txt 中出现的所有类别 ID确保 data.yaml 覆盖全部训练过程中 loss 出现 NaN标签出现越界坐标比如 1.05 这种值转换脚本里对归一化坐标做 np.clip同时把越界样本统计出来回源头修正转换后所有图片大小变成几千字节代码里把 base64 解码后的数组重新编码成 JPG画质被压缩需要保留原图的场景优先从 imagePath 复制原始文件不要走 base64 再解码的链路两个类别的标签互相混杂classes.txt 和 data.yaml 顺序不一致类别 ID 对不上统一用脚本读取 classes.txt 生成 data.yaml这张表看着短但每一条背后都是一个真实翻车故事。尤其是宽高顺序问题我至少见过三次每次都是因为图省事没有可视化抽查直接开训练浪费了好几个小时看训练日志。5.2 训练时数据加载报错的排查思路有时候转换本身没报错但训练中途崩了。YOLO 训练管道的报错往往不直接指向具体文件而是泛泛地告诉你某个 batch 加载失败。排查思路按顺序来先看 txt 文件格式是否整齐用脚本扫一遍有没有行首不是数字、坐标数量是否为偶数、某一行是否存在明显超过 1.0 的坐标再看文件路径有没有中文有些环境对中文路径支持不好会出现诡异的读取失败最后看图片扩展名如果图片是 PNG、BMP而转换脚本统一写完 .jpg标签文件和图片文件名对不上训练时就会缺标签。还有一个隐蔽问题是大文件。如果某张图的分辨率特别高比如内镜视频抽帧出来的图有 4000x3000单个多边形坐标点在几十上百个转换后的 txt 并不大但训练时 YOLO 默认会做 letterbox 缩放超大坐标处理好坏全看框架内部实现。遇到这种数据我通常先统一缩放到短边 1024 像素以内再做标注和转换否则训练时间会被拖慢很多而且模型对小目标的响应也不一定更好。5.3 医学息肉分割场景的额外注意事项如果要做的是息肉分割数据集有几点是通用流程之外的硬性要求。第一是数据隐私合规。内镜图像属于敏感医学数据转换、上传、共享全程都要做脱敏处理不能直接往公开平台传原始影像。团队内部训练也要控制访问权限标注外包时必须签保密协议这是底线。第二是标注一致性。息肉边界在有些图像里非常模糊不同标注者点出来的轮廓可能差异很大。我建议至少让两个人独立标注同一批样本然后计算两版标注的 Dice 系数挑出一致性偏低的样本重新讨论统一标准。比如规定边界内是否包含周围充血组织、是否包含血管走行区域这些细节不提前约定后面模型学到的边界模式一定是混乱的。第三是类别分布。息肉样本往往是弱阳性整个数据集里可能只有 5% 到 15% 的图像包含病灶且大多数病灶都很小。这种情况下转换时别把面积过滤阈值设得太大否则稀缺的小息肉样本会被全部滤掉。可以适当调低min_area_norm让模型在小目标上先踩一遍再用 hard example mining 的方式在后续迭代中加强这类样本。另外医学分割模型对召回率的要求通常高于精确率漏检一个息肉比误报一个非病灶严重得多。训练配置里conf_thres要调低评估指标也建议把 recall 和 F1 放前面看而不是单纯追求 mAP。6. 转换之外的一点心得做了这么多分割项目后我最大的体会是labelme2yolo 这一步看着简单但它卡住了整个数据流水线的质量上限。转换脚本跑通很快真正耗时的是数据规范的建立和异常样本的排查。很多项目模型效果上不去不是模型结构不对而是标签本身存在系统性偏差——有的类别漏标、有的边界画偏、有的坐标越界这些脏数据最后都变成模型里学到的噪声。我现在给自己定的规矩是转换脚本必须带日志每个 JSON 的处理结果都能倒查转换完必须可视化抽检不抽检不训练训练前必须统计类别分布和多边形质量指标。这三件事做完后面模型调参才有意义。一个值得尝试的扩展方向是多验证集策略。比如把息肉数据按内镜设备来源分几个子集转换后分别作为验证集跑一轮看模型在不同设备上的表现差异。这种分析工作在转换脚本里预留好数据结构会轻松很多。希望这个流程和脚本能帮你把数据集制作这步走稳省下更多时间去打磨模型本身。
返回列表