霍夫变换

霍夫变换(Hough Transform) 运用两个坐标空间之间的变换将在一个空间中具有相同形状的曲线或直线映射到另一个坐标空间的一个点上形成峰值,从而把检测任意形状的问题转化为统计峰值问题。

霍夫线变换原理

在极坐标系中,直线可由极径 r r r 和极角 θ \theta θ 表示:

直线的表达式为:

y = ( − c o s θ s i n θ ) x + ( r s i n θ ) y=\left ( -\frac{cos\theta }{sin\theta } \right )x+\left ( \frac{r}{sin\theta } \right ) y=(sinθcosθ)x+(sinθr)

对于点 ( x 0 , y 0 ) (x_0,y_0) (x0,y0) ,可以将通过这个点的一族直线定义为:

r = x cos ⁡ θ + y sin ⁡ θ r=x\cos\theta +y\sin\theta r=xcosθ+ysinθ

在极坐标下,通过点 x 0 = 8 x_0 = 8 x0=8 y 0 = 6 y_0 = 6 y0=6 的直线( r &gt; 0 r&gt;0 r>0 0 &lt; θ &lt; 2 π 0&lt;\theta&lt;2\pi 0<θ<2π )在平面 θ − r \theta-r θr中的图形如下:

对图像中所有的点进行上述操作,如果两个不同点进行上述操作后得到的曲线在平面 θ − r \theta-r θr相交, 这就意味着它们通过同一条直线,如下图:

霍夫线变换,对图像中每个点对应曲线间的交点进行追踪,如果交于一点的曲线的数量超过了阈值,就认为这个交点所对应的 ( r , θ ) (r,\theta) (r,θ) 在原图像中为一条直线。

霍夫线变换

霍夫线变换可以用来寻找边缘二值图像中的直线。

OpenCV中的霍夫线变换有如下三种:

  1. 标准霍夫变换(StandardHough Transform,SHT),由HoughLines() 函数调用。

  2. 多尺度霍夫变换(Multi-ScaleHough Transform,MSHT),由HoughLines() 函数调用。多尺度霍夫变换(MSHT)为标准霍夫变换(SHT)在多尺度下的一个变种。

  3. 累计概率霍夫变换(ProgressiveProbabilistic Hough Transform,PPHT),由HoughLinesP() 函数调用。累计概率霍夫变换(PPHT)算法是标准霍夫变换(SHT)算法的一个改进,并不将累加器平面内的所有可能的点累加,而只是累加其中的一部分,该想法是如果峰值如果足够高,只用一小部分时间去寻找它就够了。从而减少计算量,缩短计算时间。

霍夫直线检测,函数原型:

def HoughLines(image, rho, theta, threshold, lines=None, srn=None, stn=None, min_theta=None, max_theta=None)

