我的PaddlePaddle学习之路笔记九——使用SSD进行目标检测

在计算机视觉领域,目标检测是一项基础且关键的任务,它能够定位并识别图像中的多个目标。本文将详细介绍如何使用PaddlePaddle实现SSD(Single Shot Multibox Detector)目标检测模型,包括数据准备、模型构建、训练、评估和预测。

目录

  1. 数据准备
    - VOC数据集介绍
    - 数据下载与预处理
    - 生成图像列表

  2. 数据预处理
    - 图像尺寸调整
    - 标注信息解析
    - 数据增强(如镜像、裁剪)

  3. SSD神经网络
    - SSD原理
    - 网络结构设计(基于VGG16)
    - 损失函数与后处理

  4. 训练模型
    - 训练流程
    - 优化器设置
    - 训练日志与模型保存

  5. 评估模型
    - 测试集验证
    - mAP指标计算

  6. 预测与可视化
    - 单张图像预测
    - 结果可视化
    - 保存预测结果

1. 数据准备

VOC数据集介绍

本文使用PASCAL VOC 2007+2012数据集,包含20个目标类别(如人、动物、交通工具等)。数据集结构如下:

VOCdevkit/
├── VOC2007/
│   ├── Annotations/  # XML标注文件
│   ├── JPEGImages/   # 图像文件
│   └── ImageSets/    # 训练/测试图像列表
└── VOC2012/          # 类似结构

数据下载与预处理

  • 下载数据集
  wget http://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCtrainval_06-Nov-2007.tar
  wget http://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCtest_06-Nov-2007.tar
  wget http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar
  • 解压并合并数据集
  tar -xvf VOCtrainval_06-Nov-2007.tar
  tar -xvf VOCtest_06-Nov-2007.tar
  tar -xvf VOCtrainval_11-May-2012.tar

生成图像列表

编写prepare_voc_data.py生成训练/测试图像列表:

def prepare_filelist(devkit_dir, years, output_dir):
    trainval_list = []
    test_list = []
    for year in years:
        trainval, test = walk_dir(devkit_dir, year)
        trainval_list.extend(trainval)
        test_list.extend(test)
    random.shuffle(trainval_list)
    # 保存训练/测试列表
    with open(os.path.join(output_dir, 'trainval.txt'), 'w') as ftrainval:
        for item in trainval_list:
            ftrainval.write(item[0] + ' ' + item[1] + '\n')
    with open(os.path.join(output_dir, 'test.txt'), 'w') as ftest:
        for item in test_list:
            ftest.write(item[0] + ' ' + item[1] + '\n')

# 调用示例
prepare_filelist('VOCdevkit', ['2007', '2012'], '.')

2. 数据预处理

图像尺寸调整与归一化

  • 统一图像尺寸:将图像调整为300x300,并减去ImageNet均值(104, 117, 124)。
  • 数据增强:随机水平翻转、亮度调整等。

标注信息解析

解析XML标注文件,提取目标类别、边界框(xmin, ymin, xmax, ymax)和难度标签:

def parse_xml(label_path):
    root = xml.etree.ElementTree.parse(label_path).getroot()
    bbox_labels = []
    for obj in root.findall('object'):
        label = label_list.index(obj.find('name').text)
        difficult = float(obj.find('difficult').text)
        bbox = obj.find('bndbox')
        xmin = float(bbox.find('xmin').text) / img_width
        ymin = float(bbox.find('ymin').text) / img_height
        xmax = float(bbox.find('xmax').text) / img_width
        ymax = float(bbox.find('ymax').text) / img_height
        bbox_labels.append([label, xmin, ymin, xmax, ymax, difficult])
    return bbox_labels

3. SSD神经网络

SSD原理

SSD是一种单阶段目标检测算法,通过在多个特征图上生成不同尺度的候选框(Prior Box),直接预测边界框坐标和类别概率,实现端到端训练。其核心特点:
- 多尺度特征图:在不同分辨率的特征图上生成候选框(如conv4_3, conv7, conv8_2等)。
- 先验框(Prior Box):每个特征图单元格生成多个预设宽高比的候选框。
- 回归与分类:共享卷积层进行边界框回归和类别预测。

