1 霍夫变换原理

首先, 构造一个霍夫坐标系与常用的笛卡尔坐标系相对应, 在霍夫坐标系中, 横坐标采用笛卡尔坐标系中直线的斜率, 纵坐标采用笛卡尔坐标系中直线的截距。

下面是直线和点在两空间中的映射关系 :

python 求两点坐标的直线 python两点坐标间距_python

当笛卡尔坐标系中的两点同时映射到霍夫坐标系中时 :

python 求两点坐标的直线 python两点坐标间距_霍夫变换_02

霍夫坐标系中两直线交于一点, 该点即为笛卡尔坐标系中两点所确定的直线的斜率。

由这一点可以进行推广 :

python 求两点坐标的直线 python两点坐标间距_ci_03


图为笛卡尔坐标系中三点共线的情况, 三条直线于霍夫坐标系中交于一点(1, 1), 其对应关系如下

python 求两点坐标的直线 python两点坐标间距_ci_04

当然还有更一般的情况 ;

python 求两点坐标的直线 python两点坐标间距_霍夫变换_05

笛卡尔空间内的一点确定了霍夫空间的一条直线, 笛卡尔空间内的多个共线的点就会在霍夫坐标系中确定多条交于一点的直线, 该点反映于笛卡尔空间内则确定唯一的一条直线。故而, 在霍夫空间内经过一个点的直线越多, 则说明该点所确定的笛卡尔空间内的直线实际存在的可能性越大, 这就是霍夫变换选择直线的基本思路——选择有尽可能多直线交汇的点。但同样在笛卡尔空间内有时会存在垂直于x轴的直线 :

python 求两点坐标的直线 python两点坐标间距_霍夫变换_06

此时, 直线斜率为无穷大, 斜率b无法取值, 从而无法映射到霍夫空间内。

为解决此问题, 可考虑将笛卡尔坐标系映射到极坐标上 :

python 求两点坐标的直线 python两点坐标间距_ci_07

在极坐标内采用极径r与极角θ来表示, 极坐标中的曲线可表示为 : r = x * cosθ + y * sinθ , 其中r为直线LineA与坐标原点间的距离, 参数θ为过原点的LineA的垂线与x轴的夹角, 采用这种方法可以很轻松的将垂直于x轴的直线表示出来。

与笛卡尔空间类似, 通常情况下, 我们设置一个阈值, 当霍夫坐标系中交于某点的曲线达到了阈值, 就认为在对应的极坐标系(或笛卡尔坐标系)中存在这样一条直线。

2 函数介绍及程序实现

2.1 霍夫直线变换

2.1.1 cv2.HoughLines() 函数

函数cv2.HoughLines()的语法格式如下 :

lines = cv2.HoughLines(image,
					   rho,
					   theta,
					   threshould)

参数 :
(1). image 为输入图像, 必须为8位单通道的二值图像
(2). rho 为以像素为单位的距离r的精度, 一般情况下为1
(3). theta 为角度θ的精度, 一般情况下为π/180
(4). threshold 为阈值, 阈值越小则判断出来的直线就越多

返回值 :
(1). lines 为numpy.ndarray类型, 其中每一个元素均为一对浮点数, 表示检测到的直线的参数, 即(r, θ)

PS : 使用cv2.HoughLines()检测到的是图像中的直线而非线段(没有端点), 故而此时我们绘制的图像是穿过整幅图像的

2.1.2 cv2.HoughLines() 程序

先通过 cv2.Canny() 获取原始图像的边缘信息, 再将获取到的边缘信息交给 cv2.HoughLines() 得到图中直线的信息, 最后将其绘制到原始图像上。
其中, 绘制直线的方法是, 对于竖直方向上的直线, 计算其与图像水平边界(第一行与最后一行)的交叉点, 然后在这两点间画直线, 而对于水平方向上的直线也采取类似方式完成。
画线工作由 cv2.line() 完成, 其方便之处在于, 即使点的坐标超出了图像的范围, 也可以正确的画出线来。

下面是完整程序 :

from cv2 import cv2 as cv 
import numpy as np 

img = cv.imread(r"D:\anaconda\vscode-python\pic\qipan.jpg")
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
# 利用Canny边缘检测提取出原始图像的边缘信息(二值)
edges = cv.Canny(gray,50,150,apertureSize=3)

oshow = img.copy()
lines = cv.HoughLines(edges,1,np.pi/180,140)

for line in lines:
    rho,theta = line[0]
    a = np.cos(theta)
    b = np.sin(theta)
    x0 = a*rho
    y0 = b*rho

    # 使极坐标所确定的坐标等比例放大,保证(x1,y1),(x2,y2)点在图像外,从而保证在图上出现完整的线(贯穿图像)
    x1 = int(x0 + 1000*(-b))
    y1 = int(y0 + 1000*(a))
    x2 = int(x0 - 1000*(-b))
    y2 = int(y0 - 1000*(a))
    # 绘制直线
    cv.line(oshow,(x1,y1),(x2,y2),(0,0,255),2)

cv.imshow("img",img)
cv.imshow("HoughLines",oshow)

cv.waitKey()
cv.destroyAllWindows()

效果 :

python 求两点坐标的直线 python两点坐标间距_python 求两点坐标的直线_08


python 求两点坐标的直线 python两点坐标间距_python_09

2.1.3 cv2.HoughLinesP() 函数(概率霍夫变换)

