图像分割与提取

图像处理中,需要从图像中将前景对象作为目标图像分割或者提取出来,例如,视频监控中,背景是固定的我们需要的是背景中出现的车 行人等。我们希望将这些对象从视频中提取出来,忽略没有对象进入背景的视频内容。

用分水岭算法实现图像分割与提取

分水岭算法将图像形象的比喻成地理学上的地形表面,实现图像分割

算法原理

任何一副灰度图,都可以看作地理学上的地形表面。灰度值高的区域看作山峰,灰度值低的看作山谷。如果我们向每一个山谷中灌入不同颜色的水,随着水位升高,不同山谷的水就会汇集到一起,这个过程中,为了防止不同山谷的水交汇,需要再水流可能汇合的地方构建堤坝。该过程将图像分成两个不同的集合:集水盆地和分水岭线。我们构建的堤坝就是分水岭线,也就是对图像的分割。。蛮形象的

opencv血管分割 opencv分割算法_opencv


由于噪声等因素的影响,采用上述基础分水岭算法经常会得到过度分割的结果,为了改善图像分割效果,提出了基于掩模的改进的分水岭算法,允许用户将他认为是同一个分割区域的部分标注出来(就是掩模),这样再分水岭算法处理时,就会对标注部分处理为同一个分割区域。

opencv血管分割 opencv分割算法_图像分割_02

相关函数

使用 cv2.watershed() 实现,同时需要借助形态学函数,距离变换函数来完成图像分割

  1. 通过形态学函数获得边界
  2. opencv血管分割 opencv分割算法_图像分割_03

  3. 当然形态学操作和减法运算只能使用与简单的图形。
  4. 距离变换函数 distanceTransform

图像内各个子图没有连接时可以直接使用腐蚀确定前景对象,但是图个图像内各个子图连接再一起,就很难确定前景对象了,此时借助于距离变换函数 cv2.distanceTransform() 可以方便的将前景对提取出来。

距离变换函数计算二值图像内任意点到最近背景点的距离。一般来说,该函数计算的是图像内非零值像素点到最近的零值像素点的距离,即计算二值图中所有像素点距离其最近的值为0的像素点的距离,如果像素值为0,距离也是0.,这个函数反应了各像素与背景的距离关系

如果前景对象的中心(质心)距离值为0的像素点距离较远,就得到一个较大的值。较近就得到较小的值。再对计算结果进行阈值化,就可以得到图像内子图的中心,骨架等信息,距离变换函数还可以计算对象的中心,细化轮廓,获取图像的前景的。

dst=cv2.distanceTransform(src, distanceType, maskSize[, dstType]])

  • src 8位单通道二值图
  • distanceTyep 距离类型参数
  • maskSize 掩模尺寸,注意,distanceType=cv2.DIST_L1 或 DIST_C 时,maskSize 强制设置为3(3或5或更大的没啥区别)
  • dstType 目标函数的类型,默认 CV_32F
img = cv2.imread('yingbi.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
img=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
ishow=img.copy()               # 阈值处理,THRESH_OTSU 是使用Otsu算法时的可选阈值,进行二值化的高效算法
gray=cv2.GaussianBlur(gray,(5,5),0,0)            # 可以加个滤波啥的去噪
ret, thresh = cv2.threshold(gray,233,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU) # 这个阈值就不用我们自己定了随便设个
kernel = np.ones((2,2),np.uint8)        # 通用形态学函数,先腐蚀后膨胀
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel, iterations = 1)
dist_transform = cv2.distanceTransform(opening,cv2.DIST_L2,3)
ret, fore = cv2.threshold(dist_transform,0.7*dist_transform.max(),255,0) # 这些参数根据自己的图调调
plt.subplot(131)
plt.imshow(ishow)
plt.axis('off')
plt.subplot(132)
plt.imshow(dist_transform)
plt.axis('off')
plt.subplot(133)
plt.imshow(fore)
plt.axis('off')

opencv血管分割 opencv分割算法_像素点_04

看图的话,distanceTransform就是计算非零元素到零值像素的距离,距离远就是黄色,噪声去的不好就会产生一个一个的洞,,然后再通过阈值提一下,得到的应该是硬币的圆心,,至于为啥那个黄色的硬币没提出来,问就是图不好,形态学操作提不出个完整的圆,,

opencv血管分割 opencv分割算法_分水岭算法_05

根据实际嘛,,把灰度图的像素再减个100像素,黑一点效果就好多了。。。

  1. 确定未知区域
    使用形态学的膨胀操作可以将图像内的前景对象放大,相应的背景就会被压缩,所以此时得到的背景信息一定小于实际背景,不包含前景的确定背景,距离变换函数可以获取图像的中心,得到确定前景,有了确定前景,确定背景,剩下的区域就是未知区域了,这部分正是分水岭算法要进一步明确的区域。 啥确定前景,确定背景,记得是腐蚀和距离变换函数得到的就行了。。。

