作者:徐和鼎,浙江大学,Datawhale优秀学习者

遥感技术已成为获取地表覆盖信息最为行之有效的手段,已经成功应用于地表覆盖检测、植被面积检测和建筑物检测任务。本文以天池学习赛地表建筑物识别为例,对语义分割类项目的实践全流程进行了解析。具体流程如下:

赛题理解

  • 赛题名称:零基础入门语义分割-地表建筑物识别

  • 赛题地址:https://tianchi.aliyun.com/competition/entrance/531872/information

1.1 赛题数据

本赛题使用航拍数据,需要参赛选手完成地表建筑物识别,将地表航拍图像素划分为有建筑物和无建筑物两类。

如下图,左边为原始航拍图,右边为对应的建筑物标注。

1.2 数据标签

赛题为语义分割任务,因此具体的标签为图像像素类别。在赛题数据中像素属于2类(无建筑物和有建筑物),因此标签为有建筑物的像素。赛题原始图片为jpg格式,标签为RLE编码的字符串。

RLE全称(run-length encoding),翻译为游程编码或行程长度编码,对连续的黑、白像素数以不同的码字进行编码。RLE是一种简单的非破坏性资料压缩法,经常用在在语义分割比赛中对标签进行编码。

RLE与图片之间的转换代码详见本文第二节Baseline代码解析。

1.3 评价指标

赛题使用Dice coefficient来衡量选手结果与真实标签的差异性,Dice coefficient可以按像素差异性来比较结果的差异性。Dice coefficient的具体计算方式如下:

其中是预测结果, 为真实标签的结果。当与完全相同时Dice coefficient为1,排行榜使用所有测试集图片的平均Dice coefficient来衡量,分数值越大越好。

1.4 解题思路

由于本次赛题是一个典型的语义分割任务,因此可以直接使用语义分割的模型来完成:

  • 步骤1:使用FCN模型模型跑通具体模型训练过程,并对结果进行预测提交;

  • 步骤2:在现有基础上加入数据扩增方法,并划分验证集以监督模型精度;

  • 步骤3:使用更加强大模型结构(如Unet和PSPNet)或尺寸更大的输入完成训练;

  • 步骤4:训练多个模型完成模型集成操作;

Baseline代码分析

Ⅰ.将图片编码为rle格式

import numpy as np
import pandas as pd
import cv2# 将图片编码为rle格式
def rle_encode(im):'''im: numpy array, 1 - mask, 0 - backgroundReturns run length as string formated'''pixels = im.flatten(order = 'F')pixels = np.concatenate([[0], pixels, [0]])runs = np.where(pixels[1:] != pixels[:-1])[0] + 1runs[1::2] -= runs[::2]return ' '.join(str(x) for x in runs)

Ⅱ.将rle格式进行解码为图片

# 将rle格式进行解码为图片
def rle_decode(mask_rle, shape=(512, 512)):'''mask_rle: run-length as string formated (start length)shape: (height,width) of array to return Returns numpy array, 1 - mask, 0 - background'''s = mask_rle.split()starts, lengths = [np.asarray(x, dtype=int) for x in (s[0:][::2], s[1:][::2])]starts -= 1ends = starts + lengthsimg = np.zeros(shape[0]*shape[1], dtype=np.uint8)for lo, hi in zip(starts, ends):img[lo:hi] = 1return img.reshape(shape, order='F')

RLE编码的时候返回的时候每两个数字有空格为间隔,利用s = mask_rle.split()将空格去掉。

s[0:][::2]表示(从1开始的)索引,s[1:][::2]表示个数。于是starts存的是索引,lengths存的是个数,两者为一一对应关系。

starts \-= 1转化为(从0开始的)索引。

后续就是创建一副全0的一维序列,填充1,再按列排序,转为二维的二值图,就解码成图片了。

如果输入的mask_rle是空的,那么返回的就是全为0的mask,可以观察数据发现,部分图片的地表建筑不存在,他们的rle标签也就是空的。

Ⅲ.定义数据集

