零基础入门语义分割-地表建筑物识别 Task1 赛题理解 -学习笔记

简介: 零基础入门语义分割-地表建筑物识别 Task1 赛题理解 -学习笔记

相关链接地址:

天池零基础入门语义分割-地表建筑物识别 链接

学习目标以及任务链接

Task1链接

baseline链接

(一)1 赛题理解

  • 赛题名称:零基础入门语义分割-地表建筑物识别
  • 赛题目标:通过本次赛题可以引导大家熟练掌握语义分割任务的定义,具体的解题流程和相应的模型,并掌握语义分割任务的发展。
  • 赛题任务:赛题以计算机视觉为背景,要求选手使用给定的航拍图像训练模型并完成地表建筑物识别任务。

1.1 学习目标

  • 理解赛题背景和赛题数据
  • 完成赛题报名和数据下载,理解赛题的解题思路

1.2 赛题数据

遥感技术已成为获取地表覆盖信息最为行之有效的手段,遥感技术已经成功应用于地表覆盖检测、植被面积检测和建筑物检测任务。本赛题使用航拍数据,需要参赛选手完成地表建筑物识别,将地表航拍图像素划分为有建筑物和无建筑物两类。

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

赛题数据来源(Inria Aerial Image Labeling),并进行拆分处理。数据集报名后可见并可下载。赛题数据为航拍图,需要参赛选手识别图片中的地表建筑具体像素位置。

1.3 数据标签

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

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

RLE与图片之间的转换如下:

import numpy as np
import pandas as pd
import cv2
# 将图片编码为rle格式
def rle_encode(im):
    '''
    im: numpy array, 1 - mask, 0 - background
    Returns run length as string formated
    '''
    pixels = im.flatten(order = 'F')
    pixels = np.concatenate([[0], pixels, [0]])
    runs = np.where(pixels[1:] != pixels[:-1])[0] + 1
    runs[1::2] -= runs[::2]
    return ' '.join(str(x) for x in runs)
# 将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 -= 1
    ends = starts + lengths
    img = np.zeros(shape[0]*shape[1], dtype=np.uint8)
    for lo, hi in zip(starts, ends):
        img[lo:hi] = 1
    return img.reshape(shape, order='F')

1.4 评价指标

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

2 ∗ ∣ X ∩ Y ∣ ∣ X ∣ + ∣ Y ∣ \frac{2 * |X \cap Y|}{|X| + |Y|}X+Y2XY

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

1.5 读取数据

FileName Size 含义
test_a.zip 314.49MB 测试集A榜图片
test_a_samplesubmit.csv 46.39KB 测试集A榜提交样例
train.zip 3.68GB 训练集图片
train_mask.csv.zip 97.52MB 训练集图片标注

具体数据读取案例:

import pandas as pd
import cv2
train_mask = pd.read_csv('train_mask.csv', sep='\t', names=['name', 'mask'])
# 读取第一张图,并将对于的rle解码为mask矩阵
img = cv2.imread('train/'+ train_mask['name'].iloc[0])
mask = rle_decode(train_mask['mask'].iloc[0])
print(rle_encode(mask) == train_mask['mask'].iloc[0])
# 结果为True

1.6 解题思路

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

  • 步骤1:使用FCN模型模型跑通具体模型训练过程,并对结果进行预测提交;
  • 步骤2:在现有基础上加入数据扩增方法,并划分验证集以监督模型精度;
  • 步骤3:使用更加强大模型结构(如Unet和PSPNet)或尺寸更大的输入完成训练;
  • 步骤4:训练多个模型完成模型集成操作;

1.7 本章小结

本章主要对赛题背景和主要任务进行讲解,并多对赛题数据和标注读取方式进行介绍,最后列举了赛题解题思路。

1.8 课后作业

  1. 理解RLE编码过程,并完成赛题数据读取并可视化;
  2. 统计所有图片整图中没有任何建筑物像素占所有训练集图片的比例;
  3. 统计所有图片中建筑物像素占所有像素的比例;
  4. 统计所有图片中建筑物区域平均区域大小;


(二)baseline代码分析


Ⅰ.将图片编码为rle格式

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

1.输入的im为二值图像,pixels = im.flatten(order = 'F')是对二维数组展平成一维,order=‘F'代表按列展平。

得到的结果如下(例子):

2.pixels = np.concatenate([[0], pixels, [0]])两端补0,是为了后续的错位比较。

3.runs = np.where(pixels[1:] != pixels[:-1])[0] + 1

看到这一步,也许就知道为什么要两端补0了,不然没法比较。

