青浦网站建设 迎鑫,手机上如何制作app,wp网站搬家教程,广州网站建设(信科分公司)Segmentaion标签的三种表示#xff1a;poly、mask、rle
不同于图像分类这样比较简单直接的计算机视觉任务#xff0c;图像分割任务#xff08;又分为语义分割、实例分割、全景分割#xff09;的标签形式稍为复杂。在分割任务中#xff0c;我们需要在像素级上表达的是一张…Segmentaion标签的三种表示poly、mask、rle
不同于图像分类这样比较简单直接的计算机视觉任务图像分割任务又分为语义分割、实例分割、全景分割的标签形式稍为复杂。在分割任务中我们需要在像素级上表达的是一张图的哪些区域是哪个类别。
多边形坐标Polygon
第一感下要表达图像中某个区域是什么类别只要这个区域“圈起来”并给它一个标签就好了。的确用多边形来将目标圈出来确实是最符合我们视觉上对图像的感知的方法。并且在很多数据集的标注过程中来自人类的手工标注也是通过给出一个一个点的坐标从而形成一个闭合的多边形区域从而实现对图像中目标物体的分割。
我们通过 OpenCV 的 polylines 函数来将这种做法画出来看一下
import numpy as np
import cv2
cat_poly [[390.56410256410254, 1134.179487179487], # ...[407.2307692307692, 1158.5384615384614]]dog_poly [[794.4102564102564, 635.4615384615385], # ...[780.3076923076923, 531.6153846153846]]img cv2.imread(cat-dog.jpeg)cat_points np.array(cat_poly, dtypenp.int32)
cv2.polylines(img, [cat_points], True, (255, 0, 0), 3)
dog_points np.array(dog_poly, dtypenp.int32)
cv2.polylines(img, [dog_points], True, (0, 0, 255), 3)cv2.imshow(window, img)
cv2.waitKey(0)这里的数据 cat_poly 是一个 n×2n\times 2n×2 的二维数组表示多边形框的 nnn 个坐标即 [[x1,y1],[x2,y2],...[xn,yn]][[x_1,y_1],[x_2,y_2],...[x_n,y_n]][[x1,y1],[x2,y2],...[xn,yn]]。画出来大概就是下面这样子 这样的确可以划分出我们想要的区域但是没有体现出“区域”的概念即在整个多边形框内都是猫/狗区域。
掩膜区域Mask
为了体现出区域的概念我们可以将整个区域展示出来这里用到 fillPoly 函数就是下面这样大家常常见到的样子
img cv2.imread(cat-dog.jpeg)dog_poly [# ...
]
cat_poly [# ...
]cat_points np.array(cat_poly, dtypenp.int32)
dog_points np.array(dog_poly, dtypenp.int32)zeros np.zeros((img.shape), dtypenp.uint8)
mask cv2.fillPoly(zeros, [cat_points], color(255, 0, 0))
mask cv2.fillPoly(zeros, [dog_points], color(0, 0, 255))
mask_img 0.5 * mask imgcv2.imshow(window, mask_img)
cv2.waitKey(0)在模型的设计与训练中我们有时最后输出的就是与原图尺寸相同二值的 mask 图其中 1 的地方表示该位置有某一类物体0 表示没有该类物体。因此我们通常要将上面的多边形标注转为二值的 mask 图来作为直接用来计算损失的标签。由多边形标签转为掩膜标签的代码如下
def poly2mask(points, width, height):mask np.zeros((width, height), dtypenp.int32)obj np.array([points], dtypenp.int32)cv2.fillPoly(mask, obj, 1)return mask这里的 points 就是上面我们的 cat_poly 这样的二维数组的多边形数据。
就是将有该类物体的地方置为1其他为0有些区别会在语义分割和实例分割中有所不同可能是某一类有一个mask也可能是每一个实例一个 mask。大家按需调整即可。
将上述猫狗的例子转换后可视化如下
width, height img.shape[: 2]
cat_mask poly2mask(cat_poly, width, height)
dog_mask poly2mask(dog_poly, width, height)注意在做可视化时建议将上面的 poly2mask 函数中的 1 改为 255。因为灰度值为 1 也基本是黑的但是在训练中为 1 即可。 从掩膜 mask 转换回多边形 poly 的函数会比较复杂在这个过程中可能会有标签精度的损失。我们用越多的坐标点来表示掩膜自然也就越精确极端情况下将掩膜边缘处的每一个像素都连接起来这时不会有精度的损失。但我们通常不会这样做。
这里给出转换的函数该函数会返回一个数组数组的长度就是 mask 中闭合区域的个数数组的每个元素是一组坐标[x1,y1,x2,y2,...,xn,yn][x_1,y_1,x_2,y_2,...,x_n,y_n][x1,y1,x2,y2,...,xn,yn] 注意这里的坐标并不是成对的与我们上面的数据输入略有不同因此在下面的实验中笔者用 get_paired_coord 函数统一了一下接口规范。
其中 tolerance 参数中文意为容忍度表示的就是输出的多边形的每个坐标点之间的最大距离可想而知该值越大可能的精度损失越大。
from skimage import measuredef close_contour(contour):if not np.array_equal(contour[0], contour[-1]):contour np.vstack((contour, contour[0]))return contourdef binary_mask_to_polygon(binary_mask, tolerance0):Converts a binary mask to COCO polygon representationArgs:binary_mask: a 2D binary numpy array where 1s represent the objecttolerance: Maximum distance from original points of polygon to approximatedpolygonal chain. If tolerance is 0, the original coordinate array is returned.polygons []# pad mask to close contours of shapes which start and end at an edgepadded_binary_mask np.pad(binary_mask, pad_width1, modeconstant, constant_values0)contours measure.find_contours(padded_binary_mask, 0.5)contours np.subtract(contours, 1)for contour in contours:contour close_contour(contour)contour measure.approximate_polygon(contour, tolerance)if len(contour) 3:continuecontour np.flip(contour, axis1)segmentation contour.ravel().tolist()# after padding and subtracting 1 we may get -0.5 points in our segmentationsegmentation [0 if i 0 else i for i in segmentation]polygons.append(segmentation)return polygons下面看一下本例中的小狗在 tolerance 为 0 和 100 下的区别。
def get_paired_coord(coord):points Nonefor i in range(0, len(coord), 2):point np.array(coord[i: i2], dtypenp.int32).reshape(1, 2)if (points is None): points pointelse: points np.concatenate([points, point], axis0)return pointspoly_0 binary_mask_to_polygon(cat_maskdog_mask, tolerance0)
poly_100 binary_mask_to_polygon(cat_maskdog_mask, tolerance100)poly0_0 get_paired_coord(poly_0[0]) # poly_0[0]是小狗poly[1]是小猫
poly100_0 get_paired_coord(poly_100[0])p0_img img
p0_points np.array(poly0_0, dtypenp.int32)
cv2.polylines(p0_img, [p0_points], True, (255, 0, 0), 3)
cv2.imwrite(poly_dog_0.jpeg, p0_img)p100_img cv2.imread(cat-dog.jpeg)
p100_points np.array(poly100_0, dtypenp.int32)
cv2.polylines(p100_img, [p100_points], True, (255, 0, 0), 3)
cv2.imwrite(poly_dog_100.jpeg, p100_img)tolerance0tolerance100与我们的预期相符tolerance0 时不会有精度损失而当 tolerance100 时可以看到进度损失已经比较大了。
RLE编码
mask 大概是这种形式
masknp.array([[0, 0, 0, 0, 0, 0, 0, 0],[0, 0, 1, 1, 0, 0, 1, 0],[0, 0, 1, 1, 1, 1, 1, 0],[0, 0, 1, 1, 1, 1, 1, 0],[0, 0, 1, 1, 1, 1, 1, 0],[0, 0, 1, 0, 0, 0, 1, 0],[0, 0, 1, 0, 0, 0, 1, 0],[0, 0, 0, 0, 0, 0, 0, 0]])可以看到其实是有很多信息冗余的因为只有01两种元素RLE编码就是将相同的数据进行压缩计数同时记录当前数据出现的初始为位置和对应的长度例如[0,1,1,1,0,1,1,0,1,0] 编码之后为1,3,5,2,8,1。其中的奇数位表示数字1出现的对应的index而偶数位表示它对应的前面的坐标位开始数字1重复的个数。
RLE全称run-length encoding翻译为游程编码又译行程长度编码又称变动长度编码法run coding在控制论中对于二值图像而言是一种编码方法对连续的黑、白像素数(游程)以不同的码字进行编码。游程编码是一种简单的非破坏性资料压缩法其好处是加压缩和解压缩都非常快。其方法是计算连续出现的资料长度压缩之。
RLE是COCO数据集的规范格式之一也是许多图像分割比赛指定提交结果的格式。
mask转rle编码这里我们借助 pycocotools 工具包
def singleMask2rle(mask):rle mask_util.encode(np.array(mask[:, :, None], orderF, dtypeuint8))[0]rle[counts] rle[counts].decode(utf-8)return rle该函数的返回值 rle 是一个字典有两个字段 size 和 counts 该字典通常直接作为 COCO 数据集的 segmentation 字段。
RLE编码的理解推荐https://blog.csdn.net/wuda19920215/article/details/113865418
Ref
https://wall.alphacoders.com/big.php?i324547langChinese
https://blog.csdn.net/wuda19920215/article/details/113865418
https://www.cnblogs.com/aimhabo/p/9935815.html