分水岭算法(Python+OpenCV)

分水岭算法算法思想

最常用的分水岭算法是上世纪90年代提出的基于灰度图像分割的算法,分水岭算法是一种与自适应二值化有关的算法。下图给出了该方法的工作原理。假定图中的物体灰度值低,背景灰度值高。图中曲线表示沿着平面一条直线灰度分度,该线穿过了两个靠的比较近的物体。
分水岭算法(Python+OpenCV)_第1张图片
分水岭算法往往从一个偏低但仍能正确分割各个物体的阈值开始,然后随着阈值逐渐增加上升到最佳值(接接近背景的灰度值),各个物体不会被合并。只要阈值使用的合适,在此基础上增加到一个最佳的阈值(该阈值用来判断前景和背景) 也会有很好的区分效果。
我们在使用分水岭算法时,需要选择种子点(局部极小值点),可以用模板去寻找,这实际上是在确定阈值,即从该阈值开始增加(即可理解为开始从谷底注水,不断增加阈值),当阈值增加到最佳阈值时(可理解为水 注满了,该确定水坝了),此时的水坝即我们需要的物体边界分割线。
从上面的分析可以看出,初始阈值(种子点)的选取对最后的分割结果影响都较大。若初始阈值选择在阈值1 处:那么低对比度的物体在一开始就会被丢失,随着阈值增加时就会和相邻的物体合并(显然不能达到好的分割效果了)。如果种子点的值选择在阈值2处,则物体1和物体2一开始就会被合并。而最终的阈值(水坝)决定了最后的边界与实际物体的吻合程度。

OpenCV的分水岭算法,关键点在于怎样去寻找符合该API要求的标注矩阵makers(该矩阵大小和图片矩阵大小一样),代码中用的是cv2.connectedComponent()语句来对图像物体做标注。

OpenCV+Python语句

# import cv2
# import numpy as np
# from matplotlib import pyplot as plt
# img_color = cv2.imread("sources/coins.jpg",1)
# img_gray = cv2.cvtColor(img_color,cv2.COLOR_BGR2GRAY)
# img_gray = cv2.Canny(img_gray, 100,180)
#
# img_contours,contours, hierarchy = cv2.findContours(img_gray,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
# cv2.drawContours(img_color,contours,-1,(0,0,255),2)  # 画出轮廓
#
# retval, markers = cv2.connectedComponents(img_contours)
# # cv2.watershed(img_contours, markers)
# print(markers[])
# cv2.imshow("1",markers)
# cv2.waitKey(0)

"""
完成分水岭算法步骤:
1、加载原始图像
2、阈值分割,将图像分割为黑白两个部分
3、对图像进行开运算,即先腐蚀在膨胀
4、对开运算的结果再进行 膨胀,得到大部分是背景的区域
5、通过距离变换 Distance Transform 获取前景区域
6、背景区域sure_bg 和前景区域sure_fg相减,得到即有前景又有背景的重合区域
7、连通区域处理
8、最后使用分水岭算法
"""

import cv2
import numpy as np


# Step1. 加载图像
img = cv2.imread('sources/Road.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Step2.阈值分割,将图像分为黑白两部分
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
# cv2.imshow("thresh", thresh)

# Step3. 对图像进行“开运算”,先腐蚀再膨胀
kernel = np.ones((3, 3), np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)
# cv2.imshow("opening", opening)

# Step4. 对“开运算”的结果进行膨胀,得到大部分都是背景的区域
sure_bg = cv2.dilate(opening, kernel, iterations=3)
cv2.imshow("sure_bg", sure_bg)

# Step5.通过distanceTransform获取前景区域
dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)  # DIST_L1 DIST_C只能 对应掩膜为3    DIST_L2 可以为3或者5
ret, sure_fg = cv2.threshold(dist_transform, 0.1 * dist_transform.max(), 255, 0)

cv2.imshow("sure_fg", sure_fg)

# Step6. sure_bg与sure_fg相减,得到既有前景又有背景的重合区域   #此区域和轮廓区域的关系未知 
sure_fg = np.uint8(sure_fg)
unknow = cv2.subtract(sure_bg, sure_fg)

# Step7. 连通区域处理
ret, markers = cv2.connectedComponents(sure_fg,connectivity=8) #对连通区域进行标号  序号为 0 - N-1 
markers = markers + 1           #OpenCV 分水岭算法对物体做的标注必须都 大于1 ,背景为标号 为0  因此对所有markers 加1  变成了  1  -  N
#去掉属于背景区域的部分(即让其变为0,成为背景)
# 此语句的Python语法 类似于if ,“unknow==255” 返回的是图像矩阵的真值表。
markers[unknow==255] = 0   

# Step8.分水岭算法
markers = cv2.watershed(img, markers)  #分水岭算法后,所有轮廓的像素点被标注为  -1 
print(markers)

img[markers == -1] = [0, 0, 255]   # 标注为-1 的像素点标 红
cv2.imshow("dst", img)
cv2.waitKey(0)

前几步作了形态学处理(开运算和膨胀),以及距离变换都是为了寻找种子点,将种子点找出来后(即在原始图像上做好了标注),最后在执行分水岭算法。

效果图

分水岭算法(Python+OpenCV)_第2张图片

你可能感兴趣的:(随笔)