深度学习,分割后处理之通过连通成分分析去除假阳性区域,提高分割准确度

用深度学习方法得到的分割结果,会有一些假阳性区域。通过去除这些假阳性区域,可以提高分割结果。

比如说做肾分割,大家都知道,肾只有左右两边有,如果分割结果出现了三个区域,则可以根据常识,去除那个假阳性区域。


image

用到的方法就是 连通成分分析Connected-Components

这里提供两种方法:

1. opencv-python 提供的方法

安装: pip install opencv-python
cv2.connectedComponents & cv2.connectedComponentsWithStats

1.1 cv2.connectedComponents

实例:

import cv2
import numpy as np
img = np.array([[0, 255, 0, 0],
                [0, 0, 255, 255],
                [0, 0, 0, 255],
                [255, 0, 0, 0]], np.uint8)
res, labels = cv2.connectedComponents(img, connectivity=4)
res
Out[5]: 4
labels
Out[6]: 
array([[0, 1, 0, 0],
       [0, 0, 2, 2],
       [0, 0, 0, 2],
       [3, 0, 0, 0]], dtype=int32)

假设 img 是分割结果,值为255的是目标区域,可以发现,目标区域有3块。res代表区域的数量 一共有4块。

labels为经过连通区域分析后的标记,把 4邻域 内相同的值标记为一个类别。这样一共产生了4个区域,分别用【0,1,2,3】表示。

上述例子例子中,参数 connectivity=4 表示在4邻域范围内查找元素,也是可以改成8邻域对比一下

4邻域:A点的上下左右中,假设存在B点和它的值一样,就表示 AB 点属于同一区域。

这样标记后,如果我们觉得3那个区域是假阳性,那我们就可以把3那个区域的值变为0,其余区域的值标记为255,这样就消除掉3这个区域的阳性值了。

label = np.where(labels > 2, 0, labels)
# 把labels中,大于2的值,赋值为0, 其余的就是labels原来的值。这样就剩下了两个区域
print(label)

结果如下: 

[[0 1 0 0]
 [0 0 2 2]
 [0 0 0 2]
 [0 0 0 0]]

1.2 cv2.connectedComponentsWithStats


image

stats 是 bounding box 的信息,N*5的矩阵,行对应每个label,五列分别为 [x0, y0, width, height, area]

参考链接

总结:该方法对二维图像去除假阳性区域很好用,但是无法对三维图像进行操作。

2 cc3d 提供的方法

cc3d 提供了二维和三维的方法实现连通成分分析

  • 3D 方法: 提供 26、18 或 6 个连通邻域划分区域
  • 2D 方法: 提供 4 和 8 个连通域分析。

cc3d github 地址

如何安装

pip install connected-components-3d

测试

测试代码及地址

比如二维图像:


image

原始图像有 [0, 31, 199] 3个值 背景是0,绿色是31, 199是紫色。

img = np.array(Image.open('./testing_img/test2d.png'))[:,:,0]
print(np.unique(img))
image
labels = cc3d.connected_components(labels, connectivity=4)

使用4邻域后,图片多了很多种颜色,每种颜色都代表一个区域,一共有78个区域。

划分成不同区域了,自然能提取出想要的区域。比如把 区域像素<阈值的置为0,从而去除假阳性。或者只保留前两个最大的区域,其余置为0.

最后,附上我真实处理三维数据的代码

import cc3d
import nibabel as nib
from pathlib2 import Path
from tqdm import tqdm
import numpy as np
import os


def main(data, output):
    data = Path(data).resolve()
    output = Path(output).resolve()

    assert data != output, f'postprocess data will replace original data, use another output path'

    if not output.exists():
        output.mkdir(parents=True)

    predictions = sorted(data.glob('*_seg.nii.gz'))
    for pred in tqdm(predictions):
        if not pred.name.startswith('.'):
            vol_nii = nib.load(str(pred))
            affine = vol_nii.affine
            vol = vol_nii.get_fdata()
            vol = post_processing(vol)
            vol_nii = nib.Nifti1Image(vol, affine)

            vol_nii_filename = output / pred.name
            vol_nii.to_filename(str(vol_nii_filename))


def post_processing(vol):
    vol_ = vol.copy()
    vol_[vol_ > 0] = 1
    vol_ = vol_.astype(np.int64)
    vol_cc = cc3d.connected_components(vol_)
    cc_sum = [(i, vol_cc[vol_cc == i].shape[0]) for i in range(vol_cc.max() + 1)]
    cc_sum.sort(key=lambda x: x[1], reverse=True)
    cc_sum.pop(0)  # remove background
    reduce_cc = [cc_sum[i][0] for i in range(1, len(cc_sum)) if cc_sum[i][1] < cc_sum[0][1] * 0.1]
    for i in reduce_cc:
        vol[vol_cc == i] = 0

    return vol


if __name__ == '__main__':
    data = 'output/'  # 分割结果地址,图像为nii.gz  
    output = 'output_remove/' # 移除假阳性后保存地址
    if not os.path.exists(output):
        os.makedirs(output)
    main(data, output)
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容