利用opencv提取目标区域实战【乳腺癌MRI】

   日期:2020-09-03     浏览:94    评论:0    
核心提示:以下的记录是科研小白使用OpenCV的一个小实战。声明:  我的代码主要是来自利用opencv提取目标区域,可惜的是,这位大佬写的博客有代码没有图,所以我又找到了这个博客。  据我推测,后面的这个博客应该是完全复制的之前那位大佬的文章(前者是2018年发表的,后者是2020年),但是后者补充上了过程中的图。所以我边观察着图,边照着代码做实验。...

以下的记录是科研小白使用OpenCV的一个小实战。

声明

  我的文章和代码主要是来自 vSLAM算法攻城狮 的这篇文章:利用opencv提取目标区域,还加了一些基于自己的图片的其他处理。可惜的是,这位大佬写的博客有代码没有图,所以我又找到了这个博客。
  据我推测,后面的这个博客应该是完全复制的之前那位大佬的文章(前者是2018年发表的,后者是2020年),但是后者补充上了过程中的图。所以我边观察着图,边照着代码做一个小实战。

目的

  提取MRI图像中的乳腺癌部分,图片是我从百度上随便找的。预期效果如下图所示:

步骤

  1. 转灰度图,并二值化
  2. 腐蚀和膨胀
  3. 找轮廓
1.转灰度图,并二值化

  根据原文,作者直接进行了转灰度图并二值化的操作,但是我按照作者的步骤尝试之后,发现我的图片中,与乳腺组织相连接的人体其他部位中会产生很多噪音,将不利于后续的分割病灶点。所以我首先制作了掩模,只保留有效区域。

  制作掩模时,要确定掩模的形状。我要做一个不规则的多边形,所以首先来确定多边形的点的坐标。最后达到如下图所示的掩模,将掩模与原图做“与”操作,就可以只保留有效区域。

寻找不规则图形的点的坐标的代码如下:
(代码主体来自:Python cv2 图片中鼠标点击获取像素点坐标)

import cv2
img = cv2.imread('breast.jpg')
a = []
b = []
def on_EVENT_LBUTTONDOWN(event, x, y, flags, param):
    if event == cv2.EVENT_LBUTTONDOWN:
        xy = "%d,%d" % (x, y)
        a.append(x)
        b.append(y)
        cv2.circle(img, (x, y), 1, (0, 0, 255), thickness=-1)
        cv2.putText(img, xy, (x, y), cv2.FONT_HERSHEY_PLAIN,
                    1.0, (0, 0, 255), thickness=1)
        cv2.imshow("image", img)
        print(x,y)

cv2.namedWindow("image")
cv2.setMouseCallback("image", on_EVENT_LBUTTONDOWN)
cv2.imshow("image", img)
cv2.waitKey(0)
print(a[0], b[0])

产生的效果大致如图:

输入的值如下:

  由于用鼠标点坐标点的时候,图片边缘部分的点会有一点偏差,所以到时候输入的时候需要根据自己图片的大小做一些校正,最后一个点出现在图片的外侧,可以直接看打印出来的结果。例如,我的图片大小是500x393,则第一个点校正为(0,245),最后一个点校正为(500,277),x,y坐标轴的问题参考opencv-图像的坐标系。
  除了打印出来的6个点,而且此时还缺两个顶点,分别是最右上的像素点(500,0)和最左上(0,0),此时一共有8个点。大致如下图所示。依据下图8个点的顺序将其输入到掩模的代码中。

  有了掩模的坐标点之后,就可以做出掩模了。此时参考的是这位朋友的代码【OpenCV 】图像掩模。代码如下:

import cv2
import numpy as np

img = cv2.imread('breast.jpg')

print(img.shape[:2]) #获取图片的前两个元素:高,宽

# 输入图像是RGB图像,故构造一个三维数组,8个二维数组是mask八个点的坐标,

#1-8,每个点的位置
site = np.array([[[0,245],[78,195],[186,192],[255,214],[366,198],[500,277],[500,0],[0,0]]], dtype=np.int32) 
im = np.zeros(img.shape[:2], dtype="uint8")  # 生成image大小的黑色空白图(全黑图)

cv2.polylines(im, site, 1, 255)  # 在im上画site大小的线,1表示线段闭合,255表示线段颜色(白色)
cv2.fillPoly(im, site, 255)  # 在im的site区域,填充颜色为255(白色)

