肺实质分割

声明:本人博客用于记录和分享自己的学习成果,言辞不一定严谨,有错误希望各位读者批评指正。更严谨的论述请查看相关论文和开发手册。
肺实质分割_第1张图片

1、自适应阈值分割

实验用图源自LUNA16数据集,首先对原始图像采用自适应阈值分割。原理就不细讲了,比较简单。
附上源码:

ret, dst = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)

肺实质分割_第2张图片

2、填充胸腔外部区域

(1)由自适应阈值分割图可以看到,需要先去除胸腔外界的杂物使用fillHole对外界的空洞进行填充。调用opencv中的函数就可以。泛洪操作后图像的宽和高会增加两个像素点,需要resize处理恢复原样。
附上代码:

def fillHole(image):
    h, w = image.shape[:2]
    mask = np.zeros(((h+2, w+2)), np.uint8)
    cv2.floodFill(image, mask, (0, 0), 255)

    return image

(2)注意使用泛洪操作的时候会存在一个问题,如果CT的胸腔与图像左右两侧边缘连接的话(如下图所示),泛洪操作则不会越过胸前与图像边缘相连的区域,造成图像下半部分无法进行空洞填充。因此可以在做泛洪填充之前做一个边界框处理, 增加图像与边缘之间的空隙。

constant = cv2.copyMakeBorder(dst, 5, 5, 5, 5, cv2.BORDER_CONSTANT)

肺实质分割_第3张图片
(3)对泛洪操作后的图像进行翻转

    constant = cv2.bitwise_not(constant)

肺实质分割_第4张图片

3、填充空洞

在填充胸腔外部区域后,会留下一些由于噪声形成的空洞,设置区域范围,填充小于该区域一下的边缘,以此除去噪声。

def fill_small_region(image, max_area, num):
    fill_contour = []

    contours, _ = cv2.findContours(image, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
    for contour in contours:
        area = cv2.contourArea(contour)
        if area <= max_area:
            fill_contour.append(contour)
    cv2.fillPoly(image, fill_contour, num)

    return image
fill_image = fill_small_region(constant, 1000, 0)

肺实质分割_第5张图片

4、形态学处理

到这部分就是一些形态学处理了,例如开运算闭运算等等。

kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (10, 10))
fill_image = cv2.morphologyEx(fill_image, cv2.MORPH_CLOSE, kernel=kernel)

肺实质分割_第6张图片
形态学处理会造成胸腔内部区域有空洞,因此需要再进行一次填充空洞操作。结果如下
肺实质分割_第7张图片

5、获取肺实质

经过处理后的原图像除以255,即白色区域为1,黑色区域为0,得到的图像矩阵与原始图像矩阵进行点乘,即得到肺实质。注意原始图像矩阵读入为灰度图,否则读入为原始图像则会变成BGR三通道图。

6、个人实际使用总结

大多数论文的肺实质分割都会写这几个流程。但是实际上处理起来还是更麻烦一点。因为肺结节中存在胸膜旁结节和血管旁结节,这两类结节会和胸腔壁以及肺血管长在一起,在形态学处理过程中会被处理掉而无法显示出来。也有不少论文针对这类附着性的结节进行处理的,但是我本着大道至简的原则,直接使用了矩形框。如下所示。
肺实质分割_第8张图片
肺实质分割_第9张图片
以上两个图是我在处理过程中进行的肺实质提取方法。直接提取了肺实质区域的外接矩形。虽然没有直接提取肺实质区域的方式那么完美。但是在实际使用中,效果还不错。
附上代码:

def find_min_rectangle(image):
    contours, _ = cv2.findContours(image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)

    for contour in contours:
        x, y, w, h = cv2.boundingRect(contour)
        # 有些床板纹路会在预处理过程中漏掉,设定生成的矩形框位置
        if y < 384:                     
            image[y:y+h, x:x+w] = 255
        else:
            image[y:y + h, x:x + w] = 0

    return image
def find_max_rectangle(image):
    '''
    find the max rectangle include all lung parenchyma
    :param image:
    :return:
    '''
    contours, _ = cv2.findContours(image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)

    x1 = []
    x11 = []
    y1 = []
    y11 = []
    for contour in contours:
        x, y, w, h = cv2.boundingRect(contour)
        # 有些床板纹路会在处理过程中漏掉,设定生成的矩形框位置
        if y > 384:
            image[y:y + h, x:x + w] = 0
        else:
            x1.append(x)
            x11.append(x+w)
            y1.append(y)
            y11.append(y+h)

    x_min = np.min(x1)
    y_min = np.min(y1)
    x_max = np.max(x11)
    y_max = np.max(y11)
    image[y_min:y_max, x_min:x_max] = 255

    return image, [x_min, y_min, x_max, y_max]

声明:这种方法请读者以批判性的眼光参考

你可能感兴趣的:(科研狗的日常之肺结节分割,python,opencv,计算机视觉)