YOLO
目录
深度学习目标检测
一、核心思想:从手工特征到学习特征
传统方法 vs 深度学习方法
| 对比维度 | 传统方法(HOG+SVM等) | 深度学习方法 |
|---|---|---|
| 特征提取 | 手工设计特征(梯度、纹理等) | 网络自动学习特征 |
| 适应性 | 特征固定,通用性有限 | 特征自适应,任务特定 |
| 检测流程 | 滑动窗口+分类器 | 端到端学习 |
| 性能 | 精度有限,速度慢 | 精度高,速度快(优化后) |
关键转变:从“人设计特征,机器分类”到“机器自己学习特征并分类”
二、深度学习目标检测的两种基本范式
2.1 两阶段检测器(Two-Stage)
代表算法:R-CNN、Fast R-CNN、Faster R-CNN
核心流程: 1. 第一阶段:生成候选区域 - 找出图像中可能有目标的区域 - 方法:Selective Search、RPN(Region Proposal Network) - 输出:几百到几千个候选框
- 第二阶段:分类和定位
- 对每个候选框:
- 裁剪并调整到固定大小
- 通过CNN提取特征
- 分类(是什么目标)
- 回归(调整边界框位置)
# 两阶段检测器的简化实现思路
class TwoStageDetector:
def __init__(self):
# 第一阶段:候选区域生成
self.region_proposal = RegionProposalNetwork()
# 第二阶段:检测网络
self.detection_network = DetectionCNN()
def forward(self, image):
# 第一阶段:生成候选区域
proposals = self.region_proposal(image) # 输出候选框
# 第二阶段:对每个候选框检测
detections = []
for proposal in proposals:
# 裁剪候选区域
crop = crop_image(image, proposal)
# 通过CNN进行分类和回归
class_score, bbox_refine = self.detection_network(crop)
detections.append({
'bbox': refine_bbox(proposal, bbox_refine),
'class': class_score,
'score': confidence_score
})
return detections
优点:精度高,候选框质量好
缺点:速度慢,不适合实时应用
2.2 单阶段检测器(One-Stage)
代表算法:YOLO、SSD、RetinaNet
核心流程: 1. 直接回归:将目标检测视为回归问题 2. 密集预测:在特征图的每个位置直接预测边界框和类别 3. 端到端:输入图像直接输出检测结果
# 单阶段检测器的简化实现思路
class OneStageDetector:
def __init__(self, grid_size=13, num_classes=20):
self.grid_size = grid_size # 网格划分的一维个数,总个数为grid_size*grid_size
self.num_classes = num_classes
# 特征提取骨干网络
self.backbone = CNNBackbone()
# 检测头:直接预测边界框和类别
self.detection_head = nn.Conv2d(
in_channels=512,
out_channels=grid_size*grid_size*(5+num_classes),
kernel_size=1
)
def forward(self, image):
# 提取特征
features = self.backbone(image) # [batch, channels, H, W]
# 检测头直接预测
predictions = self.detection_head(features) # [batch, S*S*(5+C), H, W]
# 解码预测结果
# predictions包含了:边界框(x,y,w,h)、置信度、类别概率
return decode_predictions(predictions)
优点:速度快,适合实时检测
缺点:精度通常低于两阶段方法
注:5+num_classes中:5 是边界框本身必须带 5 个参数,num_classes 是它是某一类与否的打分,两者缺一不可,所以相加。
下面把 5 拆给你看(一句话一个数字):
x框中心在网格内的横向偏移y框中心在网格内的纵向偏移w框的宽度(相对于整图)h框的高度(相对于整图)conf框里有物体的置信度
三、YOLO:单阶段检测的代表
3.1 YOLO的核心创新
一句话理解YOLO:将图像划分成网格,每个网格负责检测中心点落在该网格内的目标。
# YOLO网格划分的直观理解
import numpy as np
# 假设416×416的图像
image_height = 416
image_width = 416
grid_cells = 13 # 划分为13×13网格
# 计算每个网格的像素范围
cell_height = image_height / grid_cells # 32像素
cell_width = image_width / grid_cells # 32像素
# 示例:判断目标(100, 150)属于哪个网格
target_x, target_y = 100, 150
grid_x = int(target_x // cell_width) # 第3列
grid_y = int(target_y // cell_height) # 第4行
print(f"目标坐标({target_x}, {target_y})属于网格({grid_x}, {grid_y})")
print(f"该网格负责检测中心在[{grid_x*32}:{(grid_x+1)*32}, {grid_y*32}:{(grid_y+1)*32}]区域的目标")
3.2 YOLO的预测内容
每个网格预测: 1. 边界框(Bounding Box):预测B个框(YOLO v1是2个,v2/v3是3个) - (x, y):框中心相对于网格的偏移(0-1之间) - (w, h):框的宽高相对于整个图像的比例 - confidence:框的置信度 = P(有目标) × IoU(预测框, 真实框)
- 类别概率:C个类别的条件概率
输出维度:S × S × (B×5 + C) - S:网格数(如13) - B:每个网格预测的框数(如3) - 5:每个框的5个参数(x, y, w, h, confidence) - C:类别数(如COCO的80类)
四、关键概念详解
4.1 Anchor Box(锚框)
为什么要用Anchor Box?
-
在 v1 里一个网格只能输出 1 组框 → 遇多目标/多尺度就冲突;v2 起让网格一次输出 3 组 anchor 框(不同尺寸),每组框独立跑回归,于是同一个网格可以“各管各”地检测多个目标。
-
v1:每网格 2 个框,但共享类别概率 → 仍只能当 1 个目标。
-
v2+:每网格 3 个 anchor 框,每个 anchor 独立带
(x,y,w,h,conf,类别), 因此同一网格可以同时预测 3 个不同尺度/形状的目标。 -
问题:一个网格只能预测一个目标(v1的问题)
- 解决:一个网格预测多个不同尺寸的框,每个框专门检测特定尺寸的目标
训练过程
- 前向预测 把一张训练图片喂进网络,得到 13×13×(3×(5+80)) 的张量 → 即每个网格 3 个 anchor 的预测框、置信度、类别概率。
- 正负样本分配 • 用 IoU 把每个真实框(gt_box) 与 所有 anchor(含位置、尺寸)做匹配。 • IoU>阈值 的 anchor 标记为 正样本(负责回归这个框); • IoU<阈值 的标记为 负样本(背景); • 其余忽略。
- 反向传播 把三类损失一起反向传播: • 回归损失:只针对正样本,学 Δx, Δy, Δw, Δh; • 置信度损失:所有样本都算,让正样本 confidence≈1,负样本 ≈0(负样本的监督信号); • 分类损失:仅正样本,让类别概率逼近真实类别。
重复 1-3 步,直到损失收敛,即模型学会用不同 anchor 检测不同尺度目标。
# Anchor Box的简单示例
class AnchorBox:
def __init__(self):
# 预定义的框尺寸(宽高)
# 这些尺寸是通过对训练集聚类得到的
self.anchors = [
[116, 90], # 适合大目标
[156, 198], # 适合中目标
[373, 326] # 适合小目标
]
def match_anchors(self, gt_box):
"""将真实框匹配到最相似的锚框"""
# 计算真实框与每个锚框的IoU
ious = []
for anchor in self.anchors:
iou = calculate_iou(gt_box, anchor)
ious.append(iou)
# 选择IoU最大的锚框
best_anchor_idx = np.argmax(ious)
return best_anchor_idx, ious[best_anchor_idx]
4.2 IoU(交并比)
IoU = 交集面积 / 并集面积
def calculate_iou(box1, box2):
"""
计算两个边界框的IoU
box: [x1, y1, x2, y2] 左上角和右下角坐标
"""
# 计算交集矩形的坐标
x1 = max(box1[0], box2[0])
y1 = max(box1[1], box2[1])
x2 = min(box1[2], box2[2])
y2 = min(box1[3], box2[3])
# 计算交集面积
inter_area = max(0, x2 - x1) * max(0, y2 - y1)
# 计算各自面积
box1_area = (box1[2] - box1[0]) * (box1[3] - box1[1])
box2_area = (box2[2] - box2[0]) * (box2[3] - box2[1])
# 计算并集面积
union_area = box1_area + box2_area - inter_area
return inter_area / union_area