mask = im
cv2.namedWindow('Mask')
cv2.imshow("Mask", mask)
masked = cv2.bitwise_and(img, img, mask=mask)  # 在模板mask上,将image和image做“与”操作
cv2.namedWindow('Mask to Image')
cv2.imshow("Mask to Image", masked)
cv2.waitKey(0)  # 图像一直显示,键盘按任意键即可关闭窗口
cv2.imwrite('breast_mask.jpg',masked)

  最终结果的掩模和我最初画的有一定的差距,但是大致形状是对应的。有了掩模之后如下图所示。此时,我们就只保留了有效的区域。最后使用imread()函数将右边的图保存,取名为breast_mask.jpg。
  

此时可以使用breast_mask.jpg进行转灰度图并二值化的操作了。
核心函数是:

img = cv2.threshold(blurred, thresh, 255, cv2.THRESH_BINARY)[1]

  原文中的图片目标区域是比较暗的,周边背景很亮,所以采用的是cv2.THRESH_BINARY_INV这个参数。而我的图片是目标区域亮,周围暗,所以我使用的参数是cv2.THRESH_BINARY。
为了更方便的调整阈值,原文中设立了一个滚动条,我觉得非常方便,效果如下:

代码如下:

import cv2
import numpy as np


def on_trace_bar_changed(args):
    pass

real = cv2.imread('breast.jpg') #原图
img_masked = cv2.imread('breast_mask.jpg')

# cv2.namedWindow("real")
# cv2.imshow("real", real)

gray = cv2.cvtColor(img_masked, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)

cv2.namedWindow("Image", cv2.WINDOW_NORMAL)

#使用进度条寻找阈值
cv2.createTrackbar('thres', 'Image', 0, 255, on_trace_bar_changed)
while True:
    cv2.imshow("Image", img_masked)
    thresh = cv2.getTrackbarPos('thres', 'Image')
    img_masked = cv2.threshold(blurred, thresh, 255, cv2.THRESH_BINARY)[1]
    # 键盘检测函数,0xFF是因为64位机器
    # https: // stackoverflow.com / questions / 20539497 / opencv - python - waitkey d- dont - respond
    k = cv2.waitKey(1) & 0xFF
    # print k
    if k == ord('q'): #按 q退出
        break

cv2.destroyAllWindows()

经过测试后阈值设定在176比较合适。(此时两个癌细胞团都比较明显的显示了出来)

2.腐蚀和膨胀

  可以看做膨胀(dilate)是将白色区域扩大,腐蚀(erode)是将黑色区域扩大。腐蚀和膨胀的次数和顺序需要手动调试,原文中写了个小文件,按e就进行erode,同时pring ‘erode’,便于统计,按d就就行dilate,按r恢复到二值图像。按e和d的次数会被打印出来,方便统计。效果如下图:(右边是经过先经过2次erode,再经过2次dilate处理过后的图)

代码如下:

img_masked = cv2.imread('breast_mask.jpg')

# cv2.namedWindow("real")
# cv2.imshow("real", real)

gray = cv2.cvtColor(img_masked, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
# 之前的结果表示最佳阈值大概是176
thresh = cv2.threshold(blurred, 176, 255, cv2.THRESH_BINARY)[1]   
cv2.namedWindow("thresh")
cv2.imshow('thresh',thresh)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))

img = thresh

while True:
    cv2.imshow("change", img)
    # 键盘检测函数,0xFF是因为64位机器
    k = cv2.waitKey(1) & 0xFF
    # print k
    if k == ord('e'):
        # 加上iterations是为了记住这个参数,不加也行
        img = cv2.erode(img, kernel, iterations=1)
        print('erode')
    if k == ord('d'):
        img = cv2.dilate(img, kernel, iterations=1)
        print('dilate')
    if k == ord('r'):
        img = thresh
        print('return threshold image')
    if k == ord('q'):
        break
cv2.destroyAllWindows()

3.找轮廓

  使用findContour函数。最后效果如下:

代码如下:

# 2次腐蚀,2次膨胀
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
thresh = cv2.erode(thresh, kernel, iterations=2)
thresh = cv2.dilate(thresh, kernel, iterations=2)

cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
                        cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0]
result = cv2.drawContours(real, cnts, -1, (0, 255, 0), 2)


cv2.imshow("Image", result)

cv2.waitKey()
cv2.destroyAllWindows()

原文中还将背景图改为黑色,但是我这张图本来就是黑色,所以不需要修改了。