参数:

  • image:8位的单通道二进制源图像

  • rho:搜索直线的半径步长

  • theta:搜索直线的角度步长

  • threshold:累加平面的阈值参数,大于阈值threshold的线段才可以被检测通过并返回到结果中

  • lines:输出检测到的线条矢量。每条线由 ( ρ , θ ) (\rho, \theta) (ρ,θ) 表示,其中, ρ \rho ρ 是离坐标原点((0,0)(也就是图像的左上角)的距离, θ \theta θ 是弧度线条旋转角度

  • srn:默认值0,是多尺度霍夫变换的rho的除数。粗略的累加器半径步长直接是rho,而精确的累加器半径步长为 r h o / s r n rho/srn rho/srn

  • stn:默认值0,是多尺度霍夫变换的theta的除数。如果srn和stn同时为0,就表示使用经典的霍夫变换。否则,这两个参数应该都为正数。

  • min_theta:默认值 0 0 0,theta 阈值下限

  • max_theta:默认值 π \pi π,theta 阈值上限

霍夫线段检测,函数原型:

def HoughLinesP(image, rho, theta, threshold, lines=None, minLineLength=None, maxLineGap=None)

参数:

  • image:8位的单通道二进制源图像

  • rho:搜索直线的半径步长

  • theta:搜索直线的角度步长

  • threshold:累加平面的阈值参数,大于阈值threshold的线段才可以被检测通过并返回到结果中

  • lines:输出检测到的线条矢量。每条线由 ( ρ , θ ) (\rho, \theta) (ρ,θ) 表示,其中, ρ \rho ρ 是离坐标原点((0,0)(也就是图像的左上角)的距离, θ \theta θ 是弧度线条旋转角度

  • minLineLength:默认值0,表示最低线段的长度,比这个设定参数短的线段就不能被显现出来。

  • maxLineGap,默认值0,允许将同一行点与点之间连接起来的最大的距离。

霍夫直线检测效果:

霍夫线段检测效果:

霍夫圆变换原理

在霍夫圆变换中,点对应的二维极径极角空间被三维的圆心点 x x x y y y 还有半径 r r r 空间取代,表示如下:
C : ( x c e n t e r , y c e n t e r , r ) C:(x_{center},y_{center},r) C:(xcenter,ycenter,r)

通过霍夫梯度法进行霍夫圆变换:

  1. 对边缘图像中的每一个非零点,用Sobel() 函数计算 x x x y y y 方向的一阶导数得到梯度。

  2. 利用得到的梯度,由斜率指定的直线上的每一个点都在累加器中被累加,这里的斜率是从一个指定的最小值到指定的最大值的距离。

  3. 标记边缘图像中每一个非0像素的位置。

  4. 从二维累加器的点中,选择候选的中心,这些中心都大于给定阈值并且大于其所有近邻。这些候选的中心按照累加值降序排列,以便于最支持像素的中心首先出现。

  5. 对每一个中心,考虑所有的非0像素。

  6. 这些像素按照其与中心的距离排序。从到最大半径的最小距离算起,选择非0像素最支持的一条半径。

  7. 如果一个中心收到边缘图像非0像素最充分的支持,并且到前期被选择的中心有足够的距离,那么它就会被保留下来。

这个实现可以使算法执行起来更高效,或许更加重要的是,能够帮助解决三维累加器中会产生许多噪声并且使得结果不稳定的稀疏分布问题。

霍夫梯度法的缺点

  • 霍夫梯度法中使用Sobel导数来计算局部梯度,可能会在输出中产生一些噪声。

  • 在边缘图像中的整个非0像素集被看做每个中心的候选部分,如果累加器的阈值设置偏低,算法将要消耗比较长的时间。

  • 由于中心是按照其关联的累加器值的升序排列的,并且如果新的中心过于接近之前已经接受的中心的话,就不会被保留下来。

  • 由于每一个中心只选择一个圆,如果有同心圆或者是近似的同心圆时,霍夫梯度法的倾向是保留最大的一个圆。

霍夫圆变换

函数原型:

def HoughCircles(image, method, dp, minDist, circles=None, param1=None, param2=None, minRadius=None, maxRadius=None)

参数:

  • image:8位的灰度单通道源图像

  • method:使用的检测方法,目前OpenCV中只有霍夫梯度法一种可以使用,它的标识符为cv2.HOUGH_GRADIENT

  • dp:累加器图像的分辨率与输入图像之比的倒数,且此参数允许创建一个比输入图像分辨率低的累加器。如 d p = 1 dp= 1 dp=1 时,累加器和输入图像具有相同的分辨率, d p = 2 dp=2 dp=2 时,累加器的分辨率为源图像的一半。

  • minDist:圆的圆心之间的最小距离,即让我们的算法能明显区分的两个不同圆之间的最小距离。

  • circles:检测到的圆的输出浮点矢量,结构为: ( x , y , r a d i u s ) (x, y, radius) (x,y,radius)

  • param1:默认值100,表示霍夫梯度法中,传递给canny边缘检测算子的高阈值,而低阈值为高阈值的一半。

  • param2:默认值100,表示霍夫梯度法中,检测阶段圆心的累加器阈值。它越小的话,就可以检测到更多根本不存在的圆,而它越大的话,能通过检测的圆就更加接近完美的圆形了。

  • minRadius:有默认值0,表示圆半径的最小值。

  • maxRadius:有默认值0,表示圆半径的最大值。

霍夫圆检测效果:

霍夫变换综合调参

import cv2
import numpy as np

def gaussianBlur(gray):
	gaussian = cv2.GaussianBlur(gray, (5, 5), 0)
	gaussian_canny = cv2.Canny(gaussian, 42, 42 * 3, apertureSize=3)
	cv2.imshow('gaussian_Canny', gaussian_canny)
	return gaussian_canny

def morphSmooth(gray):
	kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7, 7))
	close = cv2.morphologyEx(gray, cv2.MORPH_CLOSE, kernel)
	close_denoise = cv2.fastNlMeansDenoising(close, None, 10, 7, 7 * 3)
	close_denoise_canny = cv2.Canny(close_denoise, 42, 42 * 3, apertureSize=3)
	cv2.imshow('close_Denoise_Canny', close_denoise_canny)
	return close_denoise_canny

def tune_HoughLines(x):
	threshold = cv2.getTrackbarPos('threshold', 'Hough')
	min_theta = cv2.getTrackbarPos('min_theta', 'Hough')
	max_theta = cv2.getTrackbarPos('max_theta', 'Hough')
	min_theta = min_theta / 100.0
	max_theta = max_theta / 100.0

	gaussian_HoughLines = draw_HoughLines(gaussian_canny, img.copy(), min_theta, max_theta, threshold)
	close_HoughLines = draw_HoughLines(close_denoise_canny, img.copy(), min_theta, max_theta, threshold)
	cv2.imshow('gaussian_Canny_HoughLines', gaussian_HoughLines)
	cv2.imshow('close_Denoise_Canny_HoughLines', close_HoughLines)

