利用DeepLab在PascalVOC数据集中实现简单物体的多类别分割
1.作者介绍
郭政,男,西安工程大学电子信息学院,2025级研究生,张宏伟人工智能课题组
研究方向:机器视觉与人工智能
电子邮件:1225301905@qq.com
作者:李逸超,西安工程大学电子信息学院,2025级研究生张宏伟人工智能课题组
研究方向:机器视觉与人工智能
联系邮箱:2317314922@qq.com
2.背景介绍
2.1 行业痛点
2014年左右,图像分割技术面临着两大困境。第一,为提取特征而进行的池化操作,会严重丢失图像细节,导致分割边缘模糊。第二,模型很难同时处理画面中大小差异巨大的物体

2.2 deeplab相关人物介绍
在这一技术痛点背景下,Google 团队推出的 DeepLab 系列模型针对性拆解化解上述难题,依靠独特的空洞卷积(扩张卷积)、空间金字塔池化等核心模块重构特征提取逻辑,从底层卷积运算、多尺度特征融合两个维度革新像素级分割范式 面对传统 CNN 分割架构受池化带来的细节永久丢失、轮廓马赛克失真、多尺度目标感知失衡这两大核心瓶颈,学界急需一套能够不粗暴压缩分辨率、灵活适配多尺度目标、精细还原物体边缘轮廓的全新分割架构思路。



3.算法原理介绍
3.1 deeplab框架
DeepLab 模型框架:输入图像经由引入空洞卷积的全卷积网络提取特征并生成分割得分图,再通过双线性插值还原分辨率,最后借助全连接条件随机场优化物体边缘,得到最终语义分割结果。

3.2 空洞卷积
普通标准卷积是网孔紧密的小渔网,抓取范围小;空洞卷积在卷积核权重元素之间插入零值空洞,相当于把渔网网孔拉大,卷积核本身参数数量不变,但单次扫描能覆盖更大的图像区域,不用池化下采样就能放大感受野。



分别为一维空洞卷积与二维空洞卷积示意图
3.3 ASPP:多尺度并行感知

3.4应用场景

3.5 技术落地公司

3.6 语义模型汇总

4 代码实现
4.1 数据集下载