于是得到 0→1变化或者1→0变化的地方的索引了。最后一步转换为从1开始的索引。

4.runs[1::2] -= runs[::2]

当然(索引+个数)只对1进行操作,对0来说没必要了。

5.return ' '.join(str(x) for x in runs)

最后返回的是一个长度为12的字符串,每两个数之间用空格隔开(第一个数字前也要加空格)



Ⅱ.将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 -= 1
    ends = starts + lengths
    img = np.zeros(shape[0]*shape[1], dtype=np.uint8)
    for lo, hi in zip(starts, ends):
        img[lo:hi] = 1
    return 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 = paths
        self.rles = rles
        self.transform = transform
        self.test_mode = test_mode
        self.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 operation
    def __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):#取消了tqdm
        image, 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 = vloss
        torch.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))#改成PIL
    image = 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))
        # break
    subm.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 np
subm = 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张图片的预测结果




(三)作业解答

1. 理解RLE编码过程,并完成赛题数据读取并可视化:


已在baseline中说明。


2. 统计所有图片整图中没有任何建筑物像素占所有训练集图片的比例:

import pandas as pd
train_mask = pd.read_csv("数据集/train_mask.csv",sep="\t",names=["name","mask"])
train_mask["mask"]=train_mask["mask"].fillna("")
l = len(train_mask)
sum=0
for i in range(l):
    if train_mask["mask"].iloc[i]=="":
        sum+=1
print(sum/l)

    得到输出的结果(没有任何建筑物像素占所有训练集图片的比例)

0.17346666666666666

    30000张图中有5204张图是没有任何建筑的


3. 统计所有图片中建筑物像素占所有像素的比例

import pandas as pd
import numpy as np
from PIL import Image
from tqdm import tqdm
train_mask = pd.read_csv("数据集/train_mask.csv",sep="\t",names=["name","mask"])
train_mask["mask"]=train_mask["mask"].fillna("")
l = len(train_mask)
ratio_ls = []
for i in tqdm(range(l)):
    if train_mask["mask"].iloc[i]!="":
        ls = list(map(int,train_mask["mask"].iloc[i].split(" ")))
        number = sum(ls[1::2])
        pic_path = "数据集/"+"train/"+train_mask["name"].iloc[i]
        img = np.array(Image.open(pic_path))
        ratio = number/(img.shape[0]*img.shape[1])
    else:
        ratio = 0
    ratio_ls.append(ratio)
pd.Series(ratio_ls).to_csv("ratio_ls")

    将每张图片的建筑物像素占所有像素的比例存入到列表中,并保存下来。

ratio = pd.read_csv("ratio_ls")
print("所有图片中建筑像素平均占比:",np.mean(ratio.iloc[:,1]))
ratio_ = ratio.iloc[:,1][(ratio.iloc[:,1])!=0]
print("有建筑图片中建筑像素平均占比:",np.mean(ratio_))
ratio = np.array(ratio)[:,1]
print("建筑像素占比最大值",np.max(ratio))
print("有建筑图片中,建筑像素占比最小值",np.min(ratio_))

    得到

所有图片中建筑像素平均占比: 0.15708140207926433
有建筑图片中建筑像素平均占比: 0.19004847807621914
建筑像素占比最大值 0.9992218017578124
有建筑图片中,建筑像素占比最小值 3.814697265625e-06

    可以发现,有建筑图片中,建筑像素平均占比为0.2。

    但是发现一张,像素占比比较大的图片,于是将它可视化一下。

    标签认为它全图都是建筑,虽然很可能是屋顶上建了一些建筑,于是整个图都是建筑。

    但是大多数图片并不是这样的,就算是人眼也很难识别出,这是在一个大建筑的屋顶上。

    如果能把这样的异常数据给舍弃掉,然后拿剩下的数据去训练,可能会好很多。

4. 统计所有图片中建筑物区域平均区域大小;

    区域大小,也就是白色像素点数量和。

import pandas as pd
import numpy as np
from PIL import Image
from tqdm import tqdm
train_mask = pd.read_csv("数据集/train_mask.csv",sep="\t",names=["name","mask"])
train_mask["mask"]=train_mask["mask"].fillna("")
l = len(train_mask)
sum_ls = []
for i in tqdm(range(l)):
    if train_mask["mask"].iloc[i]!="":
        ls = list(map(int,train_mask["mask"].iloc[i].split(" ")))
        number = sum(ls[1::2])
        # pic_path = "数据集/"+"train/"+train_mask["name"].iloc[i]
        # img = np.array(Image.open(pic_path))
        # ratio = number/(img.shape[0]*img.shape[1])
    else:
        number = 0
    sum_ls.append(number)
