您现在的位置是:网站首页> 编程资料编程资料

python中的opencv 图像分割与提取_python_

2023-05-26 309人已围观

简介 python中的opencv 图像分割与提取_python_

图像分割与提取

图像中将前景对象作为目标图像分割或者提取出来。对背景本身并无兴趣分水岭算法及GrabCut算法对图像进行分割及提取。

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

分水岭算法将图像形象地比喻为地理学上的地形表面,实现图像分割,该算法非常有效。

算法原理

任何一幅灰度图像,都可以被看作是地理学上的地形表面,灰度值高的区域可以被看成是山峰,灰度值低的区域可以被看成是山谷。

左图是原始图像,右图是其对应的“地形表面”。

该过程将图像分成两个不同的集合:集水盆地和分水岭线。我们构建的堤坝就是分水岭线,也即对原始图像的分割。这就是分水岭算法。

由于噪声等因素的影响,采用上述基础分水岭算法经常会得到过度分割的结果。过度分割会将图像划分为一个个稠密的独立小块,让分割失去了意义。为了改善图像分割效果,人们提出了基于掩模的改进的分水岭算法。改进的分水岭算法允许用户将他认为是同一个分割区域的部分标注出来(被标注的部分就称为掩模)。分水岭算法在处理时,就会将标注的部分处理为同一个分割区域。

例如:

原始图像,对其做标注处理,其中被标注为深色的三个小色块表示,在使用掩模分水岭算法时,这些部分所包含的颜色都会被分割在同一个区域内。

相关函数介绍

在OpenCV中,可以使用函数cv2.watershed()实现分水岭算法。

在具体的实现过程中,还需要借助于形态学函数、距离变换函数cv2.distanceTransform()、cv2.connectedComponents()来完成图像分割。

  • 形态学函数
    在使用分水岭算法对图像进行分割前,需要对图像进行简单的形态学处理。
  • 开运算
    开运算是先腐蚀、后膨胀的操作,开运算能够去除图像内的噪声
    在用分水岭算法处理图像前,要先使用开运算去除图像内的噪声,以避免噪声对图像分割可能造成的干扰。
  • 获取图像边界
    通过形态学操作和减法运算能够获取图像的边界。
    使用形态学变换,获取一幅图像的边界信息
import cv2 import numpy as np import matplotlib.pyplot as plt o=cv2.imread("my.bmp", cv2.IMREAD_UNCHANGED) k=np.ones((5,5), np.uint8) e=cv2.erode(o, k) b=cv2.subtract(o, e) plt.subplot(131) plt.imshow(o) plt.axis('off') plt.subplot(132) plt.imshow(e) plt.axis('off') plt.subplot(133) plt.imshow(b) plt.axis('off') plt.show()

使用形态学操作和减法运算能够获取图像的边界信息。但是,形态学操作仅适用于比较简单的图像。如果图像内的前景对象存在连接的情况,使用形态学操作就无法准确获取各个子图像的边界了。

  • 距离变换函数distanceTransform
    当图像内的各个子图没有连接时,可以直接使用形态学的腐蚀操作确定前景对象,但是如果图像内的子图连接在一起时,就很难确定前景对象了
    此时,借助于距离变换函数cv2.distanceTransform()可以方便地将前景对象提取出来。
    函数cv2.distanceTransform()计算二值图像内任意点到最近背景点的距离。
    一般情况下,该函数计算的是图像内非零值像素点到最近的零值像素点的距离,即计算二值图像中所有像素点距离其最近的值为0的像素点的距离。

如果像素点本身的值为0,则这个距离也为0。

  • cv2.distanceTransform()的计算结果反映了各个像素与背景(值为0的像素点)的距离关系。

通常情况下:

  • 如果前景对象的中心(质心)距离值为0的像素点距离较远,会得到一个较大的值。
  • 如果前景对象的边缘距离值为0的像素点较近,会得到一个较小的值。

如果对上述计算结果进行阈值化,就可以得到图像内子图的中心、骨架等信息。距离变换函数cv2.distanceTransform()可以用于计算对象的中心,还能细化轮廓、获取图像前景等

函数cv2.distanceTransform()的语法格式为:

dst=cv2.distanceTransform(src, distanceType, maskSize[, dstType]])
  • src是8位单通道的二值图像。
  • distanceType为距离类型参数

  • maskSize为掩模的尺寸
    distanceType=cv2.DIST_L1或cv2.DIST_C时,maskSize强制为3(因为设置为3和设置为5及更大值没有什么区别)。

  • dstType为目标图像的类型,默认值为CV_32F。
  • dst表示计算得到的目标图像,可以是8位或32位浮点数,尺寸和src相同。

使用距离变换函数cv2.distanceTransform(),计算一幅图像的确定前景

import numpy as np import cv2 import matplotlib.pyplot as plt img = cv2.imread('water_coins.jpg') gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) img=cv2.cvtColor(img, cv2.COLOR_BGR2RGB) ishow=img.copy() ret, thresh = cv2.threshold(gray,0,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) # 进行开运算 dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2,5) 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') plt.show()

fore图像中: 比较准确地显示出左图内的“确定前景”。确定前景,通常是指前景对象的中心。之所以认为这些点是确定前景,是因为它们距离背景点的距离足够远,都是距离大于足够大的固定阈值(0.7*dist_transform.max())的点。

  • 确定未知区域
    使用形态学的膨胀操作能够将图像内的前景“膨胀放大”。
    当图像内的前景被放大后,背景就会被“压缩”,所以此时得到的背景信息一定小于实际背景的,不包含前景的“确定背景”。

