其实取标题一直以来都是一件麻烦的事,但是如果你要看下去,我想你得有一点语义分割的见解。

用平常的语言描述该问题就是:语义分割出我们感兴趣的目标物,然后输出该目标物的轮廓点做语义分割其实有很多种方法,你可以用不同的模型去train你的dataset,但是刚接触语义分割的朋友们可能会说,我该怎么分割出我想要的目标物,而不对其它部分的像素做修改?其实这件事并不复杂,一个很直觉的想法是:修改像素对应的RGB值。那怎么修改对应像素的RGB值呢?举例来说,假设你在mmsegmentation框架下用的是SegFormer模型,你应该在class_names.py文件中修改cityscapespalette(如上图)

问题的前半段很好解决,那如何解决输出目标物轮廓点这件事情呢?一种比较直觉的想法是:将分割好的图片转为二值图,然后在转为灰度图,之后利用cv2.findContours()函数寻找轮廓点,为了验证轮廓点的准确性,再用cv2.drawContours()函数做可视化处理。这样的处理对一些简单的图片是一种策略,但是对于已经语义分割好的图片,再这样处理会不会有一些“脱裤子放屁——多此一举”呢?答案是肯定的。你想想看,你是怎么将一幅图片语义分割出来的呢?是根据每个像素的类别逐一“上色”的对吧。那这么说来,其实要做输出轮廓点这件事,只要让模型输出一张分割后的二maskt图片,然后转为灰度图,再重复上面说的cv2.findContours()cv2.drawContours()的操作就可以了吧?

那这里有一个前提,什么前提呢?前提是你已经知道如何修改像素值这件事情。所以在SegFormer里面,你可以在base.py文件中进行修改,一些注释我已经写在代码里面:

    def show_result(self,                    img,                    result,  # 这里的result就是每个像素的类别,它的大小应该与你的图片大小有关                    palette=None,                    win_name='',                    show=False,                    wait_time=0,                    out_file=None):        """Draw `result` over `img`.        Args:            img (str or Tensor): The image to be displayed.            result (Tensor): The semantic segmentation results to draw over                `img`.            palette (list[list[int]]] | np.ndarray | None): The palette of                segmentation map. If None is given, random palette will be                generated. Default: None            win_name (str): The window name.            wait_time (int): Value of waitKey param.                Default: 0.            show (bool): Whether to show the image.                Default: False.            out_file (str or None): The filename to write the image.                Default: None.        Returns:            img (Tensor): Only if not `show` or `out_file`        """        img = mmcv.imread(img)        img = img.copy()        seg = result[0]        if palette is None:            if self.PALETTE is None:                palette = np.random.randint(                    0, 255, size=(len(self.CLASSES), 3))            else:                palette = self.PALETTE        palette = np.array(palette)        assert palette.shape[0] == len(self.CLASSES)        assert palette.shape[1] == 3        assert len(palette.shape) == 2        color_seg = np.zeros((seg.shape[0], seg.shape[1], 3), dtype=np.uint8)        for label, color in enumerate(palette):            color_seg[seg == label, :] = color  # 注意数组的特别用法        # convert to BGR        color_seg = color_seg[..., ::-1]        # from IPython import embed; embed(header='debug vis')        # img = img * 0.5 + color_seg * 0.5  # 这两步是修改的地方        # img = img.astype(np.uint8)        # if out_file specified, do not show image in window        if out_file is not None:            show = False        if show:            mmcv.imshow(img, win_name, wait_time)        if out_file is not None:            mmcv.imwrite(img, out_file)        if not (show or out_file):            warnings.warn('show==False and out_file is not specified, only '                          'result image will be returned')            return color_seg  #这一步也是修改的地方,原来是return img,这样就是分割出输出,为不是mask后的图输出

有了输出的mask图像(与原始图片的一一对应),就可以很方便的在原图进行可视化操作:

result = inference_segmentor(model_seg, p)  # 前向推理,得到每个像素的类别img_seg = show_result_pyplot(model_seg, p, result, get_palette(opt.palette))  # 得到分割后的maskimg = cv2.imread(p)  # 这一步是为了轮廓在原图中显示,我这里的p实际上是我的图片路径gray_img = cv2.cvtColor(img_seg, cv2.COLOR_BGR2GRAY)  # 转为灰度图contours, _ = cv2.findContours(gray_img, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)  # 寻找轮廓点res = cv2.drawContours(img, contours, -1, (0, 0, 255), 1)  # 绘制轮廓点

可视化后的效果(mask——原图——轮廓):

当然,轮廓检测的方法还有很多,希望你不会因此局限。