所有的灰度图像都可视为拓扑平面,我们将灰度值高的区域看成山峰,将灰度值低的区域看成山谷,我们向图像上所有的"山谷"中注入不同颜色的水,不断的注水,水位则会不断上升,注入的水将灌满山谷,并可能淹没山峰,为了防止不同颜色的山谷中的水溢出汇合,我们可在汇合的地方筑起堤坝,故可将堤坝看作是对图像的分割后形成的边界。具体的事例如下图所示。
步骤1-构建梯度图像。
步骤2-通过一定规则生成n个最初的注水区域(先验知识或局部梯度最小值)。
步骤3-往注水区域内加水,当两注水区域即将合并时,记录下此时的边界。
步骤4-当图像边缘彻底被分割成n个独立区域是算法结束。
具体的实现过程如下图所示:
# coding=utf-8
# 导入python包
from __future__ import print_function
from skimage.feature import peak_local_max
from skimage.morphology import watershed
from scipy import ndimage
import argparse
import imutils
import cv2
# 设置参数并进行参数解析
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True, help="path to input image")
args = vars(ap.parse_args())
# 读取图片
image = cv2.imread(args["image"])
# 进行mean—shift滤波
shifted = cv2.pyrMeanShiftFiltering(image, 21, 51)
cv2.imshow("Input", image)
# 进行图片灰度化处理
gray = cv2.cvtColor(shifted, cv2.COLOR_BGR2GRAY)
# 进行阈值分割
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
cv2.imshow("Thresh", thresh)
# 在阈值图片中寻找轮廓
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
print("[INFO] {} unique contours found".format(len(cnts)))
# 遍历所有的轮廓并依次显示不同的结果
for (i, c) in enumerate(cnts):
((x, y), _) = cv2.minEnclosingCircle(c)
cv2.putText(image, "#{}".format(i + 1), (int(x) - 10, int(y)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
cv2.drawContours(image, [c], -1, (0, 255, 0), 2)
# 显示输出结果
cv2.imshow("Image", image)
cv2.waitKey(0)
上图展示了使用阈值和轮廓的分割结果。通过观察上图我们可以发现,阈值分割并不能很好的将硬币区分开来,轮廓检测算法也不能很好的获取所有的硬币的完整轮廓,即这两种思路都不能很好的解决硬币检测问题。
# coding=utf-8
# 导入相应的python包
from skimage.feature import peak_local_max
from skimage.morphology import watershed
from scipy import ndimage
import numpy as np
import argparse
import imutils
import cv2
# 设置并解析参数
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True, help="path to input image")
args = vars(ap.parse_args())
# 读取图片
image = cv2.imread(args["image"])
# 进行mean shift滤波
shifted = cv2.pyrMeanShiftFiltering(image, 21, 51)
cv2.imshow("Input", image)
# 进行灰度化处理
gray = cv2.cvtColor(shifted, cv2.COLOR_BGR2GRAY)
# 进行阈值分割
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
cv2.imshow("Thresh", thresh)
# 计算从每个二元像素到最近零像素的精确欧几里得距离,然后在距离图中找到峰值
D = ndimage.distance_transform_edt(thresh)
localMax = peak_local_max(D, indices=False, min_distance=20, labels=thresh)
# 利用8连通性对局部峰进行连通分量分析,然后应用分水岭算法
markers = ndimage.label(localMax, structure=np.ones((3, 3)))[0]
labels = watershed(-D, markers, mask=thresh)
print("[INFO] {} unique segments found".format(len(np.unique(labels)) - 1))
# 循环显示标签
for label in np.unique(labels):
# 如果该标签为0,则表示其为背景,直接忽略
if label == 0:
continue
# 为标签区域分配内存并将在mask上绘制结果
mask = np.zeros(gray.shape, dtype="uint8")
mask[labels == label] = 255
# 在mask上检测轮廓并获得最大的一个轮廓
cnts = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
c = max(cnts, key=cv2.contourArea)
# 画一个圈把物体围起来
((x, y), r) = cv2.minEnclosingCircle(c)
cv2.circle(image, (int(x), int(y)), int(r), (0, 255, 0), 2)
cv2.putText(image, "#{}".format(label), (int(x) - 10, int(y)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
# 显示最终结果
cv2.imshow("Output", image)
cv2.waitKey(0)
运行方法:python watershed_segement.py --image 图片 (这里的图片需要更换为自己的图片)
上图展示了分水岭硬币分割代码的运行结果。通过观察这些结果,我们可以获得一些信息,即使用分水岭算法可以很好地将具有重叠的硬币分割开来,并自动的对图片中的硬币个数进行统计。
[1] 参考链接
[1] 该博客是本人原创博客,如果您对该博客感兴趣,想要转载该博客,请与我联系(qq邮箱:[email protected]),我会在第一时间回复大家,谢谢大家的关注.
[2] 由于个人能力有限,该博客可能存在很多的问题,希望大家能够提出改进意见。
[3] 如果您在阅读本博客时遇到不理解的地方,希望您可以联系我,我会及时的回复您,和您交流想法和意见,谢谢。
[4] 本文测试的图片可以通过该链接进行下载。网盘链接-提取码:w2ss。
[5] 本人业余时间承接各种本科毕设设计和各种小项目,包括图像处理(数据挖掘、机器学习、深度学习等)、matlab仿真、python算法及仿真等,有需要的请加QQ:1575262785详聊,备注“项目”!!!