为了方便说明将确定背景称为B。

  • 距离变换函数cv2.distanceTransform()能够获取图像的“中心”,得到“确定前景”。
    图像中有了确定前景F和确定背景B,剩下区域的就是未知区域UN了。这部分区域正是分水岭算法要进一步明确的区域。
    针对一幅图像O,通过以下关系能够得到未知区域UN:
  • 未知区域UN=图像O-确定背景B-确定前景F
  • 未知区域UN=(图像O-确定背景B)- 确定前景F

“图像O-确定背景B”,可以通过对图像进行形态学的膨胀操作得到。

标注一幅图像的确定前景、确定背景及未知区域。

import numpy as np import cv2 import matplotlib.pyplot as plt img = cv2.imread('water_coins.jpg') gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) img=cv2.cvtColor(img, cv2.COLOR_BGR2RGB) ishow=img.copy() ret, thresh = cv2.threshold(gray,0,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 = cv2.distanceTransform(opening, cv2.DIST_L2,5) ret, fore = cv2.threshold(dist,0.7*dist.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') plt.show()

  • 函数connectedComponents
    明确了确定前景后,就可以对确定前景图像进行标注了。
    在OpenCV中,可以使用函数cv2.connectedComponents()进行标注。该函数会将背景标注为0,将其他的对象使用从1开始的正整数标注。
    函数cv2.connectedComponents()的语法格式为:

retval, labels = cv2.connectedComponents( image )

  • image为8位单通道的待标注图像。
  • retval为返回的标注的数量。
  • labels为标注的结果图像。

使用函数cv2.connectedComponents()标注一幅图像

import numpy as np import cv2 import matplotlib.pyplot as plt img = cv2.imread('water_coins.jpg') gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) img=cv2.cvtColor(img, cv2.COLOR_BGR2RGB) ishow=img.copy() ret, thresh = cv2.threshold(gray,0,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) sure_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) ret, markers = cv2.connectedComponents(fore) print(ret) plt.subplot(131) plt.imshow(ishow) plt.axis('off') plt.subplot(132) plt.imshow(fore) plt.axis('off') plt.subplot(133) plt.imshow(markers) plt.axis('off') plt.show()

前景图像的中心点被做了不同的标注(用不同颜色区分)

函数cv2.connectedComponents()在标注图像时,会将背景标注为0,将其他的对象用从1开始的正整数标注。具体的对应关系为:

  • 数值0代表背景区域。
  • 从数值1开始的值,代表不同的前景区域。

在分水岭算法中,标注值0代表未知区域。所以,我们要对函数cv2.connectedComponents()标注的结果进行调整:将标注的结果都加上数值1。经过上述处理后,在标注结果中:

  • 数值1代表背景区域。
  • 从数值2开始的值,代表不同的前景区域。

为了能够使用分水岭算法,还需要对原始图像内的未知区域进行标注,将已经计算出来的未知区域标注为0即可。

关键代码:

ret, markers = cv2.connectedComponents(fore) markers = markers+1 markers[未知区域] = 0

使用函数cv2.connectedComponents()标注一幅图像,并对其进行修正,使未知区域被标注为0

import numpy as np import cv2 import matplotlib.pyplot as plt img = cv2.imread('water_coins.jpg') gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) img=cv2.cvtColor(img, cv2.COLOR_BGR2RGB) ishow=img.copy() ret, thresh = cv2.threshold(gray,0,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) sure_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) ret, markers1 = cv2.connectedComponents(fore) foreAdv=fore.copy() unknown = cv2.subtract(sure_bg, foreAdv) ret, markers2 = cv2.connectedComponents(foreAdv) markers2 = markers2+1 markers2[unknown==255] = 0 plt.subplot(121) plt.imshow(markers1) plt.axis('off') plt.subplot(122) plt.imshow(markers2) plt.axis('off') plt.show()

前景都有一个黑色的边缘,这个边缘是被标注的未知区域。

  • 函数cv2.watershed()
    完成上述处理后,就可以使用分水岭算法对预处理结果图像进行分割了。
    在OpenCV中,实现分水岭算法的函数是cv2.watershed(),其语法格式为:
markers = cv2.watershed( image, markers )
  • image是输入图像,必须是8位三通道的图像。在对图像使用

cv2.watershed()函数处理之前,必须先用正数大致勾画出图像中的期望分割区域。每一个分割的区域会被标注为1、2、3等。对于尚未确定的区域,需要将它们标注为0。我们可以将标注区域理解为进行分水岭算法分割的“种子”区域。

  • markers是32位单通道的标注结果,它应该和image具有相等大小。在markers中,每一个像素要么被设置为初期的“种子值”,要么被设置为**“-1”表示边界**。

分水岭算法图像分割实例

使用分水岭算法进行图像分割时,基本的步骤为:

  • 通过形态学开运算对原始图像O去噪。
  • 通过腐蚀操作获取“确定背景B”。
    需要注意,这里得到“原始图像-确定背景”即可。
  • 利用距离变换函数cv2.distanceTransform()对原始图像进行运算,并对其进行阈值处理,得到“确定前景F”。
  • 计算未知区域UN(UN=O -B - F)
  • 利用函数cv2.connectedComponents()对原始图像O进行标注。
  • 对函数cv2.connectedComponents()的标注结果进行修正。
  • 使用分水岭函数完成对图像的分割。

使用分水岭算法对一幅图像进行分割:

import numpy as np import cv2 import matplotlib.pyplot as plt img = cv2.imread('water_coins.jpg') gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRA
                
                

-六神源码网