python画图像_如何使用python将mask绘制到对应的图像上

在使用深度学习等方法处理计算机视觉问题而对图像进行处理的过程中,不可避免地要处理原始图像及其相应的mask。比如将mask绘制到原始图像上,将mask的轮廓绘制到原始图像上,提取mask的轮廓,或者已知mask的轮廓而将mask填充,等等。

尽管这些问题都不是复杂的问题,但使用频率比较高,而每一次对其进行处理时都会浪费时间甚至分心,而耽误真正的任务的执行。因此,本文就将在处理这些问题中的经验进行一下总结,同时也为以后的使用备忘。

当然,因为我的经验主要还是在医学图像的处理上,所以这里就以医学图像为例来进行处理。

首先我们来看这样一张原始图像:

2355154317-0.png

这是一个肺部CT的一个slice,下面我们给出肺分割的mask,即从上面原始图像中分割出肺部区域的mask,如下图:

2355152162-1.png

可以看到肺部mask刚好对应了原始图像中的肺部区域,两个图像相乘,即可从原始图像中提取出肺部区域。

这是已知mask之后,图像处理的几个需求:

1.从原始图像中取出mask区域;

2.将mask区域绘制到原始图像上。

其中,1比较简单,可以直接将原始图像与mask相乘即可;而2则有两种需求,一种是直接将mask像蒙版一样覆盖到原始图像上,这样虽然可以看到mask对应的区域,但绘制上去的mask会对原始图像造成影响;另一种则是将mask的轮廓绘制到原始图像上,这样即可以看到mask区域,对原始图像影响也较小。

1.在原始图像上绘制mask的轮廓

将原始mask使用OpenCV读进来mask = cv2.imread('mask.png', 0)mask = cv2.imread('mask.png', 0),可以看到,mask是一个二维numpy array,只有0和255两个值。显然,255对应的部分表示选中的区域,0表示未选中的区域。但是这样一个mask array却没有给出mask的轮廓;而绘制mask的轮廓,首先则要找到mask的轮廓。

找到mask的轮廓通常有这样两个包可以使用,一个就是上面用的OpenCV,还有一个是skimage里提供的,下面我们分别使用这两种工具来寻找轮廓进行绘制。

1.1 使用OpenCV寻找轮廓

OpenCV里有findContours函数,可以专门用来从mask中寻找其轮廓,如下:

def draw_mask_edge_on_image_cv2(image, mask, color=(0, 0, 255)):

coef = 255 if np.max(image) < 3 else 1

image = (image * coef).astype(np.float32)

contours, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)

cv2.drawContours(image, contours, -1, color, 1)

cv2.imwrite('test.png', image)

def draw_mask_edge_on_image_cv2(image, mask, color=(0, 0, 255)):

coef = 255 if np.max(image) < 3 else 1

image = (image * coef).astype(np.float32)

contours, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)

cv2.drawContours(image, contours, -1, color, 1)

cv2.imwrite('test.png', image)

def draw_mask_edge_on_image_cv2(image, mask, color=(0, 0, 255)):

coef = 255 if np.max(image) < 3 else 1

image = (image * coef).astype(np.float32)

contours, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)

cv2.drawContours(image, contours, -1, color, 1)

cv2.imwrite('test.png', image)

其中,第2和3行是用来对image进行一个前期处理,如果image的值是0~1的浮点数范围,将其map到0~255;如果直接是0~255的,则不进行处理;至于将image转化为float32格式,则是因为后面第5行对image进行操作时,需要image是float32或者uint8等几种格式,所以我们先期转好。

第4行,即使用findContours函数来从mask中寻找其轮廓(即contours),需要注意,这里传入的mask必须是二值图像;后面的两个参数则分别为mode和method,可以根据实际进行修改选用。

还需要注意的是这个函数寻找到的轮廓,即contours的格式,其为一个列表,列表的元素为一个个contour,每一个contour为一个numpy array,它是一个三维的array,其shape类似:(20, 1, 2),表示该contour一共有20个点,每个点包括x、y两个坐标,其顺序也是x, y。至于为什么是三维,我也搞不懂,应该是设计时就是这个样子吧。

第5行将image转化为OpenCV特殊的彩色图格式BGR,则是因为后面绘制轮廓时为了醒目,绘制了红色的轮廓,如果不先期将image转化为彩色图模式,则绘制的轮廓仍然为灰色,会造成误导。转换前,image的shape为335*335,转换后,则为335*335*3。

第6行是将寻找到的轮廓绘制到image上,这个drawContours函数的第1、2个参数分别为image和轮廓,第3个参数表示绘制第几个contour,设为-1,表示绘制所有的contours,然后就是轮廓的颜色,最后是轮廓的thickness,需要注意的是,如果设置为-1,则表示以填充模式绘制轮廓,又相当于将提取出的轮廓还原为原始mask了。

第7行将绘制了轮廓之后的image保存一下,以便查看效果。

1.2 使用skimage寻找轮廓

接着我们来看使用skimage包中的相应函数来寻找mask的轮廓并进行绘制,如下:

def draw_mask_edge_on_image_skimage(image, mask, color=(0, 0, 255)):

coef = 255 if np.max(image) < 3 else 1

image = (image * coef).astype(np.float32)

contours = measure.find_contours(mask, 0.5)

image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)

for c in contours:

c = np.around(c).astype(np.int)

image[c[:, 0], c[:, 1]] = np.array(color)

cv2.imwrite('test.png', image)

def draw_mask_edge_on_image_skimage(image, mask, color=(0, 0, 255)):

coef = 255 if np.max(image) < 3 else 1

image = (image * coef).astype(np.float32)

contours = measure.find_contours(mask, 0.5)

image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)

for c in contours:

c = np.around(c).astype(np.int)

image[c[:, 0], c[:, 1]] = np.array(color)

cv2.imwrite('test.png', image)