<DataWhale>- 语义分割 - RLE编码

Task 01 语义分割-RLE编码


目录

  • 思考
  • 源码
  • 自我理解RLE
  • 关于源码中注释部分question的解释

思考

  • RLE是什么?
  • 它是怎么运作的?
  • 在mask.csv中图片后面的一系列数字具体表达什么意思?

源码

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()

<DataWhale>- 语义分割 - RLE编码_第1张图片

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


  • RLE编码
    • run-length encoding: 游程编码、行程长度编码(非破坏性资料压缩法)
      • 对连续的黑、白像素数以不同的码字进行编码
      • 通常用在语义分割比赛中对标签进行编码

百科解释

  • RLE全称(run-length encoding),翻译为游程编码,又译行程长度编码,又称变动长度编码法(run coding),在控制论中对于二值图像而言是一种编码方法,对连续的黑、白像素数(游程)以不同的码字进行编码。游程编码是一种简单的非破坏性资料压缩法,其好处是加压缩和解压缩都非常快。其方法是计算连续出现的资料长度压缩之。

我的理解

  • RLE编码就是将相同的数据进行压缩计数,同时记录当前数据出现的初始为位置和对应的长度,例如:[0,1,1,1,0,1,1,0,1,0] 编码之后为1,3,5,2,8,1
  • 其中的奇数位表示数字1出现的对应的index,而偶数位表示它对应的前面的坐标位开始数字1重复的个数。
  • 反思:如果数据出现多个不重复无规律的数据是,使用RLE编码可能会适得其反。但是在语义分割中针对类别较少的图像数据是一个不错的选择。
  • Get it !

关于源码中注释部分question的解释

  • 在源码中,原始图像数据经过扁平化处理后,再次在其前后分别追加数字0,目的是为了下面的切片操作时防止误删有效数据
  • 而在获取数据坐标的过程中,使用的错位取反的方式计算所有数字1的开始和结束位置,具体的见下图,我举了两个例子。先简单记录下,具体的我还在找这种实现方式的逻辑
  • 以0开始的<DataWhale>- 语义分割 - RLE编码_第2张图片
  • 以1开始
  • <DataWhale>- 语义分割 - RLE编码_第3张图片

你可能感兴趣的:(学习,cv,RLE,语义分割)