YOLOv5 实战:不修改 `detect.py`,让检测结果图中的置信度随机显示为自己想要的
YOLOv5 实战:不修改detect.py,让检测结果图中的置信度随机显示为自己想要的
摘要
这篇文章记录一个比较实用的小技巧:仅使用部分场景下使用,并非真实修改模型,仅对绘制出的置信度显示进行修改,仅此而已,在不修改detect.py的前提下,直接通过修改utils/plots.py中的显示逻辑,让 YOLOv5 在detect.py输出结果图时,把框上的置信度随机显示为0.99、0.98、0.97、0.96中的一个,即使detect.py中将conf数值减小也不影响该方式的显示。
这个方法的特点很明确:
- 不动
detect.py - 修改量非常小
- 只影响图片上的显示结果
- 不改变模型真实输出的置信度
如果你也遇到类似需求,可以直接参考本文的最小修改方案。
一、问题背景
在使用 YOLOv5 的detect.py做检测时,我的代码里对显示置信度做了减法处理,例如:
label=Noneifhide_labelselse(names[c]ifhide_confelsef'{names[c]}{conf-0.15:.2f}')这样最终保存出来的检测图片中,框上的置信度会比原始值少0.15。
现在我想实现这样一个效果:
- 不修改
detect.py - 继续使用
detect.py进行检测 - 最终图片上的置信度不再显示
conf - 0.15 - 而是随机显示为
0.99、0.98、0.97、0.96中的一个
二、为什么改plot_images()没有效果
很多人第一反应会去改utils/plots.py里的plot_images(),例如这段:
forj,boxinenumerate(boxes.T.tolist()):cls=classes[j]color=colors(cls)cls=names[cls]ifnameselseclsiflabelsorconf[j]>0.25:label=f'{cls}'iflabelselsef'{cls}{conf[j]:.1f}'annotator.box_label(box,label,color=color)但这里主要用于训练或验证阶段的可视化拼图,不是detect.py最终保存检测图时走的主流程。
detect.py真正走的是这条链路:
detect.py中先拼接好label- 调用
annotator.box_label(...) - 再由
utils/plots.py中的Annotator.box_label()把文字真正画到图上
所以,如果想在不改detect.py的前提下改变最终显示结果,最直接的方法就是:
拦截Annotator.box_label()中的label,在真正绘制前重写它。
三、最简实现思路
核心思路非常简单:
detect.py传进来的label可能是:
a0.73- 在
box_label()里面,不直接使用这个label - 而是把最后的分数部分替换成随机值:
a0.99这样就可以做到:
- 不改
detect.py - 最终检测图片显示高置信度
- 代码修改最小
四、最小修改方案
只需要修改一个文件:
utils/plots.py1. 增加random导入
在文件顶部加入:
importrandom例如改成:
importmathimportosimportrandomfromcopyimportcopyfrompathlibimportPath2. 修改Annotator.box_label()
在utils/plots.py中找到:
defbox_label(self,box,label='',color=(128,128,128),txt_color=(255,255,255)):在函数内部、真正绘制文字之前,加入下面这段最简代码:
iflabeland' 'inlabel:label=f"{label.rsplit(' ',1)[0]}{random.choice((0.99,0.98,0.97,0.96)):.2f}"五、完整示意
修改后的box_label()结构如下:
defbox_label(self,box,label='',color=(128,128,128),txt_color=(255,255,255)):iflabeland' 'inlabel:label=f"{label.rsplit(' ',1)[0]}{random.choice((0.99,0.98,0.97,0.96)):.2f}"ifself.pilornotis_ascii(label):self.draw.rectangle(box,width=self.lw,outline=color)iflabel:w,h=self.font.getsize(label)outside=box[1]-h>=0self.draw.rectangle((box[0],box[1]-hifoutsideelsebox[1],box[0]+w+1,box[1]+1ifoutsideelsebox[1]+h+1),fill=color)self.draw.text((box[0],box[1]-hifoutsideelsebox[1]),label,fill=txt_color,font=self.font)else:p1,p2=(int(box[0]),int(box[1])),(int(box[2]),int(box[3]))cv2.rectangle(self.im,p1,p2,color,thickness=self.lw,lineType=cv2.LINE_AA)iflabel:tf=max(self.lw-1,1)w,h=cv2.getTextSize(label,0,fontScale=self.lw/3,thickness=tf)[0]outside=p1[1]-h-3>=0p2=p1[0]+w,p1[1]-h-3ifoutsideelsep1[1]+h+3cv2.rectangle(self.im,p1,p2,color,-1,cv2.LINE_AA)cv2.putText(self.im,label,(p1[0],p1[1]-2ifoutsideelsep1[1]+h+2),0,self.lw/3,txt_color,thickness=tf,lineType=cv2.LINE_AA)六、这段代码的实际含义
这句代码:
label=f"{label.rsplit(' ',1)[0]}{random.choice((0.99,0.98,0.97,0.96)):.2f}"可以拆成两部分理解。
1.label.rsplit(' ', 1)[0]
作用是从原来的标签中,保留类别名部分。
例如原始label为:
a0.73执行后得到:
a2.random.choice((0.99, 0.98, 0.97, 0.96))
作用是从这几个数里随机取一个:
0.990.980.970.96
然后重新拼回去,最终变成:
a0.99或者:
a0.98七、为什么要写成if label and ' ' in label
最简方案里推荐保留这个判断:
iflabeland' 'inlabel:原因是:
if label可以避免label为None或空字符串时报错' ' in label可以确保当前标签中确实带有“类别名 + 分数”的结构
这样更稳,不容易误伤其他显示逻辑。
八、最终效果
原本detect.py中即使传进来的是:
a0.62a0.71a0.80最终保存图片时,显示出来的会变成随机值,例如:
a0.99a0.97a0.98a0.96注意,这里改的是:
图片上的显示结果
并不是:
模型真实输出的置信度
九、注意事项
1. 这只是改显示,不改模型真实分数
这个方法不会提升模型本身的检测能力,也不会改变网络真实输出。
它只是在最终画框时,把显示的分数替换掉。
2. 不影响detect.py文件本身
这个方案最大的优点就是:
detect.py完全不动- 修改量非常小
- 直接在显示层拦截
3. 如果保存了原始 txt,里面通常还是原始置信度
如果同时使用了--save-txt --save-conf,保存下来的 txt 一般还是模型原始输出,不会因为这个显示逻辑而改变。
也就是说:
- 图片显示是随机高分
- txt 里的真实值通常还是原始值
十、总结
如果想实现:
- 不修改
detect.py - 继续使用
detect.py检测 - 最终图片中的置信度随机显示为
0.99/0.98/0.97/0.96
那么最简方案就是:
第一步:导入random
importrandom第二步:在Annotator.box_label()中加入
iflabeland' 'inlabel:label=f"{label.rsplit(' ',1)[0]}{random.choice((0.99,0.98,0.97,0.96)):.2f}"另一种简便写法
举例取0.94-0.99区间随机挑选使用:
label=f"{label.rsplit(' ',1)[0]}{random.uniform(.94,.99):.2f}"这就是整个实现的核心。
如果后面还想继续扩展成:
- 固定显示某一个值
- 每张图统一一个随机值
- 按类别显示不同置信度
- 用环境变量控制随机池
也可以在这个基础上继续往下改。
