螺栓销钉缺失检测:1209张VOC数据集与YOLOv8训练实践
简介输电线路螺栓销钉缺失检测图像数据集专注电力设施智能巡检场景面向计算机视觉研究者与电力运维开发人员旨在解决销钉缺失这一高危隐患的自动识别问题。数据集共2000个文件、约89.52MB包含791张高清JPEG巡检图像及1209个PASCAL VOC格式XML标注文件XML记录目标边界框与类别信息覆盖正常与缺失两类状态可直接用于YOLOv7等目标检测模型的训练。图像以大目标为主清晰呈现螺栓销钉的局部细节配合labelimg工具生成的规范标注便于开展模型微调与精度对比。基于该数据训练的目标检测模型mAP可达93.7%在输电线路缺陷识别中具有较高实用价值。目前已有1203人学习下载适合希望快速搭建电力视觉检测方案的研究者使用。1. 输电线路螺栓销钉缺失检测为什么值得用这份1209张大目标VOC数据集一条110kV线路一年拍回来的精细化巡检照片少说几万张里面最常见的金具缺陷之一就是螺栓销钉缺失。销钉一旦脱落螺栓会在风振下逐渐退出严重时整套悬垂线夹脱开这是巡线老师傅最怕的“暗病”。人工盯着屏幕找这种小目标非常容易疲劳视觉检测在这个环节几乎是刚需。这份《输电线路螺栓销钉缺失检测图像数据集1209张大目标VOC》就是为近距离精细化巡检场景准备的1209张图目标在画面里占比大标注使用Pascal VOC格式大家常说的VOC图像和XML分离能直接套YOLO、mmdetection、RT-DETR这类主流框架。用它来做迁移学习验证、算法选型和现场demo是那种“拿来就能跑”的数据底座。2. 把VOC标注盘清楚目录结构、XML字段与一次数据体检拿到数据集先别急着转格式先花半天把VOC的家底摸清楚。VOC全称Pascal VOC是视觉领域流传最广的目标检测标注格式之一。它不像LabelMe那样一张图一个JSON而是把标注拆成三块图像目录、XML标注目录、划分清单目录。LabelImg标注器默认也输出这套结构所以很多电力线路数据集都长这样。dataset/ ├── JPEGImages/ │ ├── IMG_0001.jpg │ ├── IMG_0002.jpg │ └── ... ├── Annotations/ │ ├── IMG_0001.xml │ ├── IMG_0002.xml │ └── ... └── ImageSets/ └── Main/ ├── train.txt ├── val.txt ├── trainval.txt └── test.txtImageSets/Main下的txt每行是一个去掉扩展名的图像文件名训练框架按这些文件名去找JPEG和XML。有的数据集没有test.txt只给trainval这都正常划分比例以实际拿到手为准。重点检查的是train.txt和val.txt之间有没有文件名重叠以及这些文件名是不是同一个拍摄场景的连拍。打开一个XML典型结构长这样annotation folderJPEGImages/folder filenameIMG_0001.jpg/filename size width1920/width height1080/height depth3/depth /size object namebolt/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin512/xmin ymin360/ymin xmax640/xmax ymax450/ymax /bndbox /object /annotationsize里的宽高一定要和JPEG实际分辨率对齐否则后面归一化坐标会全部错位。object里的name是类别名这个数据集里到底写的是bolt、pin还是missing不要靠猜要把所有XML里的name枚举一遍。truncated表示目标被图像边界截断difficult表示难例VOC官方评测里difficult1的目标不计入AP主流训练框架基本也不读这个字段但如果你发现数据里大量框被截断训练时要留意边界框质量。有一个根目录下常用的“体检”脚本我用它来判断这份数据的类别分布和目标大小import os import xml.etree.ElementTree as ET from collections import Counter, defaultdict ann_dir Annotations img_dir JPEGImages cls_counter Counter() area_ratio defaultdict(list) img_size_set set() per_image_obj [] for xml_name in os.listdir(ann_dir): if not xml_name.endswith(.xml): continue tree ET.parse(os.path.join(ann_dir, xml_name)) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) img_area img_w * img_h img_size_set.add((img_w, img_h)) objs 0 for obj in root.iter(object): name obj.find(name).text cls_counter[name] 1 bnd obj.find(bndbox) xmin int(bnd.find(xmin).text) ymin int(bnd.find(ymin).text) xmax int(bnd.find(xmax).text) ymax int(bnd.find(ymax).text) box_area (xmax - xmin) * (ymax - ymin) area_ratio[name].append(box_area / img_area) objs 1 per_image_obj.append(objs) print(图像尺寸种类:, img_size_set) print(类别分布:, dict(cls_counter)) print(平均每图目标数:, sum(per_image_obj) / len(per_image_obj)) for name, ratios in area_ratio.items(): sorted_r sorted(ratios) mean_r sum(ratios) / len(ratios) median_r sorted_r[len(sorted_r) // 2] print(f{name}: 相对面积均值 {mean_r:.3f}, 中位数 {median_r:.3f}, 最大 {max(ratios):.3f})这段脚本用ElementTree解析XML没有外置依赖跑完能回答三个问题类别有几种、每类目标多少、目标在图像中的相对面积有多大。相对面积是判断“大目标”的直接依据。如果用这个脚本算下来大部分目标的相对面积中位数超过0.05那就是名副其实的大目标如果普遍超过0.15说明拍摄距离很近模型输入分辨率可以适当降低或者不需要在训练时做大量尺度增强。相反如果中位数只有0.01左右那整个训练策略要换成小目标方案比如更高输入分辨率、更大程度的数据增强、专门的切片检测。体检还有一个容易被忽略的项每张图像里的目标数量。脚本里per_image_obj记录了每张图的框数你可以算一下p90。如果p90大于5说明存在密集金具场景训练时NMS的IoU阈值要适当调高一点否则邻近目标会被压成同一个框。如果大量图像只有1个目标那模型学到的主要是“金具局部特写”现场部署时遇到“一串绝缘子多个金具”的全局画面就比较容易误检这个要在后面评估阶段专门测。最后确认标注策略有的数据集只标注“完好的螺栓/销钉”缺失的销钉位置不画框模型只能学到“有”判断“无”要靠背景推理有的数据集把缺失位置也显式标成一个类别比如missing或者defect模型就能直接输出“这里缺了一个销钉”。这两种方案训练出来的模型行为完全不同所以拿到数据的第一件事就是分类别统计不要急着进训练流程。确认清楚之后才进入转换环节。3. VOC转YOLO与数据集划分转换脚本和分组策略VOC格式虽然通用但YOLO系列训练时读取的是txt标签每行一个目标格式是“类别id cx cy w h”坐标全部归一化到0~1。这个转换本身不复杂但坐标边界和类别顺序两个地方容易出幺蛾子。我一般先把VOC统一转成YOLO格式后续换模型、做坏例分析都方便。import os import xml.etree.ElementTree as ET # 类别顺序是训练时的id依据和后面data.yaml里的names必须完全一致 class_names [bolt, pin, missing] def voc_to_yolo(xml_path, out_dir, eps1e-6): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_names: print(f跳过未登记的类别: {name}, 文件: {xml_path}) continue bnd obj.find(bndbox) xmin max(int(bnd.find(xmin).text), 0) ymin max(int(bnd.find(ymin).text), 0) xmax min(int(bnd.find(xmax).text), img_w) ymax min(int(bnd.find(ymax).text), img_h) w xmax - xmin h ymax - ymin if w 2 or h 2: continue # 过滤标注误差产生的退化框 cx (xmin xmax) / 2.0 / img_w cy (ymin ymax) / 2.0 / img_h nw w / img_w nh h / img_h # 归一化坐标保护等于1.0时部分框架读取会出数组越界 cx min(max(cx, 0.0), 0.9999) cy min(max(cy, 0.0), 0.9999) nw min(nw, 0.9999) nh min(nh, 0.9999) lines.append(f{class_names.index(name)} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}) if lines: base os.path.splitext(os.path.basename(xml_path))[0] with open(os.path.join(out_dir, base .txt), w) as f: f.write(\n.join(lines)) # 示例用法 for xml_name in os.listdir(Annotations): if xml_name.endswith(.xml): voc_to_yolo(os.path.join(Annotations, xml_name), labels)坐标一定要用XML里size的宽高做分母不能假设所有图都是同一个分辨率。这个数据集虽然是“大目标”但标注时偶尔会把框画到图像边界外一点点转出来的坐标会超过1.0训练时某些数据加载器会报IndexError所以我在这里统一做了截断。w或h小于2像素的框直接丢弃这种框在训练时只会引入噪声。转换完还要做一件小事统计labels目录下txt的数量应该和Annotations里的xml数量一致不一致就查文件名匹配。再检查有没有空txt文件有空的说明该图没有目标这在检测训练里是合法负样本不需要删除但你要知道它的存在。转完之后是划分。很多新手直接random.shuffle然后按8:2切这在螺栓销钉数据集上会制造一个假高分同一杆塔同一个金具的三张连拍一张进train两张进val模型在val上等于“见过”目标跑到0.95的mAP也说明不了现场能力。我常用的做法是先看文件名能不能分组。import os import random from collections import defaultdict ann_dir labels all_ids [f[:-4] for f in os.listdir(ann_dir) if f.endswith(.txt)] # 策略1纯随机划分适合文件名没有场景语义的情况 random.seed(42) random.shuffle(all_ids) n len(all_ids) train_ids all_ids[:int(n * 0.7)] val_ids all_ids[int(n * 0.7):int(n * 0.85)] test_ids all_ids[int(n * 0.85):] # 策略2按文件名前缀分组避免同一场景同时进train和val groups defaultdict(list) for file_id in all_ids: # 以最后一个下划线前的部分作为组键比如 Tower001_angle1_0001.jpg - Tower001_angle1 prefix file_id.rsplit(_, 1)[0] groups[prefix].append(file_id) group_names list(groups.keys()) random.seed(42) random.shuffle(group_names) group_count len(group_names) train_groups group_names[:int(group_count * 0.7)] val_groups group_names[int(group_count * 0.7):int(group_count * 0.85)] test_groups group_names[int(group_count * 0.85):] train_ids [fid for g in train_groups for fid in groups[g]] val_ids [fid for g in val_groups for fid in groups[g]] test_ids [fid for g in test_groups for fid in groups[g]] def write_list(ids, path): with open(path, w) as f: f.write(\n.join(ids)) write_list(train_ids, train.txt) write_list(val_ids, val.txt) write_list(test_ids, test.txt) print(ftrain {len(train_ids)} val {len(val_ids)} test {len(test_ids)})分组键怎么取取决于文件名规律。如果命名是“杆塔号_设备名_序号”那prefix取到设备名这一级是最好的同一个设备上的螺栓销钉不会跨集合。如果命名是无序hash或者纯流水号就只能用策略1随机切但要在验证时多留个心眼抽几张val图看是否和train图高度相似。大目标场景还有一个专门针对anchor的处理COCO预训练模型自带的anchor偏中小目标直接拿来跑“大目标”会浪费大量anchor容量。跑一次k-means聚类重新生成适合这份数据的anchorimport os import numpy as np from sklearn.cluster import KMeans all_wh [] for txt in os.listdir(labels): with open(os.path.join(labels, txt)) as f: for line in f: parts line.strip().split() if len(parts) 5: w float(parts[3]) h float(parts[4]) if w 0.01 and h 0.01: all_wh.append([w, h]) all_wh np.array(all_wh) kmeans KMeans(n_clusters9, random_state42, n_init10, max_iter300) kmeans.fit(all_wh) anchors kmeans.cluster_centers_ * 640 # 训练输入分辨率按你的imgsz改 anchors_sorted sorted(anchors.tolist(), keylambda x: x[0] * x[1]) print(重新聚类的anchors:, [[int(w), int(h)] for w, h in anchors_sorted])这里的输入是归一化宽高乘640是因为我准备用640训练。如果最终imgsz用1280要重新乘1280。聚类结果如果全是(50,60)到(200,180)这种大框说明原数据集确实大目标占比极高那就在模型的yaml文件里把anchors替换成这组值同时把scale_anchor的遗传进化也放开。要注意的是anchor只影响召回上限对已经够大的大目标帮助有限真正的收益在训练速度上模型不会浪费时间在小anchor上生成大量无效预测。转完、切完、聚完类训练前的准备才算闭环。一个容易漏的检查是打开train.txt和val.txt随机各挑三行对应的图像人工看一下内容是否来自同一场景。这个动作只要五分钟但能避免训练完才发现验证集“污染”。4. YOLOv8微调训练大目标场景的参数取舍与评估指标我一般用YOLOv8s作为默认底座。为什么不是nn的参数量太少螺栓销钉在复杂背景下的边缘纹理和金属反光它学不扎实为什么不是m这份数据只有1209张模型大了过拟合来得太快。如果手上有COCO预训练的yolov8s.pt就用它做起点千万不要从头训练1209张从头训练是纯折磨。先把data.yaml写好path: /path/to/dataset train: train.txt val: val.txt test: test.txt names: 0: bolt 1: pin 2: missingnames的顺序必须和前面转换脚本里的class_names完全一致不然训练时类别id对不上推理结果会串类。写完data.yaml用YOLOv8的命令行直接开训练yolo train \ modelyolov8s.pt \ datadata.yaml \ epochs200 \ imgsz640 \ batch16 \ lr00.005 \ lrf0.01 \ mosaic0.0 \ close_mosaic0 \ degrees5 \ hsv_h0.01 \ hsv_s0.2 \ hsv_v0.4 \ fliplr0.5 \ flipud0.0 \ patience30 \ device0mosaic0.0是全命令里最关键的改动。YOLOv8默认开启mosaic训练时把四张图拼成一张。这对小目标数据集是神技对这份“大目标”数据却是反向操作一个占画面15%的销钉被mosaic切成四块之后变成四个小碎片模型学到的尺度分布完全乱了。销钉缺失检测需要看“这个位置是不是空着”一个被裁掉一半的螺栓根本没有上下文模型只能靠猜。所以直接关掉代价是数据多样性下降但配合后面的HSV增强够用了。其余参数逐个说。degrees5表示允许正负5度随机旋转模拟无人机云台的俯仰抖动超过15度会把“朝上安装的销钉”变成“朝下悬挂”金具的安装方向有语义不能乱转。fliplr0.5可以开导线和金具在左右方向上近似对称flipud0.0注意不要开垂直翻转会让重力方向颠倒模型会把“上方挂点”和“下方挂点”学混。色彩增强上hsv_h只给0.01金属件的颜色不是稳定特征色相扰动大反而引入噪声。hsv_s给0.2饱和度稍微扰动一下。hsv_v给0.4是重点电压线路现场最常见的翻车场景就是逆光、晨昏、金属反光把亮度对比度扰动拉大等于免费给模型加了“抗逆光”训练。学习率从默认的0.01降到0.005因为数据量小迁移学习时步子迈大了容易把预训练特征冲掉。batch16在24G显存上跑YOLOv8s没问题如果只有12G显存就降batch8同时把lr0降到0.002不要只降batch不动学习率否则收敛会非常不稳。epochs写200但patience30模型通常在50~80轮就收敛了后面基本在过拟合边缘早停能帮你省时间。下面这张参数表是我在这个场景下的默认配置直接抄作业问题不大参数建议值理由imgsz640大目标场景够用超过1280训练时间翻倍batch1624G显存可跑显存小的降到8mosaic0.0防止大目标被切碎导致语义破坏degrees5模拟云台小角度抖动hsv_v0.4增强对逆光和反光的鲁棒性fliplr0.5左右对称可翻转flipud0.0保持重力方向语义lr00.005小数据迁移学习不宜大patience30早停防过拟合训练过程中看日志不要只看mAP重点观察train/loss和val/loss的间距。如果val/loss连续20轮不降而train/loss还在匀速下降这是过拟合的典型信号。哪怕patience没触发也可以手动ctrlc把最后一次下降的权重挑出来验证。训练完的评估命令yolo val modelruns/detect/train/weights/best.pt datadata.yaml imgsz640 conf0.25 iou0.45输出里mAP50和mAP50-95都会给。这份数据是大目标mAP50刷到0.95以上很正常别高兴太早这是目标大带来的福利。我更看重混淆矩阵里missing类的recall也就是漏检率。漏检率等于1减去recall计算公式是FN除以TP加FN。在输电线路缺陷检测里漏掉一个缺失销钉意味着可能发展成掉线事故这个数比mAP重要得多。在验收时如果只能报一个指标我就报“missing类在conf0.25下的召回率”。如果训练完发现missing类的recall明显低于bolt和pin不要马上调阈值先回数据层面查原因。可能是缺失样本数量太少模型把“空位”当成了背景也可能是标注不一致同一个空位有的标了有的没标。数据层面的问题靠调loss和阈值救不回来反而会把模型权重带歪。我自己还会额外做一步把val集预测结果里置信度在0.25到0.45之间的框全部挑出来按“人工该不该认”的标准筛一遍。这个区间的框是模型认知最模糊的地方也是现场误检和漏检的高发带。如果很多低置信度框其实是对的说明模型学到了特征但不够自信考虑在后面的训练里把这部分图加权重重训如果低置信度框全是背景噪声那说明现场部署时把conf阈值往上提到0.35更安全。这一步没有现成命令需要写几行脚本读取val的预测结果和高保真标注做IoU匹配但它是整个训练流程里性价比最高的一次人工介入。超参数不是一次就能定死的。第一次跑完之后把预测最差的那批图打印出来看看是逆光、过曝、遮挡还是密集排列再回头调对应的增强参数。YOLO的参数组合是玄学的地方很多但大目标场景的坑相对集中关mosaic、控旋转、拉对比度这三板斧能解决八成问题。5. 五个必踩坑点从数据重叠到金属反光的排查记录这一章的坑一半是我自己踩出来的一半是看群里同行翻车后总结的。每一条都按“现象、原因、解决”写清楚训练前扫一遍能省下不少冤枉时间。5.1 验证集分数虚高到0.98现场却一塌糊涂现象训练日志里val mAP50漂亮得吓人0.98甚至0.99但拿另外一段线路的照片一测漏检和误检全出来了。跑去复查标注发现XML本身没有大问题。原因划分数据集时用了纯随机同一杆塔同一个金具的多张连拍被拆进了train和val。模型在val的图上几乎就是“开卷考试”它见过同一个螺栓的另一个角度分数自然虚高。这是小数据集最常见的隐形污染。解决回到第3章的按前缀分组划分至少保证同一个设备的图像只出现在一个集合。如果文件名没有分组语义那就手动抽看50张val图凡是发现和train图像内容几乎一样的把后出现的挪到train或者直接删掉。数据量小的时候宁缺毋滥。5.2 mosaic开启后模型学会了看“半截销钉”现象不关mosaic训练loss能正常下降但val的混淆矩阵里missing类recall特别低。打印bad case发现模型在画了半个螺栓的图上疯狂漏检。原因大目标被mosaic的四图拼接切成碎片模型被迫用“半个目标”来做特征。如果目标的语义信息依赖整体结构——比如销钉的“缺失”需要对比两侧安装位——切碎后模型根本无法判断。这在视觉上比小目标更伤因为小目标本来就只有几十个像素切碎后影响不大而大目标一旦被切成残块训练分布和真实场景严重偏移。解决mosaic直接设0。如果确实需要mosaic增加多样性可以留0.3以下但我个人在这个场景里完全不用。Ultralytics默认的close_mosaic参数是训练最后10轮自动关闭mosaic恢复真实分布如果开了mosaic这个参数一定要保持默认或更大。5.3 金属反光成了模型的“盲区”现象晴天顺光拍的照片检测效果很好一到早晨或傍晚、逆光或者刚刚下过雨的潮湿天气漏检率明显上升。尤其是不锈钢材质的螺栓表面反光在图像里形成一片高亮噪点目标边缘和背景糊在一起。原因训练数据大部分是白天顺光采集亮度分布单一。模型在高亮区域学到的特征是“这里有反光”而不是“这里有螺栓”。加上线夹周围的金属表面会互相反射形成类似镜面的纹理模型倾向于把高光区域整体划为背景。解决训练时加大hsv_v扰动把亮度范围拉到0.2到0.6之间模拟不同光照。更激进的做法是在在线增强里加入随机高斯模糊或者轻微过曝模拟雾天和晨昏。但要记住推理时不要自己做CLAHE或直方图均衡除非训练时也用了同样的预处理否则分布不一致效果只会更差。5.4 缺失销钉的样本太少模型只会找“有的”不会找“没的”现象val结果里missing类的precision很高但recall很低。换句话说模型一旦判定“缺失”基本都对但它漏掉了一半真正缺失的位置。从混淆矩阵看那些漏掉的缺失目标都被预测成了背景。原因这类数据集的天然问题是正负样本失衡。完好的螺栓销钉在每张图里都能看到但缺失的位置往往只在少数缺陷图里出现。模型训练的绝大多数梯度来自“背景认识”和“正常目标定位”对缺失位置缺少直接监督。解决从两个方向补。数据层面把缺失位置的框从原本的“不标注”改成显式标注成missing哪怕手动补标100张也比多训500张完好的图有价值。训练层面不要盲目调低conf阈值来提高召回那样会把整图的背景噪声一起拉进来应该针对missing类做二次微调或者用难例挖掘第一次训练完把所有漏检的缺失图片挑出来和原始数据混合后重训一版。5.5 归一化坐标刚好等于1.0线上推理时数组越界现象转换后训练一切正常但部署到某些边缘端框架或者用部分开源推理库时偶尔报IndexError或者预测框的中心点跑到图像外面去。原因标注框的xmax或ymax恰好等于图像宽度或高度时除以宽高后得到1.0。有些数据加载器和推理库内部会在整数化坐标时越界。VOC的边界是像素坐标从0到width所以这个情况并不罕见。解决转换时做边界保护把所有归一化坐标限制在0.0001到0.9999之间已经在第3章的转换脚本里写过了。实际部署时还要在模型输出后做一次clip防止NMS出来越界框。这一条是纯工程细节但它能解释为什么同一个模型在不同推理框架上表现不同。6. 训练后的验证习惯可视化假阴性与边缘端部署前检查训练跑完指标好看不代表可以上线。我自己的习惯是建立一个hard_examples目录每次训练结束后把val集里所有与GT匹配不上的预测框按置信度排序输出。具体做法是跑一遍val预测把每张图的GT框和预测框做IoU匹配IoU小于0.1且置信度低于阈值的GT框就是假阴性对应那张图就是需要人工复审的漏检样例把漏检样例的图像和预测结果保存到hard_examples按线路名和拍摄日期归档。这个动作的意义在于把模型的弱点变成可跟踪的列表。第一次训练完hard_examples里通常是逆光图、密集金具图和缺失样本图补标、重训一轮之后列表应该明显变短。如果某一类图像永远在列表里出现就说明训练分布和真实场景存在系统性偏差不是调阈值能解决的。迭代几轮之后这份数据集的价值会从1209张原始图慢慢扩展成一套“原始数据加难例库”的组合比单纯堆张数有意义得多。部署前还有一个检查ONNX导出和阈值设置。训练结束后用如下命令导出ONNXyolo export modelruns/detect/train/weights/best.pt formatonnx opset12导出时opset不用追新12够用。边缘端推理时conf阈值我一般从0.25起步但输电线路项目我会看hard_examples里的假阴性分布如果假阴性集中在0.2~0.3置信度就不要把阈值提得太高宁可让现场多留几个待复核框如果假阳性集中在低置信度那就把conf调到0.35以上减少无效报警。NMS的IoU阈值保持0.45左右金具彼此靠近的密集场景可以降到0.4避免邻近目标被合并成一个框。大目标场景还有一个现场后处理技巧根据无人机拍摄距离设置一个最小检测框面积阈值。比如在10米拍摄距离下一枚标准销钉在1080p画面里至少占500像素那么推理阶段可以过滤掉所有面积小于某个下限的预测框。这个后处理能挡住大部分把远处导线、背景纹理当目标的小框误报。阈值怎么定从现场实拍图里量几个框就知道不要拍脑袋。最后讲一个我自己的教训。有一版模型mAP50跑到了0.97我以为稳了拿去现场测试结果连续漏掉两个被绝缘子串遮挡的缺失销钉。后来把假阴性列表拉出来才发现模型对“遮挡一半的缺失位”完全没有泛化能力因为训练数据里这样的样本太少。从那以后我把hard_examples当成模型健康度指标每次迭代先看列表长度再看mAP。这个习惯救了我不止一次。这份1209张的VOC数据集本身是个好起点但真正让它发挥价值的是你围绕它建起的数据闭环持续收集现场难例、人工确认、重组训练集。希望帮到你。本文还有配套的精品资源点击获取