import os
import cv2
import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
from typing import Tuple
mask_csv_path = r"../dataset/train_mask.csv/train_mask.csv"
assert os.path.exists(mask_csv_path), FileExistsError("`{0}` does not exist".format(mask_csv_path))
def rle_encode(img: np.ndarray) -> str:
"""
将传入的图片编码成RLE格式
:img: 需要编码的图片数据,type -> np.ndarray
其中像素数据表示为:1 -> mask, 0 -> background
:return:
"""
# 将图片数据进行扁平化,转换成一维数组 e.g. array = np.array([[1,2],[3,4]])
# 其中order = {'C', 'F', 'A', 'K'} res = array.flatten(order=)
# C:means to flatten in row-major (C-style) order. >>> res = [1,2,3,4]
# F:means to flatten in column-major (Fortran- style) order. >>> res = [1,3,2,4]
# A: means to flatten in column-major order if a is Fortran contiguous in memory, row-major order otherwise. >>> res = [1,2,3,4]
# K: means to flatten a in the order the elements occur in memory. >>> res = [1,2,3,4]
# 默认值为:C
pixels = img.flatten(order='F')
# 数组拼接 numpy.concatenate((a1,a2,...), axis=0)
# >>> pixels = [0, *pixels, 0]
# TODO: Q1:这里在数组的前后都追加0,的目的是什么?为什么这么做?@lpliner@2021年2月19日11:26:36
# 因为下方会通过切片取值然后按位比较数据的差异,以此来记录数据更替的交界点,如果不使用追加数据0,那么在切片过程中可能会导致数据的丢失
pixels = np.concatenate([[0], pixels, [0]])
# np.where(condition)
# >>> pixels[1:] = [*pixels, 0]
# >>> pixels[:-1] = [0, *pixels]
# 当pixels[1:] != pixels[:-1] 依次按照坐标对比,当符合条件的则记下对应的下标,并返回tuple(array(...),)
# TODO: Q2:这里对比像素的目的是什么?为什么之后又要+1?@lpliner@2021年2月19日11:27:37
runs = np.where(pixels[1:] != pixels[:-1])[0] + 1
# 将runs对应的偶数位置替换成重复数据的长度
runs[1::2] -= runs[::2]
# 返回经过RLE处理后得到的数据,通过join拼接成对应的str并返回
result = " ".join(str(x) for x in runs)
return result
def rle_decode(mask_rle: str, shape: Tuple[int,int]=(512, 512)) -> np.ndarray:
"""
将RLE格式的数据进行解码成图片数据
:mask_rle: 需要处理的经过RLE编码的数据
:shape: (height, width)最终返回的图像数据的比例
:return: 解码之后的图片数据, type -> np.ndarray
"""
# 切割字符串获取对应的列表,此时的列表格式为:List[str]
s = mask_rle.split()
# 从奇数位获取像素1的坐标index(此时为index+1),同时从相邻的偶数位获取对应像素1的重复个数
starts, lengths = [np.asarray(x, dtype=int) for x in (s[0:][::2], s[1:][::2])]
# 因为此时的index实际都是index+1,所以需要自减1,以此获取真实index
starts -= 1
# 通过对应的像素1出现的index和重复数计算得到对应的结束的index
ends = starts + lengths
# 构建指定大小的image
image = np.zeros(shape[0] * shape[1], dtype=np.uint8)
# 通过遍历像素1的开始坐标和结束坐标,在之前构建的image中替换对应的数据得到最终的图像数据
for lo, hi in zip(starts, ends):
image[lo: hi] = 1
# 将构建好的image数据进行reshape,并指定方向是按照列reshape
# 注意:此时的order=“F”和rle_encode中flatten.order="F"是对应的
# TODO;猜想:这两个地方的order是否可以直接替换成"C",由于现有的数据集是事先准备好的,所以没有测试
result = image.reshape(shape, order='F')
return result
读取数据
train_mask = pd.read_csv(mask_csv_path, sep="\t", names=["name", "mask"])
train_mask.head()
name | mask | |
---|---|---|
0 | KWP8J3TRSV.jpg | 1 33 82 125 292 254 594 125 804 254 1106 125 1... |
1 | DKI3X4VFD3.jpg | NaN |
2 | AYPOE51XNI.jpg | NaN |
3 | 1D9V7N0DGF.jpg | 135016 4 135527 8 136039 12 136551 15 137062 2... |
4 | AWXXR4VYRI.jpg | 301 53 504 9 812 54 1016 9 1324 54 1528 9 1836... |
image_dir = r"../dataset/train"
assert os.path.exists(image_dir), FileExistsError("`{0}` does not exist".format(image_dir))
# 读取第一张图片,并将其利用RLE解码
image = cv2.imread(os.path.join(image_dir, train_mask["name"].iloc[0]))
mask = rle_decode(train_mask["mask"].iloc[0])
print(type(image), type(mask), image.shape, mask.shape)
(512, 512, 3) (512, 512)
plt.subplot(1, 2, 1)
plt.imshow(image)
plt.title("image")
plt.subplot(1, 2, 2)
plt.imshow(mask)
plt.axis("on")
plt.gca().axes.get_yaxis().set_visible(False)
plt.title("mask")
plt.show()
print(rle_encode(mask) == train_mask["mask"].iloc[0])
True
is_nan = train_mask["mask"].isnull().sum()
total = len(train_mask["mask"])
ratio_nan = float("%.4f" % (is_nan / total))
print("图片总数为:", total)
print("所有图片中没有任何建筑物的个数为:", is_nan)
print("其占比为:", ratio_nan)
图片总数为: 30000
所有图片中没有任何建筑物的个数为: 5204
其占比为: 0.1735
def count_pixels(mask_rle: [str, float]) -> int:
"""
计算每张图片中建筑物像素的总数
由于每个图片中如果存在建筑物,则经过RLE编码后的偶数位即为对应的像素长度,累加即可
:mask_rle: 需要计算的经过RLE编码的图片数据,可能为NAN
:return: 返回当前图片中建筑物像素的个数
"""
if isinstance(mask_rle, float):
return 0
# 获取所有的length
lengths = [int(x) for x in mask_rle.split()[1:][::2]]
# 计算总长度
sum_ = sum(lengths)
return sum_
# 总像素个数
total_pixels = len(train_mask["mask"]) * 512 * 512
construction_pixels = 0
for mask_rle in train_mask["mask"]:
construction_pixels += count_pixels(mask_rle)
print("总像素个数:", total_pixels)
print("建筑物像素个数:", construction_pixels)
print("其占比为:", float("%.4f" % (construction_pixels / total_pixels)))
总像素个数: 7864320000
建筑物像素个数: 1235338412
其占比为: 0.1571
print("所有图片中建筑物区域平均区域大小为:%d个像素,每张图片的像素个数为%d" % (int(construction_pixels / total), 512*512))
所有图片中建筑物区域平均区域大小为:41177个像素,每张图片的像素个数为262144
通常用在语义分割比赛中对标签进行编码
)百科解释:
我的理解
反思
:如果数据出现多个不重复无规律的数据是,使用RLE编码可能会适得其反。但是在语义分割中针对类别较少的图像数据是一个不错的选择。Get it !