Segmentaion标签的三种表示:poly、mask、rle
Segmentaion標(biāo)簽的三種表示:poly、mask、rle
不同于圖像分類這樣比較簡單直接的計(jì)算機(jī)視覺任務(wù),圖像分割任務(wù)(又分為語義分割、實(shí)例分割、全景分割)的標(biāo)簽形式稍為復(fù)雜。在分割任務(wù)中,我們需要在像素級上表達(dá)的是一張圖的哪些區(qū)域是哪個類別。
多邊形坐標(biāo)Polygon
第一感下,要表達(dá)圖像中某個區(qū)域是什么類別,只要這個區(qū)域“圈起來”,并給它一個標(biāo)簽就好了。的確,用多邊形來將目標(biāo)圈出來確實(shí)是最符合我們視覺上對圖像的感知的方法。并且在很多數(shù)據(jù)集的標(biāo)注過程中,來自人類的手工標(biāo)注也是通過給出一個一個點(diǎn)的坐標(biāo),從而形成一個閉合的多邊形區(qū)域,從而實(shí)現(xiàn)對圖像中目標(biāo)物體的分割。
我們通過 OpenCV 的 polylines 函數(shù)來將這種做法畫出來看一下:
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, dtype=np.int32) cv2.polylines(img, [cat_points], True, (255, 0, 0), 3) dog_points = np.array(dog_poly, dtype=np.int32) cv2.polylines(img, [dog_points], True, (0, 0, 255), 3)cv2.imshow("window", img) cv2.waitKey(0)這里的數(shù)據(jù) cat_poly 是一個 n×2n\times 2n×2 的二維數(shù)組,表示多邊形框的 nnn 個坐標(biāo),即 [[x1,y1],[x2,y2],...[xn,yn]][[x_1,y_1],[x_2,y_2],...[x_n,y_n]][[x1?,y1?],[x2?,y2?],...[xn?,yn?]]。畫出來大概就是下面這樣子:
這樣的確可以劃分出我們想要的區(qū)域,但是沒有體現(xiàn)出“區(qū)域”的概念,即在整個多邊形框內(nèi),都是貓/狗區(qū)域。
掩膜區(qū)域Mask
為了體現(xiàn)出區(qū)域的概念,我們可以將整個區(qū)域展示出來,這里用到 fillPoly 函數(shù),就是下面這樣大家常常見到的樣子:
img = cv2.imread("cat-dog.jpeg")dog_poly = [# ... ] cat_poly = [# ... ]cat_points = np.array(cat_poly, dtype=np.int32) dog_points = np.array(dog_poly, dtype=np.int32)zeros = np.zeros((img.shape), dtype=np.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)在模型的設(shè)計(jì)與訓(xùn)練中,我們有時最后輸出的就是與原圖尺寸相同二值的 mask 圖,其中 1 的地方表示該位置有某一類物體,0 表示沒有該類物體。因此我們通常要將上面的多邊形標(biāo)注轉(zhuǎn)為二值的 mask 圖來作為直接用來計(jì)算損失的標(biāo)簽。由多邊形標(biāo)簽轉(zhuǎn)為掩膜標(biāo)簽的代碼如下:
def poly2mask(points, width, height):mask = np.zeros((width, height), dtype=np.int32)obj = np.array([points], dtype=np.int32)cv2.fillPoly(mask, obj, 1)return mask這里的 points 就是上面我們的 cat_poly 這樣的二維數(shù)組的多邊形數(shù)據(jù)。
就是將有該類物體的地方置為1,其他為0,有些區(qū)別會在語義分割和實(shí)例分割中有所不同,可能是某一類有一個mask,也可能是每一個實(shí)例一個 mask。大家按需調(diào)整即可。
將上述貓狗的例子轉(zhuǎn)換后可視化如下:
width, height = img.shape[: 2] cat_mask = poly2mask(cat_poly, width, height) dog_mask = poly2mask(dog_poly, width, height)注意,在做可視化時建議將上面的 poly2mask 函數(shù)中的 1 改為 255。因?yàn)榛叶戎禐?1 也基本是黑的,但是在訓(xùn)練中為 1 即可。
從掩膜 mask 轉(zhuǎn)換回多邊形 poly 的函數(shù)會比較復(fù)雜,在這個過程中可能會有標(biāo)簽精度的損失。我們用越多的坐標(biāo)點(diǎn)來表示掩膜自然也就越精確,極端情況下,將掩膜邊緣處的每一個像素都連接起來,這時不會有精度的損失。但我們通常不會這樣做。
這里給出轉(zhuǎn)換的函數(shù),該函數(shù)會返回一個數(shù)組,數(shù)組的長度就是 mask 中閉合區(qū)域的個數(shù),數(shù)組的每個元素是一組坐標(biāo):[x1,y1,x2,y2,...,xn,yn][x_1,y_1,x_2,y_2,...,x_n,y_n][x1?,y1?,x2?,y2?,...,xn?,yn?] ,注意這里的坐標(biāo)并不是成對的,與我們上面的數(shù)據(jù)輸入略有不同,因此在下面的實(shí)驗(yàn)中,筆者用 get_paired_coord 函數(shù)統(tǒng)一了一下接口規(guī)范。
其中 tolerance 參數(shù)(中文意為容忍度)表示的就是輸出的多邊形的每個坐標(biāo)點(diǎn)之間的最大距離,可想而知,該值越大,可能的精度損失越大。
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, tolerance=0):"""Converts a binary mask to COCO polygon representationArgs:binary_mask: a 2D binary numpy array where '1's 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_width=1, mode='constant', constant_values=0)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, axis=1)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 下的區(qū)別。
def get_paired_coord(coord):points = Nonefor i in range(0, len(coord), 2):point = np.array(coord[i: i+2], dtype=np.int32).reshape(1, 2)if (points is None): points = pointelse: points = np.concatenate([points, point], axis=0)return pointspoly_0 = binary_mask_to_polygon(cat_mask+dog_mask, tolerance=0) poly_100 = binary_mask_to_polygon(cat_mask+dog_mask, tolerance=100)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, dtype=np.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, dtype=np.int32) cv2.polylines(p100_img, [p100_points], True, (255, 0, 0), 3) cv2.imwrite("poly_dog_100.jpeg", p100_img) tolerance=0 tolerance=100與我們的預(yù)期相符,tolerance=0 時不會有精度損失,而當(dāng) tolerance=100 時可以看到進(jìn)度損失已經(jīng)比較大了。
RLE編碼
mask 大概是這種形式:
mask=np.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]])可以看到其實(shí)是有很多信息冗余的,因?yàn)橹挥?,1兩種元素,RLE編碼就是將相同的數(shù)據(jù)進(jìn)行壓縮計(jì)數(shù),同時記錄當(dāng)前數(shù)據(jù)出現(xiàn)的初始為位置和對應(yīng)的長度,例如:[0,1,1,1,0,1,1,0,1,0] 編碼之后為1,3,5,2,8,1。其中的奇數(shù)位表示數(shù)字1出現(xiàn)的對應(yīng)的index,而偶數(shù)位表示它對應(yīng)的前面的坐標(biāo)位開始數(shù)字1重復(fù)的個數(shù)。
RLE全稱(run-length encoding),翻譯為游程編碼,又譯行程長度編碼,又稱變動長度編碼法(run coding),在控制論中對于二值圖像而言是一種編碼方法,對連續(xù)的黑、白像素?cái)?shù)(游程)以不同的碼字進(jìn)行編碼。游程編碼是一種簡單的非破壞性資料壓縮法,其好處是加壓縮和解壓縮都非常快。其方法是計(jì)算連續(xù)出現(xiàn)的資料長度壓縮之。
RLE是COCO數(shù)據(jù)集的規(guī)范格式之一,也是許多圖像分割比賽指定提交結(jié)果的格式。
mask轉(zhuǎn)rle編碼,這里我們借助 pycocotools 工具包:
def singleMask2rle(mask):rle = mask_util.encode(np.array(mask[:, :, None], order='F', dtype="uint8"))[0]rle["counts"] = rle["counts"].decode("utf-8")return rle該函數(shù)的返回值 rle 是一個字典,有兩個字段 size 和 counts ,該字典通常直接作為 COCO 數(shù)據(jù)集的 segmentation 字段。
RLE編碼的理解推薦:https://blog.csdn.net/wuda19920215/article/details/113865418
Ref:
https://wall.alphacoders.com/big.php?i=324547&lang=Chinese
https://blog.csdn.net/wuda19920215/article/details/113865418
https://www.cnblogs.com/aimhabo/p/9935815.html
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的Segmentaion标签的三种表示:poly、mask、rle的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php mongo 查询count,[P
- 下一篇: 谈谈东风21D,用弹道导弹破防航母。