网络结构

基于VGG16构建SSD:
1. 基础网络:冻结VGG16前10层卷积,将全连接层(fc6, fc7)转换为卷积层。
2. 特征图生成:在conv4_3conv7conv8_2等层后添加卷积层生成候选框。
3. 先验框生成:根据特征图尺寸和预设宽高比生成候选框。

代码片段:SSD网络构建

def vgg_ssd_net(mode='train'):
    # 基础VGG16特征提取
    vgg = paddle.layer.img_conv(...)  # 卷积层堆叠
    # 添加SSD特有卷积层
    conv7 = paddle.layer.img_conv(...)  # 类似VGG16的fc6/fc7转换
    # 多尺度候选框生成
    mbox_loc = paddle.layer.img_conv(...)  # 边界框回归
    mbox_conf = paddle.layer.img_conv(...)  # 类别分类
    # 损失函数与后处理
    if mode == 'train':
        loss = paddle.layer.multibox_loss(...)  # 多框损失
    return loss, mbox_loc, mbox_conf

4. 训练模型

训练流程

  1. 优化器设置:使用Momentum优化器,学习率0.001,动量0.9,L2正则化。
  2. 训练参数:批量大小32,迭代轮次200,保存模型每10轮。
  3. 训练日志:记录训练损失和验证集mAP。

代码片段:训练主函数

def train():
    # 初始化训练器
    optimizer = paddle.optimizer.Momentum(
        momentum=0.9, learning_rate=0.001, 
        regularization=paddle.optimizer.L2Regularization(0.0005)
    )
    # 构建网络并获取损失函数
    cost, _, _ = vgg_ssd_net(mode='train')
    parameters = paddle.parameters.create(cost)
    # 加载预训练模型(可选)
    if init_model_path:
        parameters.init_from_tar(gzip.open(init_model_path))
    # 创建训练器
    trainer = paddle.trainer.SGD(cost=cost, parameters=parameters, 
                                 update_equation=optimizer)
    # 训练主循环
    trainer.train(
        reader=train_reader,
        event_handler=event_handler,
        num_passes=200,
        batch_size=32
    )

5. 评估模型

测试集验证

使用PaddlePaddle的test接口在验证集上评估:

def evaluate():
    # 加载训练好的模型
    cost, _, _ = vgg_ssd_net(mode='eval')
    parameters = paddle.parameters.Parameters.from_tar(gzip.open(model_path))
    # 测试集读取
    test_reader = paddle.batch(test_data, batch_size=32)
    # 计算mAP
    result = trainer.test(reader=test_reader, metrics=['detection_map'])
    print("Test mAP: ", result.metrics['detection_map'])

6. 预测与可视化

单张图像预测

加载模型并对单张图像进行预测:

def predict(image_path):
    # 读取图像
    img = Image.open(image_path).resize((300, 300))
    # 预处理
    img = np.array(img, dtype='float32') - [104, 117, 124]
    # 加载模型
    inferer = paddle.inference.Inference(output_layer=det_out, parameters=params)
    result = inferer.infer(input=[img])
    # 解析结果(类别、边界框、得分)
    return parse_detection_result(result)

结果可视化

使用OpenCV在图像上绘制边界框:

def visualize(img_path, result):
    img = cv2.imread(img_path)
    for box in result:
        label, score, xmin, ymin, xmax, ymax = box
        xmin, ymin, xmax, ymax = int(xmin), int(ymin), int(xmax), int(ymax)
        cv2.rectangle(img, (xmin, ymin), (xmax, ymax), (0, 255, 0), 2)
        cv2.putText(img, f"{label}:{score:.2f}", (xmin, ymin-5), 
                    cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)
    cv2.imwrite('result.jpg', img)

项目代码

参考资料

  1. PaddlePaddle官方文档
  2. SSD论文
  3. VOC数据集

通过以上步骤,可实现一个基于PaddlePaddle的SSD目标检测系统,涵盖数据准备、模型构建、训练到预测的全流程。训练完成后,模型可高效检测图像中的多个目标,适用于实时检测场景。

Xiaoye