深度学习 PyTorch 实战八:预训练 fasterrcnn_resnet50_fpn 经典 Faster R-CNN:完整代码+详细原理+预测可视化:从官方数据集或自己数据采集、模型构建到训练部署
一、 引言:从 R-CNN 到 Faster R-CNN 的进化
作为目标检测领域的里程碑式模型,Faster R-CNN 彻底解决了前代模型(R-CNN, Fast R-CNN)依赖外部候选框算法(如 Selective Search)导致的速度瓶颈。在 2026 年的今天,尽管涌现了无数新架构,Faster R-CNN 凭借其极高的稳定性和精度,依然是工业界和竞赛中的常青树。
本教程将带你从零开始,利用 PyTorch 官方预训练模型 fasterrcnn_resnet50_fpn,完成从环境配置、数据准备(支持自定义数据)、模型微调(Fine-tuning)到推理可视化的全过程。
完整代码在个人GitHub:实战八
1.卷积神经网络CNN里经典网络模型之 AlexNet全网最详解(理论篇)
2.目标检测简介
3.深度学习之目标检测R-CNN模型算法流程详解说明(超详细理论篇)
4.深度学习之目标检测Fast-RCNN模型算法流程详解说明(超详细理论篇)
5.目标检测标注工具常用的三种:LabelImg、CVAT、Roboflow
6.关于R-CNN中Selective Search选择性搜索候选框原理+步骤+代码+参数详细说明
7.PASCAL VOC、ILSVRC和MS COCO三大竞赛简单介绍
8.PASCAL VOC、ILSVRC和MS COCO三大竞赛核心数据集版本总节
9.OC vs COCO vs YOLO格式终极对比:2025年目标检测项目到底该选哪个标注格式?避坑指南+决策树
10.Pascal VOC数据集划分的致命陷阱与最佳实践:为什么99%的开发者都该以JPEGImages图片文件夹为基准,而不是Annotations XML?
11.深度学习pytorch实战七:从零复现经典R-CNN:完整代码+详细原理+训练调优+预测可视化:从数据采集、模型构建到训练部署
二、 原理剖析:Faster R-CNN 的核心架构
Faster R-CNN 的核心在于端到端(End-to-End)的训练方式。它将检测任务分解为两个阶段(Two-Stage),但所有组件都在同一个网络中协同工作。
1. 整体流程
- 1.Backbone (骨干网络):输入图像通过 ResNet50 提取特征,并利用 FPN(特征金字塔网络)生成多尺度特征图。
- 2.RPN (区域提议网络):在特征图上滑动,通过 Anchor 机制预测物体的存在概率和初步位置,生成高质量的 Region Proposals。
- 3.RoI Head (检测头):将 RPN 提出的候选框映射回特征图,通过 RoI Align 提取固定尺寸特征,最终输出类别标签和精确的边界框坐标。
2. 与 R-CNN 系列的对比
| 特性 | R-CNN | Fast R-CNN | Faster R-CNN |
|---|---|---|---|
| 候选框生成 | Selective Search (外部) | Selective Search (外部) | RPN (网络内部) |
| 特征提取 | 每个框单独提取 (慢) | 共享卷积特征 | 共享卷积特征 |
| 训练方式 | 多阶段 | 单阶段 (部分端到端) | 完全端到端 |
| 速度 | 极慢 | 中等 | 快 (接近实时) |
三、 环境配置与数据准备
1. 环境安装
# 推荐使用 Conda
conda create -n faster_rcnn python=3.8
conda activate faster_rcnn
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
pip install opencv-python albumentations matplotlib tqdm
2. 数据集准备 (Pascal VOC 格式)
- 目录结构:
VOCdevkit/
VOC2026/
JPEGImages/ # 存放所有图片 (.jpg)
Annotations/ # 存放所有XML标注文件 (.xml)
ImageSets/
Main/
train.txt # 训练集图片ID列表
val.txt # 验证集图片ID列表
- 数据划分:使用脚本将数据按比例(如 8:2)划分为 train.txt 和 val.txt。
- 获取 VOCtrainval_11-May-2012.tar 数据集:一种是直接从官方源下载,另一种是使用深度学习框架(如 torchvision)内置的下载工具。
- (1)使用 torchvision 自动下载:torchvision.datasets 模块内置了 VOC2012 的下载和提取逻辑,它会自动处理文件校验和解压。
import os
import torchvision
# 定义数据保存的根目录
root_dir = "./data"
os.makedirs(root_dir, exist_ok=True)
# 下载并加载 VOC2012 数据集
# download=True 表示如果本地不存在则自动下载
# 该数据集通常用于图像分割 (Segmentation) 或 目标检测 (Detection)
dataset = torchvision.datasets.VOCSegmentation(
root=root_dir,
year='2012',
image_set='train', # 可选: 'train', 'val', 'trainval', 'test'
download=True,
transform=None, # 可在此处添加数据增强
target_transform=None
)
print("数据集下载/加载完成!")
print(f"数据存放路径: {os.path.join(root_dir, 'VOCdevkit')}")
- (2)使用 requests 或 wget 直接下载原始文件
import os
import requests
def download_voc2012(save_dir="."):
url = "http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar"
filename = os.path.join(save_dir, "VOCtrainval_11-May-2012.tar")
if os.path.exists(filename):
print(f"文件已存在: {filename}")
return
print(f"开始下载 VOC2012 数据集 (约 2GB)...")
try:
response = requests.get(url, stream=True)
response.raise_for_status()
with open(filename, "wb") as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
print("下载完成!")
# 自动解压
import tarfile
print("正在解压...")
with tarfile.open(filename, 'r') as tar:
tar.extractall(path=save_dir)
print("解压完成!")
except Exception as e:
print(f"下载失败: {e}")
print("提示:如果官方源无法连接,请检查网络或尝试使用代理。")
# 执行下载
download_voc2012()
四、 核心代码实现
- 代码分为四个模块:数据集、变换、模型构建和训练器。
1. 数据集构建 (voc_dataset.py)
- 这里使用 torchvision 的 VOCDetection 并进行封装,配合 albumentations 进行增强。
import os
import torch
import numpy as np
import xml.etree.ElementTree as ET
from torch.utils.data import Dataset
from PIL import Image
from torchvision.transforms import ToTensor
class VOCDataset(Dataset):
def __init__(self, root_dir, split='train', transforms=None):
self.root_dir = root_dir
self.transforms = transforms
self.image_set_dir = os.path.join(root_dir, 'ImageSets', 'Main')
self.img_dir = os.path.join(root_dir, 'JPEGImages')
self.ann_dir = os.path.join(root_dir, 'Annotations')
# 读取图像ID列表
split_file = os.path.join(self.image_set_dir, f'{split}.txt')
with open(split_file) as f:
self.image_ids = [x.strip() for x in f.readlines()]
# 定义类别名称
self.classes = [
'background', 'aeroplane', 'bicycle', 'bird', 'boat',
'bottle', 'bus', 'car', 'cat', 'chair', 'cow',
'diningtable', 'dog', 'horse', 'motorbike', 'person',
'pottedplant', 'sheep', 'sofa', 'train', 'tvmonitor'
]
self.class_to_idx = {cls: i for i, cls in enumerate(self.classes)}
def __len__(self):
return len(self.image_ids)
def __getitem__(self, idx):
image_id = self.image_ids[idx]
img_path = os.path.join(self.img_dir, f'{image_id}.jpg')
ann_path = os.path.join(self.ann_dir, f'{image_id}.xml')
# 加载图像
img = Image.open(img_path).convert('RGB')
# 解析XML标注
boxes, labels = self._parse_voc_xml(ann_path)
# 转换为Tensor
boxes = torch.as_tensor(boxes, dtype=torch.float32)
labels = torch.as_tensor(labels, dtype=torch.int64)
# 创建目标字典
target = {
'boxes': boxes,
'labels': labels,
'image_id': torch.tensor([idx]),
'area': (boxes[:, 3] - boxes[:, 1]) * (boxes[:, 2] - boxes[:, 0]),
'iscrowd': torch.zeros_like(labels)
}
if self.transforms is not None:
transformed = self.transforms(
image=np.array(img),
bboxes=target['boxes'].numpy(),
labels=target['labels'].numpy()
)
img = Image.fromarray(transformed['image'])
target['boxes'] = torch.as_tensor(transformed['bboxes'], dtype=torch.float32)
target['labels'] = torch.as_tensor(transformed['labels'], dtype=torch.int64)
img = ToTensor()(img)
return img, target
def _parse_voc_xml(self, xml_path):
tree = ET.parse(xml_path)
root = tree.getroot()
boxes = []
labels = []
for obj in root.findall('object'):
# 获取类别标签
name = obj.find('name').text
label = self.class_to_idx[name]
# 获取边界框坐标
bbox = obj.find('bndbox')
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(label)
return boxes, labels
2. 数据增强与变换 (transforms.py)
- 重点:目标检测的增强必须同时作用于图像和边界框。
import albumentations as A
from albumentations.pytorch import ToTensorV2
def get_transform(train):
if train:
return A.Compose([
A.Resize(height=800, width=800),
A.HorizontalFlip(p=0.5),
A.RandomBrightnessContrast(p=0.2),
], bbox_params=A.BboxParams(format='pascal_voc', label_fields=['labels']))
else:
return A.Compose([
A.Resize(height=800, width=800),
], bbox_params=A.BboxParams(format='pascal_voc', label_fields=['labels']))
3. 模型构建 (model_builder.py)
- 利用 TorchVision 的预训练模型进行迁移学习。
import torchvision
from torchvision.models.detection import fasterrcnn_resnet50_fpn
from torchvision.models.detection.faster_rcnn import FastRCNNPredictor
def get_model(num_classes):
# 加载预训练模型
model = fasterrcnn_resnet50_fpn(pretrained=True)
# 获取分类器的输入特征数
in_features = model.roi_heads.box_predictor.cls_score.in_features
# 替换预训练的头部
model.roi_heads.box_predictor = FastRCNNPredictor(in_features, num_classes)
return model
4. 训练与推理引擎 (trainer.py)
- 包含训练循环、验证逻辑和预测可视化。
import torch
import torch.optim as optim
from torch.utils.data import DataLoader
from tqdm import tqdm
from voc_dataset import VOCDataset
from transforms import get_transform
from model_builder import get_model
# 定义显式的collate函数
def collate_fn(batch):
return tuple(zip(*batch))
def train_one_epoch(model, optimizer, data_loader, device, epoch):
model.train()
progress_bar = tqdm(data_loader, desc=f'Epoch {epoch}')
for images, targets in progress_bar:
images = list(image.to(device) for image in images)
targets = [{k: v.to(device) for k, v in t.items()} for t in targets]
loss_dict = model(images, targets)
losses = sum(loss for loss in loss_dict.values())
optimizer.zero_grad()
losses.backward()
optimizer.step()
progress_bar.set_postfix({'total_loss': losses.item()})
def main():
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
root_dir = './VOCtrainval_11-May-2012/VOCdevkit/VOC2012'
train_dataset = VOCDataset(root_dir, split='train', transforms=get_transform(train=True))
val_dataset = VOCDataset(root_dir, split='val', transforms=get_transform(train=False))
# 使用定义好的collate_fn
train_loader = DataLoader(
train_dataset, batch_size=4, shuffle=True, num_workers=4,
collate_fn=collate_fn # 替换lambda
)
val_loader = DataLoader(
val_dataset, batch_size=4, shuffle=False, num_workers=4,
collate_fn=collate_fn # 替换lambda
)
num_classes = 21
model = get_model(num_classes)
model.to(device)
params = [p for p in model.parameters() if p.requires_grad]
optimizer = optim.SGD(params, lr=0.005, momentum=0.9, weight_decay=0.0005)
lr_scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.1)
num_epochs = 10
for epoch in range(num_epochs):
train_one_epoch(model, optimizer, train_loader, device, epoch)
lr_scheduler.step()
torch.save(model.state_dict(), f'faster_rcnn_voc_epoch_{epoch}.pth')
print("训练完成!")
if __name__ == "__main__":
main()
5.调优
- 学习率策略:使用 CosineAnnealingLR 或 OneCycleLR 代替固定学习率,收敛更快。
- 混合精度训练:在 trainer.py 中加入 torch.cuda.amp 自动混合精度,显存占用减少一半,速度提升。
- 预训练权重:切勿从头训练。利用 COCO 预训练权重(Feature Extractor 已经学会了识别边缘、纹理、物体部件),只需微调最后的检测头。
- Anchor 匹配策略:如果检测小物体困难,可以调整 RPN 中的 IoU 阈值或修改 Anchor 的尺寸比例。
更多推荐


所有评论(0)