class TianChiDataset(D.Dataset):def __init__(self, paths, rles, transform, test_mode=False):self.paths = pathsself.rles = rlesself.transform = transformself.test_mode = test_modeself.len = len(paths)self.as_tensor = T.Compose([T.ToPILImage(),T.Resize(IMAGE_SIZE),T.ToTensor(),T.Normalize([0.625, 0.448, 0.688],[0.131, 0.177, 0.101]),])# get data operationdef __getitem__(self, index):#img = cv2.imread(self.paths[index])img = np.array(Image.open(self.paths[index]))if not self.test_mode:mask = rle_decode(self.rles[index])augments = self.transform(image=img, mask=mask)return self.as_tensor(augments['image']), augments['mask'][None]#(3,256,256),(1,256,256)else:return self.as_tensor(img), ''        def __len__(self):"""Total number of samples in the dataset"""return self.len

定义数据集,主要作了数据的预处理。其中,我将opencv的读取图片换成了PIL读取,因为路径中包含中文
augments['mask'][None]中的[None],将(256,256)的mask形状转为(1,256,256),起到升维作用。

Ⅳ.可视化一下效果

这一步主要是为了验证上述的代码。用了rle_encode(rle_decode(RLE标签))==RLE标签来验证之前写的RLE编码和解码正确性。


train_mask = pd.read_csv('数据集/train_mask.csv', sep='\t', names=['name', 'mask'])
train_mask['name'] = train_mask['name'].apply(lambda x: '数据集/train/' + x)img = cv2.imread(train_mask['name'].iloc[0])
mask = rle_decode(train_mask['mask'].iloc[0])print(rle_encode(mask) == train_mask['mask'].iloc[0])

train_mask['name'].apply(lambda x: '数据集/train/' + x)这一步就是在图片前补全下路径

0        KWP8J3TRSV.jpg
1        DKI3X4VFD3.jpg
2        AYPOE51XNI.jpg
3        1D9V7N0DGF.jpg
4        AWXXR4VYRI.jpg
0        数据集/train/KWP8J3TRSV.jpg
1        数据集/train/DKI3X4VFD3.jpg
2        数据集/train/AYPOE51XNI.jpg
3        数据集/train/1D9V7N0DGF.jpg
4        数据集/train/AWXXR4VYRI.jpg

实例化数据集

dataset = TianChiDataset(train_mask['name'].values,train_mask['mask'].fillna('').values,trfm, False
)

fillna('')起到补全缺失值为''的作用

可视化

image, mask = dataset[0]
plt.figure(figsize=(16,8))
plt.subplot(121)
plt.imshow(mask[0], cmap='gray')
plt.subplot(122)
plt.imshow(image[0])
plt.show()# 补上


看一下第二张图片

image, mask = dataset[1]

没有建筑物,mask全黑。

Ⅴ.加载数据集

#定义数据集
train_mask = pd.read_csv('数据集/train_mask.csv', sep='\t', names=['name', 'mask'])
train_mask['name'] = train_mask['name'].apply(lambda x: '数据集/train/' + x)dataset = TianChiDataset(train_mask['name'].values,train_mask['mask'].fillna('').values,trfm, False
)#划分数据集(按index手动去划分)
valid_idx, train_idx = [], []
for i in range(len(dataset)):if i % 7 == 0:valid_idx.append(i)else:# elif i % 7 == 1:train_idx.append(i)train_ds = D.Subset(dataset, train_idx)
valid_ds = D.Subset(dataset, valid_idx)# print(len(dataset))#30000
# print(len(train_ds))#4286
# print(len(valid_ds))#4286# define training and validation data loaders
loader = D.DataLoader(train_ds, batch_size=BATCH_SIZE, shuffle=True, num_workers=0)vloader = D.DataLoader(valid_ds, batch_size=BATCH_SIZE, shuffle=False, num_workers=0)

D.subset是按照索引序列来划分数据集的, 于是按照每7个数据里面,1个当作验证集,6个当作训练集。最后放入数据加载器中。

Ⅵ.定义模型、优化器、损失函数

