2、图像基础回顾:像素、通道、卷积操作初探,为语义分割打下数学基础

好,咱们正式开始。在进入语义分割这个领域之前,我建议先花点时间把图像的基础知识捋一遍。你可能会觉得「像素、通道这些我早就会了」,但说实话,很多人在做分割任务时翻车,恰恰就是栽在这些基础概念上。

我记得刚入行那会儿,接手一个道路分割项目。模型跑出来效果奇差,路面上全是噪点。我排查了两天,最后发现是输入图像的通道顺序搞反了——模型用的是 RGB,我喂进去的是 BGR。嗯,这种低级错误,犯过一次就再也不会忘了。

2.1 像素:图像的最小单元

一张图像,说白了就是一个二维数组。数组里的每个元素就是一个像素。每个像素存储的是亮度信息,数值范围通常是 0 到 255。

你想想看,一张 640×480 的灰度图,其实就是 640 列、480 行的矩阵。每个格子里的数字,代表那个位置的明暗程度。0 是纯黑,255 是纯白,中间值就是各种灰色。

核心理解:语义分割的本质,就是对每个像素做分类。所以理解像素,就是理解分割的最小操作单元。

我在项目中遇到过一个问题:有些同学把像素值直接除以 255 归一化到 [0,1] 之后,就以为万事大吉了。其实归一化只是第一步,更重要的是理解像素值的分布。比如道路场景中,阴影区域的像素值普遍偏低,如果不做预处理,模型很容易把阴影误判成路面裂缝。

2.2 通道:图像的深度维度

灰度图只有一个通道,我们叫它单通道图像。彩色图就不一样了,它有三个通道:R(红)、G(绿)、B(蓝)。

你可以把通道想象成三张叠在一起的灰度图。每张图只记录一种颜色的亮度信息。计算机看到一张彩色图,其实是看到了三个独立的矩阵。

图像类型 通道数 数据形状示例 (H×W×C) 常见用途
灰度图 1 480×640×1 老照片、医学影像
RGB 彩色图 3 480×640×3 日常照片、道路场景
RGBA 图 4 480×640×4 带透明通道的图标
多光谱图 N 480×640×N 遥感、卫星图像

这里有个坑,我必须要提醒你。很多深度学习框架(比如 PyTorch)默认的通道顺序是 CHW(通道、高、宽),而 OpenCV 读取的图像是 HWC(高、宽、通道)。如果不做转换,模型直接炸掉。

避坑指南:我曾经因为通道顺序搞错,调试了整整一个下午。建议你在数据加载阶段就统一格式,用 torchvision.transforms.ToTensor() 自动帮你把 HWC 转成 CHW,顺便归一化到 [0,1]。

2.3 卷积操作初探:滑动窗口的数学魔法

卷积是语义分割的基石。没有卷积,就没有深度学习时代的图像理解。

卷积操作,说白了就是一个「滑动窗口」在图像上移动,每次计算窗口内像素与卷积核的加权和。这个卷积核,也叫滤波器,是一个小矩阵。

举个例子,一个 3×3 的卷积核,在 5×5 的图像上滑动。每次覆盖 3×3 的区域,做逐元素相乘再求和,得到一个输出值。滑完整个图像,就得到一张新的特征图。

# 一个简单的 3x3 卷积核示例(边缘检测)
import numpy as np

# 输入图像(假设是 5x5 的灰度图)
image = np.array([
    [10, 10, 10, 10, 10],
    [10, 10, 10, 10, 10],
    [10, 10, 10, 10, 10],
    [10, 10, 10, 10, 10],
    [10, 10, 10, 10, 10]
], dtype=np.float32)

# 垂直边缘检测核
kernel = np.array([
    [-1, 0, 1],
    [-1, 0, 1],
    [-1, 0, 1]
], dtype=np.float32)

# 手动卷积(步长=1,无填充)
output_height = image.shape[0] - kernel.shape[0] + 1
output_width = image.shape[1] - kernel.shape[1] + 1
output = np.zeros((output_height, output_width))

for i in range(output_height):
    for j in range(output_width):
        region = image[i:i+3, j:j+3]
        output[i, j] = np.sum(region * kernel)

print("卷积结果:")
print(output)

你看,这个例子中图像全是 10,没有任何变化,所以卷积结果全是 0。如果图像中有垂直方向的亮度突变,卷积结果就会在突变位置产生高响应。这就是边缘检测的原理。

个人经验:我建议初学者亲手写一次这种手动卷积的代码。虽然现在框架都封装好了,但手写一遍能让你真正理解「感受野」和「特征提取」是怎么回事。我在带团队时,新来的实习生我都会让他先手写一个卷积层,过了这关再碰模型。

2.4 卷积在语义分割中的角色

语义分割模型(比如 U-Net、DeepLab)的核心,就是堆叠卷积层来提取多尺度特征。浅层卷积捕捉边缘、纹理等细节信息,深层卷积捕捉物体、场景等语义信息。

为什么会这样?因为浅层卷积的感受野小,只能看到局部;深层卷积经过多次下采样,感受野变大,能看到全局。分割任务需要同时保留细节和语义,所以就有了「编码器-解码器」结构——编码器下采样提取语义,解码器上采样恢复细节。

嗯,这里要注意一点:卷积操作会改变特征图的空间尺寸。如果不做填充(padding),每卷积一次,图像就会缩小一圈。在分割任务中,我们通常希望输出和输入尺寸一致,所以会用 same padding(填充后尺寸不变)。

关键公式:输出尺寸 = (输入尺寸 - 卷积核尺寸 + 2×填充) / 步长 + 1

当填充 = (卷积核尺寸 - 1) / 2 且步长 = 1 时,输出尺寸 = 输入尺寸。

2.5 从像素到语义:一条清晰的路径

咱们回顾一下今天的核心脉络:

  • 像素是图像的最小单元,语义分割就是对每个像素做分类。
  • 通道是图像的深度维度,RGB 三通道是道路场景的标准输入。
  • 卷积是特征提取的基本操作,通过滑动窗口计算加权和。

这三者串起来,就是语义分割的数学基础。你想想看,一个分割模型无非就是:输入一张 H×W×3 的彩色图,经过一堆卷积层,输出一张 H×W×C 的分割图(C 是类别数)。每个像素点上的最大值对应的类别,就是那个像素的预测标签。

我在做自动驾驶道路分割时,经常跟团队说一句话:「别把分割想得太玄乎,它就是一个像素级的分类问题。你把每个像素当成一个独立样本,问题就简单了。」当然,实际做起来还有很多细节,比如类别不平衡、边界模糊、小目标漏检等等。这些咱们后面章节慢慢聊。

下一章,咱们正式进入语义分割的核心概念——感受野、特征图、上采样与下采样。这些是理解 U-Net、DeepLab 等经典模型的前提。


公众号:蓝海资料掘金营,微信deep3321