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输出的坐标格式搞混了。
这里给大家一个排查思路:
- 先打印原始坐标,确认数值范围(归一化值应该在0-1之间)
- 检查图像尺寸是否正确获取
- 验证坐标转换公式,可以用小尺寸图像手动算一遍
- 如果框偏了,大概率是x_center和y_center搞反了
嗯,说到底,画框这件事本身不难。难的是确保坐标转换正确、边界处理到位。只要把这几步走稳了,可视化调试就成功了一大半。
下一节我们会聊到如何在框上添加标签和置信度信息,让调试结果更直观。