YOLOv5 实战:不修改 detect.py,让检测结果图中的置信度随机显示为自己想要的

摘要

这篇文章记录一个比较实用的小技巧:仅使用部分场景下使用,并非真实修改模型,仅对绘制出的置信度显示进行修改,仅此而已,在 不修改 detect.py 的前提下,直接通过修改 utils/plots.py 中的显示逻辑,让 YOLOv5 在 detect.py 输出结果图时,把框上的置信度随机显示为 0.990.980.970.96 中的一个,即使detect.py中将conf数值减小也不影响该方式的显示。

这个方法的特点很明确:

  • 不动 detect.py
  • 修改量非常小
  • 只影响图片上的显示结果
  • 不改变模型真实输出的置信度

如果你也遇到类似需求,可以直接参考本文的最小修改方案。

一、问题背景

在使用 YOLOv5 的 detect.py 做检测时,我的代码里对显示置信度做了减法处理,例如:

label = None if hide_labels else (names[c] if hide_conf else f'{names[c]} {conf - 0.15:.2f}')

这样最终保存出来的检测图片中,框上的置信度会比原始值少 0.15

现在我想实现这样一个效果:

  • 不修改 detect.py
  • 继续使用 detect.py 进行检测
  • 最终图片上的置信度不再显示 conf - 0.15
  • 而是随机显示为 0.990.980.970.96 中的一个

二、为什么改 plot_images() 没有效果

很多人第一反应会去改 utils/plots.py 里的 plot_images(),例如这段:

for j, box in enumerate(boxes.T.tolist()):
    cls = classes[j]
    color = colors(cls)
    cls = names[cls] if names else cls
    if labels or conf[j] > 0.25:
        label = f'{cls}' if labels else f'{cls} {conf[j]:.1f}'
        annotator.box_label(box, label, color=color)

但这里主要用于训练或验证阶段的可视化拼图,不是 detect.py 最终保存检测图时走的主流程。

detect.py 真正走的是这条链路:

  1. detect.py 中先拼接好 label
  2. 调用 annotator.box_label(...)
  3. 再由 utils/plots.py 中的 Annotator.box_label() 把文字真正画到图上

所以,如果想在 不改 detect.py 的前提下改变最终显示结果,最直接的方法就是:

拦截 Annotator.box_label() 中的 label,在真正绘制前重写它。

三、最简实现思路

核心思路非常简单:

  • detect.py 传进来的 label 可能是:
a 0.73
  • box_label() 里面,不直接使用这个 label
  • 而是把最后的分数部分替换成随机值:
a 0.99

这样就可以做到:

  • 不改 detect.py
  • 最终检测图片显示高置信度
  • 代码修改最小

四、最小修改方案

只需要修改一个文件:

utils/plots.py

1. 增加 random 导入

在文件顶部加入:

import random

例如改成:

import math
import os
import random
from copy import copy
from pathlib import Path

2. 修改 Annotator.box_label()

utils/plots.py 中找到:

def box_label(self, box, label='', color=(128, 128, 128), txt_color=(255, 255, 255)):

在函数内部、真正绘制文字之前,加入下面这段最简代码:

if label and ' ' in label:
    label = f"{label.rsplit(' ', 1)[0]} {random.choice((0.99, 0.98, 0.97, 0.96)):.2f}"

五、完整示意

修改后的 box_label() 结构如下:

def box_label(self, box, label='', color=(128, 128, 128), txt_color=(255, 255, 255)):
    if label and ' ' in label:
        label = f"{label.rsplit(' ', 1)[0]} {random.choice((0.99, 0.98, 0.97, 0.96)):.2f}"

    if self.pil or not is_ascii(label):
        self.draw.rectangle(box, width=self.lw, outline=color)
        if label:
            w, h = self.font.getsize(label)
            outside = box[1] - h >= 0
            self.draw.rectangle((box[0],
                                 box[1] - h if outside else box[1],
                                 box[0] + w + 1,
                                 box[1] + 1 if outside else box[1] + h + 1), fill=color)
            self.draw.text((box[0], box[1] - h if outside else box[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)
        if label:
            tf = max(self.lw - 1, 1)
            w, h = cv2.getTextSize(label, 0, fontScale=self.lw / 3, thickness=tf)[0]
            outside = p1[1] - h - 3 >= 0
            p2 = p1[0] + w, p1[1] - h - 3 if outside else p1[1] + h + 3
            cv2.rectangle(self.im, p1, p2, color, -1, cv2.LINE_AA)
            cv2.putText(self.im, label, (p1[0], p1[1] - 2 if outside else p1[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 为:

a 0.73

执行后得到:

a

2. random.choice((0.99, 0.98, 0.97, 0.96))

作用是从这几个数里随机取一个:

  • 0.99
  • 0.98
  • 0.97
  • 0.96

然后重新拼回去,最终变成:

a 0.99

或者:

a 0.98

七、为什么要写成 if label and ' ' in label

最简方案里推荐保留这个判断:

if label and ' ' in label:

原因是:

  • if label 可以避免 labelNone 或空字符串时报错
  • ' ' in label 可以确保当前标签中确实带有“类别名 + 分数”的结构

这样更稳,不容易误伤其他显示逻辑。

八、最终效果

原本 detect.py 中即使传进来的是:

a 0.62
a 0.71
a 0.80

最终保存图片时,显示出来的会变成随机值,例如:

a 0.99
a 0.97
a 0.98
a 0.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

import random

第二步:在 Annotator.box_label() 中加入

if label and ' ' in label:
    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}"

这就是整个实现的核心。

如果后面还想继续扩展成:

  • 固定显示某一个值
  • 每张图统一一个随机值
  • 按类别显示不同置信度
  • 用环境变量控制随机池

也可以在这个基础上继续往下改。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