3、绘制边界框:cv2.rectangle绘制矩形框,标注检测目标位置

好了,到了这一步,我们已经拿到了YOLO吐出来的坐标信息。接下来要做的,就是把这些数字变成看得见的框框。

说白了,就是把检测到的目标用矩形框圈出来。你想想看,一堆坐标数字谁看得懂?但画上框之后,一眼就知道模型检测得准不准。

3.1 cv2.rectangle 的基本用法

OpenCV里画矩形框的函数就是 cv2.rectangle。我个人习惯把它叫做「框框函数」,因为它的工作就是画框。

先看最基础的调用方式:

cv2.rectangle(img, pt1, pt2, color, thickness)

参数说明:

参数 含义 类型
img 要画图的图像对象 numpy数组
pt1 矩形左上角坐标 (x1, y1) 元组
pt2 矩形右下角坐标 (x2, y2) 元组
color 边框颜色 (B, G, R) 元组
thickness 线条粗细(像素) 整数
小提示:OpenCV的颜色顺序是BGR,不是RGB。如果你传了(255,0,0),得到的是蓝色框,不是红色。我第一次用的时候就踩过这个坑。

3.2 从YOLO坐标到矩形框

YOLO输出的坐标格式通常是 [x_center, y_center, width, height],而且是归一化后的值。我们需要把它转换成像素坐标。

我在项目中遇到过一个问题:很多新手直接拿归一化坐标去画框,结果框全画到左上角去了。嗯,这里要注意,必须先做反归一化。

转换公式其实很简单:

x1 = int((x_center - width / 2) * img_width)
y1 = int((y_center - height / 2) * img_height)
x2 = int((x_center + width / 2) * img_width)
y2 = int((y_center + height / 2) * img_height)

为什么要这么算?你想想看,YOLO给的是中心点和宽高,但cv2.rectangle要的是左上角和右下角。所以我们要从中心往两边扩展。

3.3 完整代码示例

下面是我在实际调试中经常用的代码片段:

import cv2
import numpy as np

def draw_yolo_boxes(image, detections, class_names=None):
    """
    在图像上绘制YOLO检测框
    detections: [[x_center, y_center, width, height, conf, class_id], ...]
    """
    img_h, img_w = image.shape[:2]
    
    for det in detections:
        x_center, y_center, w, h, conf, cls_id = det
        
        # 反归一化并计算左上角和右下角
        x1 = int((x_center - w / 2) * img_w)
        y1 = int((y_center - h / 2) * img_h)
        x2 = int((x_center + w / 2) * img_w)
        y2 = int((y_center + h / 2) * img_h)
        
        # 确保坐标在图像范围内
        x1 = max(0, x1)
        y1 = max(0, y1)
        x2 = min(img_w - 1, x2)
        y2 = min(img_h - 1, y2)
        
        # 画矩形框
        color = (0, 255, 0)  # 绿色
        cv2.rectangle(image, (x1, y1), (x2, y2), color, 2)
        
        # 如果提供了类别名称,就加上标签
        if class_names is not None:
            label = f"{class_names[int(cls_id)]} {conf:.2f}"
            cv2.putText(image, label, (x1, y1 - 5),
                       cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1)
    
    return image
关键点:坐标一定要做边界检查!我曾经因为检测框超出图像边界,导致程序直接崩溃。加上max/min判断后,再也没出过问题。

3.4 颜色编码策略

当检测多个类别时,用不同颜色区分会直观很多。我一般这样分配颜色:

# 为每个类别分配固定颜色
COLORS = [
    (0, 255, 0),    # 绿色 - 人
    (255, 0, 0),    # 蓝色 - 车
    (0, 0, 255),    # 红色 - 自行车
    (0, 255, 255),  # 黄色 - 狗
    (255, 255, 0),  # 青色 - 猫
]

def get_color(class_id):
    return COLORS[class_id % len(COLORS)]

为什么要用模运算?因为类别数量可能超过颜色数量,取模可以循环使用颜色,保证每个类别都有颜色可用。

3.5 调试时的实用技巧

在实际调试过程中,我总结了几点经验:

  • 先画框再显示:画框操作会修改原图,建议复制一份再画
  • 框的粗细要适中:小图用1-2像素,大图用2-3像素
  • 配合置信度阈值:只画置信度高于0.5的框,减少干扰
注意:cv2.rectangle是在原图上直接修改的。如果你需要保留原始图像,记得先用 img_copy = img.copy() 复制一份。

3.6 常见问题排查

我曾经遇到过一个很诡异的问题:框画出来了,但位置完全不对。排查了半天,发现是YOLO输出的坐标格式搞混了。

这里给大家一个排查思路:

  1. 先打印原始坐标,确认数值范围(归一化值应该在0-1之间)
  2. 检查图像尺寸是否正确获取
  3. 验证坐标转换公式,可以用小尺寸图像手动算一遍
  4. 如果框偏了,大概率是x_center和y_center搞反了

嗯,说到底,画框这件事本身不难。难的是确保坐标转换正确、边界处理到位。只要把这几步走稳了,可视化调试就成功了一大半。

下一节我们会聊到如何在框上添加标签和置信度信息,让调试结果更直观。