# 定义模型
model = get_model()
model.to(DEVICE)
#model.load_state_dict(torch.load("model_best.pth"))#定义优化器
optimizer = torch.optim.AdamW(model.parameters(),lr=1e-4, weight_decay=1e-3)
#定义损失函数
bce_fn = nn.BCEWithLogitsLoss()
dice_fn = SoftDiceLoss()
def loss_fn(y_pred, y_true):bce = bce_fn(y_pred, y_true)dice = dice_fn(y_pred.sigmoid(), y_true)return 0.8 * bce + 0.2 * dice

Ⅶ.进行训练

header = r'''Train | Valid
Epoch |  Loss |  Loss | Time, m
'''
#          Epoch         metrics            time
raw_line = '{:6d}' + '\u2502{:7.3f}' * 2 + '\u2502{:6.2f}'
print(header)EPOCHES = 10
best_loss = 10
for epoch in range(1, EPOCHES + 1):losses = []start_time = time.time()model.train()for image, target in tqdm(loader):#取消了tqdmimage, target = image.to(DEVICE), target.float().to(DEVICE)optimizer.zero_grad()output = model(image)['out']loss = loss_fn(output, target)loss.backward()optimizer.step()losses.append(loss.item())# print(loss.item())vloss = validation(model, vloader, loss_fn)print(raw_line.format(epoch, np.array(losses).mean(), vloss,(time.time() - start_time) / 60 ** 1))losses = []if vloss < best_loss:best_loss = vlosstorch.save(model.state_dict(), 'model_best.pth')print("save successful!")

Ⅷ.使用模型对测试集进行预测

trfm = T.Compose([T.ToPILImage(),T.Resize(IMAGE_SIZE),T.ToTensor(),T.Normalize([0.625, 0.448, 0.688],[0.131, 0.177, 0.101]),
])subm = []model.load_state_dict(torch.load("./model_best.pth"))
model.eval()test_mask = pd.read_csv('数据集/test_a_samplesubmit.csv', sep='\t', names=['name', 'mask'])
test_mask['name'] = test_mask['name'].apply(lambda x: '数据集/test_a/' + x)for idx, name in enumerate(tqdm(test_mask['name'].iloc[:])):image = np.array(Image.open(name))#改成PILimage = trfm(image)with torch.no_grad():image = image.to(DEVICE)[None]score = model(image)['out'][0][0]score_sigmoid = score.sigmoid().cpu().numpy()score_sigmoid = (score_sigmoid > 0.5).astype(np.uint8)score_sigmoid = cv2.resize(score_sigmoid, (512, 512))# breaksubm.append([name.split('/')[-1], rle_encode(score_sigmoid)])subm = pd.DataFrame(subm)
subm.to_csv('./tmp.csv', index=None, header=None, sep='\t')

Ⅸ.可视化模型预测结果

from file1 import rle_decode
from PIL import Image
import pandas as pd
import numpy as npsubm = pd.read_csv("./tmp.csv",sep="\t",names=["name","mask"])
def show_predict_pic(num=0):plt.figure(figsize=(16,8))plt.subplot(121)plt.imshow(rle_decode(subm.fillna('').iloc[num,1]), cmap='gray')plt.subplot(122)plt.imshow(np.array(Image.open('数据集/test_a/' + subm.iloc[num,0])))plt.show()
if __name__ == '__main__':show_predict_pic(num=10)

查看第10张图片的预测结果


往期精彩回顾适合初学者入门人工智能的路线及资料下载机器学习及深度学习笔记等资料打印机器学习在线手册深度学习笔记专辑《统计学习方法》的代码复现专辑
AI基础下载机器学习的数学基础专辑温州大学《机器学习课程》视频
本站qq群851320808,加入微信群请扫码:

