首先解释一下第一种分水岭算法:
一、分水岭算法
分水岭算法是一种基于拓扑学的图像分割技术,广泛应用于图像处理和计算机视觉领域。它将图像视为一个拓扑表面,其中亮度值代表高度。算法的目标是通过模拟雨水从山顶流到山谷的过程,将图像分割成若干独立的区域。
分水岭算法的步骤和原理:
-
距离变换:
- 首先对图像进行预处理,将图像转化为灰度图,并进行二值化处理(如Otsu算法)。
- 对二值图像应用距离变换,计算每个前景像素到最近的背景像素的距离,生成距离图。距离变换后的图像可以看作是一幅"地形图",前景像素的距离值越大,代表的高度越高。
-
寻找局部极大值:
- 在距离图中找到局部极大值点。这些点通常位于目标物体的中心位置,将作为初始标记。局部极大值点是那些比其邻域像素值更大的点。
-
创建标记图:
- 创建一个与原始图像大小相同的标记图,将局部极大值点的位置赋值为不同的标签(从1开始编号),其余区域标记为0。
-
应用分水岭算法:
- 将距离图的负值作为输入图像,标记图作为初始标记,应用分水岭算法。分水岭算法通过模拟水从局部极大值点流向低谷的过程,不断合并像素,形成分割区域。
- 在这个过程中,水从局部极大值点流向低谷,如果两个不同的标签的水流在某处相遇,该处将被标记为边界。
-
生成分割结果:
- 分水岭算法最终会将图像分割成多个区域,每个区域对应一个标签。边界区域通常被标记为0。
分水岭算法的优点和缺点:
优点:
- 分水岭算法可以生成闭合的区域边界,适用于目标物体具有明确边界的图像。
- 算法可以自动确定分割区域的数量,无需事先设定。
缺点:
- 对噪声和边缘模糊敏感,容易产生过分割,即将一个目标物体分割成多个区域。
- 需要进行预处理以减少噪声和增强边缘(如均值漂移滤波)。
示例代码解释:
# 计算每个二值像素到最近零像素的精确欧几里得距离, 然后找到此距离图中的局部峰值
D = ndimage.distance_transform_edt(thresh)
localMax = peak_local_max(D, footprint=np.ones((3, 3)), min_distance=40, labels=thresh)# 根据找到的局部峰值创建标记数组, 标记数组的值对应于每个硬币的序号
markers = np.zeros_like(thresh, dtype=np.int32)
markers[tuple(localMax.T)] = np.arange(1, len(localMax) + 1)# 应用分水岭算法, 将图像分割为不同的区域
labels = watershed(-D, markers, mask=thresh)
- 计算距离变换:
ndimage.distance_transform_edt(thresh)
计算每个前景像素到最近背景像素的欧几里得距离,生成距离图D
。 - 寻找局部极大值:
peak_local_max(D, footprint=np.ones((3, 3)), min_distance=40, labels=thresh)
在距离图中寻找局部极大值,这些点将作为初始标记。 - 创建标记图:
markers
初始化为全零矩阵,将局部极大值点的位置赋值为不同的标签。 - 应用分水岭算法:
labels = watershed(-D, markers, mask=thresh)
使用分水岭算法对距离图的负值进行分割,生成标签图labels
。
通过以上步骤,分水岭算法将输入图像分割成若干独立区域,每个区域代表一个目标物体。
以检测这张图为例子:
使用分水岭算法流程如下:
-
读取图像并应用均值漂移滤波:
- 使用
cv2.imread
读取输入图像。 - 使用
cv2.pyrMeanShiftFiltering
对图像进行均值漂移滤波,平滑图像并减少噪点。
- 使用
-
转换为灰度图并二值化:
- 使用
cv2.cvtColor
将平滑后的图像转换为灰度图。 - 使用
cv2.threshold
结合 Otsu 算法进行自动阈值二值化,将图像转换为二值图像。
- 使用
-
计算欧几里得距离并找到局部峰值:
- 使用
ndimage.distance_transform_edt
计算每个二值像素到最近零像素的欧几里得距离,生成距离变换图。 - 使用
peak_local_max
找到距离图中的局部峰值,这些峰值将作为分水岭算法的初始标记。
- 使用
-
创建标记数组并应用分水岭算法:
- 创建一个与二值图像大小相同的标记数组
markers
,将局部峰值的位置赋值为不同的标签。 - 使用
watershed
函数进行分水岭算法,将图像分割成不同区域,每个区域对应一个硬币。
- 创建一个与二值图像大小相同的标记数组
-
遍历分割出的不同区域,绘制轮廓和标签:
- 遍历分割后的标签,跳过背景标签(标签为0)。
- 为每个硬币创建一个掩码图像,设置对应标签区域为白色,其余区域为黑色。
- 使用
cv2.findContours
查找掩码图像中的轮廓,并找到最大的轮廓(即硬币区域)。 - 使用
cv2.minEnclosingCircle
计算最小外接圆的圆心坐标和半径。 - 在原始图像上绘制圆形轮廓和标签。
-
显示最终结果图像:
- 使用
cv2.imshow
显示处理后的图像。 - 使用
cv2.waitKey
和cv2.destroyAllWindows
控制显示窗口。
- 使用
上述流程通过图像平滑、二值化、距离变换、局部峰值检测和分水岭算法,实现了对硬币图像的分割,并在分割后的图像上绘制了硬币的轮廓和编号标签。
完整代码如下:
import numpy as np
import cv2
from skimage.feature import peak_local_max
from skimage.segmentation import watershed
from scipy import ndimage
import imutils# 读取图像并应用均值漂移滤波来平滑图像,减少噪点
image = cv2.imread('/coins/1.jpg')
shifted = cv2.pyrMeanShiftFiltering(image, 21, 51)# 将图像转换为灰度图,然后使用Otsu算法自动确定阈值进行二值化
gray = cv2.cvtColor(shifted, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]# 计算每个二值像素到最近零像素的精确欧几里得距离,然后找到此距离图中的局部峰值
# 这些峰值将作为分水岭算法的初始标记
D = ndimage.distance_transform_edt(thresh)
localMax = peak_local_max(D, footprint=np.ones((3, 3)), min_distance=40, labels=thresh)# 根据找到的局部峰值创建标记数组,标记数组的值对应于每个硬币的序号
markers = np.zeros_like(thresh, dtype=np.int32)
markers[tuple(localMax.T)] = np.arange(1, len(localMax) + 1)# 应用分水岭算法,将图像分割为不同的区域
labels = watershed(-D, markers, mask=thresh)# 遍历分割出的不同区域,绘制出每个硬币的轮廓和标签
for label in np.unique(labels):if label == 0:continue# 创建一个掩码图像,将当前标签对应的区域设置为白色,其他区域设置为黑色mask = np.zeros(gray.shape, dtype="uint8")mask[labels == label] = 255# 查找掩码图像中的轮廓,并找到最大的轮廓(即硬币区域)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)
cv2.destroyAllWindows()
分割结果如下:
二、 霍夫变换
霍夫变换(Hough Transform)是图像处理中的一种重要技术,用于检测图像中的几何形状。霍夫圆检测(Hough Circle Transform)是霍夫变换的一个具体应用,用于检测图像中的圆形物体。
霍夫圆检测的原理和步骤:
-
边缘检测:
- 首先对图像进行边缘检测,常用的方法是Canny边缘检测。边缘检测可以提取出图像中的显著边缘,减少数据量并突出目标物体的轮廓。
-
参数空间定义:
- 在检测圆的过程中,需要定义圆的参数空间。一个圆由三个参数定义:圆心坐标 (x, y) 和半径 r。霍夫圆检测将在参数空间中搜索圆的可能位置和大小。
-
投票累加:
-
在边缘检测后的二值图像中,每个边缘点 (x, y) 都会在参数空间中投票支持可能的圆心和半径组合。具体而言,对于每个边缘点 (x, y) 和每个可能的半径 r,可以根据圆的方程计算圆心坐标 (a, b):
-
在参数空间中累加 (a, b) 的投票次数。
-
-
检测局部最大值:
- 在参数空间中,投票次数最多的位置即为最可能的圆心和半径组合。通过检测参数空间中的局部最大值,确定圆的存在和位置。
-
绘制检测到的圆:
- 根据检测到的圆心坐标和半径,在原始图像上绘制圆形轮廓。
示例代码:
以下是一个使用OpenCV进行霍夫圆检测的示例代码:
import cv2
import numpy as np# 读取图像
image = cv2.imread('image.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)# 应用高斯模糊,减少噪声
blurred = cv2.GaussianBlur(gray, (9, 9), 2)# 使用霍夫圆检测
circles = cv2.HoughCircles(blurred, cv2.HOUGH_GRADIENT, dp=1.2, minDist=20,param1=50, param2=30, minRadius=15, maxRadius=30)# 如果检测到圆
if circles is not None:circles = np.round(circles[0, :]).astype("int")for (x, y, r) in circles:# 绘制圆的轮廓cv2.circle(image, (x, y), r, (0, 255, 0), 4)# 绘制圆心cv2.rectangle(image, (x - 5, y - 5), (x + 5, y + 5), (0, 128, 255), -1)# 显示结果图像
cv2.imshow("output", image)
cv2.waitKey(0)
cv2.destroyAllWindows()
代码详解:
-
读取图像并转换为灰度图:
image = cv2.imread('image.jpg') gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
-
应用高斯模糊:
- 使用高斯模糊(Gaussian Blur)来平滑图像,减少噪声。
blurred = cv2.GaussianBlur(gray, (9, 9), 2)
-
使用霍夫圆检测:
- 调用
cv2.HoughCircles
函数进行霍夫圆检测。参数解释如下:blurred
:输入的灰度图像。cv2.HOUGH_GRADIENT
:检测方法,使用梯度信息。dp=1.2
:累加器分辨率与图像分辨率的反比关系。minDist=20
:检测到的圆之间的最小距离。param1=50
:Canny边缘检测的高阈值。param2=30
:累加器阈值,用于检测圆的阈值,越小越容易检测到不明显的圆。minRadius=15
和maxRadius=30
:检测圆的半径范围。
circles = cv2.HoughCircles(blurred, cv2.HOUGH_GRADIENT, dp=1.2, minDist=20,param1=50, param2=30, minRadius=15, maxRadius=30)
- 调用
-
绘制检测到的圆:
- 如果检测到圆,将其绘制在原始图像上。
if circles is not None:circles = np.round(circles[0, :]).astype("int")for (x, y, r) in circles:cv2.circle(image, (x, y), r, (0, 255, 0), 4)cv2.rectangle(image, (x - 5, y - 5), (x + 5, y + 5), (0, 128, 255), -1)
-
显示结果图像:
- 显示绘制了圆的结果图像。
cv2.imshow("output", image) cv2.waitKey(0) cv2.destroyAllWindows()
通过上述步骤和代码,霍夫圆检测可以在图像中自动识别和绘制圆形目标。
识别图中硬币的完整代码如下:
import cv2
import numpy as np# 读取图像并转换为灰度图像
image = cv2.imread('/coins/1.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)# 应用高斯模糊
gray = cv2.GaussianBlur(gray, (15, 15), 0)# 使用霍夫圆变换检测圆
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, dp=1.2, minDist=50, param1=50, param2=30, minRadius=20,maxRadius=60)# 确保至少检测到一个圆
if circles is not None:circles = np.round(circles[0, :]).astype("int")for (i, (x, y, r)) in enumerate(circles):# 绘制圆圈和中心点cv2.circle(image, (x, y), r, (0, 255, 0), 2)cv2.putText(image, str(i + 1), (x - 10, y), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)# 显示最终结果图像
cv2.imshow("Detected Coins", image)
cv2.waitKey(0)
cv2.destroyAllWindows()
检测结果如下: