《动手学深度学习(第二版)》学习笔记之 13. 计算机视觉

2025-12-31

本章开头将介绍两种可以改进模型泛化的方法,即图像增广和微调,并将它们应用于图像分类。秉承计算机视觉中利用分层表示的关键思想,我们将从物体检测的主要组件和技术开始,继而展示如何使用完全卷积网络对图像进行语义分割,然后我们将解释如何使用样式迁移技术来生成像本书封面一样的图像

13.1. 图像增广

图像增广:对训练图像进行随机变化生成相似但不同的样本,扩大训练集规模,减少模型对特定属性的依赖,提升泛化能力

13.1.1. 常用的图像增广方法

  1. 翻转和裁剪
    • RandomHorizontalFlip:随机左右翻转
    • RandomVerticalFlip:随机上下翻转
    • RandomResizedCrop:随机裁剪
  2. 改变颜色
    • ColorJitter:亮度、对比度、饱和度、色调调整
  3. 组合增广
    • Compose:组合多种增广方法

13.1.2. 使用图像增广进行训练

通常仅对训练样本应用带随机操作的增广

  • 数据处理:用 ToTensor 转换为(批量大小,通道数,高度,宽度)的 32 位浮点数(0~1)

  • 数据加载:
    • 训练集:torchvision.datasets.CIFAR10 + 训练增广 train_augs
    • 测试集:torchvision.datasets.CIFAR10 + 测试增广 test_augs
    • DataLoader 加载数据
  • 模型训练:
    • 采用 ResNet-18 模型
    • 多 GPU 训练:nn.DataParallel
    • 优化器:Adam
    • 损失函数:CrossEntropyLoss

13.2. 微调

迁移学习(transfer learning):将从源数据集学到的知识迁移到目标数据集

微调(fine-tuning):迁移学习的一种常见技术,适用于目标数据集小于源数据集的场景

13.2.1. 步骤

  1. 在源数据集上预训练源模型
  2. 构建目标模型:复制源模型除输出层外的所有设计和参数
  3. 为目标模型添加新输出层(输出数为目标数据集类别数),随机初始化其参数
  4. 在目标数据集上训练:输出层从头训练,其他层基于源模型参数微调