未知区域 UN =(图像 O - 确定背景 B)- 确定前景 F

img = cv2.imread('yingbi2.jpg')
img = cv2.subtract(img,100)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
gray = cv2.subtract(gray,100)
img=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
ishow=img.copy()    # 阈值处理,THRESH_OTSU 是使用Otsu算法时的可选阈值,进行二值化的高效算法
gray=cv2.GaussianBlur(gray,(5,5),0,0)
ret, thresh = cv2.threshold(gray,233,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
kernel = np.ones((3,3),np.uint8)  # 通用形态学函数,先腐蚀后膨胀
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel, iterations = 2)

bg = cv2.dilate(opening,kernel,iterations=3) # 腐蚀
dist_transform = cv2.distanceTransform(opening,cv2.DIST_L2,5)
ret, fore = cv2.threshold(dist_transform,0.7*dist_transform.max(),255,0)
fore = np.uint8(fore)
un = cv2.subtract(bg,fore)       # 未知区域

plt.subplot(221)
plt.imshow(ishow)
plt.axis('off')
plt.subplot(222)
plt.imshow(bg)
plt.axis('off')
plt.subplot(223)
plt.imshow(fore)
plt.axis('off')
plt.subplot(224)
plt.imshow(un)
plt.axis('off')                # 这未知区域就是硬币除了得到的圆心(近似)以外的区域吧,,,

opencv血管分割 opencv分割算法_opencv_06


右上那个图中,后面的深色背景就是 确定背景。左下那个黄色的就是确定前景

  1. 函数connectedComponents

明确了确定前景,就可以对确定前景图像进行标注,这个函数将背景标注为0,将其他对象从1开始的正整数标注
retval, labels = cv2.connectedComponents( image ) image 8位单通道待标注对象,retval 返回的标注数量,labels结果图像

opencv血管分割 opencv分割算法_像素点_07


就是多了一句话,上面的代码都是一样的,就不占位置了。。。其他对象用从1开始的正整数标注,代表不同的前景区域。分水岭算法中,标注为0 的代表未知区域,我们需要再commectedComponents() 的结果上调整一下,标注结果加一,令数值1 代表背景区域,数值2 开始代表不同的前景区域。另外,为了使用分水岭算法,还要对原始图像内的未知区域进行标注,将已经计算出来的未知区域标注为0 即可。

opencv血管分割 opencv分割算法_分水岭算法_08


右边是标记后的结果,细看,,每个确定前景都有了一个黑色(我看是紫色)的边缘,就是被标记的未知区域。

  1. 函数cv2.watershed()
    然后我们终于可以使用分水岭算法对预处理结果的图像分割了。。markers = cv2.watershed( image, markers )
  • image 输入图像,必须为 8位三通道图像,对图像进行函数处理之前,必须用正数大致勾勒出图像中期望分割区域,未确定的区域标记为0,可以将标注区域理解为进行分水岭算法分割的种子区域。
  • markers 是32位单通道的标注结果,与image 等大小,markers中,每个像素要么被设置位初期的种子值,要么就是-1 表示边界,可以省略

总结一下基本步骤:

  • 通过形态学开运算对原始图像去噪
  • 通过腐蚀操作获取确定背景,
  • 利用距离变换函数对原始图像进行运算,并对其进行阈值处理得到 确定前景
  • 计算未知区域
  • 利用connectedComponents() 对原始图像进行标注并对结果进行修正
  • 使用分水岭函数完成图像分割
img = cv2.imread('yingbi2.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
gray = cv2.subtract(gray,100)
img=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
ishow=img.copy()
gray=cv2.GaussianBlur(gray,(5,5),0,0)                  # 感觉加上高斯消除效果就好多了,,,
ret, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
kernel = np.ones((1,1),np.uint8)
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel, iterations = 1)
sure_bg = cv2.dilate(opening,kernel,iterations=3)  # 获得确定背景
dist_transform = cv2.distanceTransform(opening,cv2.DIST_L2,5) # 获得确定前景
ret, sure_fg = cv2.threshold(dist_transform,0.7*dist_transform.max(),255,0)
sure_fg = np.uint8(sure_fg)
unknown = cv2.subtract(sure_bg,sure_fg)  # 得到未知区域
ret, markers = cv2.connectedComponents(sure_fg)
markers = markers+1
markers[unknown==255] = 0          # 改变标记值
markers = cv2.watershed(img,markers) # img是8位三通道,参数markers标记修改后的
img[markers == -1] = [0,255,0]       # markers 中-1就是表示边界,用绿色显示
plt.subplot(121)
plt.imshow(ishow)
plt.axis('off')
plt.subplot(122)
plt.imshow(img)
plt.axis('off')

opencv血管分割 opencv分割算法_图像分割_09


额,这东西有啥用来着,,感觉又又又跟轮廓检测的效果一样,,,