【深度学习】CV语义分割实践指南!相关推荐

  1. CV语义分割实践指南!

    ↑↑↑关注后"星标"Datawhale 每日干货 & 每月组队学习,不错过 Datawhale干货 作者:徐和鼎,浙江大学,Datawhale优秀学习者 遥感技术已成为获取 ...

  2. HALCON 21.11:深度学习笔记---语义分割/边缘提取(12)

    HALCON 21.11:深度学习笔记---语义分割/边缘提取(12) HALCON 21.11.0.0中,实现了深度学习方法. 本章介绍了如何使用基于深度学习的语义分割,包括训练和推理阶段. 通过语 ...

  3. 【深度学习】语义分割——综述

    语义分割 文章目录 语义分割 什么是语义分割? 什么是图像中的语义信息? 语义分割中的上下文信息 语义分割方法 语义分割神经网络 语义分割待解决的问题 评价指标 目前比较经典的网络 参考文献和资料 什 ...

  4. 【深度学习】语义分割-综述(卷积)

    这里写目录标题 0.笔记参考 1. 目的 2. 困难点 3. 数据集及评价指标 3.1数据集 3.2评价指标 4.实现架构 5. 模型发展 5.1基于全卷积的对称语义分割模型 5.1.1FCN(201 ...

  5. 【深度学习】语义分割:论文阅读:(CVPR 2022) MPViT(CNN+Transformer):用于密集预测的多路径视觉Transformer

    这里写目录标题 0详情 1摘要 2 主要工作 3 网络结构 3.1 Conv-stem 3.2 Multi-Scale Patch Embedding 3.3 Multi-path Transform ...

  6. 深度学习 Deeplab语义分割

    语义分割Deeplab系列算法 背景 Deeplab Family DeeplabV1 DeeplabV2 DeeplabV3 概述 DeeplabV3+ Deeplabv3+实现 参考 背景 语义分 ...

  7. 【毕业设计】深度学习图像语义分割算法研究与实现 - python 机器视觉

    文章目录 0 前言 2 概念介绍 2.1 什么是图像语义分割 3 条件随机场的深度学习模型 3. 1 多尺度特征融合 4 语义分割开发过程 4.1 建立 4.2 下载CamVid数据集 4.3 加载C ...

  8. 当前主流的深度学习图像语义分割模型解析

    转载自[量子位]公众号 QbitAI 原文地址:http://www.sohu.com/a/155907339_610300 图像语义分割就是机器自动从图像中分割出对象区域,并识别其中的内容. 量子位 ...

  9. Halcon 深度学习之语义分割 预处理 案例解析

    语义分割 预处理 文章目录 语义分割 预处理 前言 一.预处理的目的是什么? 1.设置图像预处理的参数,此部分参数后续会写入到训练模型当中,具体参数信息如下图 2.得到语义分割的文件,用于后续的训练当 ...

最新文章

  1. mysql5.7多实例安装_MySQL数据库5.7多实例安装
  2. 混合开发的坑(7) ---输入文本时,键盘遮挡
  3. 第一次作业:项目范围管理论文的提纲
  4. 豆瓣评论9.5的《Effective Python》,帮你解决80%难题!
  5. React中ref的使用方法
  6. D3D9 effect (hlsl)(转)
  7. OpenStack精华问答 | OpenStack服务介绍
  8. 【IntelliJ】IntelliJ IDEA的安装破解及使用
  9. php导入csv wps乱码,《excel打开csv格式乱码》 WPS不兼容EXCEL打开后出现乱码怎么解决...
  10. ios与java交互_5、与iOS、Android的交互 实践篇——主动调用
  11. 苹果mac os x系统的两种快捷截图技巧
  12. 史上最全最详细的APP运营推广策划方案
  13. 三角形外心的坐标公式
  14. 解决局域网文件共享“****无法复制,指定的网络名不可用”
  15. 统计学-【假设检验】 知识点总结
  16. WannaCrypt(永恒之蓝) down.bddp.net
  17. 【​观察】数据驱动零售变革 重塑消费体验是关键
  18. 光速入门Docker 和 Kubernetes,一起学~
  19. 基于STM32的智慧矿山之矿井安全监控终端的设计与实现
  20. Play框架最快上手!

热门文章

  1. 关于activity和thread生命周期
  2. HDU 3507 Print Article(斜率优化DP)
  3. JSP中的pageEncoding和contentType属性(转)
  4. 创建dynamics CRM client-side (四) - Namespace Notation in JS
  5. 如何优化页面的响应速度 以及如何减少项目初次加载时间(转https://www.cnblogs.com/MarcoHan/p/5295398.html)...
  6. 增加 Eclipse color Theme
  7. 正则表达式,grep,sed,
  8. MAC通过SSH使用PEM文件登录
  9. 利用python创建一个新本地文件
  10. Hihocoder-1135-Magic Box