简介:这份数据集面向施工工地、工厂等场景的安全着装智能检测任务,提供YOLO系列目标检测模型训练与验证所需的标注文件,可服务于安全帽、反光衣与工作服识别算法的复现、微调及课程实践。压缩包共2000个文件,主体为1999个txt标注文件,另有1个yaml配置文件用于数据集路径与类别信息组织,包体约952.16MB,适合直接接入常见YOLO训练流程。内容围绕人员着装合规检测展开,可用于实时监测画面中人员是否按要求穿戴安全帽、反光衣或工作服,并为违规告警、抓拍与弹窗提示等应用提供数据基础。已有1614人学习下载,具备一定参考热度;对于需要快速搭建安全检测实验、扩充训练样本或验证模型效果的读者,可据此省去部分数据整理成本,集中精力完成模型训练、评估与场景适配。
1. 工地门口那台摄像头,为什么总把反光衣认成水泥袋
凌晨换班时,某工地门禁摄像头抓拍的一组图里,安全帽的框还算稳,反光衣却频繁套在水泥袋和塔吊护栏上。这不是模型“笨”,而是数据集里三类目标的边界没定义清楚:安全帽是头部小目标,反光衣是躯干大面积高亮区域,工作服又常与反光衣同色同款。基于YOLO的安全帽/反光衣/工作服自动识别数据集,要解决的就是把这三类目标在真实工地光照、遮挡和尺度变化下标得一致、训得稳、验得准。适合做智慧工地视觉算法、边缘盒部署和标注流程搭建的人。下面按数据定义、清洗切分、训练参数和上线排查四段推进。
2. 数据集不是“越多越好”:三类目标、五类场景和标注边界的取舍
很多团队一上来就爬几万张图,标完发现一半是重复角度,反光衣和工作服混标,夜间样本不到百分之三。数据集的第一道工序是定义,不是采集。安全帽/反光衣/工作服三个类别的语义边界、场景配额、标注规则,决定后面模型能不能收敛,也决定换一个工地之后会不会翻车。我一般先把“标什么、不标什么、每个场景要多少”写成一张表,再让标注同学开工,否则返工成本比采集还高。
2.1 安全帽、反光衣、工作服:三个类别怎么定才不打架
类别定义的核心是“一个框只回答一个问题”。安全帽只标佩戴在头部的帽子,手持帽、放在地上的帽子、挂在脚手架上的帽子都不标;反光衣只标穿在躯干外、带反光条或高亮色块的背心/外套;工作服标未穿反光衣时的常规工装躯干。最容易打架的是“反光衣里面还穿着工作服”:如果反光衣覆盖了大部分躯干,只标反光衣,工作服不再重复标;如果反光衣敞开、露出大面积内层工作服,仍然只标反光衣,因为业务要判断的是“有没有穿反光衣”,不是“里面穿了什么”。另一个坑是颜色:黄色反光衣和黄色工作服在低分辨率下几乎同分布,必须在标注规范里用“是否有反光条/高亮拼接”作为判据,而不是只看颜色。
| 类别名 | 标什么 | 不标什么 | 常见易混目标 |
|---|---|---|---|
| helmet | 佩戴在头部的安全帽 | 手持帽、地面帽、挂墙帽 | 安全帽形状的灯罩、头盔式遮阳帽 |
| vest | 躯干外穿的反光背心/反光外套 | 反光条背包、反光锥、反光贴纸 | 黄色工作服、雨衣 |
| uniform | 未穿反光衣的常规工装躯干 | 反光衣内层、裸露手臂 | 深色外套、马甲 |
负样本也要进数据集,比例控制在5%到10%:没有目标的空场景、只有水泥袋和护栏的图、只有反光锥的图。负样本不是为了“凑数”,而是让模型学会把高亮色块和人体躯干区分开。标注文件里一张图可以没有txt,但文件名要保留在训练列表里,否则负样本等于没加。类别名统一小写,不要出现Helmet、helmet、安全帽三种写法,后面转换脚本会直接映射失败。
2.2 采集清单:塔吊下、基坑边、夜间补光各要多少张
场景配额比总数量更重要。我一般按五类场景配:出入口/门禁、基坑边与脚手架、材料堆场、塔吊作业面、夜间补光。每类先保底三百到五百张有效图,其中夜间不少于夜间场景总量的四成,因为反光衣在夜间过曝和逆光下最容易掉点。拍摄距离分三档:近景3到8米、中景8到20米、远景20米以上;远景图里安全帽短边可能只有十几个像素,必须单独统计,不能和近景混在一起看mAP。采集设备不挑,手机、球机抓拍、记录仪截帧都行,但同一场景不要只用一个焦距,否则模型会记住背景而不是目标。
| 场景 | 文件名前缀 | 建议占比 | 重点覆盖 |
|---|---|---|---|
| 出入口/门禁 | gate_ | 20% | 人流密集、逆光、口罩遮挡 |
| 基坑边/脚手架 | pit_ | 25% | 仰拍、遮挡、多人重叠 |
| 材料堆场 | yard_ | 20% | 反光衣与水泥袋/护栏同色 |
| 塔吊作业面 | crane_ | 15% | 远景小目标、俯拍 |
| 夜间补光 | night_ | 20% | 过曝、光斑、低照度噪点 |
采集时用文件名前缀把场景固定下来,后面分层切分和跨场景验证都靠它。每个场景至少留一个“脏场景”子集不参与训练,只做验收:比如某工地新装的补光灯导致反光条大面积过曝,或者某队工人穿的是旧款灰色工作服。这个子集不需要很大,一两百张就够,但必须和训练集来源分离,否则验证分数会虚高。血泪经验是:验证集如果和训练集同一天、同一台摄像头、同一批人,mAP看着漂亮,换一个门口就现原形。
2.3 标注框的三条硬规则:遮挡、截断、小目标
遮挡目标只标可见轮廓的外接框,不脑补被遮挡的部分。比如一个人被脚手架钢管挡住半边身体,反光衣只露出右半侧,框就画右半侧可见区域,不要把钢管后面的推测区域补进去。这样做的代价是框偏小,但换来的是一致性:不同标注员不会因为“猜多少”产生分歧。截断目标框到图像边界,xmin不小于0、xmax不大于图像宽度,超出部分直接裁掉,不要出现负坐标。YOLO训练时对越界坐标虽然有一定容忍,但归一化后会挤到边缘,小目标定位会抖。
小目标规则要写死:安全帽短边小于10像素不标,10到16像素标但单独打标签统计,大于16像素正常标。反光衣短边小于20像素不标,因为躯干框太小已经失去业务意义。有人会问“不标的小目标会不会被当成背景,拉低召回”,会,所以远景小目标靠切片推理和更高输入尺寸解决,而不是靠硬标一堆模糊框。标注完成后抽10%做交叉复核,重点看三类边界:反光衣和工作服是否重复标、手持安全帽是否误标、夜间过曝区域是否把光斑框成反光衣。复核不通过的批次整批返工,不要只改几个框,否则规范会越标越松。
3. 从原始图片到 YOLO 标签:清洗、分层切分和格式转换的可复现流程
原始素材进来之后,先做减法。重复帧、模糊图、无目标图会稀释训练信号,尤其是视频抽帧得到的连续帧,十张里有八张几乎一样。清洗顺序我一般按“去重→去糊→去无目标→分层切分→格式转换”走,每一步留一份文件清单,后面出问题能回溯到是哪一步把图删错了。下面三段脚本可以直接改成自己的路径,参数按现场分辨率调。
3.1 先做减法:去重、去糊、去无目标图的过滤脚本
去重用感知哈希做初筛,不追求百分百准确,先把连续帧和重复角度压下去。模糊过滤用拉普拉斯方差,阈值不要照抄,先抽五十张正常图看方差分布再定。无目标图不要全删,留5%到10%做负样本,其余剔除。
import os import cv2 from PIL import Image def dhash(path, size=8): # 缩小到 (size+1, size) 灰度图,逐行比较相邻像素,生成 64 位哈希 img = Image.open(path).convert("L").resize((size + 1, size), Image.LANCZOS) px = list(img.getdata()) bits = [] for row in range(size): for col in range(size): left = px[row * (size + 1) + col] right = px[row * (size + 1) + col + 1] bits.append("1" if left > right else "0") return int("".join(bits), 2) def hamming(a, b): return bin(a ^ b).count("1") def blur_score(path): # 拉普拉斯方差越小越糊,手机图和球机图阈值不同,先抽样再定 img = cv2.imread(path, cv2.IMREAD_GRAYSCALE) if img is None: return 0 return cv2.Laplacian(img, cv2.CV_64F).var() src_dir = "raw_images" keep_dir = "clean_images" os.makedirs(keep_dir, exist_ok=True) hashes = [] kept = 0 for name in sorted(os.listdir(src_dir)): if not name.lower().endswith((".jpg", ".png", ".jpeg")): continue path = os.path.join(src_dir, name) h = dhash(path) # 汉明距离小于等于5视为重复,阈值越小去重越保守 if any(hamming(h, old) <= 5 for old in hashes): continue if blur_score(path) < 60: # 模糊阈值,按实际分辨率调整 continue hashes.append(h) Image.open(path).save(os.path.join(keep_dir, name)) kept += 1 print("kept:", kept)这段脚本只做两件事:dHash去重和拉普拉斯模糊过滤。size=8生成64位哈希,计算量小,适合几万张图先跑一遍;hamming <= 5是保守阈值,工地背景相似度高,阈值放到10会误删不同角度的人。blur_score < 60不是通用标准,1080p球机图可以更低,手机近景图可以更高,先抽五十张正常图打印方差,取第10百分位做阈值更稳。无目标图不用脚本硬删,标注完成后按txt是否为空统计,空标签图保留一小部分放到负样本目录,其余从训练列表移除。
3.2 按场景分层切分,避免验证集“太干净”
切分不能直接random.shuffle。门禁图占一半、夜间图只有几十张的时候,随机切分很可能验证集里没有夜间样本,训练日志好看,上线就翻车。我一般按文件名前缀分场景,每个场景按70/15/15切训练、验证、测试,并固定随机种子。切完打印每个场景在每个集合里的数量,夜间和远景小目标占比不够就手动补。
import os import random from collections import defaultdict random.seed(42) image_dir = "clean_images" out_dir = "splits" os.makedirs(out_dir, exist_ok=True) groups = defaultdict(list) for name in sorted(os.listdir(image_dir)): if not name.lower().endswith((".jpg", ".png", ".jpeg")): continue prefix = name.split("_")[0] # gate / pit / yard / crane / night groups[prefix].append(name) train, val, test = [], [], [] for prefix, names in groups.items(): random.shuffle(names) n = len(names) n_train = int(n * 0.70) n_val = int(n * 0.15) train.extend(names[:n_train]) val.extend(names[n_train:n_train + n_val]) test.extend(names[n_train + n_val:]) for split_name, items in [("train", train), ("val", val), ("test", test)]: with open(os.path.join(out_dir, f"{split_name}.txt"), "w", encoding="utf-8") as f: for name in items: f.write(os.path.join(image_dir, name) + "\n") print(split_name, len(items))按前缀分组是关键,name.split("_")[0]依赖采集时定的命名规范,所以第2章强调文件名前缀要先定好。70/15/15不是死数,数据量小于两千张时验证集至少留两百张,否则mAP抖动很大。切分完做一次交叉检查:训练集里出现过的连续帧编号,验证集里不能再出现相邻帧,否则模型等于背答案。如果视频抽帧没有编号,用dHash再查一遍训练集和验证集之间的近重复,汉明距离小于等于3的从验证集挪走。
3.3 VOC/COCO 转 YOLO txt:归一化坐标的四个边界坑
标注工具导出VOC XML很常见,转YOLO txt时四个坑最容易翻车:坐标越界、宽高为0、空标签文件丢失、类别名大小写不一致。下面脚本把越界坐标裁到图像边界,跳过无效框,并按映射表转类别id。转换完一定要抽查,不要只看脚本没报错。
import os import xml.etree.ElementTree as ET from PIL import Image # 类别映射必须和 data.yaml 的 names 顺序一致 cls_map = {"helmet": 0, "vest": 1, "uniform": 2} def convert(xml_path, img_path, out_txt): tree = ET.parse(xml_path) root = tree.getroot() w = int(root.find("size/width").text) h = int(root.find("size/height").text) lines = [] for obj in root.findall("object"): name = obj.find("name").text.strip().lower() if name not in cls_map: continue bbox = obj.find("bndbox") xmin = max(0, min(w - 1, float(bbox.find("xmin").text))) ymin = max(0, min(h - 1, float(bbox.find("ymin").text))) xmax = max(0, min(w - 1, float(bbox.find("xmax").text))) ymax = max(0, min(h - 1, float(bbox.find("ymax").text))) bw, bh = xmax - xmin, ymax - ymin if bw <= 1 or bh <= 1: continue xc = (xmin + xmax) / 2.0 / w yc = (ymin + ymax) / 2.0 / h nw, nh = bw / w, bh / h lines.append(f"{cls_map[name]} {xc:.6f} {yc:.6f} {nw:.6f} {nh:.6f}") # 空标签也要写文件,负样本靠空txt保留 with open(out_txt, "w", encoding="utf-8") as f: f.write("\n".join(lines)) # 批量转换时保持 images 和 labels 目录同名cls_map的顺序必须和训练配置里的names完全一致,否则模型会把反光衣学成安全帽。坐标裁剪用w-1、h-1而不是w、h,避免归一化后出现1.0导致边界框贴边。bw <= 1和bh <= 1跳过的是标注工具误点产生的零宽框,不是小目标,小目标在前面已经按像素阈值筛过。空标签文件一定要写,负样本图没有txt时训练框架可能直接忽略这张图,等于白留。转换完抽十张图用可视化脚本画框,重点看贴边目标有没有偏移、类别颜色对不对,这一步比看损失曲线更早发现问题。
4. 训练配置别照抄:小目标安全帽的模型选型、data.yaml 与超参
数据准备好之后,训练配置的难点不是“能不能跑”,而是“小目标召回能不能上来”。安全帽在远景图里只有十几像素,反光衣又容易受高亮干扰,模型选大了显存吃紧,选小了召回不够。我一般先用轻量模型跑通全流程,确认数据没有硬伤,再换中等模型调输入尺寸。下面三节按选型、配置、看日志的顺序讲,参数给的是起点,不是标准答案。
4.1 n/s/m 怎么选:参数量、显存和召回率的三角权衡
轻量模型适合边缘盒和初步验证,中等模型适合服务器训练后蒸馏或直接部署,大模型不是不能用,而是标注质量跟不上时收益很快见顶。安全帽和反光衣都属于“类别少、目标小、背景杂”的任务,输入尺寸往往比模型深度更影响召回。我的经验是:先用n跑640,确认mAP能到0.6以上,再换s跑960,看小目标召回有没有明显提升;如果s跑960和n跑640差不多,问题多半在标注或数据分布,不在模型。
| 模型档位 | 输入尺寸起点 | 显存压力 | 适用场景 | 注意 |
|---|---|---|---|---|
| n | 640 | 低 | 边缘盒、流程验证 | 远景小目标召回偏弱 |
| s | 960 | 中 | 服务器训练、常规部署 | 训练时长约为n的2到3倍 |
| m | 960/1280 | 高 | 高召回要求、离线分析 | 标注噪声会被放大 |
输入尺寸不是越大越好。960比640的显存和耗时都上去了,如果远景小目标占比不到10%,先做切片推理比硬拉尺寸更划算。切片推理是把大图裁成重叠小图分别检测,再合并框,适合塔吊作业面这种目标极小的场景。训练阶段也可以把远景图单独过采样,但不要超过总样本的30%,否则近景召回会掉。
4.2 data.yaml 与训练命令:imgsz、batch、cls_pw 的取值逻辑
data.yaml只写三件事:路径、类别数、类别名。路径用绝对路径或相对训练脚本的路径,不要用中文目录。nc和names顺序必须和转换脚本一致。训练命令里imgsz、batch、close_mosaic、hsv_v是优先调的,cls_pw是分类损失权重,不是类别平衡开关,别指望它单独解决反光衣样本少的问题。
# hardhat_vest.yaml path: /data/hardhat_vest train: splits/train.txt val: splits/val.txt test: splits/test.txt nc: 3 names: ["helmet", "vest", "uniform"]yolo detect train \ data=hardhat_vest.yaml \ model=yolov8s.pt \ imgsz=960 \ epochs=120 \ batch=16 \ patience=30 \ hsv_v=0.5 \ mosaic=1.0 \ close_mosaic=15 \ cls_pw=0.8 \ project=runs_hardhat \ name=exp_s_960imgsz=960是给远景小目标留余量,显存不够就降到768,不要一边爆显存一边加batch。batch=16是起点,显存占用主要看输入尺寸和模型档位,8也常见。patience=30表示30轮验证指标不升就早停,数据集小的时候可以降到20。hsv_v=0.5提高亮度扰动,对夜间补光和逆光有帮助,但别开到0.8,否则反光衣颜色会失真。mosaic=1.0增强小目标上下文,close_mosaic=15在最后15轮关掉马赛克,让模型在真实拼接分布上收尾,这一步对小目标定位很关键。cls_pw=0.8调分类损失权重,类别不平衡严重时先做过采样,再考虑动这个值。
4.3 看日志的顺序:先召回、后 mAP、最后看误检
训练日志里先看召回率,尤其vest的召回。安全帽召回高、反光衣召回低,通常是反光衣样本少或者过曝标注不一致。召回稳定之后再看mAP50,最后看mAP50-95,因为后者对框的定位质量更敏感,小目标框稍微偏一点就掉。验证时不要只看总体,按场景前缀分开跑:门禁场景和夜间场景各出一份指标,夜间掉超过10个点就先查过曝和低照度,不要急着调模型。
# 训练完成后在测试集上按场景分别验证,把 night_ 前缀的图单独列一个txt yolo detect val \ data=hardhat_vest.yaml \ model=runs_hardhat/exp_s_960/weights/best.pt \ imgsz=960 \ split=test验证命令跑完看混淆矩阵:反光衣被预测成工作服,通常是类别定义边界没写清;工作服被预测成反光衣,通常是黄色工装和反光衣同色。误检看背景图,把负样本单独跑一遍,如果水泥袋和护栏上出现大量反光衣框,说明高亮色块学到了错误的纹理,回去补负样本或提高亮度扰动。训练过程中保存last.pt和best.pt,但上线前用测试集重新验一遍best.pt,不要拿验证集分数当上线分数。
5. 避坑与排查:类别不平衡、反光衣过曝和跨工地掉点
这一章按现场最常遇到的三类问题写,每条按“现象→原因→解决”走。安全帽/反光衣/工作服数据集的问题很少是单一原因,通常是标注、采样、光照三个因素叠在一起。排查时先固定变量:同一份验证集、同一个输入尺寸、同一个置信度阈值,再改一个条件看指标变化,否则调了半天不知道是哪个参数起了作用。
5.1 反光衣样本只有安全帽的十分之一:重采样还是改损失
现象:训练日志里helmet的召回能到0.85,vest只有0.55,混淆矩阵显示大量反光衣被预测成背景或工作服。原因不复杂:反光衣样本少,而且很多反光衣在远景图里只占几十像素,模型先学会了“安全帽在头部”这个强先验,反光衣的躯干特征被背景淹没。另一个隐藏原因是标注时反光衣和工作服混标,模型学到两类边界模糊,干脆把不确定的躯干都归到样本更多的工作服。
解决顺序我一般分三步。第一步离线过采样:统计每个类别的图片数量,把含vest的图片复制到训练列表,复制倍数控制在2到3倍,不要超过5倍,否则过拟合到少数场景。第二步补场景,不是补同一张图的副本:夜间反光衣、逆光反光衣、多人重叠反光衣各补一批,让模型看到不同光照下的高亮纹理。第三步再考虑损失权重,cls_pw微调,或者用focal loss替换分类损失,但改损失之前一定先确认标注一致,否则只是把错误标签学得更狠。
import os import shutil from collections import defaultdict label_dir = "labels/train" image_dir = "images/train" out_dir = "images/train_oversample" os.makedirs(out_dir, exist_ok=True) # 统计每张图包含的类别 img_classes = defaultdict(set) for txt in os.listdir(label_dir): if not txt.endswith(".txt"): continue stem = os.path.splitext(txt)[0] with open(os.path.join(label_dir, txt), encoding="utf-8") as f: for line in f: if line.strip(): img_classes[stem].add(int(line.split()[0])) for stem, classes in img_classes.items(): src = os.path.join(image_dir, stem + ".jpg") if not os.path.exists(src): continue shutil.copy(src, os.path.join(out_dir, stem + ".jpg")) # 只对含 vest(1) 的图做2倍复制,避免安全帽被过度采样 if 1 in classes: shutil.copy(src, os.path.join(out_dir, stem + "_dup1.jpg")) shutil.copy(src, os.path.join(out_dir, stem + "_dup2.jpg"))这段脚本按标签内容判断类别,不依赖文件名。_dup1、_dup2复制的是同一张图,训练时要注意标签文件也要对应复制,否则复制出来的图没有标签。更稳妥的做法是在生成训练列表时把同一路径写多次,让采样器自然过采样,但不同训练框架对重复路径处理不一样,离线复制最直接。复制倍数2到3倍是经验值,验证集不能复制,否则指标虚高。
5.2 反光条过曝把框撑大:从标注到推理的排查链路
现象:夜间图片里反光衣的预测框明显偏大,把旁边的人或护栏框进来;白天逆光图里反光条区域出现多个重叠框。原因通常不是模型本身,而是标注时把高亮光斑当成反光衣边界,框画到了光晕外沿。另一个原因是训练图里过曝样本太少,模型没见过“反光条一片白”的情况,推理时把白色区域都当成反光衣。
排查从标注开始:抽二十张夜间过曝图,把标注框和原图叠在一起看,如果框明显大于人体躯干,先改标注规范,按躯干可见外接框画,不按光斑画。然后看推理端:把置信度阈值从0.25提到0.35,观察框是否收缩;如果收缩明显,说明模型对过曝区域信心不足,需要补过曝样本而不是继续提阈值。最后看图像预处理:夜间图如果做了自动对比度拉伸,反光条会更容易爆,训练和推理要用同一套预处理,不要训练用原图、推理用增强图。
import cv2 import numpy as np def overexpose_ratio(path, thresh=245): # 统计接近纯白的像素比例,用来筛过曝样本 img = cv2.imread(path, cv2.IMREAD_GRAYSCALE) if img is None: return 0.0 return float(np.mean(img >= thresh)) # 批量扫描夜间图,比例高于0.08的单独放进复核目录 for name in os.listdir("raw_images"): if name.startswith("night_") and overexpose_ratio(os.path.join("raw_images", name)) > 0.08: print("check:", name)thresh=245是接近纯白的阈值,0.08表示高亮像素超过8%就值得人工看一眼。这个比例不是绝对标准,夜间补光灯直射时整张图都可能偏亮,要结合直方图看。筛出来的图不要直接删,先复核标注框,再决定是保留、重新标还是剔除。过曝样本本身有价值,全删掉模型更怕夜间,关键是框要标在躯干上,不是标在光斑上。
5.3 换一个工地就掉 20 个点:跨场景验证的三个检查项
现象:A工地测试集mAP50能到0.82,换到B工地同一个模型掉到0.61,安全帽还行,反光衣和工作服混淆严重。原因通常是背景单一、工作服颜色分布不同、摄像头安装角度不同。A工地训练图里全是灰色围挡,B工地是蓝色围挡,模型把围挡纹理当成了上下文;A工地反光衣以橙黄色为主,B工地有大量荧光绿,颜色先验失效。
检查项一:验证集来源。确认B工地有没有哪怕一百张图进过训练集,如果进了,指标不可信,重新切一个完全没见过的场景做测试。检查项二:类别颜色分布。把两个工地的反光衣裁出来做颜色直方图,如果峰值差很远,训练时加HSV色相扰动,但不要过大,否则橙色和绿色会互相映射。检查项三:摄像头角度和焦距。A工地是门口平视,B工地是塔吊俯拍,目标尺度和长宽比完全不同,这种差异靠增强解决不了,只能补俯拍样本或做切片推理。
跨场景验证不要只看总体mAP,按类别和场景拆开:vest在B工地的召回、uniform在夜间的误检、安全帽在远景的漏检。拆开之后通常能定位到一两个具体场景,再决定是补数据、改标注还是调推理尺寸。我自己的习惯是每次上线前留10%的“脏场景”不训练,只做验收;如果这10%掉点超过8个点,先别调模型,回去查标注和场景分布。
6. 上线前多做一步:用置信度分层和误检回流压住漏检
模型训练完,很多人只看一个总体mAP就部署,结果上线后漏检集中在低置信度区间。我的做法是按置信度分桶统计召回:把测试集预测结果按0.25到0.35、0.35到0.5、0.5到0.7、0.7以上分四桶,分别看每个类别的召回和误检。安全帽漏检如果集中在0.25到0.35,说明模型“看到了但不敢确认”,这时候补难例比调阈值有效;反光衣误检如果集中在0.5以上,说明高亮纹理学偏了,回去补负样本。
| 置信度区间 | 主要看什么 | 处理动作 |
|---|---|---|
| 0.25-0.35 | 漏检是否集中 | 抽检后回流难例 |
| 0.35-0.5 | 漏检与误检比例 | 人工复核,决定是否降阈值 |
| 0.5-0.7 | 框是否偏大/偏小 | 查标注框质量 |
| 0.7以上 | 高置信误检 | 补负样本或查类别定义 |
具体操作上,把0.25到0.5的预测框连同原图一起导出,人工只做“对/错”二分类复核,对的加入训练集,错的也加入训练集但作为负样本或修正标注。这个回流集不要一次加太多,每轮控制在原训练集的10%以内,加完重新训练时固定随机种子,和上一版对比同一批脏场景的召回。阈值不要为了降误检一路往上提,反光衣在夜间的置信度本来就偏低,提到0.5以上漏检会很难看。
我自己的习惯是每次上线前留一份“后悔药”:把当前版本的误检和漏检各存一百张,标注好原因,下一次迭代先跑这份清单,再看总体指标。模型玄学的地方在于,总体mAP涨了两个点,可能只是背景更单一了,真正难的车夜间场景还在掉。希望帮到你。
本文还有配套的精品资源,点击获取