计算机视觉核心技术全景:从图像增广到语义分割
深度学习在计算机视觉领域已经形成了一套完整的技术栈。本文从工程实践角度,系统梳理图像分类、目标检测、语义分割、风格迁移等核心方向的关键技术,带你建立完整的知识体系。
一、图像增广:用数据”欺骗”模型
为什么需要图像增广?
模型过拟合的根本原因是训练数据不够多样。图像增广(Image Augmentation)通过对训练图像随机变换,制造出”假的新样本”,让模型学到更鲁棒的特征,而不是记住特定的像素排列。
AlexNet 的成功在很大程度上要归功于图像增广。
常用增广方法
几何变换: 翻转、随机裁剪是最常用的。左右翻转不改变物体类别,是低成本、高收益的增广手段。
import torchvisiontrain_augs = torchvision.transforms.Compose([ torchvision.transforms.RandomHorizontalFlip(), # 随机左右翻转 torchvision.transforms.RandomResizedCrop( # 随机裁剪并缩放 (200, 200), scale=(0.1, 1), ratio=(0.5, 2)), torchvision.transforms.ToTensor(),])
颜色扰动: 随机改变亮度、对比度、饱和度和色调,降低模型对光照条件的敏感性。
color_aug = torchvision.transforms.ColorJitter( brightness=0.5, contrast=0.5, saturation=0.5, hue=0.5)
组合增广: 实践中通常叠加多种方法:
augs = torchvision.transforms.Compose([ torchvision.transforms.RandomHorizontalFlip(), color_aug, shape_aug,])
关键原则
-
训练时增广,预测时不增广。 预测需要确定性结果,随机操作会导致同一张图像每次输出不同。 -
增广只在训练集上应用;测试集使用确定性预处理(Resize + CenterCrop + Normalize)。
二、迁移学习与微调:站在巨人的肩膀上
问题背景
假设你要分类 100 种椅子,每种只有 1000 张图片——这比 ImageNet 小 100 倍以上。从零训练复杂模型必然过拟合,而微调(Fine-tuning)能解决这个问题。
迁移学习的核心思想
在 ImageNet 上预训练的模型已经学会了识别边缘、纹理、形状这类通用特征,这些特征对任何视觉任务都有价值。微调就是:借用别人学到的特征提取能力,只重新训练适配自己任务的输出层。
微调四步走
-
加载在源数据集(如 ImageNet)上预训练好的模型; -
替换输出层,改为目标类别数; -
用小学习率更新原有参数; -
用大学习率从头训练新输出层。
# 加载预训练 ResNet-18finetune_net = torchvision.models.resnet18(pretrained=True)# 替换输出层为 2 类(热狗 / 非热狗)finetune_net.fc = nn.Linear(finetune_net.fc.in_features, 2)nn.init.xavier_uniform_(finetune_net.fc.weight)# 输出层学习率设为其他层的 10 倍trainer = torch.optim.SGD([ {'params': [p for n, p in finetune_net.named_parameters()if n notin ["fc.weight", "fc.bias"]]}, {'params': finetune_net.fc.parameters(), 'lr': learning_rate * 10}], lr=learning_rate, weight_decay=0.001)
效果对比
在热狗识别任务上,微调模型(测试准确率 94.3%)显著优于从零训练(85.9%),而训练速度也更快。
结论: 只要目标数据集与源数据集有相关性,微调几乎总是优于从零训练。
三、目标检测基础:边界框、锚框与 IoU
从分类到检测
图像分类只关心”图里有什么”,目标检测还要回答”在哪里”。位置信息通常用边界框(Bounding Box)表示,有两种常用格式:
-
左上-右下格式: -
中心-宽高格式:
两种格式可以互转:
defbox_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 - y1return torch.stack((cx, cy, w, h), axis=-1)
锚框:候选区域的”模板”
目标检测的核心策略是:先撒一批候选框(锚框),再对每个候选框分类和修正位置。
以每个像素为中心,按不同缩放比 和宽高比 ,生成锚框:
为控制数量,实践中只用 或 参与组合,每个像素生成 个锚框。
交并比(IoU):衡量相似度
IoU 是两个边界框相交面积与相并面积之比,取值 :
-
IoU = 0:完全不重叠 -
IoU = 1:完全重合 -
实践中通常用 IoU > 0.5 作为”正样本”阈值
非极大值抑制(NMS)
模型预测时会产生大量重叠的边界框。NMS 的逻辑:
-
按置信度降序排列所有预测框; -
取置信度最高的框 作为保留框; -
移除所有与 的 IoU 超过阈值 的框; -
对剩余框重复上述过程。
defnms(boxes, scores, iou_threshold): B = torch.argsort(scores, dim=-1, descending=True) keep = []while B.numel() > 0: i = B[0] keep.append(i)if B.numel() == 1: break iou = box_iou(boxes[i, :].reshape(-1, 4), boxes[B[1:], :].reshape(-1, 4)).reshape(-1) inds = torch.nonzero(iou <= iou_threshold).reshape(-1) B = B[inds + 1]return torch.tensor(keep, device=boxes.device)
四、多尺度检测:同时看大和小
尺度问题
一张图里可能既有小到几像素的远处行人,也有占据半张图的近处汽车。单一尺度的锚框无法兼顾。
解决方案:特征金字塔
卷积神经网络天然产生多尺度特征图:
-
浅层特征图:分辨率高,感受野小 → 擅长检测小目标 -
深层特征图:分辨率低,感受野大 → 擅长检测大目标
在不同尺度的特征图上分别生成锚框,小特征图用大锚框,大特征图用小锚框,就能覆盖各种大小的目标。
# 在 4×4 的特征图上检测小目标(锚框尺度 0.15)display_anchors(fmap_w=4, fmap_h=4, s=[0.15])# 在 2×2 的特征图上检测中等目标(锚框尺度 0.4)display_anchors(fmap_w=2, fmap_h=2, s=[0.4])# 在 1×1 的特征图上检测大目标(锚框尺度 0.8)display_anchors(fmap_w=1, fmap_h=1, s=[0.8])
五、单发多框检测(SSD):快速目标检测
设计思路
SSD(Single Shot MultiBox Detector)将多尺度检测思想落地成一个端到端的模型:
-
基础网络(如 VGG/ResNet)提取图像特征; -
多个特征块逐步降采样,形成多尺度特征图; -
在每个尺度的特征图上并行预测:类别 + 边界框偏移量。
核心模块
类别预测层: 用 卷积,输出通道数为 ( 个锚框, 个类别含背景):
defcls_predictor(num_inputs, num_anchors, num_classes):return nn.Conv2d(num_inputs, num_anchors * (num_classes + 1), kernel_size=3, padding=1)
边界框预测层: 每个锚框预测 4 个偏移量:
defbbox_predictor(num_inputs, num_anchors):return nn.Conv2d(num_inputs, num_anchors * 4, kernel_size=3, padding=1)
降采样块: 两个 卷积 + 最大池化,高宽减半,感受野扩大:
defdown_sample_blk(in_channels, out_channels): blk = []for _ in range(2): blk.append(nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1)) blk.append(nn.BatchNorm2d(out_channels)) blk.append(nn.ReLU()) in_channels = out_channels blk.append(nn.MaxPool2d(2))return nn.Sequential(*blk)
损失函数
SSD 的损失 = 类别损失(交叉熵)+ 偏移量损失( 范数):
负样本(背景锚框)不参与偏移量损失的计算,通过掩码(mask)实现。
一个 256×256 的 TinySSD 会生成多少锚框?
六、R-CNN 系列:精度优先的两阶段检测
R-CNN:思路清晰但太慢
R-CNN(2014)将检测拆成两步:
-
用选择性搜索生成约 2000 个候选区域; -
对每个区域分别用 CNN 提取特征 → SVM 分类 + 回归框位置。
致命缺点: 2000 次独立 CNN 前向传播,速度极慢。
Fast R-CNN:共享特征图
Fast R-CNN(2015)改进:只对整张图做一次 CNN 前向传播,在输出特征图上标记各候选区域的位置,再用 RoI Pooling 将不同大小的候选区域统一为固定尺寸:
# RoI Pooling:将任意大小的 RoI 统一为 2×2torchvision.ops.roi_pool(X, rois, output_size=(2, 2), spatial_scale=0.1)
RoI Pooling 将 的区域划分为 的子网格,每格取最大值,输出形状固定为 。
Faster R-CNN:去掉选择性搜索
Faster R-CNN(2015)将候选区域生成网络化,用区域提议网络(RPN)替代选择性搜索,整个模型端到端训练。RPN 在特征图每个位置生成锚框,预测”是否含目标”和位置偏移,筛选出高质量候选区域。
Mask R-CNN:像素级精度
Mask R-CNN(2017)在 Faster R-CNN 基础上增加了像素级分割分支:
-
用RoI Align(双线性插值)替代 RoI Pooling,避免量化误差; -
额外输出每个候选区域的像素级 mask。
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
七、转置卷积:特征图的”放大镜”
为什么需要转置卷积?
普通卷积会缩小特征图(下采样)。语义分割需要输出与输入等大的像素级预测,必须把特征图放大回去(上采样)。转置卷积就是专门做这件事的。
转置卷积怎么算?
普通卷积:输入元素通过卷积核”汇聚”到输出的一个位置。转置卷积:输入的每个元素乘以卷积核,结果”广播”到输出的一个区域,所有区域叠加求和。
deftrans_conv(X, K): h, w = K.shape Y = torch.zeros((X.shape[0] + h - 1, X.shape[1] + w - 1))for i in range(X.shape[0]):for j in range(X.shape[1]): Y[i: i + h, j: j + w] += X[i, j] * Kreturn Y
对于 输入和 卷积核,输出为 :
与矩阵转置的关系
普通卷积可以用矩阵乘法表示:。转置卷积对应:。
这就是”转置卷积”名称的由来:它交换了卷积层正向传播和反向传播的方向。
# PyTorch 中使用转置卷积tconv = nn.ConvTranspose2d(1, 1, kernel_size=2, stride=2, bias=False)
步幅为 、填充为 、卷积核为 时,输出尺寸恰好是输入的 倍。
八、全卷积网络(FCN):逐像素分类
核心思路
FCN(Fully Convolutional Network,2015)用于语义分割,把每个像素都分类:
-
用预训练 CNN(如 ResNet-18)提取图像特征(特征图高宽为输入的 1/32); -
用 卷积将通道数变换为类别数; -
用转置卷积上采样 32 倍,恢复到原图尺寸。
# 截取 ResNet-18 去掉最后的分类层net = nn.Sequential(*list(pretrained_net.children())[:-2])# 添加 1×1 卷积:通道数 → 类别数net.add_module('final_conv', nn.Conv2d(512, num_classes, kernel_size=1))# 添加转置卷积:上采样 32 倍net.add_module('transpose_conv', nn.ConvTranspose2d(num_classes, num_classes, kernel_size=64, padding=16, stride=32))
双线性插值初始化
转置卷积的权重用双线性插值初始化,效果远好于随机初始化:
defbilinear_kernel(in_channels, out_channels, kernel_size): factor = (kernel_size + 1) // 2 center = factor - 1if kernel_size % 2 == 1else factor - 0.5 og = (torch.arange(kernel_size).reshape(-1, 1), torch.arange(kernel_size).reshape(1, -1)) filt = (1 - torch.abs(og[0] - center) / factor) * \ (1 - torch.abs(og[1] - center) / factor) weight = torch.zeros((in_channels, out_channels, kernel_size, kernel_size)) weight[range(in_channels), range(out_channels), :, :] = filtreturn weightW = bilinear_kernel(num_classes, num_classes, 64)net.transpose_conv.weight.data.copy_(W)
FCN 在 Pascal VOC 数据集上训练 5 轮,测试准确率可达 85%+。
九、神经风格迁移:让 AI 成为画家
问题定义
给定一张内容图像(如风景照)和一张风格图像(如梵高油画),生成一张既保留内容又具有风格的合成图像。
三种损失函数
内容损失: 合成图像与内容图像在深层特征上的均方误差:
风格损失: 用格拉姆矩阵(Gram Matrix)捕捉风格特征之间的相关性:
格拉姆矩阵的直觉: 是通道 和通道 的特征相关度,代表了”哪些纹理同时出现”——这正是风格的本质。
全变分损失: 减少高频噪点,让相邻像素更平滑:
总损失:
训练过程
风格迁移与常规训练不同:模型参数固定(预训练 VGG),被优化的是合成图像本身。
classSynthesizedImage(nn.Module):def__init__(self, img_shape, **kwargs): super().__init__(**kwargs) self.weight = nn.Parameter(torch.rand(*img_shape)) # 图像像素即参数defforward(self):return self.weight
用 Adam 优化器迭代 500 步,不断更新合成图像的像素值。
十、Kaggle 实战:模型调优的工程技巧
ImageNet 子集上的狗品种识别
在 Kaggle 的狗品种识别比赛(120 类)中,数据集是 ImageNet 子集,图像尺寸大且不统一。关键策略:
数据增广(训练时):
transform_train = torchvision.transforms.Compose([ torchvision.transforms.RandomResizedCrop(224, scale=(0.08, 1.0), ratio=(3.0/4.0, 4.0/3.0)), torchvision.transforms.RandomHorizontalFlip(), torchvision.transforms.ColorJitter(brightness=0.4, contrast=0.4, saturation=0.4), torchvision.transforms.ToTensor(), torchvision.transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])
微调策略: 冻结 ResNet-34 的所有层,只训练新增的两层全连接网络:
defget_net(devices): finetune_net = nn.Sequential() finetune_net.features = torchvision.models.resnet34(pretrained=True) finetune_net.output_new = nn.Sequential( nn.Linear(1000, 256), nn.ReLU(), nn.Linear(256, 120)) finetune_net = finetune_net.to(devices[0])for param in finetune_net.features.parameters(): param.requires_grad = False# 冻结特征提取部分return finetune_net
这种”特征提取”策略(冻结主干,只训练头部)大幅节省计算资源,在小数据集上尤为有效。
十一、技术路线图总结
输入图像 │ ├─ 图像分类 ──────────────── 微调预训练模型 │ ├─ 目标检测 │ ├─ 边界框 + 锚框 + IoU + NMS(基础组件) │ ├─ 多尺度特征(特征金字塔思想) │ ├─ SSD(单阶段,速度快) │ └─ R-CNN / Fast / Faster / Mask(两阶段,精度高) │ ├─ 语义分割 │ ├─ 转置卷积(上采样核心操作) │ └─ FCN(像素级分类) │ └─ 风格迁移 └─ 内容损失 + 风格损失(Gram矩阵)+ 全变分损失
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
小结
计算机视觉的核心技术链条非常清晰:
-
数据层面:图像增广 + 迁移学习,解决数据不足的问题; -
检测层面:锚框 + IoU + NMS 是基础,SSD 和 R-CNN 系列代表快/准两条路线; -
像素层面:转置卷积实现上采样,FCN 把分类推广到逐像素的语义分割; -
创意层面:风格迁移把优化对象从参数改为图像本身,展示了深度学习的另一面。
理解这条链条,你就掌握了计算机视觉工程实践的核心框架。