1. 语义分割:让计算机看懂世界的“像素级”地图
想象一下,你给计算机看一张街景照片,它不仅能认出里面有汽车、行人、树木,还能精确地用不同颜色把每一辆汽车、每一个行人、每一棵树都勾勒出来,生成一张详细的“成分说明书”。这就是语义分割在做的事情——像素级的图像理解。
我刚开始接触这个领域时,觉得目标检测(画个框框出物体)已经很酷了,但语义分割更让我着迷。它不再满足于“这里有个东西”,而是追求“这个东西的每一个像素都属于这里”。这就像是把一张照片变成了一个所有物体都被精确标注的矢量地图,对于自动驾驶汽车理解路况、医疗影像分析病灶区域、甚至是我们手机里“一键换天”的修图功能,都至关重要。
简单来说,语义分割的任务就是为输入图像中的每一个像素点分配一个语义类别标签。比如,在自动驾驶场景中,算法需要把每个像素归类为“道路”、“车辆”、“行人”、“天空”、“建筑物”等。这听起来简单,但实际操作起来挑战巨大。物体有不同大小、形状,会相互遮挡,光照条件也千变万化。早期的传统方法,比如靠人工设计规则来分割灰度图像,或者用条件随机场(CRF) 来建模像素间的关系,效果非常有限,难以处理复杂的真实场景。
转折点出现在深度学习,尤其是卷积神经网络(CNN)的爆发。当研究者们开始思考“能不能用CNN来干这个精细活”时,整个领域被点燃了。从2015年那个划时代的模型开始,语义分割进入了一个高速发展的黄金时期。接下来,我就带你一起回顾这段激动人心的技术演进史,从开山鼻祖到如今的性能王者,看看它们是怎么一步步解决难题,并分享一些我实际调参、训练中踩过的坑和实战经验。
2. 开天辟地:FCN与编码器-解码器架构的诞生
2.1 FCN:全卷积网络的革命
时间回到2015年,UC Berkeley的团队在CVPR上发表了《Fully Convolutional Networks for Semantic Segmentation》。这篇论文现在看几乎是语义分割的“圣经”,它做了三件开创性的事情,彻底改变了游戏规则。
第一,扔掉全连接层,实现端到端像素预测。 在这之前,主流的CNN(比如在ImageNet上大杀四方的AlexNet、VGG)最后都带有全连接层,这会把特征图“拍扁”成一个固定长度的向量,用于图像级别的分类(比如判断整张图是猫还是狗)。但分割需要像素级的输出,尺寸要对得上。FCN的天才之处在于,它把网络最后的全连接层全部替换成了卷积层。你可以理解为,把原本用来做“全局总结”的层,变成了一个能在特征图上“滑动观察”的层。这样,无论输入图片多大,网络都能输出一个二维的、空间结构对应的分割图。
第二,使用反卷积进行上采样。 经过多次卷积和池化后,特征图尺寸变得很小(比如原图224x224,最后可能只剩7x7),丢失了大量细节。FCN引入了转置卷积(Transposed Convolution),也有人叫它反卷积(Deconvolution)。它的作用就像是“放大镜”,可以把小尺寸的高维特征图,逐步上采样回原始图像的尺寸。我最初理解反卷积时,喜欢把它想象成卷积的“逆过程”:卷积是拿着小窗口在输入图上滑动做加权求和,得到一个小一点的输出;反卷积则是把这个过程反过来,根据一个小输入,去“填充”出一个更大的输出。
第三,引入跳跃连接(Skip Connections)。 这是FCN另一个点睛之笔。直接上采样回去的结果很粗糙,边界模糊。为什么呢?因为深层特征语义信息强(知道“这是一辆车”),但位置细节差;浅层特征位置信息准(知道边缘在哪),但语义性弱。FCN创造性地将深层特征上采样后,与来自网络前期的、尺寸相同的浅层特征直接相加(融合)。这就像是在画一幅画:先用深层的理解勾勒出物体的大致轮廓和内容(这是车,那是树),然后再用浅层的细节去描绘精确的边缘和纹理。这个操作显著改善了分割的精细度。
在实际用PyTorch或TensorFlow复现FCN时,我建议你重点关注跳跃连接融合的部分。不同层的特征通道数不同,通常需要用1x1卷积先进行通道调整,再进行相加或拼接。下面是一个简化的代码示意,展示了如何融合浅层特征low_level_feat和深层特征high_level_feat:
import torch
import torch.nn as nn
import torch.nn.functional as F
class FCNHead(nn.Module):
def __init__(self, in_channels, out_channels):
super().__init__()
# 用1x1卷积将深层特征的通道数调整到与浅层特征一致或目标类别数
self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=1)
# 上采样层,放大倍数需要根据网络结构计算
self.up_sample = nn.ConvTranspose2d(out_channels, out_channels, kernel_size=4, stride=2, padding=1)
def forward(self, high_level_feat, low_level_feat):
# 处理深层特征
x = self.conv(high_level_feat)
x = self.up_sample(x)
# 调整浅层特征通道数(如果需要)
low_level_feat = nn.Conv2d(low_level_feat.size(1), x.size(1), kernel_size=1)(low_level_feat)
# 特征融合(要求空间尺寸H, W一致)
x = x + low_level_feat
# 最后再用一次反卷积上采样到原图尺寸
x = self.up_sample(x)
return x
FCN家族有F


1111

被折叠的 条评论
为什么被折叠?



