在图像中要解决的霍夫直线检测是针对二值图的, 验证哪些前景或者边缘像素点是共线的。 如图9-11所示是一个宽度为10、 高度为10的二值图, 在这里前景像素点是用白色(灰度值是255) 标注的, 目的是验证哪些白色像素点是共线的。
首先要根据每一个白色像素点的坐标, 对应“画”出霍夫空间中的曲线, 但是真正在程序实现中因为自变量0≤θ<180°有无数个点, 所以需要描出无数个点才能“画”出对应的曲线, 因此程序中我们只能离散化处理,可以每间隔△θ计算一个对应的ρ, △θ通常取1°, 即计算0°、 1°、 2°、 …、 179°对应的ρ值。 当然, 为了描出更多的曲线上的点, 可以令△θ取更小的值, 但是一般取1°就足够了, 所以根据每一个白色像素点的坐标就需要计算180个坐标点, 如表9-3所示。
那么如何利用计算出的θoρ空间中的这些点去验证哪些像素点是共线的呢? 在9.2.1节中, 是用曲线相交方式来验证的, 但是这里只是从曲线上取了一些离散的点, 所以需要引入一个工具, 称为“计数器”, 或者“投票器”, 或者二维直方图。 如图9-12所示, 假设有10个坐标, 向一个计数器(投票器) 投票。
投票的汇总结果如图9-13所示, 可以从投票器中很容易得出坐标(3, 1) 出现了3次, 坐标(0, -1) 出现了2次等。
如何构造霍夫空间中的计数器? 假设在xoy平面内有任意一点(x1, y1) , 过该点有无数条直线, 但是原点到这些直线的距离不会超过 。 图像矩阵宽度为W、 高度为H, 那么可以构造以下计数器, 用L代表整数round()+1, 如图9-14所示:
其中mΔθ<180, -L+nΔρ≤L, 一般就取Δθ=1°, Δρ=1。 以图9-11所示的二值图为例, 这幅图像宽度为10、 高度为10, 所以令
四个前景像素点的坐标分别为(6, 4) 、 (5, 5) 、 (3, 7) 、 (2, 8) , 按照表9-3求出的所有霍夫空间中的点坐标一共有4×180个, 比如(45°, round(6 cos 45°+4 sin 45°) ) =(45°,7) , (45°, round(5 cos 45°+5 sin 45°) ) =(45°, 7) , (45°, round(3 cos 45°+7 sin45°) ) =(45°, 7) , (45°, round(2 cos 45°+8 sin 45°) ) =(45°, 7) 等, 投票到如图9-14所示的计数器中, 在计数器(45°, 7) 这个位置的计数是4, 这里的(45°, 7) 对应到图9-9中相交的点 , 表明有四个像素点是共线的。 通过以上过程就可以实现标准的霍夫直线检测了。 通过定义函数HTLine来实现该功能, 其中输入参数image是一张二值图, 返回值是计数器及对应的哪些点是共线的。
coda:
# -*- coding: utf-8 -*-
import sys
import numpy as np
import cv2
import math
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
#霍夫极坐标变换:直线检测
def HTLine (image,stepTheta=1,stepRho=1):
#宽、高
rows,cols = image.shape
#图像中可能出现的最大垂线的长度
L = round(math.sqrt(pow(rows-1,2.0)+pow(cols-1,2.0)))+1
#初始化投票器
numtheta = int(180.0/stepTheta)
numRho = int(2*L/stepRho + 1)
accumulator = np.zeros((numRho,numtheta),np.int32)
#建立字典
accuDict={}
for k1 in range(numRho):
for k2 in range(numtheta):
accuDict[(k1,k2)]=[]
#投票计数
for y in range(rows):
for x in range(cols):
if(image[y][x] == 255):#只对边缘点做霍夫变换
for m in range(numtheta):
#对每一个角度,计算对应的 rho 值
rho = x*math.cos(stepTheta*m/180.0*math.pi)+y*math.sin(stepTheta*m/180.0*math.pi)
#计算投票哪一个区域
n = int(round(rho+L)/stepRho)
#投票加 1
accumulator[n,m] += 1
#记录该点
accuDict[(n,m)].append((x,y))
return accumulator,accuDict
#主函数
if __name__ == "__main__":
if len(sys.argv)>1:
#输入图像
I = cv2.imread(sys.argv[1],cv2.IMREAD_GRAYSCALE)
else:
print ("Usage: python HTPLine.py image")
#canny 边缘检测
edge = cv2.Canny(I,50,200)
#显示二值化边缘
cv2.imshow("edge",edge)
#霍夫直线检测
accumulator,accuDict = HTLine(edge,1,1)
#计数器的二维直方图方式显示
rows,cols = accumulator.shape
fig = plt.figure()
ax = fig.gca(projection='3d')
X,Y = np.mgrid[0:rows:1, 0:cols:1]
surf = ax.plot_wireframe(X,Y,accumulator,cstride=1, rstride=1,color='gray')
ax.set_xlabel(u"$\\rho$")
ax.set_ylabel(u"$\\theta$")
ax.set_zlabel("accumulator")
ax.set_zlim3d(0,np.max(accumulator))
#计数器的灰度级显示
grayAccu = accumulator/float(np.max(accumulator))
grayAccu = 255*grayAccu
grayAccu = grayAccu.astype(np.uint8)
#只画出投票数大于 60 直线
voteThresh = 60
for r in range(rows):
for c in range(cols):
if accumulator[r][c] > voteThresh:
points = accuDict[(r,c)]
cv2.line(I,points[0],points[len(points)-1],(255),2)
cv2.imshow('accumulator',grayAccu)
#显示原图
cv2.imshow("I",I)
plt.show()
cv2.imwrite('accumulator.jpg',grayAccu)
cv2.imwrite('I.jpg',I)
cv2.waitKey(0)
cv2.destroyAllWindows()
结果:
(a)计数器的三维展示图 (b)计数器的灰度级显示 (c)检测到的直线