011、检测模型精度上不去?先把标注质量查一遍:错标、漏标、框偏移的排查方法
011、检测模型精度上不去?先把标注质量查一遍:错标、漏标、框偏移的排查方法
去年有个项目,客户给了一堆工业质检数据,我调了半个月的YOLOv8,mAP卡在0.65死活上不去。换backbone、调anchor、加数据增强,能试的都试了,结果还不如人家baseline。后来实在没辙,拉了一整天的标注文件出来看,发现标注框歪得离谱——有的框把背景全包进去了,有的只框了目标的一半。改完标注,mAP直接跳到0.82。那之后我养成了一个习惯:模型训不动,先查标注。
标注问题比你想的常见
很多团队拿到数据就开训,觉得标注是标注员的事。但标注质量对检测模型的影响,比模型结构、超参数都大。一个错标样本,可能在训练时产生几十甚至上百个错误梯度更新。尤其是小目标、密集场景、遮挡情况,标注稍微偏一点,模型就学歪了。
我见过最夸张的案例:一个行人检测数据集,标注员把“人”和“影子”全标上了,模型学到最后,看到地上的影子就兴奋。还有一次,某个类别只有200个样本,其中30个框偏移超过50%,模型对这个类别的召回率直接崩到0.1。
错标:类别标错了,模型就学歪了
错标是最致命的问题。一个“猫”被标成“狗”,模型就会在猫的位置上学习“狗”的特征。如果错标比例超过5%,模型基本就废了。
怎么查?
写个脚本,把每个类别的标注框可视化出来,按类别分组看。别只看单张图,要批量看。我习惯的做法是:随机抽1000张图,把标注框画上去,然后按类别排序,快速翻一遍。如果某个类别的框里出现大量其他类别的物体,那就是错标。
代码里可以加个统计:每个类别的标注框数量、平均面积、长宽比。如果某个类别的平均面积突然比同类小很多,大概率是标错了——比如把“汽车”标成了“自行车”。
# 这里踩过坑:别只统计数量,要结合面积和长宽比defcheck_label_quality(ann_file):importjsonwithopen(ann_file)asf:data=json.load(f)# 按类别统计cls_stats={}forannindata['annotations']:cat_id=ann['category_id']bbox=ann['bbox']# [x, y, w, h]area=bbox[2]*bbox[3]aspect_ratio=bbox[2]/max(bbox[3],1)ifcat_idnotincls_stats:cls_stats[cat_id]={'count':0,'areas':[],'ratios':[]}cls_stats[cat_id]['count']+=1cls_stats[cat_id]['areas'].append(area)cls_stats[cat_id]['ratios'].append(aspect_ratio)# 输出异常类别forcat_id,statsincls_stats.items():avg_area=np.mean(stats['areas'])avg_ratio=np.mean(stats['ratios'])# 别这样写:if avg_area < 100: 这种硬阈值不靠谱# 应该和同类别的其他样本比ifstats['count']>10:# 检查是否有面积异常小的样本small_samples=[aforainstats['areas']ifa<avg_area*0.3]iflen(small_samples)>stats['count']*0.1:print(f"类别{cat_id}有{len(small_samples)}个面积异常小的框,建议检查")漏标:模型学不到的东西,永远检测不到
漏标比错标更隐蔽。错标至少还有个框,漏标是完全没有标注。模型在训练时看到这个物体,会把它当成背景,学习“这个特征=背景”。等到推理时,模型看到同样的物体,就会认为它是背景。
怎么查?
最直接的方法:用训练好的模型去预测训练集,把预测结果和标注结果对比。如果模型在某个区域预测出高置信度的框,但标注里没有,那大概率是漏标。
我写过一个工具:把预测框和标注框画在同一张图上,预测框用红色,标注框用绿色。如果某个区域只有红色框没有绿色框,那就是漏标候选。
# 这里踩过坑:别用模型自己的预测结果去查漏标,模型可能已经学歪了# 应该用另一个独立训练的模型,或者用预训练模型deffind_missing_labels(model,dataset,threshold=0.5):missing_candidates=[]forimg,targetsindataset:preds=model.predict(img)forpredinpreds:ifpred['confidence']>threshold:# 检查是否有标注框覆盖这个预测框matched=Falsefortintargets:iou=compute_iou(pred['bbox'],t['bbox'])ifiou>0.3:# 别用0.5,漏标的框可能和标注框有部分重叠matched=Truebreakifnotmatched:missing_candidates.append((img,pred))returnmissing_candidates另一个技巧:看类别分布。如果某个类别的样本数明显少于预期,或者某个场景下的样本数异常少,很可能是漏标了。比如“夜间行人”只有10个样本,但“白天行人”有1000个,那夜间场景大概率漏标严重。
框偏移:框歪了,模型就学歪了
框偏移是最常见的问题。标注员手一抖,框就偏了。偏移5%以内还能忍,超过10%基本就废了。
怎么查?
看标注框的边界是否紧贴目标。我写过一个脚本:计算每个标注框内像素的梯度分布。如果框内边缘梯度集中在框的边界附近,说明框标得准;如果梯度集中在框内部,说明框可能偏大了;如果梯度集中在框外部,说明框偏小了。
# 这里踩过坑:别用简单的边缘检测,要结合目标本身的纹理特征defcheck_bbox_alignment(image,bbox):x,y,w,h=bbox# 取框内区域crop=image[y:y+h,x:x+w]# 计算梯度grad_x=cv2.Sobel(crop,cv2.CV_64F,1,0,ksize=3)grad_y=cv2.Sobel(crop,cv2.CV_64F,0,1,ksize=3)grad_mag=np.sqrt(grad_x**2+grad_y**2)# 计算框边界附近的梯度强度border_grad=np.mean(grad_mag[0:2,:])+np.mean(grad_mag[-2:,:])+\ np.mean(grad_mag[:,0:2])+np.mean(grad_mag[:,-2:])# 计算框内部的梯度强度inner_grad=np.mean(grad_mag[2:-2,2:-2])# 如果边界梯度远小于内部梯度,说明框可能偏大了ifborder_grad<inner_grad*0.5:return"框可能偏大"# 如果边界梯度远大于内部梯度,说明框可能偏小了ifborder_grad>inner_grad*3:return"框可能偏小"return"正常"另一个更直观的方法:看标注框的长宽比分布。如果某个类别的长宽比集中在几个离散值上,说明标注员可能用了固定的模板,没有根据实际目标调整。比如“汽车”的长宽比应该在1.2到2.0之间,如果全是1.5,那肯定有问题。
批量排查的实用工具
手动一张张看太慢,我整理了一套批量排查流程:
- 统计异常样本:计算每个标注框的置信度(用预训练模型预测,看标注框和预测框的IoU),IoU低于0.5的标出来。
- 聚类分析:把标注框的坐标、面积、长宽比做聚类,离群点大概率有问题。
- 时序对比:如果数据是按时间采集的,看标注质量随时间的变化。标注员疲劳时,标注质量会下降。
- 交叉验证:让两个标注员标同一批数据,对比差异。差异大的样本就是问题样本。
# 这里踩过坑:别用单一指标,要综合判断defbatch_check(ann_file,img_dir,model):# 1. 加载标注# 2. 用模型预测# 3. 计算每个标注框的IoU# 4. 找出IoU低的样本# 5. 可视化输出pass# 具体实现略,核心思路如上修复标注的实操建议
发现问题后,怎么修?
- 错标:直接改类别ID。如果错标比例高,建议重新标注。
- 漏标:用模型预测结果作为候选,人工确认后加入标注。
- 框偏移:用模型预测的框作为参考,人工微调。或者用分割模型先分割出目标,再取外接矩形。
别想着一次性全修完。先修影响最大的:错标优先,其次是漏标,最后是框偏移。修完一批,训一个模型看看效果,再决定要不要继续修。
个人经验
标注质量这件事,永远不要相信“标注员说标好了”。我见过太多团队,数据标注花了几个月,结果模型训出来效果不好,最后发现是标注问题。与其花时间调模型,不如先花两天把标注查一遍。
一个实用的建议:在项目开始前,先让标注员标100张图,你亲自检查一遍,把标准定好。等标注员标完第一批数据,再抽检一次。这样能避免后期大规模返工。
另一个建议:标注规范里要写清楚“模糊目标怎么标”“遮挡目标怎么标”“小目标怎么标”。这些边界情况最容易出问题。
最后,标注质量检查应该是一个持续的过程,不是一次性的。每次模型训完,看看哪些样本预测错了,分析一下是模型的问题还是标注的问题。很多时候,你会发现模型预测错的地方,标注本身就有问题。
模型精度上不去,别急着换网络结构,先把标注查一遍。这可能是性价比最高的优化手段。