13.2.2. 实现要点

  • 数据预处理:
    • 训练:RandomResizedCropRandomHorizontalFlip、标准化
    • 测试:ResizeCenterCrop、标准化
  • 模型初始化:
    • 加载预训练模型(如 resnet18(pretrained=True)
    • 替换输出层:fc = nn.Linear(in_features, 目标类别数),用 Xavier 初始化新层权重
  • 训练配置:
    • 预训练层用较小学习率,新输出层用较大学习率(如 10 倍)
    • 优化器:SGD,带权重衰减(weight_decay=0.001
  • 关键对比:
    • 微调模型:利用预训练参数,收敛更快,效果更好
    • 从头训练:所有参数随机初始化,需更大学习率,效果通常较差

13.3. 目标检测和边界框

目标检测(object detection):识别图像中多个感兴趣目标的类别及其位置

13.3.1. 边界框

边界框(bounding box):描述对象空间位置的矩形,有两种表示方法

  • 两角表示法:矩形左上角和右下角坐标

  • 中心宽高表示法:中心坐标及宽、高

转换函数:

# 从(左上,右下)转换到(中心,宽度,高度)
def box_corner_to_center(boxes):
    x1, y1, x2, y2 = boxes[:, 0], boxes[:, 1], boxes[:, 2], boxes[:, 3]
    cx = (x1 + x2) / 2
    cy = (y1 + y2) / 2
    w = x2 - x1
    h = y2 - y1
    boxes = torch.stack((cx, cy, w, h), axis=-1)
    return boxes

# 从(中心,宽度,高度)转换到(左上,右下)
def box_center_to_corner(boxes):
    cx, cy, w, h = boxes[:, 0], boxes[:, 1], boxes[:, 2], boxes[:, 3]
    x1 = cx - 0.5 * w
    y1 = cy - 0.5 * h
    x2 = cx + 0.5 * w
    y2 = cy + 0.5 * h
    boxes = torch.stack((x1, y1, x2, y2), axis=-1)
    return boxes

图像坐标原点为左上角,向右为 $x$ 轴正方向,向下为 $y$ 轴正方向

绘图辅助函数:

def bbox_to_rect(bbox, color):
    return d2l.plt.Rectangle(
        xy=(bbox[0], bbox[1]), width=bbox[2]-bbox[0], height=bbox[3]-bbox[1],
        fill=False, edgecolor=color, linewidth=2)

13.4. 锚框

锚框(anchor box):预先定义在输入图像上的一系列固定大小和比例的矩形框,用于检测不同大小和形状的目标

13.4.1. 锚框的生成

以每个像素为中心生成多个不同缩放比(scale)宽高比(aspect ratio)的锚框

生成公式:设缩放比为 $s$,宽高比为 $r$,则锚框的宽和高分别是 $ws\sqrt{r}$ 和 $hs/\sqrt{r}$

13.4.2. 标注和预测锚框

交并比(IoU):两个边界框相交面积与相并面积之比

类别(class):预测每个锚框包含目标的类别

偏移量(offset):预测锚框与目标边界框的位置偏差

13.4.3. 非极大值抑制(NMS)

目的:去除重叠度高的冗余预测框

步骤:将预测框按置信度降序排序,从置信度最高的框开始,依次作为基准,移除与其 IoU 大于阈值的框,直至所有框都曾被用作基准

13.5. 多尺度目标检测

13.5.1. 多尺度锚框

减少锚框数量的方式:均匀采样部分像素作为锚框中心

不同尺度策略:小目标对应更多锚框,大目标对应更少锚框

特征图与锚框关系:通过定义特征图形状,确定锚框中心的均匀分布

# 生成并显示锚框的函数
def display_anchors(fmap_w, fmap_h, s):
    d2l.set_figsize()
    fmap = d2l.zeros((1, 10, fmap_h, fmap_w))  # 特征图
    anchors = d2l.multibox_prior(fmap, sizes=s, ratios=[1, 2, 0.5])  # 生成锚框
    bbox_scale = d2l.tensor((w, h, w, h))  # 边界框缩放
    d2l.show_bboxes(d2l.plt.imshow(img).axes, anchors[0] * bbox_scale)

13.5.2. 多尺度检测

特征图特性:CNN 正向传播的中间输出,同一空间位置的 $c$ 个单元具有相同感受野

预测机制:用感受野区域信息预测相近位置锚框的类别和偏移量

尺度对应关系:靠近输出层的特征图单元具有更宽的感受野,可以检测到较大的目标

13.6. 目标检测数据集

目标检测领域无类似 MNIST 的小型标准数据集,使用自定义香蕉检测数据集

包含 1000 张不同角度、大小的香蕉图像,标注边界框信息

13.6.1. 下载数据集

d2l.DATA_HUB['banana-detection'] = (
    d2l.DATA_URL + 'banana-detection.zip',
    '5de26c8fce5ccdea9f91267273464dc968d20d72')

13.6.2. 读取数据集

  1. 读取函数:read_data_bananas(is_train)
    • 读取图像和 CSV 标签文件(含类别及边界框坐标)
    • 返回图像列表和标签张量(归一化至 0~1)
    • 标签格式:(类别,左上角 x, 左上角 y, 右下角 x, 右下角 y),香蕉类别索引为 0
  2. 自定义数据集类:BananasDataset(torch.utils.data.Dataset)
    • __init__:初始化加载数据
    • __getitem__:返回 (图像张量,标签张量)
    • __len__:返回样本数量
  3. 数据加载器:load_data_bananas(batch_size)
    • 返回训练集和验证集的数据加载器
    • 训练集打乱顺序,验证集保持原顺序

图像批量形状:(批量大小、通道数、高度、宽度)

标签批量形状:(批量大小,m,5),m 为单图最大边界框数(香蕉数据集 m=1)

标签中 -1 表示填充的非法边界框

13.7. 单发多框检测(SSD)

13.7.1. 模型

SSD 是一种多尺度目标检测模型,由基础网络和多个多尺度特征块串联而成

模型组成:基础网络 + 多个多尺度特征块。基础网络(如VGG、ResNet)用于提取特征,生成高分辨率特征图以检测小物体。后续每个特征块逐步减小特征图尺寸,增大感受野以检测大物体

多尺度检测:模型在不同尺度特征图上生成不同大小的锚框,以检测不同尺寸的目标

13.7.1.1. 类别预测层

对每个锚框预测 $q+1$ 个类别(含背景)

使用 $3×3$ 卷积层,不改变特征图空间尺寸。输出通道数为 $a(q+1)$,其中 $a$ 是每个位置生成的锚框数

13.7.1.2. 边界框预测层

对每个锚框预测 4 个偏移量(中心坐标及宽高)

结构与类别预测层类似,输出通道数为 $4a$

13.7.1.3. 连结多尺度的预测

不同尺度的特征图产生的预测输出形状不同

通过调整维度(将通道维度移到最后并展平)连接来自不同尺度的预测

13.7.1.4. 高和宽减半块

用于减小特征图尺寸、增加感受野

结构:两个 $3×3$ 卷积层(含批归一化和 ReLU)+ 一个 $2×2$ 最大池化层(步幅为 2)

13.7.1.5. 基本网络块

示例:串联 3 个高和宽减半块,通道数依次为 16、32、64

输入 $256 \times 256$ 图像,输出 $32 \times 32$ 特征图

13.7.1.6. 完整的模型

包含 5 个块:1 个基本网络块 + 3 个高和宽减半块 + 1 个全局最大池化层

各块输出用于生成锚框并预测其类别和偏移量

锚框尺度由 sizesratios 列表定义,计算生成不同大小的锚框

13.7.2. 训练模型

13.7.2.1. 定义损失函数和评价函数

  • 损失函数
    • 类别损失:使用交叉熵损失
    • 边界框偏移损失:使用 $L_1$ 范数损失(绝对值损失)。仅对正类锚框计算
    • 总损失:类别损失 + 偏移损失
  • 评价指标
    • 分类准确率:预测类别与真实类别的匹配度
    • 边界框平均绝对误差(MAE):预测偏移量与真实偏移量的绝对误差均值

13.7.2.2. 训练模型

  1. 前向传播生成多尺度锚框及预测

  2. 根据标签信息为锚框标记类别和偏移量

  3. 计算损失并反向传播更新模型参数

13.7.3. 预测目标

  1. 输入图像,模型输出所有锚框的类别概率和偏移量

  2. 使用 multibox_detection 函数(结合非极大值抑制)得到最终检测框

  3. 按置信度阈值(如 0.9)筛选并显示结果

13.8. 区域卷积神经网络(R-CNN)系列

13.8.1. R-CNN

核心思想:将深度卷积网络引入目标检测,通过区域提议进行目标分类和定位

步骤:

  1. 使用选择性搜索从输入图像中提取若干(例如 2000 个)不同尺度和形状的提议区域

  2. 将每个提议区域调整为固定尺寸,通过预训练的 CNN(在输出层之前截断)进行前向传播,提取特征

  3. 使用多个支持向量机根据提取的特征对每个提议区域进行分类

  4. 使用线性回归模型对每个提议区域的边界框进行微调

缺点:计算量大,需要对每个提议区域独立运行 CNN 前向传播,速度慢

13.8.2. Fast R-CNN

改进核心:共享计算,提高效率

主要步骤:

  1. 整图特征提取:对整个输入图像进行一次 CNN 前向传播,得到特征图

  2. 兴趣区域汇聚层(RoI Pooling)
    • 将选择性搜索生成的提议区域映射到特征图上
    • RoI 池化层将不同形状的提议区域转换为固定尺寸(如 $h_2 \times w_2$)的特征,以便后续处理
  3. 全连接层:将固定尺寸的特征展平并通过全连接层

  4. 多任务输出:通过两个分支分别预测类别(使用 softmax)和边界框偏移量

优势:避免了 R-CNN 中大量的重复计算,显著提升了训练和推断速度

13.8.3. Faster R-CNN

改进核心:用区域提议网络(region proposal network)替代选择性搜索,实现端到端训练

模型结构:

  1. 共享卷积层:与 Fast R-CNN 类似,先提取整图特征

  2. 区域提议网络:
    • 在特征图的每个位置上生成多个锚框
    • 使用一个小型网络预测每个锚框是“目标”还是“背景”,并预测边界框偏移量
    • 应用非极大值抑制筛选出高质量的提议区域
  3. 后续步骤:将区域提议网络生成的提议区域送入与 Fast R-CNN 相同的 RoI 池化层及后续分类和回归头

优势:区域提议网络与检测网络共享特征,并通过端到端联合训练,能以更少的提议区域保持检测精度

13.8.4. Mask R-CNN

关键改进:

  1. 将兴趣区域汇聚层替换为了兴趣区域对齐层,使用双线性插值(bilinear interpolation)来保留特征图上的空间信息,从而更适于像素级预测

  2. 在 Faster R-CNN 的基础上引入了一个全卷积网络,从而借助目标的像素级位置进一步提升目标检测的精度

13.9. 语义分割和数据集

语义分割(semantic segmentation):将图像按语义类别分割成不同区域,实现像素级别的分类和理解

13.9.1. 对比

  • 图像分割(image segmentation)不同,语义分割有明确的语义类别标签

  • 实例分割(instance segmentation)不同,语义分割只区分语义类别,不区分实例

13.9.2. Pascal VOC2012 数据集

重要语义分割数据集之一

结构:

  • ImageSets/Segmentation:包含训练集和测试集的文件列表
  • JPEGImages:存放原始图像
  • SegmentationClass:存放对应的语义分割标签图像(颜色代表类别)

标签特点:标签图像与原始图像尺寸相同,相同颜色的像素属于同一语义类别

13.9.2.1. 数据集预处理

  1. 读取数据:同时读取图像和对应的标签图像

  2. 颜色映射与类别索引:
    • 定义 VOC_COLORMAP(含背景)和 VOC_CLASSES
    • 构建颜色到类别索引的映射函数 voc_colormap2label
    • 使用 voc_label_indices 将标签图像的 RGB 值转换为类别索引矩阵
  3. 随机裁剪:
    • 使用 voc_rand_crop 同时对图像和标签进行相同区域的随机裁剪,以保持像素级对齐
    • 避免缩放导致的像素类别映射不准确

13.9.2.2. 自定义语义分割数据集类

类名:VOCSegDataset,继承自 torch.utils.data.Dataset

关键方法:

  • __init__:初始化,读取图像和标签,进行标准化,并过滤尺寸小于裁剪尺寸的图像
  • __getitem__:返回裁剪后的图像张量和对应的类别索引矩阵

数据标准化:使用 torchvision.transforms.Normalize 对图像进行标准化处理

13.9.2.3. 数据加载

使用 torch.utils.data.DataLoader 构建数据迭代器

输出形状:

  • 图像张量:(batch_size, 3, height, width)
  • 标签张量:(batch_size, height, width),每个元素为类别索引

最终函数:load_data_voc 返回训练和测试集的数据加载器

13.10. 转置卷积

转置卷积(transposed convolution):用于上采样,增大特征图的空间尺寸(高和宽),是常规卷积的下采样操作的逆过程

13.10.1. 基本操作

输入张量尺寸:$n_h \times n_w$,卷积核尺寸:$k_h \times k_w$

步幅为 1、无填充时,输出尺寸为:$(n_h + k_h - 1) \times (n_w + k_w - 1)$

核心操作:输入中的每个元素与卷积核相乘,得到一个与卷积核同尺寸的中间张量,并将其累加到输出张量的对应位置(位置由输入元素的位置决定)。所有中间结果求和得到最终输出

与常规卷积对比:常规卷积减少输入,转置卷积广播输入,从而产生更大的输出

实现:

tconv = nn.ConvTranspose2d(in_channels, out_channels, kernel_size, ...)
  • 可通过直接设置 weight.data 来指定卷积核

13.10.2. 填充、步幅和多通道

填充:应用于输出(常规卷积应用于输入)。例如,padding=1 会去除输出的首尾行和列

步幅:指定为中间结果(即输出)的步幅,而不是输入的步幅。增大步幅会增大输出尺寸

多通道:与常规卷积工作方式相同。若输入有 $c_i$ 个通道,则为每个输入通道分配一个 $k_h \times k_w$ 的核;若指定多个输出通道,则每个输出通道对应一个 $c_i \times k_h \times k_w$ 的核张量

尺寸恢复性质:若卷积层 $f$ 将输入 $\mathsf{X}$ 映射为 $\mathsf{Y}=f(\mathsf{X})$,则创建一个与 $f$ 超参数相同(仅输出通道数改为 $\mathsf{X}$ 的通道数)的转置卷积层 $g$,可使 $g(Y)$ 的形状与 $\mathsf{X}$ 相同

13.10.3. 与矩阵变换的联系

卷积运算可通过稀疏权重矩阵 $\mathbf{W}$ 与向量化输入的矩阵乘法实现

转置卷积运算可通过转置权重矩阵 $\mathbf{W}^\top$ 与向量化输入的矩阵乘法实现

数学关系:

  • 卷积前向传播:$\mathbf{y} = \mathbf{W} \mathbf{x}$,反向传播梯度:$\nabla_{\mathbf{x}} \mathbf{y} = \mathbf{W}^\top$
  • 转置卷积层交换了卷积层的前向和反向传播函数:前向用 $\mathbf{W}^\top$,反向用 $\mathbf{W}$

13.11. 全卷积网络

全卷积网络(fully convolutional network,FCN):将输入图像的每个像素映射到输出图像对应像素的类别预测

实现方式:通过转置卷积将中间特征图的高和宽恢复至输入尺寸

13.11.1. 构造模型

  1. 特征提取:使用预训练 CNN(如 ResNet-18)提取图像特征,去除原网络的全局平均池化层和全连接层

  2. $1\times 1$ 卷积层:将通道数转换为类别数(如 Pascal VOC2012 的 21 类)

  3. 转置卷积层:将特征图的高和宽放大至输入图像尺寸

    • 示例设计:输入尺寸 320×480,经 ResNet-18 下采样 32 倍至 10×15,使用转置卷积(核 64,填充 16,步幅 32)恢复至 320×480
    • 一般规则:若转置卷积核尺寸为 $2s$,填充 $s/2$,步幅 $s$,则输出尺寸放大 $s$ 倍

13.11.2. 初始化转置卷积层

上采样(upsampling)方法:双线性插值

作用:用双线性插值核初始化转置卷积层权重,以获得更平滑的上采样效果

实现:使用 bilinear_kernel 函数生成核权重,并赋值给 transpose_conv.weight.data

其他层初始化:$1\times 1$ 卷积层使用 Xavier 初始化

13.11.3. 数据集与训练

数据集:Pascal VOC2012,使用随机裁剪固定尺寸(320×480,高和宽可被 32 整除)

损失函数:交叉熵损失,需指定通道维度(像素级分类)

准确率计算:基于所有像素的预测类别正确性

13.11.4. 预测

  1. 图像预处理:标准化并转换为四维张量(批量大小×通道×高×宽)

  2. 模型推断:输出每个像素的类别预测(argmax 沿通道维)

  3. 后处理:将预测的类别索引映射回对应颜色(使用 VOC_COLORMAP

  4. 处理任意尺寸图像:当输入高或宽不能被 32 整除时,可裁剪多个 32 倍数的区域分别预测,对重叠像素取平均

13.12. 风格迁移

13.12.1. 方法

风格迁移(style transfer):使用卷积神经网络自动将风格图像的风格应用到内容图像上。核心是通过优化合成图像,使其内容特征接近内容图像,风格特征接近风格图像,同时通过全变分损失减少噪声

13.12.2. 预处理和后处理

  • 预处理:将图像标准化,调整尺寸,转换为 CNN 输入格式

  • 后处理:将输出还原并限制像素值在 0~1 之间

13.12.3. 抽取图像特征

使用预训练的 VGG-19 网络(ImageNet)。选择特定层作为内容层(如第四卷积块最后一层)和风格层(如各卷积块第一层)

13.12.4. 定义损失函数

总损失函数由三部分加权和组成:

  1. 内容损失

    合成图像与内容图像在内容层输出的均方误差

  2. 风格损失

    合成图像与风格图像在风格层输出的 Gram 矩阵的均方误差。Gram 矩阵衡量不同通道间风格特征的相关性

  3. 全变分损失

    减少合成图像中相邻像素间的差异,起到去噪作用

13.12.5. 初始化合成图像

将合成图像定义为模型参数(SynthesizedImage 类),初始化为内容图像

13.12.6. 训练模型

不断前向传播提取合成图像的特征,计算总损失,通过反向传播更新合成图像(即模型参数)