opencv HoughLine 理解
参考:
- opencv HoughLine Transform Tutorial
- https://guiqing.blog.csdn.net/article/details/8058336
- https://www.cnblogs.com/lancer2015/p/6852488.html
opencv HoughLine结果分析
opencv中,HoughLine的结果,是找到的所有直线的集合,其中每条直线的表示是(ρ, θ)的形式,过图像左上角(0,0)点作找到的直线的垂线,ρ即是(0,0)点到垂足的距离,θ是指垂线与图像水平方向的夹角,如下图所示:
从图中可以看出,用一对这样的(ρ,θ)就能确定一条唯一的直线。ρ的计算值始终是非负值,但opencv进行了处理,当直线与Y轴的交点位于(0,0)点下方时,θ的范围是(0,π),ρ,θ值不变,当直线与Y轴的交点位于(0,0)点上方时,θ的范围为(π,2π),此时,将θ值变为θ-π,变换到(0,π)范围,而ρ值取-ρ。
这样变换的依据是直线的另一种表示方式,即用直线上的一个点坐标加上直线的角度来表示,此处选择的点为上述的垂足,其坐标可表示为
(ρ∗cos(θ),ρ∗sin(θ))=(−ρ∗cos(θ−π),−ρ∗sin(θ−π))(ρ*cos(θ), ρ*sin(θ)) = (-ρ*cos(θ-π), -ρ*sin(θ-π)) (ρ∗cos(θ),ρ∗sin(θ))=(−ρ∗cos(θ−π),−ρ∗sin(θ−π))
故将θ变为θ-π,ρ变为-ρ,计算得到的垂足是一样的,由于θ为法线角度,直线与法线垂直,θ变为θ-π即法线旋转180度,直线的角度不变,故变换前后表示的是同一条直线。
而且用垂线与水平轴的夹角和垂足的坐标来表示直线的话,在绘制该直线时比较方便,直接从垂足向直线两头分别移动一段距离得到两个点,即可用来进行绘制,计算方式如下:
cx=ρ∗cos(θ)cx = ρ*cos(θ) cx=ρ∗cos(θ)
cy=ρ∗sin(θ)cy = ρ*sin(θ) cy=ρ∗sin(θ)
x1=cx+1000∗sin(θ)x1 = cx + 1000 * sin(θ) x1=cx+1000∗sin(θ)
y1=cy−1000∗cos(θ)y1 = cy - 1000 * cos(θ) y1=cy−1000∗cos(θ)
x2=cx−1000∗sin(θ)x2 = cx - 1000 * sin(θ) x2=cx−1000∗sin(θ)
y2=cy+1000∗cos(θ)y2 = cy + 1000 * cos(θ) y2=cy+1000∗cos(θ)
计算原理如下图所示:
opencv HoughLine 原理分析
对于Hough变换,很多文章都会提到图像空间和参数空间的对应关系,参考文章[3]中有如下转换(稍加了修改):
y=mx+b(图像空间)y = mx + b(图像空间) y=mx+b(图像空间)
b=−xm+y(参数空间)b = -xm + y(参数空间) b=−xm+y(参数空间)
在图像空间中,点的坐标是(x,y),而x,y在参数空间中是方程的系数,一对系数就确定参数空间中的一条直线,因此说图像空间中的一个点,对应参数空间中的一条直线,反过来,在参数空间中,点的坐标是(m,b),而m,b在图像空间中是系数,一对系数确定图像空间中的一条直线,因此说参数空间的一个点对应图像空间的一条直线。
但是这对于算法的意义何在?其实HoughLine的本质,是针对图像中所有可能出现的直线的情况,对于每条直线涵盖的图像上的点进行加和,然后看每条线加和的值是否达到阈值,关键就在于对每一对(m,b),确定图像上的哪些点在此对(m,b)确定的直线上,这实现起来比较麻烦,所以转换了另一种思路,对于图像上的每个点(x,y),用参数空间方程计算每个可能的m值对应的b值,由此得到一对(m,b),因为参数空间方程和图像空间方程只是一个简单的形式变换,这对(x,y)和这对(m,b)是由参数空间方程计算得来的,也就必然满足图像空间方程,也就是说,该图像空间的点(x,y)必然在参数空间的点(m,b)确定的图像空间直线上,在同一条直线上的点(x,y),计算得到的(m,b)也必然相同,这样,遍历整个图像,将每个点(x,y)的亮度值分别累加到每条经过该点的直线(m,b)上,就相当于把每条直线涵盖的图像上的点进行了累加,简化了实现,现在关键的问题在于,所有可能的直线是连续的,而算法需要进行离散化,就需要对参数空间中的参数(m,b)进行细分,而这种细分是要做到均匀取值的。
在图像空间方程中,系数b是截距,也就是x为0时,y的值,其取值范围是无穷的,对截距进行离散化无法实现,系数m是斜率,其取值范围也是无穷的,对其进行等离散化也无法实现,故难以直接用该形式的参数空间来进行HoughLine计算,通过上面对opencv HoughLine结果的分析,我们知道可以用一对(ρ,θ)来表示一条直线,且ρ的范围为[0,sqrt(w*w + h*h)],θ的范围为[0,π),我们将参数空间方程看作以下形式:
ρ=f((x,y),θ)ρ = f((x,y), θ) ρ=f((x,y),θ)
这时,问题转换到对图像上的每个点(x,y),θ取[0,π)范围内的每个离散值,计算对应的ρ的离散值,然后将该像素的亮度累加到(ρ,θ)表示的直线中,最后每条直线的累加值和阈值比较得到符合条件的直线。
现在的问题就是如何由((x,y), θ)计算ρ?考虑上图中的点(x,y),有如下关系:
x=r∗cos(α)x = r * cos(α) x=r∗cos(α)
y=r∗sin(α)y = r * sin(α) y=r∗sin(α)
ρ=r∗cos(θ−α)=r∗cos(θ)∗cos(α)+r∗sin(θ)∗sin(α)=x∗cos(θ)+y∗sin(θ)ρ = r * cos(θ-α) =r*cos(θ)*cos(α)+r*sin(θ)*sin(α) =x*cos(θ)+y*sin(θ) ρ=r∗cos(θ−α)=r∗cos(θ)∗cos(α)+r∗sin(θ)∗sin(α)=x∗cos(θ)+y∗sin(θ)
这就是最终的参数空间方程。
到此为止,所有的谜题都皆晓了,最根本的原理,就是对所有可能的情况进行累加计算,再比较每个情况的累加值与阈值,玄妙的空间变换,其根本,只是为了方便算法的实现。
HoughLine python实现
# -*- coding: utf-8 -*-
import cv2 as cv
import time
import numpy as np
from matplotlib import pyplot as pltdef HoughLines(img, rho, theta, threshold, srn=1, stn=1, min_theta=0, max_theta=np.pi):"""HoughLine 实现# Return找到的直线列表 每条直线表示为[[rho, theta]]# Argumentsimg: 输入的二值化图像rho: 极径分辨率theta: 极角分辨率threshold: 阈值srn: 多尺度计算时,精细极径分辨率=rho/srn 此处直接实现为精细版stn: 多尺度计算时,精细极角分辨率=theta/stn 此处直接实现为精细版min_theta: 最小极角max_theta: 最大极角"""rho_acc = rho / srntheta_acc = theta / stn# 小于180度的 rho为正# 大于180度的 rho取负 角度-180# rho 以 rho_range//2 为0点rho_range = int((img.shape[0] + img.shape[1]) / rho_acc) * 2# 直接计算从[0,pi)范围, 再最后取结果时再过滤pi_range = int(np.pi / theta_acc)hough = np.zeros([pi_range, rho_range], dtype='int32')for y in range(img.shape[0]):for x in range(img.shape[1]):if img[y, x] < 128:continuefor theta in range(pi_range*2):ct = np.cos(theta * theta_acc)st = np.sin(theta * theta_acc)# 参数空间方程rho = int((y * st + x * ct) / rho_acc)if theta < pi_range:the = thetarho = rho_range // 2 + rhohough[the, rho] += 1else:the = theta - pi_rangerho = rho_range // 2 - rhohough[the, rho] += 1# plt.imshow(np.array(hough))# plt.show()res = []for theta in range(pi_range * 2):if theta * theta_acc < min_theta or theta * theta_acc > max_theta:continueif theta >= pi_range:theta -= pi_rangefor rho in range(rho_range):if hough[theta, rho] > threshold:res.append([[(rho - rho_range // 2) * rho_acc, theta * theta_acc]])return np.array(res)def test(img, method=0):gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)if method == 0:lines = HoughLines(gray, 1, np.pi / 90, 250)else:lines = cv.HoughLines(gray, 1, np.pi / 90, 250)print('lines:', np.array(lines).shape)finds = []for line in lines:rho, theta = line[0]ct = np.cos(theta)st = np.sin(theta)x0 = ct * rhoy0 = st * rho# x0, y0 为垂足坐标 下面的两个点是垂足延直线向两头各移动1000个单位x1 = int(x0 - 1000 * st)y1 = int(y0 + 1000 * ct)x2 = int(x0 + 1000 * st)y2 = int(y0 - 1000 * ct)# 过滤重合的线for find in finds:a1 = np.array([rho, theta * 180 / np.pi])a2 = np.array([x1, y1, x2, y2])b1 = np.array(find[:2])b2 = np.array(find[2:])d1 = np.abs(a1 - b1)d2 = np.abs(a2 - b2)if np.all(d1 < 20) or np.all(d2 < 20):# print('line', line[0], [x1,y1,x2,y2], 'find', find, 'overlap')breakelse:finds.append([rho, theta * 180 / np.pi, x1, y1, x2, y2])# print('line', line[0], [x1,y1,x2,y2])cv.line(img, (x1, y1), (x2, y2), (0,0,255), 2)print('finds', len(finds))return imgdef main():img = cv.imread('d:/0.png')time1 = time.time()t1 = test(img, method=0)time2 = time.time()print('self time:', time2 - time1)time1 = time.time()t2 = test(img, method=1)time2 = time.time()print('opencv time:', time2 - time1)t = np.hstack([t1, t2])cv.imshow('self <-----> opencv', t)cv.waitKey()if __name__ == '__main__':main()
对比手动实现版本和opencv版本,结果基本一致,不过python实现版本的效率相比opencv差了很多,如下图所示,左边是手动实现版本,右边是opencv版本:
以上就是本人对opencv HoughLine的理解,由于水平有限,可能有理解错误的地方,欢迎大家交流和讨论。
opencv HoughLine 理解相关推荐
- Python+OpenCV:理解K-Means聚类(K-Means Clustering)
Python+OpenCV:理解K-Means聚类(K-Means Clustering) 理论 We will deal this with an example which is commonly ...
- Python+OpenCV:理解支持向量机(SVM)
Python+OpenCV:理解支持向量机(SVM) 理论 线性可分数据(Linearly Separable Data) Consider the image below which has two ...
- Python+OpenCV:理解k近邻(kNN)算法(k-Nearest Neighbour (kNN) algorithm)
Python+OpenCV:理解k近邻(kNN)算法(k-Nearest Neighbour (kNN) algorithm) 理论 kNN is one of the simplest classi ...
- 双目视觉下空间坐标计算matlab,双目视觉下空间坐标计算 opencv+ 个人理解
简单的理解思路:(世界坐标系固定到左目) 空间中一点P,在左目像素坐标(u1,v1),转成mm为单位的坐标(x1,y1),在左目坐标系下建立过(x,y)的直线lineL: 同样的思路,空间中同一点P, ...
- Opencv copyTo()理解
image.copyTo(imageROI) 作用是把image的内容复制粘贴到imageROI上: 是将logoImage直接复制黏贴在imgROI区域. image.copyTo(imageROI ...
- [机器学习]基于OpenCV实现最简单的数字识别
http://blog.csdn.net/jinzhuojun/article/details/8579416 本文将基于OpenCV实现简单的数字识别.这里以游戏Angry Birds为例,通过以下 ...
- 2、OpenCV图像的读写操作
OpenCV图像的读写操作 概要 图像由像素组成. 像素可以被认为是非常小的正方形结构,当连接在一起时会生成图像. 它们是任何图像的最小组成部分. 如果您仔细查看前面的图像,您将能够在图像中看到一些正 ...
- RIKIBOT使用系列-基于Opencv HSV的色块检测
目录 一. 简介 二.查找色值 1.摄像头的角度调 2.启动检测与查找 三. 验证HSV色值 1.写入色值到文件 2.启动检测 四.交流方式 一. 简介 这里学习一下如何用摄像头检测HSV色值,Ope ...
- Halcon Opencv 数据的不同
1.基本数据类型 halcon 只具备 两种数据类型 HTuple (tuple) .HObject (object). 对于基本数据的处理应用 HTuple 类型存储与计算.数组.字符串.数字.均可 ...
最新文章
- linux安装grub
- 知识图谱最新论文清单,高阶炼丹师为你逐一解读
- 用计算机语言画曲线,用C语言控制台画简单的曲线
- SRS性能、内存优化工具用法
- 判断两个数组中是否存在相同的数字
- 命中率极高的 Go 面试题,赶紧收藏!
- 医疗人工智能市场有多大?
- 说一下朗数可视化快速开发平台
- Java Swing/AWT和GTK混合GUI编程
- oracle 启动报错03113,oracle数据库无法启动,总报ora-03113错误
- VS2013使用技巧汇总
- SCSI硬盘接口是什么
- ft232h引脚_K9K8G08U0B-PIB0--斗门--镁光MICRON内存收购
- C语言旅途之输出N的M次方的后三位数
- 苹果服务器维护不能刷机,iphone刷机失败不开机报错维修方法分享
- 为什么要配置环境变量以及配置环境变量的步骤
- stm32实现毫秒ms微秒us级延时
- 空间分析工具:GIS
- springboot整合redis后整合es,报错Failed to instantiate [org.elasticsearch.client.transport.TransportClient]
- 终于搞懂了,用大白话给你解释Zookeeper的选举机制,包教会
热门文章
- android ifw 启动广告,应用控制器清爽无广告版-应用控制器官方最新版v1.9.5 免费版-腾牛安卓网...
- Python Pandas 导入dta文件的方法
- android 漂亮的开源ui框架
- 前端学习笔记(四)网站SEO优化之TDK三大标签
- 三平面映射TriPlanar
- 【例8-13】用字符指针操作字符串
- 中间件_Redis_02_Redis的数据类型
- 最优的宽带出租解决方案——wayos+easyradius
- 计算用1分钱、2分钱、5分钱组成1元钱的方式
- linux删除磁带设备,Linux下磁带管理命令