pd.Series(sum_ls).to_csv("point_sum_ls")
ls = pd.read_csv("point_sum_ls")
print(np.mean(ls.iloc[:,1]))
ls_ = ls[(ls.iloc[:,1])!=0]
print(np.mean(ls_.iloc[:,1]))

    得到结果

所有图中建筑物区域平均区域大小 41177.94706666667
含有建筑物的图中建筑物区域平均区域大小 49820.06823681239


相关文章
|
机器学习/深度学习 数据可视化 PyTorch
零基础入门语义分割-地表建筑物识别 Task5 模型训练与验证-学习笔记
零基础入门语义分割-地表建筑物识别 Task5 模型训练与验证-学习笔记
582 2
|
PyTorch 算法框架/工具 计算机视觉
零基础入门语义分割-地表建筑物识别 Task4 评价函数与损失函数 -学习笔记
零基础入门语义分割-地表建筑物识别 Task4 评价函数与损失函数 -学习笔记
286 0
|
Linux Python
如何在服务器上跑python程序
购买服务器 首先你需要一个服务器,阿里云云翼计划有一个9.9云服务器ECS服务。你怎么买我不管,反正你最后给我搞到一个云服务器。 购买的配置界面 由于阿里云现在限量购买,所以这里只是截个图说明而已,主要说明一点公共镜像选择ubuntu14.04 64位,还有root密码别忘了。
11214 1
|
8月前
|
人工智能 物联网 API
又又又上新啦!魔搭免费模型推理API支持DeepSeek-R1,Qwen2.5-VL,Flux.1 dev及Lora等
通过API接口进行标准化,能让开源模型以更加轻量和迅速的方式被开发者使用起来,并集成到不同的AI应用中。魔搭通过API-Inference,支持广大开发者无需本地的GPU和环境设置,就能轻松的依托不同开源模型的能力,展开富有创造力的尝试,与工具结合调用,来构建多种多样的AI应用原型。
754 7
|
7月前
|
数据采集 前端开发 JavaScript
PDF预览:利用vue3-pdf-app实现前端PDF在线展示
通过本文的介绍,我们详细了解了如何在Vue3项目中使用vue3-pdf-app实现PDF文件的在线展示。从项目初始化、插件集成到高级功能的实现和部署优化,希望对你有所帮助。在实际项目中,灵活运用这些技术可以大大提升用户体验和项目质量。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
10月前
|
机器学习/深度学习 人工智能 PyTorch
【AI系统】计算图原理
本文介绍了AI框架中使用计算图来抽象神经网络计算的必要性和优势,探讨了计算图的基本构成,包括标量、向量、矩阵、张量等数据结构及其操作,并详细解释了计算图如何帮助解决AI工程化中的挑战。此外,文章还通过PyTorch实例展示了动态计算图的特点和实现方法,包括节点(张量或函数)和边(依赖关系)的定义,以及如何通过自定义Function实现正向和反向传播逻辑。
404 7
【AI系统】计算图原理
|
数据可视化 数据挖掘 API
【R语言实战】聚类分析及可视化
【R语言实战】聚类分析及可视化
|
消息中间件 NoSQL Java
Redis系列学习文章分享---第六篇(Redis实战篇--Redis分布式锁+实现思路+误删问题+原子性+lua脚本+Redisson功能介绍+可重入锁+WatchDog机制+multiLock)
Redis系列学习文章分享---第六篇(Redis实战篇--Redis分布式锁+实现思路+误删问题+原子性+lua脚本+Redisson功能介绍+可重入锁+WatchDog机制+multiLock)
471 0
|
机器学习/深度学习 IDE TensorFlow
【Python】已解决ModuleNotFoundError: No module named ‘tensorflow‘
【Python】已解决ModuleNotFoundError: No module named ‘tensorflow‘
1374 1
|
数据安全/隐私保护 Perl
批量计算地震波PGA/PGV/PGD、PSA/PSV/PSD、特征周期、卓越频率、Arias强度、特征强度、能量密度、Housner强度等30+参数
地震波格式转换、时程转换、峰值调整、规范反应谱、计算反应谱、计算持时、生成人工波、时频域转换、数据滤波、基线校正、Arias截波、傅里叶变换、耐震时程曲线、脉冲波合成与提取、三联反应谱、地震动参数、延性反应谱、地震波缩尺、功率谱密度