===
圆形检测概率霍夫变换算法
前言
概率霍夫变换圆检测是一种用于在图像中识别圆形的算法。传统霍夫变换需要在完整的参数空间中进行投票,计算量巨大,难以在实时系统中应用。概率霍夫变换通过随机采样边缘点,大幅减少了计算量,同时保持了良好的检测效果。通过概率霍夫变换,可以快速准确地定位图像中的圆形目标。
博客作者微信公众号(计算机视觉技术)集中收录了100种计算机视觉技术算法,每一种算法都具体介绍了概念、原理、步骤、运行效果,以及完整的源码实现,您可以据此系统全面掌握计算机视觉技术算法。
关注微信公众号"计算机视觉技术”,系统掌握100种计算机视觉技术算法。 微信公众号提供获取10000+计算机视觉技术最新技术PDF的URL链接和获取PDF的脚本工具。实际体验亲测稳定可靠。
计算机视觉技术算法 概念+原理+实现
微信视频号:计算机视觉技术
微信公众号:计算机视觉技术
感谢您的关注!
概念
霍夫变换是一种参数空间投票方法,用于在图像中检测特定形状的几何对象。对于圆检测,标准圆可以用三个参数表示:圆心横坐标、圆心纵坐标和半径。霍夫变换将图像空间中的每个边缘点映射到三维参数空间中的投票累加器,通过查找累加器的局部极大值来确定圆的参数。
概率霍夫变换是对标准霍夫变换的改进,它不使用所有边缘点进行投票,而是随机选择部分边缘点进行参数空间累加。这种随机采样策略在不显著降低检测准确率的前提下,大幅提升了计算效率。
原理
概率霍夫变换圆检测的核心思想基于圆的几何特性。对于图像中的每个边缘点,梯度方向指向圆心方向或背离圆心方向。因此,圆心位于沿梯度方向距离边缘点等于半径的位置。
算法采用两阶段检测策略(参考OpenCV cv2.HoughCircles的实现):
圆心检测
- 首先对图像进行边缘检测,使用Canny算子提取边缘
- 计算每个像素的梯度幅值和方向
- 在圆心空间(2D)进行累加投票,对于每个随机采样的边缘点,根据其梯度方向估算可能的圆心位置
- 使用多个半径值进行圆心投票,提高鲁棒性
半径确定
- 对每个候选圆心,在半径空间(1D)进行累加投票
- 检查边缘点的梯度方向是否与圆心到边缘点的向量方向一致
- 根据梯度幅值使用不同的角度约束和投票权重,实现对强边缘和弱边缘的差异化处理
- 找到半径累加器的最大值,确定最佳半径
由于使用了随机采样策略,算法从所有边缘点中随机选择一部分进行投票。这大大减少了需要处理的边缘点数量,从而降低了计算复杂度。投票完成后,在参数空间中查找局部极大值,这些极大值对应的参数即为检测到的圆的参数。
步骤
概率霍夫变换圆检测算法的主要步骤如下:
-
图像预处理:将彩色图像转换为灰度图像,使用加权平均保持彩色对比度。
-
Canny边缘检测:使用Canny算子提取边缘,包括非极大值抑制和双阈值检测。
-
梯度计算:使用Sobel算子计算图像在x和y方向的梯度,进而计算梯度幅值和梯度方向。
-
边缘点提取:根据边缘二值图提取边缘点坐标,并可设置ROI(感兴趣区域)过滤。
-
圆心空间累加
-
从边缘点中随机选择部分点
-
根据梯度方向和多个半径值估算圆心位置
-
在圆心空间(2D)中进行梯度幅值加权投票
-
半径空间累加
-
对每个候选圆心,在半径空间(1D)进行累加投票
-
使用梯度幅值自适应的角度约束(强边缘30°,中等边缘37°,弱边缘50°)
-
对弱边缘给予额外投票权重(1.5倍),提高黄色等弱对比度圆形的检测率
-
局部极大值检测:在参数空间中查找累加器值的局部极大值,使用距离约束避免重复检测。
-
结果可视化:在原图上绘制检测到的圆形轮廓(5像素宽红色线条)和圆心,并标注ROI边界。
效果
算法运行效果图。
圆形检测概率霍夫变换算法 微信公众号:计算机视觉技术
算法运行输出信息如下所示:
~~~
python .\圆形检测概率霍夫变换算法.py
正在进行圆形检测...
检测区域: Y轴范围 [0, 330], 高度 330
边缘点数量(原始): 5493, 边缘点数量(ROI): 2772
边缘点总数: 2772, 采样数量: 2772 (100.0%)
圆心累加器维度: (400, 600)
圆心累加器最大值: 6561.92431640625, 平均值: 33.00
~~~
从对照图可以看出,算法成功检测到了图像中的多个圆形。每个被检测到的圆形用红色轮廓(5像素宽)标出,圆心用红色圆点标记。图像中包含不同大小、不同位置、不同颜色的圆形,算法都能准确定位。
算法采用梯度幅值自适应检测策略:
- 对强边缘圆形(如红色、深蓝色)使用严格的角度约束(30°)
- 对中等强度边缘使用中等角度约束(37°)和1.2倍加权
- 对弱边缘圆形(如黄色、淡蓝色)使用宽松的角度约束(50°)和1.5倍加权
这种策略能够有效检测到对比度较低的黄色和淡蓝色圆形,同时避免强边缘圆形的重复检测。图像底部设置了ROI边界(蓝色线),跳过文字区域,避免误检。
算法对图像中的所有圆形进行了检测,包括较大的圆形、中等大小的圆形以及较小的圆形。检测到的圆心位置与实际圆心位置高度吻合,圆形半径也与实际半径保持一致。这验证了两阶段参数空间投票方法的有效性。
生成测试图片源码
测试图片生成程序创建了一个包含多个圆形的彩色图像。程序首先创建一个灰色的画布,然后在画布上绘制多个具有不同颜色、不同位置、不同大小的圆形。每个圆形的参数经过精心设计,确保它们在图像中分布合理且互不重叠。
程序实现了Bresenham圆绘制算法,可以精确绘制圆形轮廓。对于实心圆,使用扫描线算法进行填充,确保圆的内部完全填充指定颜色。程序还提供了添加噪声的选项,可以通过添加高斯噪声来测试算法的鲁棒性。
最后,程序在图像底部添加了"BUTIANYUN.COM"文本标识。文本使用黑色背景和白色文字,确保在图像中清晰可见。生成的测试图像高度固定为400像素,宽度根据宽高比自动计算。
'''
生成适用于概率霍夫变换圆检测算法的测试图片。
创建包含多个不同大小、位置和颜色的圆形的测试图像,用于验证圆检测算法的效果。
图像高度固定为400像素,底部包含BUTIANYUN.COM标识文本。
使用彩色图像以更好地展示检测效果。
'''
############################################################
# 微信公众号:计算机视觉技术
# 微信视频号:计算机视觉技术
# 网站 :BUTIANYUN.COM
############################################################
import cv2
import numpy as np
def butianyun_draw_circle(image, center, radius, color, thickness=-1):
'''
在图像上绘制实心圆。
使用Bresenham算法绘制圆形轮廓,然后填充内部。
'''
cx, cy = center
height, width = image.shape[:2]
# 绘制圆的边界
x = 0
y = radius
d = 3 - 2 * radius
boundary_points = set()
while x <= y:
points = [
(cx + x, cy + y), (cx - x, cy + y),
(cx + x, cy - y), (cx - x, cy - y),
(cx + y, cy + x), (cx - y, cy + x),
(cx + y, cy - x), (cx - y, cy - x)
]
for px, py in points:
if 0 <= px < width and 0 <= py < height:
boundary_points.add((px, py))
x += 1
if d < 0:
d = d + 4 * x + 6
else:
d = d + 4 * (x - y) + 10
y -= 1
# 填充圆的内部
if thickness < 0:
# 使用扫描线算法填充
min_y = min(p[1] for p in boundary_points)
max_y = max(p[1] for p in boundary_points)
for y in range(min_y, max_y + 1):
# 找到当前y坐标上的左右边界
x_coords = [p[0] for p in boundary_points if p[1] == y]
if x_coords:
min_x = min(x_coords)
max_x = max(x_coords)
for x in range(min_x, max_x + 1):
image[y, x] = color
else:
# 只绘制边界
for px, py in boundary_points:
image[py, px] = color
return image
def butianyun_add_circles(image):
'''
在图像中添加多个圆形。
圆形具有不同的位置、大小和颜色,以测试算法的鲁棒性。
'''
height, width = image.shape[:2]
# 定义圆形参数:(center_x, center_y, radius, color)
# 颜色格式:(B, G, R)
circles = [
(120, 150, 50, (0, 165, 255)), # 橙色圆,较大
(280, 120, 35, (0, 255, 255)), # 青色圆
(450, 180, 60, (255, 0, 255)), # 紫色圆,大圆
(180, 280, 40, (0, 255, 0)), # 绿色圆
(350, 260, 45, (255, 255, 0)), # 黄色圆
(520, 140, 55, (255, 128, 0)), # 深橙色
(100, 320, 30, (255, 0, 0)), # 蓝色圆
(540, 300, 38, (128, 0, 255)), # 玫红色圆
(320, 340, 42, (128, 128, 128)), # 灰色圆
(200, 100, 25, (128, 255, 128)), # 浅绿色小圆
]
for cx, cy, radius, color in circles:
butianyun_draw_circle(image, (cx, cy), radius, color)
return image
def butianyun_add_noise(image, noise_level=10):
'''
为图像添加随机噪声。
噪声可以增加检测难度,测试算法的鲁棒性。
'''
height, width = image.shape[:2]
# 生成高斯噪声
noise = np.random.normal(0, noise_level, image.shape)
# 添加噪声并确保像素值在有效范围内
noisy_image = image.astype(np.float64) + noise
noisy_image = np.clip(noisy_image, 0, 255).astype(np.uint8)
return noisy_image
def butianyun_add_text_info(image, text='BUTIANYUN.COM'):
'''
在图像底部添加文本信息。
文本居中显示,使用适当的字体大小以适应图像尺寸。
'''
height, width = image.shape[:2]
# 文本参数
font_scale = 0.6
thickness = 2
color = (255, 255, 255) # 白色文本
bg_color = (0, 0, 0) # 黑色背景
# 获取文本大小
font = cv2.FONT_HERSHEY_SIMPLEX
(text_width, text_height), baseline = cv2.getTextSize(text, font, font_scale, thickness)
# 计算文本位置(底部居中)
text_x = (width - text_width) // 2
text_y = height - 20
# 绘制文本背景
bg_height = text_height + 20
bg_top = text_y - text_height - 5
cv2.rectangle(image, (0, bg_top), (width, height), bg_color, -1)
# 绘制文本
cv2.putText(image, text, (text_x, text_y), font, font_scale, color, thickness)
return image
def butianyun_generate_test_image(height=400, add_noise=False):
'''
生成测试图像。
参数:
- height: 图像高度(像素)
- add_noise: 是否添加噪声
返回生成的测试图像。
'''
# 计算图像宽度(保持合理的宽高比)
width = int(height * 1.5)
# 创建空白图像(浅灰色背景)
background_color = (200, 200, 200)
image = np.full((height, width, 3), background_color, dtype=np.uint8)
# 添加圆形
image = butianyun_add_circles(image)
# 可选:添加噪声
if add_noise:
image = butianyun_add_noise(image, noise_level=15)
# 添加文本信息
image = butianyun_add_text_info(image)
return image
def main():
'''
主函数:生成测试图片并保存。
'''
print("正在生成测试图片...")
# 生成测试图像(不添加噪声)
test_image = butianyun_generate_test_image(height=400, add_noise=False)
# 保存测试图像
output_filename = 'butianyun_computer_vision_input.png'
cv2.imwrite(output_filename, test_image)
print(f"测试图片已保存为 {output_filename}")
print(f"图像尺寸:{test_image.shape[1]} x {test_image.shape[0]}")
if __name__ == '__main__':
main()
具体算法实现源码
概率霍夫变换圆检测算法采用两阶段检测策略,参考OpenCV cv2.HoughCircles的实现。
主要函数说明:
- butianyun_sobel - Sobel边缘检测算子
- 计算3x3邻域内的梯度
- 使用反射填充处理边界
- 使用向量化操作优化计算速度
- 使用float32数据类型减少内存占用
- butianyun_canny - 简化的Canny边缘检测
- 非极大值抑制
- 双阈值检测(强边缘和弱边缘)
- 边缘跟踪连接
- butianyun_gradient - 计算梯度幅值和方向
- 返回梯度幅值、方向、x方向梯度、y方向梯度
- butianyun_get_edge_points - 提取边缘点
- 根据梯度幅值阈值提取边缘点坐标
- butianyun_accumulate_centers - 第一阶段:圆心空间累加
- 随机采样边缘点(概率霍夫变换核心)
- 使用实际半径范围进行圆心投票
- 根据梯度幅值加权投票
- butianyun_accumulate_radii - 第二阶段:半径空间累加
- 对每个候选圆心,在半径空间累加投票
- 梯度幅值自适应角度约束:
- 强边缘(>50):30°严格约束,无额外加权
- 中等边缘(30-50):37°约束,1.2倍加权
- 弱边缘(≤30):50°宽松约束,1.5倍额外加权
- butianyun_find_local_maxima - 查找局部极大值
- 在圆心累加器中查找峰值
- 对每个候选圆心,在半径空间累加并找最大值
- 使用距离约束避免重复检测
- butianyun_detect_circles - 主检测函数
- 支持ROI(感兴趣区域)设置
- 参数对应OpenCV:canny_high_threshold(param1)、center_threshold(param2)、min_distance(minDist)、dp
- butianyun_draw_circles - 绘制检测到的圆
- 使用Bresenham算法绘制圆形轮廓
- 默认5像素宽红色线条
- butianyun_show_all_images - 显示所有图像
-
输入图像、调试图(灰度、梯度、边缘)、检测结果
-
'''
圆形检测概率霍夫变换算法实现。
概率霍夫变换通过随机采样边缘点进行参数空间投票,相比标准霍夫变换大幅降低计算量。
算法先对图像进行边缘检测,计算每个边缘点的梯度方向,然后根据梯度方向在参数空间中累加投票。
最终通过查找累加器中的局部极大值来确定圆的参数(圆心坐标和半径)。
适用于工业检测中的圆形零件定位、生物医学图像中的细胞分割等场景。
'''
############################################################
# 微信公众号:计算机视觉技术
# 微信视频号:计算机视觉技术
# 网站 :BUTIANYUN.COM
############################################################
import cv2
import numpy as np
import matplotlib.pyplot as plt
def butianyun_sobel(image, axis='x'):
'''
Sobel边缘检测算子实现。
计算3x3邻域内的梯度,用于检测图像边缘。
axis参数指定计算x方向还是y方向的梯度。
使用向量化操作优化计算速度。
'''
kernel_x = np.array([[-1, 0, 1],
[-2, 0, 2],
[-1, 0, 1]], dtype=np.float32)
kernel_y = np.array([[-1, -2, -1],
[ 0, 0, 0],
[ 1, 2, 1]], dtype=np.float32)
kernel = kernel_x if axis == 'x' else kernel_y
# 使用反射填充处理边界(比零填充更准确)
padded = np.pad(image, 1, mode='reflect').astype(np.float32)
# 向量化卷积操作
result = np.zeros_like(image, dtype=np.float32)
for i in range(3):
for j in range(3):
result += padded[i:image.shape[0]+i, j:image.shape[1]+j] * kernel[i, j]
return result
def butianyun_canny(image, high_threshold, low_threshold=None):
'''
简化的Canny边缘检测算法实现(仅用于参考OpenCV的param1参数)。
param1是Canny的高阈值,低阈值通常是高阈值的一半。
'''
if low_threshold is None:
low_threshold = high_threshold * 0.5
# 计算梯度
grad_x = butianyun_sobel(image.astype(np.float32), axis='x')
grad_y = butianyun_sobel(image.astype(np.float32), axis='y')
magnitude = np.sqrt(grad_x**2 + grad_y**2)
direction = np.arctan2(grad_y, grad_x)
# 非极大值抑制
height, width = image.shape
suppressed = np.zeros_like(magnitude)
for y in range(1, height-1):
for x in range(1, width-1):
angle = direction[y, x] * 180 / np.pi
if angle < 0:
angle += 180
# 确定梯度方向
if (0 <= angle < 22.5) or (157.5 <= angle <= 180):
q, r = magnitude[y, x+1], magnitude[y, x-1]
elif (22.5 <= angle < 67.5):
q, r = magnitude[y+1, x-1], magnitude[y-1, x+1]
elif (67.5 <= angle < 112.5):
q, r = magnitude[y+1, x], magnitude[y-1, x]
else:
q, r = magnitude[y-1, x-1], magnitude[y+1, x+1]
if (magnitude[y, x] >= q) and (magnitude[y, x] >= r):
suppressed[y, x] = magnitude[y, x]
# 双阈值检测
strong_edges = suppressed >= high_threshold
weak_edges = (suppressed >= low_threshold) & (suppressed < high_threshold)
# 边缘跟踪
edges = strong_edges.copy()
for y in range(1, height-1):
for x in range(1, width-1):
if weak_edges[y, x]:
# 检查8邻域是否有强边缘
if np.any(strong_edges[y-1:y+2, x-1:x+2]):
edges[y, x] = True
return edges.astype(np.uint8) * 255
def butianyun_gradient(image):
'''
计算图像的梯度幅值和方向。
使用Sobel算子计算x和y方向的梯度,然后合成梯度幅值和方向。
方向角度以弧度表示,范围[-pi, pi]。
使用float32优化计算速度。
'''
grad_x = butianyun_sobel(image.astype(np.float32), axis='x')
grad_y = butianyun_sobel(image.astype(np.float32), axis='y')
magnitude = np.sqrt(grad_x**2 + grad_y**2).astype(np.float32)
direction = np.arctan2(grad_y, grad_x)
return magnitude, direction, grad_x, grad_y
def butianyun_get_edge_points(gradient_magnitude, threshold):
'''
提取边缘像素点。
根据梯度幅值阈值,提取满足条件的边缘点坐标。
返回边缘点坐标列表。
'''
edge_points = np.where(gradient_magnitude > threshold)
edge_coords = list(zip(edge_points[0], edge_points[1]))
return edge_coords
def butianyun_accumulate_centers(edge_coords, grad_dirs, grad_mags,
center_x_range, center_y_range,
num_samples, radius_min, radius_max, dp=1):
'''
第一阶段:在圆心空间累加投票(参考OpenCV的两阶段检测)。
对于每个边缘点,根据梯度方向估计可能的圆心位置并投票。
dp是累加器分辨率与图像分辨率的反比(1=相同分辨率,2=一半分辨率)。
OpenCV使用霍夫梯度法:先在圆心空间累加,找到候选圆心;
然后对每个候选圆心,在半径空间累加,确定最佳半径。
优化:使用实际的半径范围进行投票,而不是固定的距离范围。
'''
height = int(len(center_y_range) / dp)
width = int(len(center_x_range) / dp)
# 使用float32减少内存占用
center_accumulator = np.zeros((height, width), dtype=np.float32)
# 采样比例:使用30%的边缘点(概率霍夫变换的核心)
num_samples = min(num_samples, len(edge_coords))
print(f"边缘点总数: {len(edge_coords)}, 采样数量: {num_samples} ({100*num_samples/len(edge_coords):.1f}%)")
# 随机选择采样点索引
sampled_indices = np.random.choice(len(edge_coords), num_samples, replace=False)
for idx in sampled_indices:
y, x = edge_coords[idx]
grad_dir = grad_dirs[y, x]
grad_mag = grad_mags[y, x]
# 计算梯度方向的单位向量
cos_dir = np.cos(grad_dir)
sin_dir = np.sin(grad_dir)
# 在圆心空间投票,使用实际的半径范围
# 圆心位置 = 边缘点 - 半径 * 梯度方向
# 使用更大的步长减少计算量,同时保持覆盖整个半径范围
for r in range(radius_min, radius_max + 1, 5):
center_x = x - r * cos_dir
center_y = y - r * sin_dir
# 缩放到累加器空间
cx_scaled = int(center_x / dp + 0.5)
cy_scaled = int(center_y / dp + 0.5)
if 0 <= cx_scaled < width and 0 <= cy_scaled < height:
# 使用梯度幅值加权投票
center_accumulator[cy_scaled, cx_scaled] += grad_mag
return center_accumulator
def butianyun_accumulate_radii(edge_coords, grad_dirs, grad_mags,
center_x, center_y, radius_range):
'''
第二阶段:对于给定的圆心,在半径空间累加投票,确定最佳半径。
参考OpenCV的圆心确定后,再找半径的策略。
优化:使用梯度幅值加权投票,对强边缘和弱边缘分别处理。
对弱边缘给予额外加权以提高检测率。
'''
radius_accumulator = np.zeros(len(radius_range), dtype=np.float32)
for y, x in edge_coords:
grad_dir = grad_dirs[y, x]
grad_mag = grad_mags[y, x]
# 计算从圆心到边缘点的向量
dx = x - center_x
dy = y - center_y
dist = np.sqrt(dx**2 + dy**2)
# 避免除以0:当边缘点与圆心重合时跳过
if dist < 0.1:
continue
# 检查距离是否在半径范围内
if dist < radius_range[0] - 2 or dist > radius_range[-1] + 2:
continue
# 计算边缘点的梯度方向
cos_dir = np.cos(grad_dir)
sin_dir = np.sin(grad_dir)
# 计算从圆心到边缘点的单位向量
dir_x = dx / dist
dir_y = dy / dist
# 检查梯度方向是否与圆心方向一致
# 对于圆边界,梯度应该指向圆外,即从圆心指向边缘点
grad_dot = dir_x * cos_dir + dir_y * sin_dir
# 根据梯度幅值使用不同的角度约束和投票权重
# 强边缘使用严格约束,弱边缘使用宽松约束
if grad_mag > 50:
# 强边缘:约30度容差(严格)
if grad_dot > 0.866:
r_idx = int(round(dist - radius_range[0]))
if 0 <= r_idx < len(radius_range):
# 对强边缘不进行额外加权,避免过度累积
radius_accumulator[r_idx] += grad_mag
elif grad_mag > 30:
# 中等强度边缘:约37度容差
if grad_dot > 0.8: # cos(37°) ≈ 0.8
r_idx = int(round(dist - radius_range[0]))
if 0 <= r_idx < len(radius_range):
# 对中等边缘给予适当加权
radius_accumulator[r_idx] += grad_mag * 1.2
else:
# 弱边缘(如黄色):约50度容差(宽松)
if grad_dot > 0.643: # cos(50°) ≈ 0.643
r_idx = int(round(dist - radius_range[0]))
if 0 <= r_idx < len(radius_range):
# 对弱边缘给予额外加权,提高检测率
radius_accumulator[r_idx] += grad_mag * 1.5
return radius_accumulator
def butianyun_find_local_maxima(center_accumulator, edge_coords, grad_dirs, grad_mags,
radius_range, center_threshold, radius_threshold,
min_distance=10, dp=1):
'''
两阶段查找圆:先找圆心,再找半径(参考OpenCV的检测策略)。
参数:
- center_accumulator: 圆心空间累加器
- edge_coords: 边缘点坐标
- grad_dirs: 梯度方向
- grad_mags: 梯度幅值
- radius_range: 半径范围
- center_threshold: 圆心累加器阈值(对应OpenCV的param2)
- radius_threshold: 半径累加器阈值
- min_distance: 圆心之间的最小距离(对应OpenCV的minDist)
- dp: 累加器分辨率缩放因子
返回:[(center_y, center_x, radius, score), ...]
'''
circles = []
height, width = center_accumulator.shape
# 第一阶段:在圆心累加器中找到局部极大值
mask = center_accumulator > center_threshold
indices = np.argwhere(mask)
if len(indices) == 0:
return []
# 按圆心累加器值排序
scores = center_accumulator[indices[:, 0], indices[:, 1]]
sorted_indices = np.argsort(scores)[::-1]
min_distance_sq = min_distance ** 2
for idx in sorted_indices:
cy_scaled, cx_scaled = indices[idx]
center_score = center_accumulator[cy_scaled, cx_scaled]
# 转换回图像坐标系
cx = int(cx_scaled * dp)
cy = int(cy_scaled * dp)
# 检查是否与已检测的圆距离太近
too_close = False
for detected_cy, detected_cx, detected_r, detected_score in circles:
dist_sq = (cy - detected_cy)**2 + (cx - detected_cx)**2
if dist_sq < min_distance_sq:
too_close = True
break
if too_close:
continue
# 第二阶段:对于每个候选圆心,在半径空间累加,找最佳半径
radius_accumulator = butianyun_accumulate_radii(
edge_coords, grad_dirs, grad_mags, cx, cy, radius_range
)
# 找到半径累加器的最大值
max_r_idx = np.argmax(radius_accumulator)
max_r_score = radius_accumulator[max_r_idx]
# 如果半径累加器值超过阈值
if max_r_score >= radius_threshold:
r = radius_range[max_r_idx]
circles.append((cy, cx, r, max_r_score))
return circles
def butianyun_draw_circles(image, circles, color=(0, 255, 0), thickness=2):
'''
在图像上绘制检测到的圆。
circles格式:[(center_y, center_x, radius, score), ...]
'''
result = image.copy()
for cy, cx, r, score in circles:
# 使用Bresenham算法绘制圆
x = 0
y = r
d = 3 - 2 * r
# 在8个对称点绘制
while x <= y:
points = [
(cx + x, cy + y), (cx - x, cy + y),
(cx + x, cy - y), (cx - x, cy - y),
(cx + y, cy + x), (cx - y, cy + x),
(cx + y, cy - x), (cx - y, cy - x)
]
for px, py in points:
if 0 <= px < result.shape[1] and 0 <= py < result.shape[0]:
result[py, px] = color
x += 1
if d < 0:
d = d + 4 * x + 6
else:
d = d + 4 * (x - y) + 10
y -= 1
return result
def butianyun_draw_centers(image, circles, color=(0, 0, 255), radius=3):
'''
在图像上绘制检测到的圆心。
'''
result = image.copy()
for cy, cx, r, score in circles:
# 绘制实心圆点表示圆心
for y in range(cy - radius, cy + radius + 1):
for x in range(cx - radius, cx + radius + 1):
if 0 <= x < result.shape[1] and 0 <= y < result.shape[0]:
if (x - cx)**2 + (y - cy)**2 <= radius**2:
result[y, x] = color
return result
def butianyun_detect_circles(image, canny_high_threshold=50, radius_min=10, radius_max=50,
num_samples=2000, center_threshold=100, radius_threshold=50,
min_distance=20, dp=1, roi_y_range=None):
'''
概率霍夫变换圆检测主函数(参考OpenCV cv2.HoughCircles的两阶段检测策略)。
参数:
- canny_high_threshold: Canny边缘检测的高阈值(对应OpenCV的param1)
- radius_min, radius_max: 检测圆的半径范围
- num_samples: 随机采样的边缘点数量(概率霍夫变换核心)
- center_threshold: 圆心累加器阈值(对应OpenCV的param2)
- radius_threshold: 半径累加器阈值
- min_distance: 圆心之间的最小距离(对应OpenCV的minDist)
- dp: 累加器分辨率与图像分辨率的反比(1=相同分辨率,2=一半分辨率)
- roi_y_range: 感兴趣区域的Y轴范围,格式为(y_min, y_max),None表示使用整幅图像
返回:(circles, gray, gradient_mag, edge_image)
- circles: 检测到的圆列表:[(center_y, center_x, radius, score), ...]
- gray: 灰度图
- gradient_mag: 梯度幅值图
- edge_image: 边缘二值图
'''
# 转换为灰度图,使用加权灰度转换以保持彩色对比度
if len(image.shape) == 3:
# 使用加权平均:0.299*R + 0.587*G + 0.114*B
gray = (image[:, :, 2] * 0.299 + image[:, :, 1] * 0.587 + image[:, :, 0] * 0.114).astype(np.uint8)
else:
gray = image.copy()
# 确定感兴趣区域(ROI)
if roi_y_range is None:
roi_y_min, roi_y_max = 0, gray.shape[0]
else:
roi_y_min, roi_y_max = roi_y_range
# 确保ROI在图像范围内
roi_y_min = max(0, roi_y_min)
roi_y_max = min(gray.shape[0], roi_y_max)
print(f"检测区域: Y轴范围 [{roi_y_min}, {roi_y_max}], 高度 {roi_y_max - roi_y_min}")
# 计算梯度
gradient_mag, gradient_dir, grad_x, grad_y = butianyun_gradient(gray)
# 保存调试图像
cv2.imwrite('debug_gray.png', gray)
gradient_mag_norm = cv2.normalize(gradient_mag, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
cv2.imwrite('debug_gradient.png', gradient_mag_norm)
# 使用Canny边缘检测(参考OpenCV的param1参数)
canny_low_threshold = canny_high_threshold * 0.5
edge_binary = butianyun_canny(gray, canny_high_threshold, canny_low_threshold)
cv2.imwrite('debug_edge.png', edge_binary)
# 提取边缘点,并过滤到ROI区域
edge_coords_all = butianyun_get_edge_points(edge_binary, 127)
# 只保留ROI区域内的边缘点
edge_coords = [(y, x) for y, x in edge_coords_all if roi_y_min <= y < roi_y_max]
print(f"边缘点数量(原始): {len(edge_coords_all)}, 边缘点数量(ROI): {len(edge_coords)}")
# 创建边缘图像用于调试
edge_image = edge_binary.copy()
# 在边缘图像上标记ROI区域边界
if roi_y_min > 0:
edge_image[roi_y_min, :] = 128 # 标记ROI上边界
if roi_y_max < gray.shape[0]:
edge_image[roi_y_max, :] = 128 # 标记ROI下边界
if len(edge_coords) == 0:
return [], gray, gradient_mag, edge_image
# 定义参数空间
center_x_range = np.arange(0, gray.shape[1])
center_y_range = np.arange(0, gray.shape[0])
radius_range = np.arange(radius_min, radius_max + 1)
# 第一阶段:圆心空间累加(使用实际的半径范围)
center_accumulator = butianyun_accumulate_centers(
edge_coords, gradient_dir, gradient_mag,
center_x_range, center_y_range,
num_samples, radius_min, radius_max, dp
)
print(f"圆心累加器维度: {center_accumulator.shape}")
print(f"圆心累加器最大值: {np.max(center_accumulator)}, 平均值: {np.mean(center_accumulator):.2f}")
# 两阶段查找局部极大值
circles = butianyun_find_local_maxima(
center_accumulator, edge_coords, gradient_dir, gradient_mag,
radius_range, center_threshold, radius_threshold, min_distance, dp
)
# 过滤掉圆心在ROI区域之外的圆
circles = [(cy, cx, r, score) for cy, cx, r, score in circles
if roi_y_min - radius_max <= cy <= roi_y_max + radius_max]
return circles, gray, gradient_mag, edge_image
def butianyun_show_all_images(input_image, gray, gradient_mag, edge_image, output_image, title='圆形检测概率霍夫变换算法'):
'''
显示所有图像:输入图像、调试图像、检测结果
布局:第一行输入图,第二行调试图,第三行结果图
'''
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
# 创建3行子图
fig = plt.figure(figsize=(15, 12))
# 第一行:输入图像
ax1 = plt.subplot(3, 1, 1)
if len(input_image.shape) == 2:
ax1.imshow(input_image, cmap='gray')
else:
ax1.imshow(cv2.cvtColor(input_image, cv2.COLOR_BGR2RGB))
ax1.set_title('输入图像', fontsize=12)
ax1.axis('off')
# 第二行:调试图像
ax2 = plt.subplot(3, 3, 4)
ax2.imshow(gray, cmap='gray')
ax2.set_title('灰度图', fontsize=10)
ax2.axis('off')
ax3 = plt.subplot(3, 3, 5)
ax3.imshow(gradient_mag, cmap='gray')
ax3.set_title('梯度幅值图', fontsize=10)
ax3.axis('off')
ax4 = plt.subplot(3, 3, 6)
ax4.imshow(edge_image, cmap='gray')
ax4.set_title('边缘图', fontsize=10)
ax4.axis('off')
# 第三行:检测结果
ax5 = plt.subplot(3, 1, 3)
if len(output_image.shape) == 2:
ax5.imshow(output_image, cmap='gray')
else:
ax5.imshow(cv2.cvtColor(output_image, cv2.COLOR_BGR2RGB))
ax5.set_title('检测结果', fontsize=12)
ax5.axis('off')
plt.suptitle(title, fontsize=16, y=0.995)
plt.tight_layout()
plt.subplots_adjust(top=0.93, hspace=0.3)
plt.savefig('圆形检测概率霍夫变换算法.png', dpi=150, bbox_inches='tight')
plt.show()
plt.close()
def main():
'''
主函数:读取输入图像,进行圆形检测,保存结果并对照显示。
参数参考OpenCV cv2.HoughCircles:
- canny_high_threshold: 对应param1(Canny高阈值)
- center_threshold: 对应param2(圆心累加器阈值)
- min_distance: 对应minDist(圆心最小距离)
- dp: 累加器分辨率缩放因子
- roi_y_range: 检测区域Y轴范围(跳过底部文字区域)
'''
# 读取输入图像
input_image = cv2.imread('butianyun_computer_vision_input.png')
if input_image is None:
print("错误:无法读取输入图像")
return
# 设置检测区域(跳过底部文字区域)
# 图像高度400像素,底部70像素是文字区域,只检测前330像素
roi_y_range = (0, 330)
# 进行圆形检测(参考OpenCV cv2.HoughCircles的参数设置)
print("正在进行圆形检测...")
circles, gray, gradient_mag, edge_image = butianyun_detect_circles(
input_image,
canny_high_threshold=80, # 降低Canny高阈值,检测更多边缘
radius_min=20, # 扩大半径范围下限
radius_max=70, # 扩大半径范围上限,检测大黄色圆形
num_samples=4000, # 增加采样数量提高检测率
center_threshold=120, # 降低圆心累加器阈值,检测黄色圆形
radius_threshold=40, # 进一步降低半径累加器阈值
min_distance=45, # 增加圆心最小距离,减少深色圆形重复检测
dp=1, # 累加器分辨率与图像分辨率相同
roi_y_range=roi_y_range # 只检测前330像素,跳过底部文字
)
print(f"检测到 {len(circles)} 个圆")
# 在图像上绘制检测到的圆和圆心
output_image = input_image.copy()
output_image = butianyun_draw_circles(output_image, circles, color=(0, 0, 255), thickness=5) # 5像素宽圆形线条
output_image = butianyun_draw_centers(output_image, circles, color=(0, 0, 255), radius=3)
# 在输出图像上绘制ROI区域边界(可视化,边界线移至350像素)
roi_boundary_y = 350
cv2.line(output_image, (0, roi_boundary_y), (output_image.shape[1], roi_boundary_y), (255, 0, 0), 3)
# 保存输出图像
cv2.imwrite('butianyun_computer_vision_output.png', output_image)
print("结果图像已保存为 butianyun_computer_vision_output.png")
# 显示所有图像
print("正在显示所有图像...")
butianyun_show_all_images(input_image, gray, gradient_mag, edge_image, output_image)
print("所有图像已显示")
if __name__ == '__main__':
main()
总结
概率霍夫变换圆检测算法采用两阶段检测策略,通过随机采样边缘点和参数空间投票,实现了高效的圆形检测。相比标准霍夫变换,该方法在保持检测准确率的同时大幅降低了计算复杂度,适用于实时应用场景。
算法主要优势:
- 两阶段检测策略(圆心检测+半径确定)大幅减少计算量
- 梯度幅值自适应的角度约束,实现对强边缘和弱边缘的差异化处理
- Canny边缘检测比简单梯度阈值更稳定
- ROI区域设置可以跳过不感兴趣的区域(如文字)
- 通过调整Canny阈值、中心阈值、半径阈值、最小距离等参数,可以适应不同场景的检测需求
算法实现中使用了Sobel算子计算梯度、Canny算子进行边缘检测、Bresenham算法绘制圆形等经典方法,确保了算法的准确性和稳定性。
计算机视觉技术算法 概念+原理+实现
微信视频号:计算机视觉技术
微信公众号:计算机视觉技术
感谢您的关注!
计算机视觉技术算法 概念+原理+实现
微信视频号:计算机视觉技术
微信公众号:计算机视觉技术
感谢您的关注!
关注微信公众号"计算机视觉技术”,系统掌握100种计算机视觉技术算法。
01均值滤波算法
02高斯滤波算法
03中值滤波算法
04双边滤波算法
05引导滤波算法
06直方图均衡化算法
07自适应直方图均衡化算法
08限制对比度自适应直方图均衡化算法
09伽马校正算法
10对数变换算法
11线性变换算法
12高斯噪声去除算法
13直方图匹配算法
14椒盐噪声去除算法
15脉冲噪声滤波算法
16图像灰度化算法
17形态学腐蚀算法
18形态学膨胀算法
19色彩量化算法
20颜色空间转换算法
21形态学开运算算法
22形态学闭运算算法
23顶帽变换算法
24底帽变换算法
25图像旋转算法
26图像缩放算法
27图像裁剪算法
28图像镜像翻转算法
29图像金字塔构建算法
30图像去雾暗通道先验算法
31FAST角点检测算法
32ORB特征提取与描述算法
33Shi-Tomasi角点检测算法
34SIFT特征提取与描述算法
35SURF特征提取与描述算法
36Harris角点检测算法
37BRISK特征提取与描述算法
38BRIEF特征描述算法
39FREAK特征描述算法
40Sobel边缘检测算法
41Prewitt边缘检测算法
42Roberts边缘检测算法
43Canny边缘检测算法
44Laplacian边缘检测算法
45LOG边缘检测算法
46方向梯度直方图特征提取算法
47局部二值模式特征提取算法
48中心对称局部二值模式特征提取算法
49局部相位量化特征提取算法
50形态学骨架提取算法
51尺度不变特征变换算法
52离散余弦变换特征提取算法
53最大稳定极值区域检测算法
54极值稳定区域检测算法
55简单线性迭代聚类算法
56选择性搜索区域推荐算法
57密集特征提取算法
58傅里叶变换特征提取算法
59Gabor滤波器特征提取算法
60阈值分割全局阈值算法
61阈值分割迭代阈值算法
62阈值分割最大类间方差算法
63区域生长分割算法
64区域分裂与合并分割算法
65分水岭分割算法
66活动轮廓模型分割算法
67基于图割的图像分割算法
68水平集图像分割算法
69梯度矢量流活动轮廓分割算法
70K-Means聚类图像分割算法
71模糊C均值聚类图像分割算法
72谱聚类图像分割算法
73均值漂移图像分割算法
74条件随机场图像分割算法
75马尔可夫随机场图像分割算法
76实例分割掩码生成算法
77超像素分割SLIC算法
78滑动窗口目标检测算法
79积分图目标检测算法
80级联分类器目标检测算法
81交并比计算算法
82非极大值抑制算法
83卡尔曼滤波目标跟踪算法
84粒子滤波目标跟踪算法
85均值漂移目标跟踪算法
86连续自适应均值漂移目标跟踪算法
87相关滤波目标跟踪算法
88核相关滤波目标跟踪算法
89判别相关滤波目标跟踪算法
90多目标跟踪匈牙利匹配算法
91多目标跟踪余弦距离匹配算法
92基于特征的图像配准算法
93基于区域的图像配准算法
94RANSAC图像配准算法
95同态滤波算法
96多尺度Retinex增强算法
97PCA主成分分析算法
98线段检测LSD算法
99光流法目标追踪算法
100圆形检测概率霍夫变换算法
关注微信公众号"计算机视觉技术”,系统掌握100种计算机视觉技术算法。 微信公众号提供获取10000+计算机视觉技术最新技术PDF的URL链接和获取PDF的脚本工具。实际体验亲测稳定可靠。
100种算法,每一种算法都具体介绍了概念、原理、步骤、运行效果,以及完整的源码实现,您可以据此系统全面掌握计算机视觉技术算法。
如果您认为这篇技术博客对您有一点帮助,请一定点赞+评论+收藏+转发。您的支持是作者创作更多更好的技术博客的动力。