4.2实验核心代码
基于 PyTorch 的 DeepLabv3+ 语义分割全流程实战
项目概述
本项目利用 PyTorch 框架,在 PASCAL VOC 2012 数据集上微调(Fine-tune)官方预训练的 DeepLabv3+ 模型,实现对 20 种常见物体的像素级语义分割。
第一步:环境配置
在一个干净的虚拟环境中安装 PyTorch 及相关依赖,避免与其他项目冲突。
-
创建并激活 Conda 虚拟环境:
Bash
conda create -n deeplab_voc python=3.10 -y
conda activate deeplab_voc
-
安装 PyTorch(推荐 CUDA 11.8 版本以保证最大兼容性)及辅助工具:
Bash
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
pip install matplotlib pillow tqdm numpy
第二步:项目结构与数据准备
合理的项目结构能极大提升开发效率,避免路径混乱。
1. 创建目录结构 在你的工作空间中创建如下结构:
Plaintext
deeplab/
├── data/ # 存放解压后的 VOCdevkit
├── weights/ # 存放训练生成的 .pth 权重文件
├── test_images/ # 存放测试图片和输出结果
├── train.py # 训练脚本
└── predict.py # 推理脚本
2. 数据集处理 为了避免跨国下载限速和网络中断导致的坏包问题,采用手动下载与解压的方式:
-
通过国内镜像源(如 OpenDataLab)下载
VOCtrainval_11-May-2012.tar压缩包。 -
将压缩包放入
data/目录下,并手动解压提取出VOCdevkit文件夹。 -
重要提示: 解压完成后,务必删除残留的
.tar压缩包,防止 PyTorch 数据加载器误判。
第三步:编写与运行训练脚本
创建 train.py。该脚本负责加载本地数据集、替换模型分类头、定义交叉熵损失(忽略背景边界像素 255)并执行训练循环。
完整 train.py 代码:
Python
import os
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision.datasets import VOCSegmentation
from torchvision.transforms import v2
from torchvision.models.segmentation import deeplabv3_resnet50, DeepLabV3_ResNet50_Weights
from tqdm import tqdm
def main():
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"Using device: {device}")
# 动态获取当前脚本所在目录,确保路径绝对正确
base_dir = os.path.dirname(os.path.abspath(__file__))
os.makedirs(os.path.join(base_dir, 'weights'), exist_ok=True)
transforms = v2.Compose([
v2.Resize((512, 512)),
v2.ToImage(),
v2.ToDtype(torch.float32, scale=True),
v2.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
target_transforms = v2.Compose([
v2.Resize((512, 512), interpolation=v2.InterpolationMode.NEAREST),
v2.ToImage(),
v2.ToDtype(torch.long, scale=False)
])
print("Loading VOC2012 Dataset from local directory...")
data_dir = os.path.join(base_dir, 'data')
train_dataset = VOCSegmentation(
root=data_dir,
year='2012',
image_set='train',
download=False, # 禁止联网下载,直接读本地数据
transform=transforms,
target_transform=target_transforms
)
train_loader = DataLoader(train_dataset, batch_size=4, shuffle=True, num_workers=4)
print("Initializing DeepLabv3+ Model...")
weights = DeepLabV3_ResNet50_Weights.DEFAULT
model = deeplabv3_resnet50(weights=weights)
# 修改输出通道数为 21 (20类物体 + 1类背景)
model.classifier[4] = nn.Conv2d(256, 21, kernel_size=(1, 1), stride=(1, 1))
model.aux_classifier[4] = nn.Conv2d(256, 21, kernel_size=(1, 1), stride=(1, 1))
model = model.to(device)
criterion = nn.CrossEntropyLoss(ignore_index=255)
optimizer = optim.SGD([
{'params': model.backbone.parameters(), 'lr': 0.001},
{'params': model.classifier.parameters(), 'lr': 0.01},
{'params': model.aux_classifier.parameters(), 'lr': 0.01}
], momentum=0.9, weight_decay=1e-4)
num_epochs = 10
print("Starting Training Loop...")
for epoch in range(num_epochs):
model.train()
running_loss = 0.0
pbar = tqdm(train_loader, desc=f"Epoch {epoch+1}/{num_epochs}")
for images, masks in pbar:
images = images.to(device)
masks = masks.squeeze(1).to(device)
optimizer.zero_grad()
outputs = model(images)
loss_main = criterion(outputs['out'], masks)
loss_aux = criterion(outputs['aux'], masks)
loss = loss_main + 0.5 * loss_aux
loss.backward()
optimizer.step()
running_loss += loss.item()
pbar.set_postfix({'loss': f"{loss.item():.4f}"})
epoch_loss = running_loss / len(train_loader)
print(f"Epoch {epoch+1} Completed | Average Loss: {epoch_loss:.4f}")
save_path = os.path.join(base_dir, f"weights/deeplabv3_epoch_{epoch+1}.pth")
torch.save(model.state_dict(), save_path)
if __name__ == '__main__':
main()
运行命令: python train.py
第四步:编写与运行推理脚本
创建 predict.py。该脚本负责加载训练好的权重,对新图片进行推理,在终端打印识别出的物体类别,并生成彩色分割掩码图像。
完整 predict.py 代码:
Python
import os
import torch
import torch.nn as nn
from torchvision.models.segmentation import deeplabv3_resnet50
from torchvision.transforms import v2
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
VOC_COLORMAP = [
[0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0],
[0, 0, 128], [128, 0, 128], [0, 128, 128], [128, 128, 128],
[64, 0, 0], [192, 0, 0], [64, 128, 0], [192, 128, 0],
[64, 0, 128], [192, 0, 128], [64, 128, 128], [192, 128, 128],
[0, 64, 0], [128, 64, 0], [0, 192, 0], [128, 192, 0],
[0, 64, 128]
]
VOC_CLASSES = [
'background (背景)', 'aeroplane (飞机)', 'bicycle (自行车)', 'bird (鸟)', 'boat (船)',
'bottle (瓶子)', 'bus (巴士)', 'car (小汽车)', 'cat (猫)', 'chair (椅子)', 'cow (牛)',
'diningtable (餐桌)', 'dog (狗)', 'horse (马)', 'motorbike (摩托车)', 'person (人)',
'pottedplant (盆栽)', 'sheep (羊)', 'sofa (沙发)', 'train (火车)', 'tvmonitor (显示器)'
]
def decode_segmap(image_idx):
r = np.zeros_like(image_idx).astype(np.uint8)
g = np.zeros_like(image_idx).astype(np.uint8)
b = np.zeros_like(image_idx).astype(np.uint8)
for l in range(0, 21):
idx = image_idx == l
r[idx] = VOC_COLORMAP[l][0]
g[idx] = VOC_COLORMAP[l][1]
b[idx] = VOC_COLORMAP[l][2]
return np.stack([r, g, b], axis=2)
def main():
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
base_dir = os.path.dirname(os.path.abspath(__file__))
# 开启 aux_loss=True 确保模型结构与训练时完全一致
model = deeplabv3_resnet50(weights=None, aux_loss=True)
model.classifier[4] = nn.Conv2d(256, 21, kernel_size=(1, 1), stride=(1, 1))
model.aux_classifier[4] = nn.Conv2d(256, 21, kernel_size=(1, 1), stride=(1, 1))
weight_path = os.path.join(base_dir, "weights/deeplabv3_epoch_10.pth")
print(f"Loading weights from: {weight_path}")
model.load_state_dict(torch.load(weight_path, map_location=device))
model = model.to(device)
model.eval()
transform = v2.Compose([
v2.Resize((512, 512)),
v2.ToImage(),
v2.ToDtype(torch.float32, scale=True),
v2.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
image_path = os.path.join(base_dir, "test_images/test.jpg")
if not os.path.exists(image_path):
print(f"找不到测试图片!请确保上传了照片到: {image_path}")
return
img_pil = Image.open(image_path).convert("RGB")
input_tensor = transform(img_pil).unsqueeze(0).to(device)
print("Running inference...")
with torch.no_grad():
output = model(input_tensor)['out'][0]
prediction = torch.argmax(output, dim=0).cpu().numpy()
present_classes = np.unique(prediction)
print("\n" + "="*30)
print("画面物体识别结果:")
for class_id in present_classes:
if class_id == 0:
continue
print(f" - 发现目标: {VOC_CLASSES[class_id]} (类别ID: {class_id})")
print("="*30 + "\n")
colorized_mask = decode_segmap(prediction)
result_path = os.path.join(base_dir, "test_images/result.png")
os.makedirs(os.path.join(base_dir, 'test_images'), exist_ok=True)
fig, ax = plt.subplots(1, 2, figsize=(10, 5))
ax[0].imshow(img_pil.resize((512, 512)))
ax[0].set_title('Original Image')
ax[0].axis('off')
ax[1].imshow(colorized_mask)
ax[1].set_title('Predicted Segmentation Mask')
ax[1].axis('off')
plt.tight_layout()
plt.savefig(result_path, bbox_inches='tight', dpi=150)
print(f"推理完成!对比结果已保存至: {result_path}")
if __name__ == '__main__':
main()
运行说明: 将任意 .jpg 照片重命名为 test.jpg,放进 test_images/ 目录,然后执行 python predict.py 即可在同目录下查看生成的 result.png。
4.3实验结果


AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)