在完成边缘检测、提取兴趣区域之后,我们得到了区域散点的集合,不仅有多条线,还有一些点状和块状区域,哈夫变换的目的就是找到途中的线,并与原图进行叠加。
1.简单介绍
Hough变换是图像处理中从图像中识别几何形状的基本方法之一。Hough变换的基本原理在于利用点与线的对偶性,将原始图像空间的给定的曲线通过曲线表达形式变为参数空间的一个点。这样就把原始图像中给定曲线的检测问题转化为寻找参数空间中的峰值问题。也即把检测整体特性转化为检测局部特性。比如直线、椭圆、圆、弧线等。
2.Hough变换的基本思想
设已知一黑白图像上画了一条直线,要求出这条直线所在的位置。我们知道,直线的方程可以用y=kx+b 来表示,其中k和b是参数,分别是斜率和截距。过某一点(x0,y0)的所有直线的参数都会满足方程y0=kx0+b。即点(x0,y0)确定了一族直线。方程y0=kx0+b在参数k–b平面上是一条直线,(你也可以是方程b=-x0k+y0对应的直线)。这样,图像x–y平面上的一个前景像素点就对应到参数平面上的一条直线。
我们举个例子说明解决前面那个问题的原理。设图像上的直线是y=x, 我们先取上面的三个点:A(0,0), B(1,1), C(22)。可以求出,过A点的直线的参数要满足方程b=0, 过B点的直线的参数要满足方程1=k+b, 过C点的直线的参数要满足方程2=2k+b, 这三个方程就对应着参数平面上的三条直线,而这三条直线会相交于一点(k=1,b=0)。 同理,原图像上直线y=x上的其它点(如(3,3),(4,4)等) 对应参数平面上的直线也会通过点(k=1,b=0)。这个性质就为我们解决问题提供了方法,就是把图像平面上的点对应到参数平面上的线,最后通过统计特性来解决问题。假如图像平面上有两条直线,那么最终在参数平面上就会看到两个峰值点,依此类推。
**简而言之,Hough变换思想为:**在原始图像坐标系下的一个点对应了参数坐标系中的一条直线,同样参数坐标系的一条直线对应了原始坐标系下的一个点,然后,原始坐标系下呈现直线的所有点,它们的斜率和截距是相同的,所以它们在参数坐标系下对应于同一个点。这样在将原始坐标系下的各个点投影到参数坐标系下之后,看参数坐标系下有没有聚集点,这样的聚集点就对应了原始坐标系下的直线。
在实际应用中,y=kx+b形式的直线方程没有办法表示x=c形式的直线(这时候,直线的斜率为无穷大)。所以实际应用中,是采用参数方程p=xcos(theta)+y*sin(theta)。这样,图像平面上的一个点就对应到参数p—theta平面上的一条曲线上,其它的还是一样。
3.Hough变换推广
(1)已知半径的圆
其实Hough变换可以检测任意的已知表达形式的曲线,关键是看其参数空间的选择,参数空间的选择可以根据它的表达形式而定。比如圆的表达形式为,所以当检测某一半径的圆的时候,可以选择与原图像空间同样的空间作为参数空间。那么圆图像空间中的一个圆对应了参数空间中的一个点,参数空间中的一个点对应了图像空间中的一个圆,圆图像空间中在同一个圆上的点,它们的参数相同即a,b相同,那么它们在参数空间中的对应的圆就会过同一个点(a,b),所以,将原图像空间中的所有点变换到参数空间后,根据参数空间中点的聚集程度就可以判断出图像空间中有没有近似于圆的图形。如果有的话,这个参数就是圆的参数。
(2)未知半径的圆
对于圆的半径未知的情况下,可以看作是有三个参数的圆的检测,中心和半径。这个时候原理仍然相同,只是参数空间的维数升高,计算量增大。图像空间中的任意一个点都对应了参数空间中的一簇圆曲线。 ,其实是一个圆锥型。参数空间中的任意一个点对应了图像空间中的一个圆。
(3)椭圆
椭圆有5个自由参数,所以它的参数空间是5维的,因此他的计算量非常大,所以提出了许多的改进算法。
4.哈夫变换的性质
参数空间中的一个点对应直角坐标系中的一条直线;
直角坐标系中的共点线映射到参数空间中为一条曲线;
直角坐标系中的过一个点映射到参数空间中为一条正弦曲线;
直角坐标系中的共线点映射到参数空间后为一个交于同一点的曲线簇。
5.总结
图像空间中的在同一个圆,直线,椭圆上的点,每一个点都对应了参数空间中的一个图形,在图像空间中这些点都满足它们的方程这一个条件,所以这些点,每个投影后得到的图像都会经过这个参数空间中的点。也就是在参数空间中它们会相交于一点。所以,当参数空间中的这个相交点的越大的话,那么说明元图像空间中满足这个参数的图形越饱满。越象我们要检测的东西。
Hough变换能够查找任意的曲线,只要你给定它的方程。Hough变换在检验已知形状的目标方面具有受曲线间断影响小和不受图形旋转的影响的优点,即使目标有稍许缺损或污染也能被正确识别。
基本代码原理如下图所示:
基于Opencv的哈夫变换实例中,我们将Hough space划分成网格状,如果经过一个格子的线的数目大于threshold,我们认为这个经过格子的线在原image space对应的点在同一条直线上。
cv2.HoughLinesP(img,rho,theta,threshold,np.array([]),minLineLength=min_line_len,maxLineGap=max_line_gap)
根据得到的线计算出左车道和右车道,可以采用以下步骤
实例中霍夫变换代码如下:
#**********************Hough 变换***********************
def hough_lines(img, rho, theta, threshold, min_line_len, max_line_gap):
lines = cv2.HoughLinesP(img, rho, theta, threshold, np.array([]), minLineLength=min_line_len, maxLineGap=max_line_gap)
line_img = np.zeros((img.shape[0], img.shape[1], 3), dtype=np.uint8)
# 划线函数,设置线型等
draw_lanes(line_img, lines)
return line_img
# 划线子程序
def draw_lines(img, lines, color=[255, 0, 0], thickness=2):
for line in lines:
for x1, y1, x2, y2 in line:
cv2.line(img, (x1, y1), (x2, y2), color, thickness)
#**********************Hough变换后处理***********************
#********************Lane Extrapolation处理***********************
def draw_lanes(img, lines, color=[255, 0, 0], thickness=8):
# 根据斜率正负划分某条线属于左车道还是右车道
left_lines, right_lines = [], []
for line in lines:
for x1, y1, x2, y2 in line:
k = (y2 - y1) / (x2 - x1)
if k < 0:
left_lines.append(line)
else:
right_lines.append(line)
if (len(left_lines) <= 0 or len(right_lines) <= 0):
return img
# 移除左右偏差偏差过大的线
clean_lines(left_lines, 0.1)
clean_lines(right_lines, 0.1)
left_points = [(x1, y1) for line in left_lines for x1,y1,x2,y2 in line]
left_points = left_points + [(x2, y2) for line in left_lines for x1,y1,x2,y2 in line]
right_points = [(x1, y1) for line in right_lines for x1,y1,x2,y2 in line]
right_points = right_points + [(x2, y2) for line in right_lines for x1,y1,x2,y2 in line]
# 分别对左右车道线的顶点集合做Linear regression
left_vtx = calc_lane_vertices(left_points, 325, img.shape[0])
right_vtx = calc_lane_vertices(right_points, 325, img.shape[0])
# 得到最终的车道线
cv2.line(img, left_vtx[0], left_vtx[1], color, thickness)
cv2.line(img, right_vtx[0], right_vtx[1], color, thickness)
# 移除左右偏差偏差过大的线子程序
# 迭代计算各条直线的斜率与斜率均值的差
# 逐一移除差值过大的线
def clean_lines(lines, threshold):
slope = [(y2 - y1) / (x2 - x1) for line in lines for x1, y1, x2, y2 in line]
while len(lines) > 0:
mean = np.mean(slope)
diff = [abs(s - mean) for s in slope]
idx = np.argmax(diff)
if diff[idx] > threshold:
slope.pop(idx)
lines.pop(idx)
else:
break
# 对车道线顶点集合做Linear regression
def calc_lane_vertices(point_list, ymin, ymax):
x = [p[0] for p in point_list]
y = [p[1] for p in point_list]
#做平滑拟合
fit = np.polyfit(y, x, 1)
#多项式变换函数
fit_fn = np.poly1d(fit)
xmin = int(fit_fn(ymin))
xmax = int(fit_fn(ymax))
return [(xmin, ymin), (xmax, ymax)]
rho = 1
theta = np.pi / 180
threshold = 15
min_line_length = 40
max_line_gap = 20
# roi_edges 是边缘检测后,经过ROI处理后的图像。
line_img = hough_lines(roi_edges, rho, theta, threshold, min_line_length, max_line_gap)
使用cv2.addWeighted(img,alpha,line_img,beta,0)
res_img = cv2.addWeighted(img, 0.8, line_img, 1, 0)