概率霍夫变换对基本霍夫变换算法进行了一些修正(优化), 它并没有考虑所有的点, 相反, 它只需要一个足以进行线检测的随机点集即可。
为了更好的判断直线(线段), 概率霍夫算法对选取直线的方法进行了两点改进 :
(1). 所接受直线的最小长度
如果有超过阈值个数的像素点构成了一条直线但其实际长度很短, 那么就不会接受这条直线作为判断结果。
(2). 接受之直线时所允许的最大间距
如果有超过阈值个数的像素点构成了一条直线但这组像素点之间的距离都很远, 那么就不会接受这条直线作为判断结果。

在OpenCV中函数 cv2.HoughLinesP()

lines = cv2.HouguLinesP(image,
						rho,
						theta,
						threshold,
						minLineLength,
						maxLineGap)

参数 :
(1). image 为输入图像, 必须为8位单通道二值图像
(2). rho 为以像素为单位的距离r的精度(一般情况下为1)
(3). theta 为角度θ的精度(一般情况下为np.pi/180), 表示要搜索的可能角度
(4). minLineLength 用来控制 “接受直线的最小长度” 默认值为0
(5). maxLineGap 用来控制 “接受共线线段之间的最小间隔” 如果两点之间的间隔超过了maxLineGaP的值, 则认为两点不在同一直线上, 默认值为0

返回值 :
(1). lines 是由np.ndarray类型的元素构成的, 每个元素都是一对浮点数, 表示检测到的直线的参数, 即(r,θ)

2.1.4 cv2.HoughLinesP() 程序

from cv2 import cv2 as cv 
import numpy as np

img = cv.imread(r"D:\anaconda\vscode-python\pic\new_building.jpg")
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
edges = cv.Canny(gray,50,150,apertureSize=3)

show = img.copy()
lines = cv.HoughLinesP(edges,1,np.pi/180,1,minLineLength=60,maxLineGap=7)

for line in lines:
    x1,y1,x2,y2 = line[0]
    cv.line(img,(x1,y1),(x2,y2),(255,0,0),5)

cv.imshow("img",show)
cv.imshow("show",img)

cv.waitKey()
cv.destroyAllWindows()

效果 :

python 求两点坐标的直线 python两点坐标间距_霍夫变换_10


python 求两点坐标的直线 python两点坐标间距_ci_11


注意楚河汉界与九宫(田字格)处的斜线, 并未像前面一样直接延伸到图片外面, 而是准确贴合了实际的长度。

2.2 霍夫圆环变换

与使用霍夫直线变换的原理类似, 在霍夫圆环变换中需要考虑圆的半径及圆心(x,y) 一共三个参数。
在OpenCV中, 采用的策略是两轮筛选 : 第一轮找出可能存在圆的位置(圆心), 第二轮再根据第一轮的结果筛选出半径大小
与前文中 cv2.HoughLineP()

2.2.1 cv2.HoughCircles() 函数

该函数将Canny边缘检测与霍夫变换相结合, 其语法格式如下 :

circles = cv2.HoughCircles(image,
						   method,
						   dp,
						   minDist,
						   param1,
						   param2,
						   minRadius,
						   maxRadius)

参数 :
(1). image 输入图像, 8位单通道灰度图像
(2). method 检测方法, 目前opencv只有霍夫梯度法一种方法可用,该参数填HOUGH_GRADIENT即可(opencv 4.1.2及以下)
(3). dp 累计器分辨率(分割比率), 用来指定图像分辨率与圆心累加器分辨率的比例。例如 : dp=1 则输入图像和累加器之间有相同的分辨率
(4). minDist 圆心间的最小间距(阈值), 如果存在圆心间距小于该值的多个圆, 则仅有一个会被检测出来
(5). param1 缺省参数, 对应的是Canny边缘检测器的高阈值(低阈值为高阈值的1/2)
(6). param2 圆心位置收到的投票数的下限, 只有在第一轮投票中投票数超过该值的圆才能进入下一轮筛选, 因此, 该值越大, 检测到的圆越少
(7). minRadius 圆半径的最小值, 小于该值的圆不会被检测出来, 默认缺省, 默认值为0(即无效)
(8). maxRadius 圆半径的最大值, 大于该值的圆不会被检测出来, 缺省, 默认0, 即无效

返回值 :
(1). circles 由圆心坐标和半径构成的np.ndarray

PS :
在调用该函数时要先对源图像进行平滑操作, 以减小图像中的噪声, 避免发生误判

2.2.2 cv2.HoughCircles() 程序

from cv2 import cv2 as cv 
import numpy as np

img = cv.imread(r"D:\anaconda\vscode-python\pic\2.jpg",0)
imgo = cv.imread(r"D:\anaconda\vscode-python\pic\2.jpg",-1)
o = cv.cvtColor(imgo,cv.COLOR_BGR2RGB)

oshow = o.copy()
img = cv.medianBlur(img,5)
circles = cv.HoughCircles(img,cv.HOUGH_GRADIENT,1,300,
            param1=50,param2=10,minRadius=10,maxRadius=200)
np.uint16(np.around(circles))

for i in circles[0,:]:
    cv.circle(o,(i[0],i[1]),i[2],(255,0,0),12)
    cv.circle(o,(i[0],i[1]),2,(255,0,0),12)

cv.imshow("img",oshow)
cv.imshow("show",o)
cv.waitKey()
cv.destroyAllWindows()

效果 :

python 求两点坐标的直线 python两点坐标间距_python 求两点坐标的直线_12


python 求两点坐标的直线 python两点坐标间距_python 求两点坐标的直线_13