第 25 / 30 课。 前置:上一课。本篇的运行范围以实验说明和验证记录为准,构造样例不代表真实任务精度或板端性能。
为什么分割需要两类信息
分类可以把空间压缩到一个向量,分割还要为每个位置做判断。低分辨率特征有利于整合上下文,但边界细节可能丢失。DeepLab 路线使用空洞卷积等方式处理上下文,BiSeNet 路线强调细节与语义分支的协作;本课用双分支小网络观察这种分工,不复现完整论文结构。
细节支保留较高分辨率,上下文支进一步降采样再用空洞卷积。两支先对齐空间大小再拼接,最后恢复输入尺寸。上采样不能保证恢复原本丢失的信息,它需要训练数据约束。
标签插值与 logits 插值不同
离散类别掩码缩放通常用最近邻,避免生成不存在的类别编号。连续 logits 可以双线性插值,再在类别轴 argmax。若先 argmax 再插值,得到的边界可能与前一种处理不同。
脚本构造矩形前景,执行一次像素交叉熵更新并计算每类 IoU。背景可能占大多数,单看像素准确率会掩盖前景完全漏检。没有出现的类如何计入平均值,应在评测协议中写明。
实例分割增加了什么
语义分割把所有同类目标赋同一类别;实例分割还要区分各个目标身份。连通区域可在互不接触的简单前景上形成实例,但相邻同类目标粘连后,不能靠它自动恢复真实实例。
本课演示两个分离矩形的连通组件,只解释这一有限条件。实际实例分割可能需要实例掩码头、原型组合或查询结构,不能把语义掩码直接称为实例输出。
练习
让两个矩形接触,观察组件数减少;降低前景面积,比较像素准确率与前景 IoU。将输入改成奇数尺寸,确认最终 logits 仍与标签严格同形状。
配套实验与运行记录
实验:轻量语义分割与实例分割
查看完整代码:main.py
"""搭建细节与上下文双分支,理解空洞卷积、掩码插值和实例身份。"""
import cv2, numpy as np
import torch
from torch import nn
import torch.nn.functional as F
class Segmenter(nn.Module):
def __init__(self):
super().__init__()
self.detail = nn.Sequential(nn.Conv2d(3, 8, 3, 2, 1), nn.ReLU())
self.context = nn.Sequential(
nn.Conv2d(3, 8, 3, 4, 1),
nn.ReLU(),
nn.Conv2d(8, 8, 3, padding=2, dilation=2),
nn.ReLU(),
)
self.head = nn.Conv2d(16, 2, 1)
def forward(self, x):
detail = self.detail(x)
context = F.interpolate(
self.context(x),
size=detail.shape[-2:],
mode="bilinear",
align_corners=False,
)
return F.interpolate(
self.head(torch.cat([detail, context], 1)),
size=x.shape[-2:],
mode="bilinear",
align_corners=False,
)
def main():
torch.manual_seed(7)
torch.set_num_threads(2)
mask = torch.zeros(1, 31, 29, dtype=torch.long)
mask[:, 5:15, 4:12] = 1
mask[:, 20:25, 18:24] = 1
image = mask[:, None].repeat(1, 3, 1, 1).float() + torch.rand(1, 3, 31, 29) * 0.1
net = Segmenter()
opt = torch.optim.Adam(net.parameters(), lr=0.001)
logits = net(image)
assert logits.shape == (1, 2, 31, 29)
loss = F.cross_entropy(logits, mask)
opt.zero_grad()
loss.backward()
opt.step()
pred = logits.argmax(1)
for c in [0, 1]:
inter = ((pred == c) & (mask == c)).sum().item()
union = ((pred == c) | (mask == c)).sum().item()
print("class", c, "IoU", inter / union if union else None)
count, _ = cv2.connectedComponents(mask[0].numpy().astype(np.uint8))
assert count - 1 == 2
print("constructed disconnected instances", count - 1, "loss", loss.item())
if __name__ == "__main__":
main()
在项目根目录执行;下载包解压后,在解压目录执行 python3 main.py。
cd 'content/posts/AI-深度学习/嵌入式视觉模型进阶/25-轻量语义分割与实例分割/code' && python3 main.py
在本篇代码目录执行 python3 main.py。依赖安装、可选入口见包内 README.md;实际运行结果见 verification.md。下载包包含本课使用的辅助模块与样例。
参考与进一步阅读
原始论文或官方文档用于核对结构、接口和适用范围。本篇代码是围绕教学目标编写的最小实验;除明确注明的完整入口外,不视为对应论文模型的完整复现。