完整代码

  我分成了两个python文件,分别是mask.py和opencv_breast.py。其中mask.py用于制作掩模,保留有效区域,最后生成带掩模的图片breast_mask.jpg。opencv_breast.py用于进行乳腺癌的分割。

mask.py的完整代码如下:

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('breast.jpg')

#寻找坐标点
""" a = [] b = [] def on_EVENT_LBUTTONDOWN(event, x, y, flags, param): if event == cv2.EVENT_LBUTTONDOWN: xy = "%d,%d" % (x, y) a.append(x) b.append(y) cv2.circle(img, (x, y), 1, (0, 0, 255), thickness=-1) cv2.putText(img, xy, (x, y), cv2.FONT_HERSHEY_PLAIN, 1.0, (0, 0, 255), thickness=1) cv2.imshow("image", img) print(x,y) cv2.namedWindow("image") cv2.setMouseCallback("image", on_EVENT_LBUTTONDOWN) cv2.imshow("image", img) cv2.waitKey(0) print(a[0], b[0]) """

print(img.shape[:2]) #获取图片的前两个元素:高,宽

# 输入图像是RGB图像,故构造一个三维数组,8个二维数组是mask8个点的坐标,

site = np.array([[[0,245],[78,195],[186,192],[255,214],[366,198],[500,277],[500,0],[0,0]]], dtype=np.int32) #1-8,每个点的位置
im = np.zeros(img.shape[:2], dtype="uint8")  # 生成image大小的黑色空白图(全黑图)

cv2.polylines(im, site, 1, 255)  # 在im上画site大小的线,1表示线段闭合,255表示线段颜色(白色)
cv2.fillPoly(im, site, 255)  # 在im的site区域,填充颜色为255(白色)

mask = im
cv2.namedWindow('Mask')
cv2.imshow("Mask", mask)
masked = cv2.bitwise_and(img, img, mask=mask)  # 在模板mask上,将image和image做“与”操作
cv2.namedWindow('Mask to Image')
cv2.imshow("Mask to Image", masked)
cv2.waitKey(0)  # 图像一直显示,键盘按任意键即可关闭窗口
cv2.imwrite('breast_mask.jpg',masked)

opencv_breast.py的完整代码如下:

import cv2
import numpy as np

def on_trace_bar_changed(args):
    pass

real = cv2.imread('breast.jpg') #原图
img_masked = cv2.imread('breast_mask.jpg') #带掩模的图

# cv2.namedWindow("real")
# cv2.imshow("real", real)

gray = cv2.cvtColor(img_masked, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)


""" cv2.namedWindow("Image", cv2.WINDOW_NORMAL) #使用进度条寻找阈值 cv2.createTrackbar('thres', 'Image', 0, 255, on_trace_bar_changed) while True: cv2.imshow("Image", img_masked) thresh = cv2.getTrackbarPos('thres', 'Image') img_masked = cv2.threshold(blurred, thresh, 255, cv2.THRESH_BINARY)[1] # 键盘检测函数,0xFF是因为64位机器 # https: // stackoverflow.com / questions / 20539497 / opencv - python - waitkey d- dont - respond k = cv2.waitKey(1) & 0xFF # print k if k == ord('q'): break """

# 之前的结果表示最佳阈值大概是176
thresh = cv2.threshold(blurred, 176, 255, cv2.THRESH_BINARY)[1]

# cv2.namedWindow("thresh")
# cv2.imshow('thresh',thresh)

""" kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) img = thresh while True: cv2.imshow("change", img) # 键盘检测函数,0xFF是因为64位机器 k = cv2.waitKey(1) & 0xFF # print k if k == ord('e'): # 加上iterations是为了记住这个参数,不加也行 img = cv2.erode(img, kernel, iterations=1) print('erode') if k == ord('d'): img = cv2.dilate(img, kernel, iterations=1) print('dilate') if k == ord('r'): img = thresh print('return threshold image') if k == ord('q'): break cv2.destroyAllWindows() """


# 2次腐蚀,2次膨胀
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
thresh = cv2.erode(thresh, kernel, iterations=2)
thresh = cv2.dilate(thresh, kernel, iterations=2)

cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
                        cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0]
result = cv2.drawContours(real, cnts, -1, (0, 255, 0), 2)


cv2.imshow("Image", result)

cv2.waitKey()
cv2.destroyAllWindows()

总结

仅看这张图的话效果还不错。但是固定阈值的方式泛化能力差,不能应用到大数据集上。考虑继续学习其他算法

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服