文章目录
-
- [01 本篇要解决什么问题](#01 本篇要解决什么问题)
- [02 背景:检测输出的是一组目标](#02 背景:检测输出的是一组目标)
- [03 实操:把图像和标注送进检测模型](#03 实操:把图像和标注送进检测模型)
-
- (1)下载数据与评价依赖
- (2)从实例掩膜生成边界框
- (3)一批图片用list组织
- [(4)用较轻的Faster R-CNN版本开始](#(4)用较轻的Faster R-CNN版本开始)
- (5)训练时模型直接返回损失字典
- (6)AP、IoU与显示阈值要分清
- [04 一个具体例子:微调并检测一张新图片](#04 一个具体例子:微调并检测一张新图片)
- [05 常见问题与自测](#05 常见问题与自测)
- [06 本篇回顾与下一步](#06 本篇回顾与下一步)
01 本篇要解决什么问题
前五个项目给整张图片预测一个类别。现在一张街景图里可能有一个行人,也可能有好几个,我们需要同时回答:哪里有人,每个人的位置在哪里?
这就进入目标检测。本篇从Penn-Fudan的标注掩膜生成边界框,微调Faster R-CNN,最后把预测框画回原图,并用COCO评价工具检查定位效果。
- 本篇关键词:目标检测、边界框、RPN、FPN、NMS、COCO AP。
- 完成标志:完成数据下载、标注转换、微调、验证选模型与单图检测。
- 运行约定 :将配套
examples完整放到练习根目录,所有单行命令从根目录执行,PowerShell与Bash均适用。默认支持CPU,小规模跑通后可用GPU继续训练。
02 背景:检测输出的是一组目标
Penn-Fudan发布页介绍为170张图片和345个标注行人,压缩包约51 MB。需要留意版本差别:当前下载包的readme.txt说明补标了较小或严重遮挡的行人,added-object-list.txt列出78个新增目标。本次逐张统计实际掩膜共有423个实例,工程使用下载包中的全部实例,不按网页介绍截成345个。
每张图片至少有一个行人,同时提供实例掩膜,适合从检测进一步过渡到第19篇的实例分割。这个例子也提醒我们:数据集简介、压缩包说明和实际标签,需要一起核对。
本专栏按排序后的文件名、固定seed重新划分120张训练、25张验证、25张测试。这是项目自己的教学划分,不能称为数据集官方划分。实际文件名会保存在split_manifest.json中,第19篇沿用同一规则。
一张图的输出不再固定只有一个标签,而是一组框、类别和分数。即使我们只检测"行人"一个前景类别,一张图的目标数量仍然可以变化。
03 实操:把图像和标注送进检测模型
(1)下载数据与评价依赖
按第03篇准备相互匹配的torch与torchvision,再安装本批基础依赖:
powershell
python -m pip install numpy pillow pycocotools truststore
python examples/download_data.py --dataset penn
pycocotools用于标准COCO评价,truststore用于下载时使用系统可信证书。下载脚本保持TLS校验,检查ZIP完整性并记录实际SHA256;这个记录是本次下载文件的指纹,不冒充发布方预先提供的校验值。
(2)从实例掩膜生成边界框
Penn-Fudan标注中,0表示背景,不同非零数值区分同一张图里的行人实例。代码先提取每个实例的二值掩膜,再找到像素范围。
python
import numpy as np
import torch
def masks_to_target(raw,index):
ids=np.unique(raw)
ids=ids[ids!=0]
h,w=raw.shape
masks=(raw[None,:,:]==ids[:,None,None]).astype(np.uint8)
boxes=[]
for mask in masks:
ys,xs=np.nonzero(mask)
boxes.append([xs.min(),ys.min(),xs.max()+1,ys.max()+1])
return {"boxes":torch.tensor(np.asarray(boxes,dtype=np.float32).reshape(-1,4)),
"labels":torch.ones(len(ids),dtype=torch.int64),
"masks":torch.tensor(masks.reshape(-1,h,w),dtype=torch.uint8),
"image_id":torch.tensor(index,dtype=torch.int64),
"area":torch.tensor(masks.sum((1,2)),dtype=torch.float32),
"iscrowd":torch.zeros(len(ids),dtype=torch.int64)}
边界框采用[x1,y1,x2,y2],本版把最大像素坐标加1,得到右下边界不包含在内的表示,框宽高分别为x2-x1、y2-y1。对应模型约定见Faster R-CNN输入接口。
| 字段 | 形状与类型 | 含义 |
|---|---|---|
| boxes | [M,4] float32 |
M个目标框 |
| labels | [M] int64 |
本项目所有行人均为1 |
| masks | [M,H,W] uint8 |
各实例独立掩膜,供第19篇复用 |
| image_id | 整数Tensor | 图片标识,与前景类别不同 |
| area、iscrowd | [M] |
评价元数据 |
检测模型将0保留给背景,因此分类头有两类:背景和行人。不要照搬上一组二分类的"前景编号0、1"写法,给真实行人标成背景。
(3)一批图片用list组织
图片尺寸与每张图的目标数都可能不同,不能直接套用普通分类中的堆叠方式。自定义整理函数保留图片列表和标签字典列表:
python
def collate_detection(batch):
images,targets=zip(*batch)
return list(images),list(targets)
输入图片为[3,H,W]、0~1浮点Tensor。模型内部会进行标准化和尺寸处理,本篇不在外部再次套用分类模型的Normalize。
训练的随机水平翻转同时作用于图像和原始掩膜,随后重新计算框,确保框与目标一起移动。
(4)用较轻的Faster R-CNN版本开始
Faster R-CNN先提取特征,由RPN提出候选区域,再进行区域类别判断与位置回归,原始思路见Faster R-CNN论文。FPN为不同尺度提供特征,NMS帮助去除重复候选。
本篇选用MobileNetV3-Large-320-FPN版本,加载官方COCO预训练状态,再将分类预测头替换为背景与行人两类,见模型官方说明。
python
from dense_models import build_model
spec = {"task": "detection", "image_size": 320}
model = build_model(spec, pretrained=True)
320表示模型内部缩放的短边目标,长边上限为640,并不是强制把图片拉成320×320。推理返回的框会映射回输入原图坐标。
工程将主干归一化统一为FrozenBatchNorm,保证预训练加载与离线权重重建使用相同模块。训练时更新卷积和预测头参数,FrozenBatchNorm的统计与仿射状态保持固定。
(5)训练时模型直接返回损失字典
python
optimizer.zero_grad(set_to_none=True)
losses = model(images, targets)
loss = sum(losses.values())
loss.backward()
optimizer.step()
其中包括分类、框回归、RPN目标性和RPN框回归等部分。这里不需要再在外面手写一个整图CrossEntropyLoss。
验证时切到eval(),模型返回预测,不返回同一种训练损失字典。本篇按验证集bbox AP最高选择权重,分数并列保留较早轮次。
(6)AP、IoU与显示阈值要分清
IoU衡量两个区域的交并比;AP还需要结合预测分数排序、与真值的一对一匹配以及Precision-Recall曲线。本篇使用COCO官方评价实现,报告AP@0.50:0.95、AP50、AP75和AR等指标。
这是单前景类别任务;AP50不能解释为"分类准确率"。没有对应尺寸的真值时,工具返回的无定义项会写为null,不伪装成0。
图上默认只显示score≥0.5的预测。定量评价使用模型返回的全部结果,不先套这个显示阈值。模型保留最多100个最终目标,COCO按maxDets=1、10、100评价;候选数量等教学设置保存在代码中。

图:本篇原创示意。实例编号、类别编号和边界框承担不同作用。
04 一个具体例子:微调并检测一张新图片
小规模流程检查:
powershell
python examples/train_detection.py --epochs 1 --image-size 128 --limit-train 2 --limit-val 2 --limit-test 2
python examples/visualize_run.py --run artifacts/17_detection
128是为检查流程而降低的内部短边。使用完整教学划分时,可运行:
powershell
python examples/train_detection.py --device cuda --epochs 5 --output artifacts/17_detection_full
没有CUDA时改用CPU;三个limit参数默认为0,表示完整相应划分。--scratch表示随机初始化,不能将其结果称为COCO预训练微调。
主入口如下,完整训练与评价模块见配套工程和公共代码附录。
python
"""Chapter 17: Faster R-CNN pedestrian detection on Penn-Fudan."""
from dense_train import parser_for,train_project
def main():
parser=parser_for("detection","artifacts/17_detection")
args=parser.parse_args()
train_project("detection",args)
if __name__=="__main__":
main()
准备一张行人图片到指定位置后执行:
powershell
python examples/infer.py --checkpoint artifacts/17_detection/best.pt --image data/my_street.png --output artifacts/17_predict
程序输出原图坐标下的检测框JSON和叠加图。训练目录还保存实际划分、逐轮各项损失、最优权重、COCO指标及测试预测。短训练只能确认工程跑通,效果结论应依据更充分的实验。
05 常见问题与自测
问题1:只有行人一类,为什么分类头输出两类?
本接口把0留给背景,真实行人为1,所以需要背景与行人两个类别。
问题2:为什么DataLoader不能直接堆叠所有标签?
不同图片的尺寸和目标数可能不同,需要自定义collate保留列表。
问题3:翻转图像后,框跑到另一边怎么办?
检查标签是否同步变换。本篇翻转图像和掩膜后重新生成框,保持对应关系。
问题4:训练loss下降,AP为什么不一定同步提高?
训练损失同时包含多项代理目标,AP关注定位、排序和匹配;还受到泛化与数据量影响。
问题5:调高显示阈值,会不会说明模型性能提高?
显示可能更干净,但召回也可能降低。评价应按固定协议计算,不能只看一张叠加图。
06 本篇回顾与下一步
检测项目增加了可变数量目标、几何标注和匹配评价。先把图片、框、标签和坐标系统对齐,再谈网络与超参数,排查问题会更直接。
| 中文 | 常见英文或写法 |
|---|---|
| 区域建议网络 | Region Proposal Network / RPN |
| 特征金字塔 | Feature Pyramid Network / FPN |
| 非极大值抑制 | Non-maximum Suppression / NMS |
| 平均精度 | Average Precision / AP |
下一篇进入细胞核语义分割,学习从"框在哪里"进一步走到"哪些像素属于目标"。
参考与延伸阅读