环境:Python3.8 和 OpenCV
内容:Hough圆检测
将直角坐标系中的一个圆映射为新坐标系中的一个点,对于原直角坐标系中的每一个圆,可以对应(a, b, r) 这样一个点,这个点即为新三维中的点。
标准法实现步骤:
1.获取原图像的边缘检测图像;
2.设置最小半径、最大半径和半径分辨率等超参数;
3.根据转化后空间的圆心分辨率等信息,设置计数器N(a, b, r);
4.对边缘检测图像的每个白色边缘像素点,其坐标记为(x, y),对于指定的半径r,根据公式 (a-x)^2 + (b-y)^2 = r^2 得到点(a, b)形成的轨迹圆,将轨迹圆经过的计数器N(a, b, r) 加1;
5.如果某个计数器的点数超过设定阈值,则认为存在对应的圆,取出(a, b, r),求出对应的圆方程。
梯度法实现步骤:
1.获取原图像的边缘检测图像;
2.设置最小半径、最大半径和半径分辨率等超参数;
3.计算原图像各点x和y方向的梯度;
4.设置计数器N(a, b);
5.对边缘图像中任意一点p,其梯度为(px, py),由该点p和其梯度决定一条直线,将直线经过的计数器N(a, b)加1。
6.如果某计数器点数超过阈值,则认为存在圆,取出的(a, b)记为圆心,通过不断变换半径,若与边缘图的交集点数超过设定阈值,则认为存在圆,从而获得圆方程。
实现方式1(梯度法):
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
# 封装图片显示函数
def image_show(image):
if image.ndim == 2:
plt.imshow(image, cmap='gray')
else:
image = cv.cvtColor(image, cv.COLOR_BGR2RGB)
plt.imshow(image)
plt.show()
# Hough圆检测
if __name__ == '__main__':
# 读取灰度图
img_desk = cv.imread('desk.png')
# 转换为灰度图
img_gray = cv.cvtColor(img_desk, cv.COLOR_BGR2GRAY)
# 参数设置
img_dp = 2 # 图像分辨率与累加器分辨率之比,建议范围为1-2
MinDist = 50 # 两个不同圆圆心之间的距离
Param1 = 400 # Canny边缘阈值的上限,其下限为上限的1/2
Param2 = 80 # cv.HOUGH_GRADIENT 方法的累加器阈值,阈值越小,检测的圆越多
# cv.HOUGH_GRADIENT_ALT 方法中,该参数用于衡量圆的完美度,范围为0-1
MinRadius = 10 # 最小圆半径
MaxRadius = 100 # 最大圆半径
# 霍夫圆检测
circles = cv.HoughCircles(img_gray, cv.HOUGH_GRADIENT, dp=img_dp, minDist=MinDist,
param1=Param1, param2=Param2, minRadius=MinRadius, maxRadius=MaxRadius)
# 绘制结果
for (x, y, r) in circles.squeeze():
cv.circle(img_desk, (int(x), int(y)), int(r), (0, 0, 255), 2)
image_show(img_desk)
实现方式2(标准法):
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
# 封装图片显示函数
def image_show(image):
if image.ndim == 2:
plt.imshow(image, cmap='gray')
else:
image = cv.cvtColor(image, cv.COLOR_BGR2RGB)
plt.imshow(image)
plt.show()
if __name__ == '__main__':
# 读取灰度图
img_desk = cv.imread('desk.png')
# 转换为灰度图
img_gray = cv.cvtColor(img_desk, cv.COLOR_BGR2GRAY)
# 边缘检测
img_canny = cv.Canny(img_gray, 100, 400)
# 参数设置
img_dr = 1 # 半径分辨率
MinRadius = 10 # 最小圆半径
MaxRadius = 100 # 最大圆半径
thresh = 80 # 累加的阈值
# 初始化计数器
height, width = img_gray.shape
radius = int((MaxRadius - MinRadius) / img_dr) + 1
N = np.zeros((width, height, radius))
# 获取边缘像素位置
edge_y, edge_x = np.where(img_canny == 255)
# 循环绘制不同圆
for (x, y) in zip(edge_x, edge_y):
for r in range(MinRadius, MaxRadius, img_dr):
# 根据个点绘制不同圆
circle = np.zeros((height, width), np.uint8)
cv.circle(circle, (x, y), r, 255, 1)
# 获取不同圆的轨迹位置
bsi, asi = np.where(circle == 255)
# 将轨迹经过位置计数器累加
N[asi, bsi, r - MinRadius] += 1
# 显示累加图
plt.imshow(N[:, :, 50], cmap='jet')
plt.show()
# 获取圆的位置和半径
xs, ys, rs = np.where(N > thresh)
# 绘制图上检测的圆
for (x, y, r) in zip(xs, ys, rs):
cv.circle(img_desk, (x, y), r, (0, 0, 255), 2)
# 显示结果
image_show(img_desk)
实现方式3(梯度法):
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
# 封装图片显示函数
def image_show(image):
if image.ndim == 2:
plt.imshow(image, cmap='gray')
else:
image = cv.cvtColor(image, cv.COLOR_BGR2RGB)
plt.imshow(image)
plt.show()
if __name__ == '__main__':
# 读取灰度图
img_desk = cv.imread('desk.png')
# 转换为灰度图
img_gray = cv.cvtColor(img_desk, cv.COLOR_BGR2GRAY)
# 边缘检测
img_canny = cv.Canny(img_gray, 100, 400)
# 参数设置
img_dr = 1 # 半径分辨率
MinRadius = 10 # 最小圆半径
MaxRadius = 100 # 最大圆半径
thresh1 = 40 # 累加的阈值
thresh2 = 90 # 交集的阈值
# 设置直线长度
height, width = img_canny.shape
lmax = height + width
# 计算梯度
Ix = cv.Sobel(img_gray, cv.CV_64F, 1, 0)
Iy = cv.Sobel(img_gray, cv.CV_64F, 0, 1)
Ix = Ix / (np.sqrt(Ix**2 + Iy**2) + 1e-5)
Iy = Iy / (np.sqrt(Ix ** 2 + Iy ** 2) + 1e-5)
# 设置计数器
N = np.zeros((height, width), np.int32)
# 获取边缘像素位置
edge_y, edge_x = np.where(img_canny == 255)
# 循环绘制不同直线
for (x, y) in zip(edge_x, edge_y):
# 获取不同点梯度
grad_x = Ix[y, x]
grad_y = Iy[y, x]
# 根据点和梯度绘制直线
line_sx = int(x + grad_x * lmax)
line_sy = int(y + grad_y * lmax)
line_ex = int(x - grad_x * lmax)
line_ey = int(y - grad_y * lmax)
line = np.zeros((height, width), np.uint8)
cv.line(line, (line_sx, line_sy), (line_ex, line_ey), 255, 1)
# 获取不同圆的轨迹位置
bsi, asi = np.where(line == 255)
# 将轨迹经过位置计数器累加
N[bsi, asi] += 1
# 显示累加图
plt.imshow(N, cmap='jet')
plt.show()
# 获取圆的位置和半径
bsi, asi = np.where(N > thresh1)
# 根据已知的圆心绘制不同半径的圆
cir_abr = []
for (a, b) in zip(asi, bsi):
for r in range(MinRadius, MaxRadius, img_dr):
# 绘制不同半径的圆
circles = np.zeros((height, width), np.uint8)
cv.circle(circles, (a, b), r, 255, 1)
# 获取绘制圆和边缘图的交集
intersection = cv.bitwise_and(circles, img_canny)
# 累计交集的点数
num_sum = (intersection == 255).sum()
# 获取超过阈值的圆心和半径
if num_sum > thresh2:
cir_abr.append((a, b, r))
# 绘制检测的圆
for (a, b, r) in cir_abr:
cv.circle(img_desk, (a, b), r, (0, 0, 255), 2)
# 显示结果
image_show(img_desk)