在目标检测任务中,边界框(Bounding Box)是最常用的标注形式之一。它通过矩形框标记出图像中目标物体的位置和范围,通常由四个关键参数定义:(x, y)坐标表示矩形中心点,width和height表示框的宽高。这种表示方法简洁高效,被广泛应用于YOLO、Faster R-CNN等主流目标检测算法。
注意:OpenCV中的坐标系原点(0,0)位于图像左上角,x轴向右延伸,y轴向下延伸。这与数学中的笛卡尔坐标系不同,需要特别注意。
边界框标注的核心价值在于:
实际项目中,我们通常从模型得到的是边界框的原始坐标数据,如:
json复制{
"predictions": [
{
"x": 320,
"y": 240,
"width": 100,
"height": 80,
"class": "person",
"confidence": 0.95
}
]
}
首先确保已安装必要的Python库:
bash复制pip install opencv-python numpy
基础绘制流程包含以下步骤:
完整代码框架如下:
python复制import cv2
import numpy as np
# 读取图像
image = cv2.imread("input.jpg")
# 模拟预测结果
predictions = {
"predictions": [
{
"x": 320, "y": 240,
"width": 100, "height": 80,
"class": "person",
"confidence": 0.95
}
]
}
# 绘制边界框和标签
for box in predictions["predictions"]:
# 计算角点坐标
x0 = int(box["x"] - box["width"] / 2)
y0 = int(box["y"] - box["height"] / 2)
x1 = int(box["x"] + box["width"] / 2)
y1 = int(box["y"] + box["height"] / 2)
# 绘制矩形框
cv2.rectangle(image, (x0, y0), (x1, y1), (0, 255, 0), 2)
# 添加标签文本
label = f"{box['class']} {box['confidence']:.2f}"
cv2.putText(image, label, (x0, y0-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
# 保存结果
cv2.imwrite("output.jpg", image)
边界框的中心点表示法需要转换为OpenCV需要的角点表示法。转换公式为:
code复制左上角点(x0, y0) = (x - width/2, y - height/2)
右下角点(x1, y1) = (x + width/2, y + height/2)
实操技巧:务必对计算结果进行int()转换,因为像素坐标必须是整数。浮点数坐标会导致绘制错误。
对于特殊情况的处理:
改进后的安全计算方式:
python复制height, width = image.shape[:2]
x0 = max(0, int(box["x"] - box["width"] / 2))
y0 = max(0, int(box["y"] - box["height"] / 2))
x1 = min(width, int(box["x"] + box["width"] / 2))
y1 = min(height, int(box["y"] + box["height"] / 2))
cv2.putText方法的完整参数说明:
python复制cv2.putText(
img, # 输入图像
text, # 要绘制的文本
org, # 文本左下角坐标
fontFace, # 字体类型
fontScale, # 字体缩放比例
color, # 文本颜色(BGR)
thickness, # 文本线宽
lineType, # 线型(可选)
bottomLeftOrigin # 坐标系标志(可选)
)
常用字体类型:
为提升标签可读性,可以添加半透明背景框:
python复制# 计算文本尺寸
(text_width, text_height), _ = cv2.getTextSize(
label, cv2.FONT_HERSHEY_SIMPLEX, 0.6, 2)
# 绘制半透明背景
cv2.rectangle(image, (x0, y0-20), (x0+text_width, y0), (0, 0, 0), -1)
alpha = 0.6 # 透明度
image[y0-20:y0, x0:x0+text_width] = (
image[y0-20:y0, x0:x0+text_width] * (1 - alpha)
+ np.array([0, 0, 0]) * alpha
).astype(np.uint8)
# 绘制文本
cv2.putText(image, label, (x0, y0-5),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
典型的目标检测标签可能包含:
示例代码:
python复制label = f"ID:{box.get('id', 'NA')} {box['class']} {box['confidence']:.2f}"
if box.get('action'):
label += f" | {box['action']}"
标签不显示或位置错误
边界框错位
性能问题
当图像经过预处理(如padding或resize)时,需要将预测坐标转换回原始图像坐标系:
python复制# 假设原始图像被resize到640x640进行预测
scale_x = original_width / 640
scale_y = original_height / 640
for box in predictions:
# 转换坐标到原始图像空间
x = box["x"] * scale_x
y = box["y"] * scale_y
width = box["width"] * scale_x
height = box["height"] * scale_y
# 后续绘制代码...
当多个边界框交叠时,可采用以下策略:
实现示例:
python复制colors = {
"person": (0, 255, 0),
"car": (255, 0, 0),
"dog": (0, 0, 255)
}
for i, box in enumerate(sorted(predictions, key=lambda x: -x["confidence"])):
color = colors.get(box["class"], (255, 255, 255))
# 绘制时添加垂直偏移避免标签重叠
y_offset = i * 20
cv2.putText(image, label, (x0, y0-10-y_offset), ...)
Roboflow提供了便捷的API来获取模型预测:
python复制from roboflow import Roboflow
rf = Roboflow(api_key="your_api_key")
project = rf.workspace().project("project_name")
model = project.version(1).model
# 预测本地图像
predictions = model.predict("image.jpg", confidence=50).json()
# 预测URL图像
predictions = model.predict("https://example.com/image.jpg").json()
Roboflow返回的预测结果结构示例:
json复制{
"predictions": [
{
"x": 320.5,
"y": 240.3,
"width": 100.2,
"height": 80.7,
"class": "person",
"confidence": 0.956,
"points": [...] # 实例分割时会有多边形点
}
],
"image": {
"width": 640,
"height": 480
}
}
python复制import cv2
from roboflow import Roboflow
# 初始化Roboflow
rf = Roboflow(api_key="your_api_key")
project = rf.workspace("workspace").project("project")
model = project.version(1).model
# 获取预测
image_path = "test.jpg"
predictions = model.predict(image_path, confidence=50).json()
# 读取图像
image = cv2.imread(image_path)
# 绘制预测结果
for box in predictions["predictions"]:
x0 = int(box["x"] - box["width"] / 2)
y0 = int(box["y"] - box["height"] / 2)
x1 = int(box["x"] + box["width"] / 2)
y1 = int(box["y"] + box["height"] / 2)
# 绘制边界框
cv2.rectangle(image, (x0, y0), (x1, y1), (0, 255, 0), 2)
# 绘制标签
label = f"{box['class']} {box['confidence']:.2f}"
cv2.putText(image, label, (x0, y0-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
# 保存结果
cv2.imwrite("annotated_image.jpg", image)
在实际项目中,我发现合理设置置信度阈值非常重要。对于关键任务,可能需要设置较高的阈值(如0.7)以减少误报;而对于需要高召回率的场景,可以适当降低阈值(如0.3)。此外,不同类别的理想阈值可能不同,可以考虑为每个类别设置独立的置信度阈值。