代码实例讲解用分水岭算法实现图片分割

8人浏览 / 0人评论 / 添加收藏

      OpenCV中的分水岭算法是一种基于拓扑理论的图像分割方法,它把图像的灰度值看作地形高度,通过“浸水”过程形成分水岭(边界)。下面我会详细介绍它的原理、传统方法的问题,以及最常用的标记控制分水岭算法的完整实现。

1、基本原理:

      分水岭分割方法是基于图像形态学和图像结构来实现的一种图像分割方法。将灰度图像想象成一幅地形图,像素值高的地方是山峰,低的地方是山谷。在每个局部极小值点(山谷)钻孔,让水匀速上升。来自不同山谷的水即将汇合时,筑起“堤坝”,这些堤坝就是分水岭,也就是图像中物体的边界。
      现实中我们可以或者说可以想象有山有湖的景象,那么那一定是水绕山,山围水的情形。当然在需要的时候,要人工构筑分水岭,以防集水盆之间的互相穿透。而区分高山(plateaus)与水的界线,以及湖与湖之间的间隔或都是连通的关系,就是分水岭(watershed)。
      我们绘制灰度图像的梯度图,可以得到近似下图的梯度走势.梯度低的地方我们可以认为是低洼区或者山谷,梯度高的地方可以认为是山峰.我们往山谷中注,为了防止山谷中的水溢出汇合我们可以在汇合的地方筑起堤坝,可将堤坝看做是对图像分割后形成的边界。这就是分水岭算法的基本原理。

2、分水岭法涉及的API:
·distanceTransform(img, distanceType,maskSize)计算img中非零值到距离它最丘的0值之间的距离

   img要处理的图像

   odistanceType计算距离的方式:DST_L1, DIST L2

   maskSize:进行扫描时的kernel的小,L1用3, L2用5

· connectedComponents(imagel, labels[, connectivity[, Itype]]])求连通域,用O记图像的背景,用大于0的整数标记其他对象
   connectivity: 4, 8(默认)

· watershed(image, markers) 执行分水岭法

   markers:它是一个与原始图像大小相同的矩阵,int32数据类型,表示哪些是背景哪些是前景。分水岭算法将标记的0的区域视为不确定区域,将标记为1的区域视为背景区域,将标记大于1的正整数表示我们想得到的前景。

3、Python代码实例:

import cv2
import numpy as np
import matplotlib.pyplot as plt
from opencv_jupyter_ui import cv2_imshow
from utils import plt_imshow

def cv_imshow(title, img):
   cv2.imshow(title, img)
   cv2.waitKey(0)
   # if key & 0xFF == ord('q'):
   cv2.destroyAllWindows()

img = cv2. imread('./images/water_coins.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# cv_show('gray', gray)
# cv2_imshow('gray', gray)
# plt_imshow('gray', gray)
# _ = plt.hist(gray.ravel(), bins=256, range=[0, 255])

#二值化处理,这是一个典型的双峰结构,我们使用大津算法进行二值化处理
# THRESH_OTSU 要求输入图像必须是 8位或16位的单通道灰度图。如果你的图像是彩色的(三通道),需要先用 cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) 转换一下
# thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
# _,thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY)
# plt_imshow('thresh', thresh)

#做一个开运算.
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)
# plt_imshow('opening', opening)

#想办法找到图中的背景和前景.
#对opening进行膨胀.
bg = cv2.dilate(opening, kernel, iterations=2)
# cv2_imshow('bg', np.hstack((opening, bg)))
#腐蚀,
fg = cv2.erode(opening, kernel, iterations=2)
# cv_imshow('fg', np.hstack((opening, bg, fg)))
# plt_imshow('fg', np.hstack((opening, bg, fg)))

#剩下的区域(硬币边界附近)不能确定是背景还是前景.
#可以通过膨胀之后的图减去腐蚀之后的图,得到未知区域大小.
unknown = cv2.subtract(bg, fg)
plt_imshow('unknown', np.hstack((opening, bg, fg, unknown)))

#因为硬币之间彼此是接触的,导致腐蚀之后的前景图不太对,硬币和硬币之间形成了通道
#腐蚀在这里不适合.
# distanceTransform来确定前景.
dist_transform = cv2.distanceTransform(opening, cv2. DIST_L2, 5)
print(dist_transform.max())
#对dist_transform做归一化方便展示结果
cv2.normalize(dist_transform, dist_transform,0,1.0,cv2.NORM_MINMAX)
# plt_imshow('dist_transform', dist_transform)

#对dist_transform做二值化处理
_, fg = cv2.threshold(dist_transform, 0.5*dist_transform.max(), 255, cv2.THRESH_BINARY)
# plt_imshow('fg', fg)

fg = np.uint8(fg)
#顺便把位置区域算出
unknown = cv2.subtract(bg, fg)
# plt_imshow('gg',np.hstack((bg, fg, unknown)))
#connectedComponents要求输入的图片是个8位的单通道图片,即单通道的0到255的图片.
# connectedComponents用0来标记背景,用大于0的整数来标记前景
_,markers = cv2.connectedComponents(fg)
print(markers.max())
print(markers.min())
# plt_imshow('markers', markers)

#因为watershed中0认为是不确定区域,1是背景,大于1的是前景.#markers+1把原来的0变成1了.
markers += 1
#从markers中筛选出位置区域,赋值为0.
markers[unknown == 255] = 0
# 展示markers
markers_copy = markers.copy()
#未知区域
markers_copy[markers == 0] = 150
#背景
markers_copy[markers == 1] = 0
#前景
markers_copy[markers > 1] = 255
markers_copy = np.uint8(markers_copy)
# plt_imshow('markers_copy', markers_copy)

#到此为止,我们的markers就已经生成好了.watershed返回的markers已经做了修改.边界区域标记为-1了.
markers = cv2.watershed(img, markers)
print(markers.min(),markers.max())

#显示一下前景图片的边缘
# img[markers == -1]=[0,0,255]
#单独取出或者标记出前景
# img[markers > 0] = [ 0, 255, 0]
# img[markers > 1] = [ 0, 255, 0]

# coins = img[markers > 1].copy()
#抠出硬币#mask把要抠图的地方赋值为255,其他位置,即背景赋值为0
mask = np.zeros(shape=img.shape[:2], dtype=np.uint8)
mask[markers > 1] = 255
coins = cv2.bitwise_and(img,img,mask=mask)

plt_imshow('coins', coins)
plt_imshow('img', img)
 

4、运行示例图片展示如下:

全部评论