目标检测基础:框住物体的艺术
本教程共 60 篇 · 第 37 篇 · 更新于 2026-08-17 · 约 4 分钟阅读
本节目标:弄懂目标检测在解决什么问题,认识边界框、IoU、mAP 这些基础概念,理解两阶段与单阶段两条技术路线,并学会用 torchvision 的预训练模型跑一次检测推理。
图像分类回答的是「图里有什么」,答案是一个类别。目标检测(Object Detection)要回答两个问题:有什么,以及在哪里。它既要认出物体类别,还要用矩形框把位置标出来。这也是计算机视觉里最接地气的任务:手机相册按人脸自动归档、工厂质检挑出瑕疵零件、自动驾驶识别前车和行人,背后都是它。
分类的输出只有一个标签,检测的输出则是一串结果:图里有几个物体,就输出几个框。每个框带三样信息——边界框坐标、类别、置信度。拿安防摄像头举例:画面里走过三个人,模型输出三个框,每个框都标着「人」和它有多确定。
边界框、IoU 与 mAP
这个矩形框叫边界框(bounding box),通常用四个数表示:左上角坐标 (x0, y0) 和右下角坐标 (x1, y1)。模型对每个框还会输出置信度(confidence),代表它有多确定框里是那个类别。
怎么衡量框得准不准?看交并比(IoU,Intersection over Union):两个框的交集面积除以并集面积,结果在 0 到 1 之间。预测框和人工标注的真值框重叠越多,IoU 越接近 1。惯例是 IoU 超过 0.5 就算预测正确。
整场比赛的得分叫 mAP(mean Average Precision),按置信度排序逐框核对,再对各类别取平均。它是检测任务的标准指标。新手记住一句就够:mAP 越高,检测越准。
原始输出还要过一道后处理。置信度太低的框先扔掉;同一物体被框了好几次怎么办?用非极大值抑制(NMS,Non-Maximum Suppression):保留得分最高的框,把和它重叠太多的框全部干掉,一个物体只留一个框。这套流程非常通用:无论两阶段还是单阶段,原始框都要过一遍「按置信度过滤加 NMS」,厂商宣传的检测能力差距,很大程度就藏在这两步的调校里。
检测还有两个老大难,这里也提一句:小目标(远处的人、桌上的杯子)容易被漏掉,密集场景里框会互相打架。遇到这类问题,往往要上多尺度特征融合的改进模型。
两阶段与单阶段
检测模型分两大流派。两阶段(two-stage)派先找出「可能有物体」的候选区域,再对每个区域仔细分类。代表作 Faster R-CNN 里有个区域提议网络(RPN,Region Proposal Network),专门负责海选候选框;随后 ROI 池化把候选区域裁成统一尺寸,交给后面的网络做分类和框修正。精度高,但两步走,速度慢。
RPN 海选的候选框从哪来?答案是锚框(anchor box)。在特征图的每个位置,预设一批不同大小、不同长宽比的框模板,RPN 负责给这些模板打分和微调。把「从零画框」变成「修正模板」,难度小了一大截。两阶段的准,就准在这个先圈后猜的流程上。
单阶段(one-stage)派则一步到位。代表作 YOLO 把图像划成网格,每个格子直接预测框和类别,没有海选环节,速度飞快,适合视频监控这类实时场景。从 YOLO 第一代到现在的 YOLOv8 系列,单阶段模型越做越准,如今产品里的检测模型大多是单阶段派或它的改良版。
选型有个简单口诀:追求精度、离线运行,选 Faster R-CNN;要实时、要上边端设备,选 YOLO 系。torchvision 还内置了 fasterrcnn_mobilenet_v3_large_fpn 这样的轻量检测模型,想省算力可以试试。
两个流派也催生了一批常用数据集:PASCAL VOC 是 20 类的老牌入门数据集,COCO 有 80 类日常物体,是现在预训练模型的默认基准。
用 torchvision 跑一次检测
用 PyTorch 跑检测,比想象中简单。torchvision 内置了一批在 COCO 上预训练好的模型:
import torch
import torchvision
model = torchvision.models.detection.fasterrcnn_resnet50_fpn(weights="DEFAULT")
model.eval()
# 检测模型的输入是图片列表,不是批次张量
x = [torch.rand(3, 800, 600)]
with torch.no_grad():
pred = model(x)[0]
print(pred["boxes"]) # 预测的边界框,形状 [N, 4]
print(pred["labels"]) # 类别编号,对应 COCO 的 80 类
print(pred["scores"]) # 置信度,可以用来过滤低分框
Note老教程里常见的
pretrained=True参数已弃用,统一换成weights="DEFAULT"。另外检测模型的输入是「图片列表」——每张图里物体数量不同,框数不固定,没法拼成一个规整的批次张量。
拿到结果后,用置信度过滤掉低分框:
keep = pred["scores"] > 0.5
boxes = pred["boxes"][keep]
labels = pred["labels"][keep]
换成自己的类别
想让模型检测你自己的类别?套路是换掉分类头,其余部分继续用预训练权重。假设你的数据只有 1 类物体:
from torchvision.models.detection.faster_rcnn import FastRCNNPredictor
model = torchvision.models.detection.fasterrcnn_resnet50_fpn(weights="DEFAULT")
num_classes = 2 # 1 类物体 + 背景
in_features = model.roi_heads.box_predictor.cls_score.in_features
model.roi_heads.box_predictor = FastRCNNPredictor(in_features, num_classes)
这就是第 26 章聊过的迁移学习,小数据集也能训出不错的效果。换头之后还有两个选择:数据集小就冻结主干只训新头,数据多就整体微调,取舍原则照搬第 26 章。数据这边,自定义数据集要按约定返回一个字典,字段缺一不可:
target = {
"boxes": torch.as_tensor([[10.0, 20.0, 60.0, 80.0]], dtype=torch.float32),
"labels": torch.tensor([1], dtype=torch.int64), # 0 永远留给背景
"image_id": 0,
"area": torch.tensor([3000.0]),
"iscrowd": torch.tensor([0], dtype=torch.int64),
}
字段含义不难猜:boxes 是框坐标,labels 是类别,image_id 是图片编号,area 是面积,iscrowd 标记拥挤场景。多给一个 masks 字段就能训练实例分割,多给一个 keypoints 就能做关键点检测,同一套接口通吃。
Tip这里有两个高频坑。一是类别编号:0 代表背景,自己的类别从 1 开始数。二是框的格式:[x0, y0, x1, y1] 是左上角和右下角,不是中心点加宽高,搞反了训练会原地爆炸。
到这里,检测的地图已经铺开:概念、两条路线、推理代码、数据格式都有了。真上手时,建议先从 COCO 上的小图玩起,看看预测框效果,再动手改自己的数据,最后才谈训练。
最后预告两个近亲任务。实例分割(instance segmentation)在检测的基础上,再给每个物体描出精确轮廓——Mask R-CNN 就是在 Faster R-CNN 上加了一个画掩码的分支。语义分割(semantic segmentation)则给每个像素贴类别标签,不区分个体。下一章我们就聊聊它。