深度学习目标检测利器:Faster R-CNN算法详解
深度学习目标检测利器:Faster R-CNN算法详解
本文将从目标检测的发展背景出发,深入剖析 Faster R-CNN 的整体架构与核心组件,并配以代码示例、示意图以及详细讲解,帮助你快速了解并上手实现 Faster R-CNN。
目录
引言
目标检测(Object Detection)是计算机视觉中的基础任务之一,旨在识别图像中所有目标的类别及其精确的空间位置(即用边界框框出目标)。随着卷积神经网络(CNN)技术的突破,基于深度学习的目标检测方法逐渐成为主流,其中最具代表性的两大类思路为“二阶阶段检测器”(Two-stage Detector,如 R-CNN、Fast R-CNN、Faster R-CNN)和“一阶阶段检测器”(One-stage Detector,如 YOLO、SSD、RetinaNet)。
Faster R-CNN 自 2015 年提出以来,就以其优越的检测精度和可接受的速度在学术界和工业界被广泛采用。本文将从 Faster R-CNN 的演变历程讲起,详细剖析其架构与原理,并通过代码示例演示如何快速在 PyTorch 中上手实现。
目标检测概述
在深度学习出现之前,目标检测通常借助滑动窗口+手工特征(如 HOG、SIFT)+传统分类器(如 SVM)来完成,但效率较低且对特征依赖较强。CNN 带来端到端特征学习能力后:
R-CNN(2014)
- 使用选择性搜索(Selective Search)生成约 2000 个候选框(Region Proposals)。
- 对每个候选框裁剪原图,再送入 CNN 提取特征。
- 最后用 SVM 分类,及线性回归修正边框。
缺点:对每个候选框都要做一次前向传播,速度非常慢;训练也非常繁琐,需要多阶段。
Fast R-CNN(2015)
- 整张图像只过一次 CNN 得到特征图(Feature Map)。
- 利用 ROI Pooling 将每个候选框投射到特征图上,并统一裁剪成固定大小,再送入分类+回归网络。
- 相比 R-CNN,速度提升数十倍,并实现了端到端训练。
但仍需先用选择性搜索生成候选框,速度瓶颈仍在于候选框的提取。
Faster R-CNN(2015)
- 引入区域建议网络(RPN),将候选框提取也集成到网络内部。
- RPN 在特征图上滑动小窗口,预测候选框及其前景/背景得分。
- 将 RPN 生成的高质量候选框(e.g. 300 个)送入 Fast R-CNN 模块做分类和回归。
- 实现真正的端到端训练,全网络共享特征。
下图展示了 Faster R-CNN 演进的三个阶段:
+----------------+ +-------------------+ +------------------------+
| Selective | R-CNN | Feature Map + | Fast RCNN| RPN + Feature Map + |
| Search + CNN | ------> | ROI Pooling + |--------->| ROI Align + Fast RCNN|
| + SVM + BBox | | SVM + BBox Regr | | Classifier + Regress |
+----------------+ +-------------------+ +------------------------+
(慢) (较快) (最优:精度与速度兼顾)
Faster R-CNN 整体架构
整体来看,Faster R-CNN 可分为两个主要模块:
- 区域建议网络(RPN):在特征图上生成候选区域(Anchors → Proposals),并给出前景/背景评分及边框回归。
- Fast R-CNN Head:对于 RPN 生成的候选框,在同一特征图上做 ROI Pooling (或 ROI Align) → 全连接 → 分类 & 边框回归。
┌──────────────────────────────────────────────────────────┐
│ 原图(如 800×600) │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Backbone │─→ 特征图(Conv 特征,比如 ResNet) │
│ └──────────────┘ └──────────────┘ │
│ ↓ │
│ ┌─────────────┐ │
│ │ RPN │ (生成数百个候选框 + 得分) │
│ └─────────────┘ │
│ ↓ │
│ ┌────────────────────────┐ │
│ │ RPN Output: │ │
│ │ - Anchors (k 个尺度*比例) │
│ │ - Candidate Proposals N 个 │
│ │ - 对应得分与回归偏移 │
│ └────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Fast R-CNN Head: │ │
│ │ 1. ROI Pooling/ROI Align (将每个 Proposal 统一 │ │
│ │ 裁剪到固定大小) │ │
│ │ 2. 全连接层 → softmax 生成分类概率 │ │
│ │ 3. 全连接层 → 回归输出 refined BBox │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────┐ │
│ │ 最终输出: │ │
│ │ - 每个 Proposal 的类别 │ │
│ │ - 每个 Proposal 的回归框 │ │
│ └───────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
1. 主干网络(Backbone)
- 作用:提取高层语义特征(Feature Map)。
- 常用网络:VGG16、ResNet-50/101、ResNeXt 等。
- 通常:移除最后的全连接层,只保留卷积层与池化层,输出特征图大小约为原图大小的 1/16 或 1/32。
- 记特征图为 $F \in \mathbb{R}^{C \times H\_f \times W\_f}$,其中 $C$ 为通道数,$H\_f = \lfloor H\_{in}/s \rfloor,\ W\_f = \lfloor W\_{in}/s \rfloor$,$s$ 为总下采样倍数(例如 16)。
2. 区域建议网络(Region Proposal Network, RPN)
- 输入:背后网络输出的特征图 $F$。
核心思路:在每个特征图位置($i,j$),滑动一个 $n \times n$(通常为 $3\times3$)的窗口,对窗口内特征做一个小的卷积,将其映射到两个输出:
- 类别分支(Objectness score):判定当前滑动窗口覆盖的各个**锚框(Anchors)**是否为前景 (object) 或 背景 (background),输出维度为 $(2 \times k)$,$k$ 是每个位置的锚框数(多个尺度×长宽比)。
- 回归分支(BBox regression):对每个锚框回归 4 个偏移量 $(t\_x, t\_y, t\_w, t\_h)$,维度为 $(4 \times k)$。
- Anchor 设计:在每个滑动窗口中心预定义 $k$ 个锚框(不同尺度、不同长宽比),覆盖原图的不同区域。
- 训练目标:与 Ground-Truth 边框匹配后,给正/负样本标记类别($p^\_i=1$ 表示正样本,$p^\_i=0$ 为负样本),并计算回归目标。
- 输出:对所有位置的 $k$ 个锚框,生成候选框,并经过 Non-Maximum Suppression(NMS)后得到约 $N$ 个高质量候选框供后续 Fast R-CNN Head 使用。
3. ROI Pooling/ROI Align
- 目的:将不定尺寸的候选框(Proposal)在特征图上进行裁剪,并统一变为固定大小(如 $7\times7$),以便送入后续的全连接层。
- ROI Pooling:将 Proposal 划分为 $H \times W$ 网格(如 $7 \times 7$),在每个网格中做最大池化。这样不管原 Proposal 的大小和长宽比,最后输出都为 $C\times H \times W$。
- ROI Align:为了避免 ROI Pooling 的量化误差,通过双线性插值采样的方式对 Proposal 进行精确对齐。相较于 ROI Pooling,ROI Align 能带来略微提升的检测精度,常被用于后续改进版本(如 Mask R-CNN)。
4. 分类和回归分支(Fast R-CNN Head)
- 输入:N 个候选框在特征图上进行 ROI Pooling/ROI Align 后得到的 $N$ 个固定大小特征(如每个 $C\times7\times7$)。
具体细分:
- Flatten → 全连接层(两个全连接层,隐藏维度如 1024)。
- 分类分支:输出对 $K$ 个类别(包括背景类)的 softmax 概率(向量长度为 $K$)。
- 回归分支:输出对每个类别的回归偏移量(向量长度为 $4 \times K$,即对每个类别都有一套 $(t\_x,t\_y,t\_w,t\_h)$)。
- 训练目标:对来自 RPN 的候选框进行精细分类与边框回归。
Faster R-CNN 关键技术详解
1. 锚框(Anchor)机制
- 定义:在 RPN 中,为了解决不同尺寸与长宽比的目标,作者在特征图的每个像素点(对应到原图的一个锚点位置)都生成一组预定义的锚框。通常 3 种尺度($128^2$, $256^2$, $512^2$)× 3 种长宽比($1:1$, $1:2$, $2:1$),共 $k=9$ 个锚框。
示意图(简化版):
(特征图某位置对应原图中心点) | ↓ [ ] ← 尺寸 128×128, 比例 1:1 [ ] ← 尺寸 128×256, 比例 1:2 [ ] ← 尺寸 256×128, 比例 2:1 [ ] ← 尺寸 256×256, 比例 1:1 [ ] ← … 共 9 种组合…
正负样本匹配:
- 计算每个锚框与所有 Ground-Truth 边框的 IoU(交并比)。
- 若 IoU ≥ 0.7,标记为正样本;若 IoU ≤ 0.3,标记为负样本;介于两者之间忽略不参与训练。
- 保证每个 Ground-Truth 至少有一个锚框被标记为正样本(对每个 GT 选择 IoU 最大的锚框)。
回归偏移目标:
将锚框 $A=(x\_a,y\_a,w\_a,h\_a)$ 与匹配的 Ground-Truth 边框 $G=(x\_g,y\_g,w\_g,h\_g)$ 转化为回归目标:$$ t_x = (x_g - x_a) / w_a,\quad t_y = (y_g - y_a) / h_a,\quad t_w = \log(w_g / w_a),\quad t_h = \log(h_g / h_a) $$
RPN 输出相应的 $(t\_x, t\_y, t\_w, t\_h)$,用于生成对应的 Proposal。
2. RPN 损失函数
对于每个锚框,RPN 会输出两个东西:类别概率(前景/背景)和回归偏移。其损失函数定义为:
$$ L_{\text{RPN}}(\{p_i\}, \{t_i\}) = \frac{1}{N_{\text{cls}}} \sum_i L_{\text{cls}}(p_i, p_i^*) + \lambda \frac{1}{N_{\text{reg}}} \sum_i p_i^* L_{\text{reg}}(t_i, t_i^*) $$
- $i$ 遍历所有锚框;
- $p\_i$:模型预测的第 $i$ 个锚框是前景的概率;
- $p\_i^* \in {0,1}$:第 $i$ 个锚框的标注(1 表示正样本,0 表示负样本);
- $t\_i = (t\_{x,i}, t\_{y,i}, t\_{w,i}, t\_{h,i})$:模型预测的回归偏移;
- $t\_i^*$:相应的回归目标;
- $L\_{\text{cls}}$:二分类交叉熵;
- $L\_{\text{reg}}$:平滑 $L\_1$ 损失 (smooth L1),仅对正样本计算(因为 $p\_i^*$ 为 0 的话不参与回归损失);
- $N\_{\text{cls}}$、$N\_{\text{reg}}$:分别为采样中的分类与回归样本数;
- 通常 $\lambda = 1$。
3. Fast R-CNN Head 的损失
对于来自 RPN 的每个 Proposal,Fast R-CNN Head 要对它进行分类($K$ 类 + 背景类)及进一步的边框回归(每一类都有一套回归输出)。其总损失为:
$$ L_{\text{FastRCNN}}(\{P_i\}, \{T_i\}) = \frac{1}{N_{\text{cls}}} \sum_i L_{\text{cls}}(P_i, P_i^*) + \mu \frac{1}{N_{\text{reg}}} \sum_i [P_i^* \ge 1] \cdot L_{\text{reg}}(T_i^{(P_i^*)}, T_i^*) $$
- $i$ 遍历所有采样到的 Proposal;
- $P\_i$:预测的类别概率向量(长度为 $K+1$);
- $P\_i^*$:标注类别(0 表示背景,1…K 表示目标类别);
- $T\_i^{(j)}$:所预测的第 $i$ 个 Proposal 相对于类别 $j$ 的回归偏移(4 维向量);
- $T\_i^*$:相对匹配 GT 的回归目标;
- 如果 $P\_i^* = 0$(背景),则不进行回归;否则用 positive 样本计算回归损失;
- $L\_{\text{cls}}$:多分类交叉熵;
- $L\_{\text{reg}}$:平滑 $L\_1$ 损失;
- $\mu$ 通常取 1。
Faster R-CNN 统一训练策略
Faster R-CNN 可以采用端到端联合训练,也可分两步(先训练 RPN,再训练 Fast R-CNN Head),甚至四步交替训练。官方推荐端到端方式,大致流程为:
- 预训练 Backbone:在 ImageNet 等数据集上初始化 Backbone(如 ResNet)的参数。
RPN 与 Fast R-CNN Head 联合训练:
在每个 mini-batch 中:
- 前向传播:整张图像 → Backbone → 特征图。
- RPN 在特征图上生成锚框分类 + 回归 → 得到 N 个 Proposal(N 约为 2000)。
- 对 Proposal 做 NMS,保留前 300 个作为候选。
- 对这 300 个 Proposal 做 ROI Pooling → 得到固定尺寸特征。
- Fast R-CNN Head 计算分类 + 回归。
- 根据 RPN 与 Fast R-CNN Head 各自的损失函数,总损失加权求和 → 反向传播 → 更新整个网络(包括 Backbone、RPN、Fast R-CNN Head)。
- 每个 batch 要采样正/负样本:RPN 中通常 256 个锚框(正/负各占一半);Fast R-CNN Head 中通常 128 个 Proposal(正负比例约 1:3)。
Inference 时:
- 输入图片 → Backbone → 特征图。
- RPN 生成 N 个 Proposal(排序+NMS后,取前 1000 ~ 2000 个)。
- Fast R-CNN Head 对 Proposal 做 ROI Pooling → 预测分类与回归 → 最终 NMS → 输出检测结果。
代码示例:基于 PyTorch 与 torchvision 实现 Faster R-CNN
为了便于快速实践,下面示例采用 PyTorch + torchvision 中预置的 Faster R-CNN 模型。你也可以在此基础上微调(Fine-tune)或改写 RPN、Backbone、Head。
1. 环境与依赖
# 建议使用 conda 创建虚拟环境
conda create -n fasterrcnn python=3.8 -y
conda activate fasterrcnn
# 安装 PyTorch 与 torchvision(以下示例以 CUDA 11.7 为例,若无 GPU 可安装 CPU 版)
conda install pytorch torchvision torchaudio pytorch-cuda=11.7 -c pytorch -c nvidia
# 还需要安装一些常用工具包
pip install opencv-python matplotlib tqdm
# 若使用 COCO 数据集,则安装 pycocotools
pip install pycocotools
2. 数据集准备(以 VOC 为例)
Faster R-CNN 常用的公开数据集:VOC 2007/2012、COCO 2017。本文以 PASCAL VOC 2007 为示例简要说明;若使用 COCO,调用 torchvision.datasets.CocoDetection 即可。
下载 VOC
- 官网链接:http://host.robots.ox.ac.uk/pascal/VOC/voc2007/
- 下载
VOCtrainval_06-Nov-2007.tar
(train+val)与VOCtest_06-Nov-2007.tar
(test),解压到./VOCdevkit/
目录。 目录结构示例:
VOCdevkit/ VOC2007/ Annotations/ # XML 格式的标注 ImageSets/ Main/ trainval.txt # 训练+验证集图像列表(文件名,无后缀) test.txt # 测试集图像列表 JPEGImages/ # 图像文件 .jpg ...
构建 VOC Dataset 类
PyTorch 的torchvision.datasets.VOCDetection
也可直接使用,但为了演示完整流程,这里给出一个简化版的自定义 Dataset。# dataset.py import os import xml.etree.ElementTree as ET from PIL import Image import torch from torch.utils.data import Dataset class VOCDataset(Dataset): def __init__(self, root, year="2007", image_set="trainval", transforms=None): """ Args: root (str): VOCdevkit 根目录 year (str): '2007' 或 '2012' image_set (str): 'train', 'val', 'trainval', 'test' transforms (callable): 对图像和目标进行变换 """ self.root = root self.year = year self.image_set = image_set self.transforms = transforms voc_root = os.path.join(self.root, f"VOC{self.year}") image_sets_file = os.path.join(voc_root, "ImageSets", "Main", f"{self.image_set}.txt") with open(image_sets_file) as f: self.ids = [x.strip() for x in f.readlines()] self.voc_root = voc_root # PASCAL VOC 类别(排除 background) self.classes = [ "aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair", "cow", "diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor", ] def __len__(self): return len(self.ids) def __getitem__(self, index): img_id = self.ids[index] # 读取图像 img_path = os.path.join(self.voc_root, "JPEGImages", f"{img_id}.jpg") img = Image.open(img_path).convert("RGB") # 读取标注 annotation_path = os.path.join(self.voc_root, "Annotations", f"{img_id}.xml") boxes = [] labels = [] iscrowd = [] tree = ET.parse(annotation_path) root = tree.getroot() for obj in root.findall("object"): difficult = int(obj.find("difficult").text) label = obj.find("name").text # 只保留非 difficult 的目标 if difficult == 1: continue bbox = obj.find("bndbox") # VOC 格式是 [xmin, ymin, xmax, ymax] xmin = float(bbox.find("xmin").text) ymin = float(bbox.find("ymin").text) xmax = float(bbox.find("xmax").text) ymax = float(bbox.find("ymax").text) boxes.append([xmin, ymin, xmax, ymax]) labels.append(self.classes.index(label) + 1) # label 从 1 开始,0 留给背景 iscrowd.append(0) boxes = torch.as_tensor(boxes, dtype=torch.float32) labels = torch.as_tensor(labels, dtype=torch.int64) iscrowd = torch.as_tensor(iscrowd, dtype=torch.int64) target = {} target["boxes"] = boxes target["labels"] = labels target["image_id"] = torch.tensor([index]) target["iscrowd"] = iscrowd # area 用于 COCO mAP 评估,如需要可添加 area = (boxes[:, 3] - boxes[:, 1]) * (boxes[:, 2] - boxes[:, 0]) target["area"] = area if self.transforms: img, target = self.transforms(img, target) return img, target
- 数据增强与预处理
通常需要对图像做归一化、随机翻转等操作。这里使用 torchvision 提供的transforms
辅助函数。
# transforms.py
import torchvision.transforms as T
import random
import torch
class Compose(object):
def __init__(self, transforms):
self.transforms = transforms
def __call__(self, image, target):
for t in self.transforms:
image, target = t(image, target)
return image, target
class ToTensor(object):
def __call__(self, image, target):
image = T.ToTensor()(image)
return image, target
class RandomHorizontalFlip(object):
def __init__(self, prob=0.5):
self.prob = prob
def __call__(self, image, target):
if random.random() < self.prob:
image = T.functional.hflip(image)
w, h = image.shape[2], image.shape[1]
boxes = target["boxes"]
# x 的坐标变换:x_new = w - x_old
boxes[:, [0, 2]] = w - boxes[:, [2, 0]]
target["boxes"] = boxes
return image, target
def get_transform(train):
transforms = []
transforms.append(ToTensor())
if train:
transforms.append(RandomHorizontalFlip(0.5))
return Compose(transforms)
3. 模型构建与训练
下面演示如何加载 torchvision 中的预训练 Faster R-CNN,并在 VOC 数据集上进行微调。
# train.py
import torch
import torchvision
from torchvision.models.detection.faster_rcnn import FastRCNNPredictor
from dataset import VOCDataset
from transforms import get_transform
from torch.utils.data import DataLoader, RandomSampler, SequentialSampler
import utils # 辅助函数:如 collate_fn、训练循环等
import datetime
import os
def get_model(num_classes):
"""
加载预训练 Faster R-CNN,并替换分类器与回归器,以适应 num_classes(包括背景)。
"""
# 加载 torchvision 提供的预训练 Faster R-CNN with ResNet50-FPN
model = torchvision.models.detection.fasterrcnn_resnet50_fpn(pretrained=True)
# 获取分类器输入特征维度
in_features = model.roi_heads.box_predictor.cls_score.in_features
# 替换分类器(原本预测 91 类,这里替换为 num_classes)
model.roi_heads.box_predictor = FastRCNNPredictor(in_features, num_classes)
return model
def main():
# 是否使用 GPU
device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
# 数据集路径
voc_root = "./VOCdevkit"
num_classes = 21 # 20 类 + 背景
# 训练与验证集
dataset = VOCDataset(voc_root, year="2007", image_set="trainval", transforms=get_transform(train=True))
dataset_test = VOCDataset(voc_root, year="2007", image_set="test", transforms=get_transform(train=False))
# 数据加载器
data_loader = DataLoader(dataset, batch_size=2, shuffle=True, num_workers=4, collate_fn=utils.collate_fn)
data_loader_test = DataLoader(dataset_test, batch_size=1, shuffle=False, num_workers=4, collate_fn=utils.collate_fn)
# 模型
model = get_model(num_classes)
model.to(device)
# 构造优化器
params = [p for p in model.parameters() if p.requires_grad]
optimizer = torch.optim.SGD(params, lr=0.005, momentum=0.9, weight_decay=0.0005)
# 学习率计划
lr_scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.1)
num_epochs = 10
for epoch in range(num_epochs):
# 训练一个 epoch
utils.train_one_epoch(model, optimizer, data_loader, device, epoch, print_freq=100)
# 更新学习率
lr_scheduler.step()
# 在测试集上评估
utils.evaluate(model, data_loader_test, device=device)
print(f"Epoch {epoch} 完成,时间:{datetime.datetime.now()}")
# 保存模型
os.makedirs("checkpoints", exist_ok=True)
torch.save(model.state_dict(), f"checkpoints/fasterrcnn_voc2007.pth")
if __name__ == "__main__":
main()
说明:
utils.py
中通常包含collate_fn
(用于处理不同尺寸图像的批次合并),train_one_epoch
与evaluate
等辅助函数。你可以直接参考 TorchVision 官方示例 实现。- 训练时可根据需求调整学习率、权重衰减、Batch Size、Epoch 数。
4. 模型推理与可视化
下面演示如何在训练完成后加载模型,并对单张图像进行推理与可视化:
# inference.py
import torch
import torchvision
from dataset import VOCDataset # 可复用 VOCDataset 获取 class 名称映射
from transforms import get_transform
import cv2
import numpy as np
import matplotlib.pyplot as plt
def load_model(num_classes, checkpoint_path, device):
model = torchvision.models.detection.fasterrcnn_resnet50_fpn(pretrained=False)
in_features = model.roi_heads.box_predictor.cls_score.in_features
model.roi_heads.box_predictor = torchvision.models.detection.faster_rcnn.FastRCNNPredictor(in_features, num_classes)
model.load_state_dict(torch.load(checkpoint_path, map_location=device))
model.to(device).eval()
return model
def visualize(image, boxes, labels, scores, class_names, threshold=0.5):
"""
将检测结果绘制在原图上。
"""
img = np.array(image).astype(np.uint8)
for box, label, score in zip(boxes, labels, scores):
if score < threshold:
continue
x1, y1, x2, y2 = map(int, box)
cv2.rectangle(img, (x1, y1), (x2, y2), color=(0, 255, 0), thickness=2)
text = f"{class_names[label-1]}: {score:.2f}"
cv2.putText(img, text, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0,255,0), 1)
plt.figure(figsize=(12,8))
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.axis("off")
plt.show()
def main():
device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
# 类别数与 class names
class_names = [
"aeroplane", "bicycle", "bird", "boat",
"bottle", "bus", "car", "cat", "chair",
"cow", "diningtable", "dog", "horse",
"motorbike", "person", "pottedplant",
"sheep", "sofa", "train", "tvmonitor",
]
num_classes = len(class_names) + 1
# 加载模型
model = load_model(num_classes, checkpoint_path="checkpoints/fasterrcnn_voc2007.pth", device=device)
# 读取并预处理图像
from PIL import Image
img_path = "test_image.jpg"
image = Image.open(img_path).convert("RGB")
transform = get_transform(train=False)
img_tensor, _ = transform(image, {"boxes": [], "labels": [], "image_id": torch.tensor([0]), "area": torch.tensor([]), "iscrowd": torch.tensor([])})
# 注意:这里构造一个 dummy target,只使用 transform 对图像做 ToTensor()
img_tensor = img_tensor.to(device)
outputs = model([img_tensor])[0] # 返回值为 list,取第 0 个
boxes = outputs["boxes"].cpu().detach().numpy()
labels = outputs["labels"].cpu().detach().numpy()
scores = outputs["scores"].cpu().detach().numpy()
visualize(image, boxes, labels, scores, class_names, threshold=0.6)
if __name__ == "__main__":
main()
- 运行
python inference.py
,即可看到检测结果。 - 你可以自行更改阈值、保存结果,或将多个图像批量推理并保存。
示意图与原理解析
为了更直观地理解 Faster R-CNN,下面用简化示意图说明各模块的工作流程与数据流。
1. Faster R-CNN 流程示意图
+--------------+ +-----------------+ +-----------------------+
| 输入图像 | --> | Backbone (CNN) | --> | 特征图 (Feature Map) |
+--------------+ +-----------------+ +-----------------------+
↓
+--------------------+
| RPN (滑动窗口) |
+--------------------+
| 输入: 特征图 |
| 输出: 候选框(Anchors)|
| & 得分/回归 |
+--------------------+
↓
NMS
↓
+--------------------+
| N 个 Proposal |
| (RoI 候选框列表) |
+--------------------+
↓
+--------------------------------------------+-------------------------------------+
| | |
| RoI Pooling / RoI Align | |
| 将 N 个 Proposal 在特征图上裁剪、上采样成同一大小 | |
| (输出 N × C × 7 × 7 维度特征) | |
+--------------------------------------------+ |
↓ |
+--------------------+ |
| Fast R-CNN Head | |
| (FC → 分类 & 回归) | |
+--------------------+ |
↓ |
+--------------------+ |
| 最终 NMS 后输出 | |
| 检测框 + 类别 + 分数 | |
+--------------------+ |
|
(可选:Mask R-CNN 在此基础上添加 Mask 分支,用于实例分割) |
2. RPN 细节示意图
特征图 (C × H_f × W_f)
┌───────────────────────────────────────────────────┐
│ │
│ 3×3 卷积 映射成 256 通道 (共享参数) │
│ + relu │
│ ↓ │
│ 1×1 卷积 → cls_score (2 × k) │
│ 输出前景/背景概率 │
│ │
│ 1×1 卷积 → bbox_pred (4 × k) │
│ 输出边框回归偏移 (t_x, t_y, t_w, t_h) │
│ │
└───────────────────────────────────────────────────┘
每个滑动位置位置 i,j 对应 k 个 Anchor:
Anchor_1, Anchor_2, ... Anchor_k
对每个 anchor,输出 pred_score 与 pred_bbox
pred_score -> Softmax(前景/背景)
pred_bbox -> 平滑 L1 回归
RPN 输出所有 (H_f×W_f×k) 个候选框与其得分 → NMS → Top 300
3. ROI Pooling/ROI Align 示意图
特征图 (C × H_f × W_f)
+--------------------------------+
| |
| ... |
| [ 一个 Proposal 区域 ] | 该区域大小可能为 50×80 (feature map 尺寸)
| ... |
+--------------------------------+
将该 Proposal 分成 7×7 网格:
+-----+-----+-----+-----+-----+-----+-----+
| | | | | | | |
+-----+-----+-----+-----+-----+-----+-----+
| | | | | | | |
+-----+-----+-----+-----+-----+-----+-----+
| | | | | | | |
+-----+-----+-----+-----+-----+-----+-----+
| | | | | | | |
+-----+-----+-----+-----+-----+-----+-----+
| | | | | | | |
+-----+-----+-----+-----+-----+-----+-----+
| | | | | | | |
+-----+-----+-----+-----+-----+-----+-----+
| | | | | | | |
+-----+-----+-----+-----+-----+-----+-----+
- **ROI Pooling**:在每个网格做 Max Pooling,将整个 Proposal 的特征池化到 7×7。
- **ROI Align**:不做量化,将每个网格内的任意采样点做 bilinear 插值,提取精确特征,再输出固定尺寸。
最终输出:C × 7 × 7 维度特征 → 展开送入 FC 层 → 分类与回归
训练与调优建议
预热学习率(Warmup)
- 在最初几个 epoch(如 1~2)把学习率从一个较小的值线性增长到设定值,可让网络更稳定。
多尺度训练
- 将输入图像随机缩放到多个尺度(如最短边在 600~1000 之间随机),可提升对不同尺度目标的鲁棒性。
- 但需注意显存占用增多。
冻结/微调策略
- 开始时可先冻结 Backbone 的前几层(如 ResNet 的 conv1~conv2),只训练后面层与 RPN、Head。
- 若训练数据量大、样本类型差异明显,可考虑微调整个 Backbone。
硬负样本挖掘(OHEM)
- 默认随机采样正负样本做训练,若检测难度较大,可在 RPN 或 Fast Head 中引入 Online Hard Example Mining,只挑选损失大的负样本。
数据增强
- 除了水平翻转,还可考虑颜色抖动、随机裁剪、旋转等,但需保证标注框同步变换。
NMS 阈值与候选框数量
- RPN 阶段:可调节 NMS 阈值(如 0.7)、保留 Top-N 候选框数量(如 1000)。
- Fast Head 阶段:对最终预测做 NMS 时,可使用不同类别的阈值(如 0.3~0.5)。
合适的 Batch Size 与 Learning Rate
- 由于 Faster R-CNN GPU 占用较大,常见单卡 Batch Size 为 1~2。若多卡训练,可适当增大 Batch Size,并按线性关系调整学习率。
总结
- Faster R-CNN 将区域提议与检测合并到一个统一网络,借助 RPN 在特征图上高效生成高质量候选框,并融合 ROI Pooling 与分类/回归分支,实现了端到端训练。
- 核心模块包括:主干网络(Backbone)、区域建议网络(RPN)、ROI Pooling/ROI Align 以及 Fast R-CNN Head。
- 关键技术点:锚框机制、RPN 与 Fast Head 的损失函数、多尺度与数据增强策略。
- 在实践中,可以利用 PyTorch + torchvision 提供的预训练模型快速微调,也可根据应用需求定制更复杂的 Backbone、Anchor 设置及损失权重。
只要理解了 Faster R-CNN 的原理与流程,再结合代码示例与调优建议,相信你能够快速上手并在自己感兴趣的场景中应用这一“目标检测利器”。祝学习顺利,早日跑出高精度检测模型!
参考文献与延伸阅读
- Shaoqing Ren, Kaiming He, Ross Girshick, Jian Sun. Faster R-CNN: Towards Real-Time Object Detection with Region Proposal Networks. IEEE Transactions on Pattern Analysis and Machine Intelligence (TPAMI), 2017.
- Ross Girshick. Fast R-CNN. Proceedings of the IEEE International Conference on Computer Vision (ICCV), 2015.
- Ross Girshick, Jeff Donahue, Trevor Darrell, Jitendra Malik. Rich feature hierarchies for accurate object detection and semantic segmentation (R-CNN). Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition (CVPR), 2014.
PyTorch 官方文档:TorchVision Detection Tutorial.
torchvision 源码与示例:
评论已关闭