声明:本人博客用于记录和分享自己的学习成果,言辞不一定严谨,有错误希望各位读者批评指正。更严谨的论述请查看相关论文和开发手册。
实验用图源自LUNA16数据集,首先对原始图像采用自适应阈值分割。原理就不细讲了,比较简单。
附上源码:
ret, dst = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
(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)
constant = cv2.bitwise_not(constant)
在填充胸腔外部区域后,会留下一些由于噪声形成的空洞,设置区域范围,填充小于该区域一下的边缘,以此除去噪声。
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)
到这部分就是一些形态学处理了,例如开运算闭运算等等。
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (10, 10))
fill_image = cv2.morphologyEx(fill_image, cv2.MORPH_CLOSE, kernel=kernel)
形态学处理会造成胸腔内部区域有空洞,因此需要再进行一次填充空洞操作。结果如下
经过处理后的原图像除以255,即白色区域为1,黑色区域为0,得到的图像矩阵与原始图像矩阵进行点乘,即得到肺实质。注意原始图像矩阵读入为灰度图,否则读入为原始图像则会变成BGR三通道图。
大多数论文的肺实质分割都会写这几个流程。但是实际上处理起来还是更麻烦一点。因为肺结节中存在胸膜旁结节和血管旁结节,这两类结节会和胸腔壁以及肺血管长在一起,在形态学处理过程中会被处理掉而无法显示出来。也有不少论文针对这类附着性的结节进行处理的,但是我本着大道至简的原则,直接使用了矩形框。如下所示。
以上两个图是我在处理过程中进行的肺实质提取方法。直接提取了肺实质区域的外接矩形。虽然没有直接提取肺实质区域的方式那么完美。但是在实际使用中,效果还不错。
附上代码:
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]
声明:这种方法请读者以批判性的眼光参考