def tune_HoughLinesP(x):
	threshold = cv2.getTrackbarPos('threshold', 'Hough')
	minLineLength = cv2.getTrackbarPos('minLineLength', 'Hough')
	maxLineGap = cv2.getTrackbarPos('maxLineGap', 'Hough')

	gaussian_HoughLinesP = draw_HoughLinesP(gaussian_canny, img.copy(), minLineLength, maxLineGap, threshold)
	close_HoughLinesP = draw_HoughLinesP(close_denoise_canny, img.copy(), minLineLength, maxLineGap, threshold)
	cv2.imshow('gaussian_Canny_Hough', gaussian_HoughLinesP)
	cv2.imshow('close_Denoise_Canny_Hough', close_HoughLinesP)

def tune_HoughCircles(x):
	minDist = cv2.getTrackbarPos('minDist', 'Hough')
	param1 = cv2.getTrackbarPos('param1', 'Hough')
	param2 = cv2.getTrackbarPos('param2', 'Hough')
	minRadius = cv2.getTrackbarPos('minRadius', 'Hough')
	maxRadius = cv2.getTrackbarPos('maxRadius', 'Hough')

	gaussian_HoughCircles = draw_HoughCircles(gaussian_canny, img.copy(), minDist, param1, param2, minRadius, maxRadius)
	cv2.imshow('gaussian_HoughCircles', gaussian_HoughCircles)

def draw_HoughLines(edge, img, min_theta, max_theta, threshold):
	lines = cv2.HoughLines(edge, 1, np.pi / 180, threshold)
	for line in lines:
		rho, theta = line[0]
		if min_theta < theta < max_theta:
			continue
		a = np.cos(theta)
		b = np.sin(theta)
		x0 = a * rho
		y0 = b * rho
		x1 = int(x0 + 1000 * (-b))
		y1 = int(y0 + 1000 * (a))
		x2 = int(x0 - 1000 * (-b))
		y2 = int(y0 - 1000 * (a))
		cv2.line(img, (x1, y1), (x2, y2), line_color, 2)
	return img

def draw_HoughLinesP(edge, img, minLineLength, maxLineGap, threshold):
	lines = cv2.HoughLinesP(edge, 1, np.pi / 180, threshold, None, minLineLength, maxLineGap)
	for line in lines:
		l = line[0]
		cv2.line(img, (l[0], l[1]), (l[2], l[3]), line_color, 2)
	return img

def draw_HoughCircles(edge, img, minDist, param1, param2, minRadius, maxRadius):
	circles = cv2.HoughCircles(edge, cv2.HOUGH_GRADIENT, 1.5, minDist, None, param1, param2, minRadius, maxRadius)
	if circles is None: return img

	for circle in circles[0]:
		center = (circle[0], circle[1])
		radius = circle[2]
		cv2.circle(img, center, radius, line_color,3)
	return img

def show_HoughLines():
	cv2.createTrackbar('threshold', 'Hough', 74, 100, tune_HoughLines)
	cv2.createTrackbar('min_theta', 'Hough', 55, max_theta, tune_HoughLines)
	cv2.createTrackbar('max_theta', 'Hough', 250, max_theta, tune_HoughLines)
	tune_HoughLines(0)

def show_HoughLinesP():
	cv2.createTrackbar('threshold', 'Hough', 55, 100, tune_HoughLinesP)
	cv2.createTrackbar('minLineLength', 'Hough', 130, 300, tune_HoughLinesP)
	cv2.createTrackbar('maxLineGap', 'Hough', 76, 300, tune_HoughLinesP)
	tune_HoughLinesP(0)

def show_HoughCircles():
	cv2.createTrackbar('minDist', 'Hough', 40, 100, tune_HoughCircles)
	cv2.createTrackbar('param1', 'Hough', 200, 300, tune_HoughCircles)
	cv2.createTrackbar('param2', 'Hough', 41, 200, tune_HoughCircles)
	cv2.createTrackbar('minRadius', 'Hough', 4, 100, tune_HoughCircles)
	cv2.createTrackbar('maxRadius', 'Hough', 20, 300, tune_HoughCircles)
	tune_HoughCircles(0)

cv2.namedWindow('Hough')
img = cv2.imread("./timg.jpg")
cv2.imshow('Hough', img)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gaussian_canny = gaussianBlur(gray)
close_denoise_canny = morphSmooth(gray)

max_theta = int(np.pi * 100)
line_color = (39, 127, 255)

show_HoughCircles()
# show_HoughLines()
# show_HoughLinesP()
if cv2.waitKey(0) == 27:
	cv2.destroyAllWindows()

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值