2. YOLO模型加载与推理基础
好,咱们直接进入正题。YOLO模型再厉害,也得先把它加载进来,然后跑一次前向推理,拿到输出结果。这一步是后面所有可视化和调试工作的前提。
说白了,就是三步:加载模型 → 前向推理 → 解析输出。听起来简单,但里面有几个坑,我当年刚接触时都踩过。今天咱们一个一个说清楚。
2.1 使用cv2.dnn.readNet加载YOLO模型
OpenCV的dnn模块提供了统一的接口来加载各种深度学习模型。对于YOLO,我们通常需要两个文件:
- 权重文件(如
yolov4.weights):训练好的网络参数 - 配置文件(如
yolov4.cfg):网络结构定义
加载代码非常简单:
import cv2
# 加载YOLO模型
net = cv2.dnn.readNet('yolov4.weights', 'yolov4.cfg')
# 可选:设置计算后端
net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV)
net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU)
我的习惯: 我一般会先检查文件是否存在,避免路径错误导致程序崩溃。另外,如果机器有GPU,可以设置
DNN_TARGET_CUDA,速度能快好几倍。
这里有个细节——readNet会自动解析配置文件,识别网络结构。它支持DarkNet、TensorFlow、Caffe等多种框架。YOLOv3/v4用的是DarkNet格式,所以直接传.cfg和.weights就行。
2.2 前向推理:从图像到输出张量
模型加载好了,接下来就是把图像送进去,跑一次前向传播。这个过程分两步:
- 图像预处理:将图像转换成模型需要的输入格式
- 执行推理:调用
net.forward()获取输出
看代码:
# 读取图像
image = cv2.imread('test.jpg')
height, width = image.shape[:2]
# 构建blob(图像预处理)
blob = cv2.dnn.blobFromImage(
image,
scalefactor=1/255.0, # 归一化到[0,1]
size=(416, 416), # YOLO输入尺寸
mean=(0, 0, 0), # 不减去均值
swapRB=True, # OpenCV是BGR,YOLO需要RGB
crop=False
)
# 设置网络输入
net.setInput(blob)
# 前向推理
outputs = net.forward()
我曾经踩过的坑: 有一次我忘了设置
swapRB=True,结果检测出来的颜色全乱了。红色物体被识别成蓝色,排查了半天才发现是通道顺序的问题。记住:OpenCV默认是BGR,而YOLO训练时用的是RGB。
blobFromImage这个函数做了好几件事:缩放、归一化、通道转换、裁剪(如果需要)。我个人习惯把size设为416×416,这是YOLOv4的默认输入尺寸。如果你用YOLOv5或更高版本,可能是640×640,记得对应调整。
2.3 理解输出张量结构
好,现在outputs拿到了。它到底是什么?
对于YOLOv4,net.forward()返回的是一个列表,里面包含三个层级的输出。每个层级对应一个检测尺度(大、中、小物体)。
每个输出张量的形状是:
[batch_size, num_boxes, 85]
其中85这个数字的含义:
| 索引 | 含义 | 说明 |
|---|---|---|
| 0-3 | 边界框坐标 | cx, cy, w, h(相对于网格单元) |
| 4 | 目标置信度 | 该位置是否有物体的概率 |
| 5-84 | 类别概率 | COCO数据集80个类别 |
你想想看,每个检测框有85个值。前5个是位置和置信度,后面80个是每个类别的概率。最终检测结果就是:置信度 × 类别概率,取最大值对应的类别。
咱们用代码验证一下:
# 查看输出结构
print(f"输出层数量: {len(outputs)}")
for i, out in enumerate(outputs):
print(f"层{i} 形状: {out.shape}")
# 输出类似: (1, 507, 85) 或 (1, 2028, 85)
# 解析第一个检测框
first_box = outputs[0][0][0]
cx, cy, w, h = first_box[0:4]
confidence = first_box[4]
class_scores = first_box[5:]
class_id = np.argmax(class_scores)
score = class_scores[class_id]
print(f"检测框: cx={cx:.4f}, cy={cy:.4f}, w={w:.4f}, h={h:.4f}")
print(f"置信度: {confidence:.4f}")
print(f"类别ID: {class_id}, 得分: {score:.4f}")
关键点: 这里的坐标是相对于网格单元的偏移量,不是图像像素坐标。后面我们需要用
cv2.dnn.NMSBoxes做非极大值抑制,并转换到原始图像尺寸。这部分下一章会详细讲。
2.4 知识结构总览
为了让你对整个流程有个直观印象,我画了一张图:
2.5 常见问题与避坑
- 模型加载失败:检查文件路径,确认
.cfg和.weights版本匹配。我遇到过用v3的cfg加载v4的weights,直接报错。 - 推理速度慢:试试设置
DNN_TARGET_CUDA,或者降低输入尺寸(比如从608降到416)。 - 输出张量维度不对:不同YOLO版本输出结构不同。YOLOv3有3个输出层,YOLOv5只有一个。记得先
print(outputs.shape)确认一下。
调试小技巧: 我经常在
net.forward()之后加一句print([o.shape for o in outputs]),一眼就能看出输出结构对不对。如果形状是(1, 507, 85),说明输入是416×416;如果是(1, 2028, 85),说明是608×608。