图像处理:变换——霍夫线变换 OpenCV v4.8.0

上一个教程Canny边缘检测器

下一个教程霍夫圆变换

原作者Ana Huamán
兼容性OpenCV >= 3.0

目标

在本教程中,您将学习如何

理论

注释
以下解释来自 Bradski 和 Kaehler 合著的《学习 OpenCV》一书。

霍夫线变换

  1. Hough Line 变换是一种用于检测直线的变换。
  2. 要应用该变换,首先需要进行边缘检测预处理。

它是如何工作的?

  1. 众所周知,图像空间中的一条直线可以用两个变量来表示。例如

    a. 在直角坐标系中: 参数:(m,b)。
    b. 在极坐标系中 参数: (r,θ)
    在这里插入图片描述

对于 Hough 变换,我们将用极坐标系来表示直线。因此,直线方程可以写成

y = ( − cos ⁡ θ sin ⁡ θ ) x + ( r sin ⁡ θ ) y=\left( -\frac{\cos \theta}{\sin \theta} \right) x+\left( \frac{r}{\sin \theta} \right) y=(sinθcosθ)x+(sinθr)

排列项:r=xcosθ+ysinθ

  1. 一般来说,对于每一点(x0,y0),我们可以将经过该点的线段族定义为

r θ = x 0 ⋅ c o s θ + y 0 ⋅ s i n θ r_{\theta}=x_0⋅cos\theta +y_0⋅sin\theta rθ=x0cosθ+y0sinθ

这意味着每对(rθ,θ)代表经过(x0,y0)的每条直线。

  1. 对于给定的 (x0,y0),如果我们绘制出经过它的直线族,就会得到一个正弦曲线。例如,对于 x0=8,y0=6,我们可以得到下面的曲线图(在平面 θ - r 中):

在这里插入图片描述

我们只考虑 r>0 且 0<θ<2π 的点。

  1. 我们可以对图像中的所有点进行上述操作。如果两个不同点的曲线相交于平面 θ - r,则表示这两个点属于同一条直线。例如,按照上面的例子,再绘制两个点的曲线图:x1=4, y1=9 和 x2=12, y2=3,我们可以得到

在这里插入图片描述

这三幅图相交于一个点(0.925,9.6),这些坐标就是参数 ( θ,r) 或 (x0,y0)、(x1,y1) 和 (x2,y2) 所在的直线。

  1. 上面这些是什么意思呢?这意味着,一般来说,可以通过寻找曲线之间的交点数量来检测一条直线。一般来说,我们可以定义一个阈值,即检测一条直线所需的最小交点数。
  2. 这就是 Hough Line Transform 的作用。它可以跟踪图像中每个点的曲线交点。如果交叉点的数量超过某个阈值,那么它就会用交叉点的参数(θ,rθ)将其宣布为一条直线。
    标准 Hough 线变换和概率 Hough 线变换

OpenCV 实现了两种 Hough 线变换:

a. 标准 Hough 变换

  • 它与我们在上一节中解释的基本相同。其结果是一个耦合向量 (θ,rθ)
  • 在 OpenCV 中,它是通过函数 HoughLines() 实现的。

b. 概率 Hough 线变换

  • Hough Line Transform 的一种更有效的实现方式。它的输出结果是检测到的线条的极值 (x0,y0,x1,y1)
  • 在 OpenCV 中,它是通过函数 HoughLinesP() 实现的。

这个程序要做什么?

  • 加载图像
  • 应用标准 Hough 线条变换和概率线条变换。
  • 在三个窗口中显示原始图像和检测到的线条。

代码

更高级的版本(同时显示标准 Hough 和概率 Hough,并带有用于更改阈值的轨迹条)可在此处找到。

C++
我们将讲解的示例代码可从此处下载。


#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
using namespace cv;
using namespace std;
int main(int argc, char** argv)
{
 // 声明输出变量
 Mat dst, cdst, cdstP;
 const char* default_file = "sudoku.png"const char* filename = argc >=2 ? argv[1] : default_file;
 // 加载图像
 Mat src = imread( samples::findFile( filename ), IMREAD_GRAYSCALE )// 检查图片加载是否正常
 if(src.empty()){
 printf(" Error opening image\n")printf(" Program Arguments: [image_name -- default %s] \n", default_file)return -1}
 // 边缘检测
 Canny(src, dst, 50, 200, 3)// 将边缘复制到将在 BGR 中显示结果的图像中
 cvtColor(dst, cdst, COLOR_GRAY2BGR);
 cdstP = cdst.clone()// 标准 Hough 线条变换
 vector<Vec2f> lines; // 将保存检测结果
 HoughLines(dst, lines, 1, CV_PI/180, 150, 0, 0 ); // 运行实际检测
 // 绘制线条
 for( size_t i = 0; i < lines.size(); i++ )
 {
 float rho = lines[i][0], theta = lines[i][1];
 点 pt1、pt2;
 double a = cos(theta), b = sin(theta)double x0 = a*rho, y0 = b*rho;
 pt1.x = cvRound(x0 + 1000*(-b));
 pt1.y = cvRound(y0 + 1000*(a));
 pt2.x = cvRound(x0 - 1000*(-b));
 pt2.y = cvRound(y0 - 1000*(a))line( cdst, pt1, pt2, Scalar(0,0,255), 3, LINE_AA)}
 // 概率直线变换
 vector<Vec4i> linesP; // 将保存检测结果
 HoughLinesP(dst, linesP, 1, CV_PI/180, 50, 50, 10 ); // 运行实际检测
 // 绘制线条
 for( size_t i = 0; i < linesP.size(); i++ )
 {
 Vec4i l = linesP[i]line( cdstP, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0,0,255), 3, LINE_AA)}
 // 显示结果
 imshow("Source", src)imshow("Detected Lines (in red) - Standard Hough Line Transform", cdst)imshow("Detected Lines (in red) - Probabilistic Line Transform", cdstP)// 等待并退出
 waitKey();
 return 0;
}

Java
我们将讲解的示例代码可从此处下载。


import org.opencv.core.*;
import org.opencv.core.Point;
import org.opencv.highgui.HighGui;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
class HoughLinesRun {
 public void run(String[] args) {
 // 声明输出变量
 Mat dst = new Mat(), cdst = new Mat(), cdstP;
 String default_file = ".../../../../data/sudoku.png"String filename = ((args.length > 0) ? args[0] : default_file)// 加载图像
 Mat src = Imgcodecs.imread(filename, Imgcodecs.IMREAD_GRAYSCALE)// 检查图片加载是否正常
 if( src.empty() ) {
 System.out.println("Error opening image!")System.out.println("Program Arguments: [image_name -- 默认 "
 + default_file +"] \n");
 System.exit(-1)}
 // 边缘检测
 Imgproc.Canny(src, dst, 50, 200, 3, false)// 将边缘复制到将在 BGR 中显示结果的图像中
 Imgproc.cvtColor(dst, cdst, Imgproc.COLOR_GRAY2BGR);
 cdstP = cdst.clone()// 标准霍夫线变换
 Mat lines = new Mat(); // 将保存检测结果
 Imgproc.HoughLines(dst, lines, 1, Math.PI/180, 150); // 运行实际检测
 // 绘制线条
 for (int x = 0; x < lines.rows(); x++) {
 double rho = lines.get(x, 0)[0]、
 theta = lines.get(x, 0)[1]double a = Math.cos(theta),b = Math.sin(theta)double x0 = a*rho, y0 = b*rho;
 Point pt1 = new Point(Math.round(x0 + 1000*(-b)), Math.round(y0 + 1000*(a)))Point pt2 = new Point(Math.round(x0 - 1000*(-b)), Math.round(y0 - 1000*(a)))Imgproc.line(cdst, pt1, pt2, new Scalar(0, 0, 255), 3, Imgproc.LINE_AA, 0)}
 // 概率直线变换
 Mat linesP = new Mat(); // 将保存检测结果
 Imgproc.HoughLinesP(dst, linesP, 1, Math.PI/180, 50, 50, 10); // 运行实际检测结果
 // 绘制线条
 for (int x = 0; x < linesP.rows(); x++) {
 double[] l = linesP.get(x, 0)Imgproc.line(cdstP, new Point(l[0], l[1]), new Point(l[2], l[3]), new Scalar(0, 0, 255), 3, Imgproc.LINE_AA, 0)}
 // 显示结果
 HighGui.imshow("Source", src)HighGui.imshow("Detected Lines (in red) - Standard Hough Line Transform", cdst)HighGui.imshow("Detected Lines (in red) - Probabilistic Line Transform", cdstP)// 等待并退出
 HighGui.waitKey()System.exit(0)}
}
public class HoughLines {
 public static void main(String[] args) { // Load the native library.
 // 加载本地库
 System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
 new HoughLinesRun().run(args);
 }
}

Python
我们将讲解的示例代码可从此处下载。

"""
@file hough_lines.py
@brief This program demonstrates line finding with the Hough transform
"""
import sys
import math
import cv2 as cv
import numpy as np
def main(argv):
 
 default_file = 'sudoku.png'
 filename = argv[0] if len(argv) > 0 else default_file
 # 加载图像
 src = cv.imread(cv.samples.findFile(filename), cv.IMREAD_GRAYSCALE)
 # 检查图像是否加载正常
 if src is None:
 print ('Error opening image!')
 print ('Usage: hough_lines.py [image_name -- default ' + default_file + '] \n')
 return -1
 
 
 dst = cv.Canny(src, 50, 200, None, 3)
 
 # 将边缘复制到将在 BGR 中显示结果的图像上
 cdst = cv.cvtColor(dst, cv.COLOR_GRAY2BGR)
 cdstP = np.copy(cdst)
 
 lines = cv.HoughLines(dst, 1, np.pi / 180, 150, None, 0, 0)
 
 if lines is not None:
 for i in range(0, len(lines)):
 rho = lines[i][0][0]
 theta = lines[i][0][1]
 a = math.cos(theta)
 b = math.sin(theta)
 x0 = a * rho
 y0 = b * rho
 pt1 = (int(x0 + 1000*(-b)), int(y0 + 1000*(a)))
 pt2 = (int(x0 - 1000*(-b)), int(y0 - 1000*(a)))
 cv.line(cdst, pt1, pt2, (0,0,255), 3, cv.LINE_AA)
 
 
 linesP = cv.HoughLinesP(dst, 1, np.pi / 180, 50, None, 50, 10)
 
 if linesP is not None:
 for i in range(0, len(linesP)):
 l = linesP[i][0]
 cv.line(cdstP, (l[0], l[1]), (l[2], l[3]), (0,0,255), 3, cv.LINE_AA)
 
 cv.imshow("Source", src)
 cv.imshow("Detected Lines (in red) - Standard Hough Line Transform", cdst)
 cv.imshow("Detected Lines (in red) - Probabilistic Line Transform", cdstP)
 
 cv.waitKey()
 return 0
 
if __name__ == "__main__":
 main(sys.argv[1:])

说明

加载图片

C++

 const char* default_file = "sudoku.png";
 const char* filename = argc >=2 ? argv[1] : default_file;
 // Loads an image
 Mat src = imread( samples::findFile( filename ), IMREAD_GRAYSCALE );
 // Check if image is loaded fine
 if(src.empty()){
 printf(" Error opening image\n");
 printf(" Program Arguments: [image_name -- default %s] \n", default_file);
 return -1;
 }

Java

 String default_file = "../../../../data/sudoku.png";
 String filename = ((args.length > 0) ? args[0] : default_file);
 // Load an image
 Mat src = Imgcodecs.imread(filename, Imgcodecs.IMREAD_GRAYSCALE);
 // Check if image is loaded fine
 if( src.empty() ) {
 System.out.println("Error opening image!");
 System.out.println("Program Arguments: [image_name -- default "
 + default_file +"] \n");
 System.exit(-1);
 }

Pyhton

 default_file = 'sudoku.png'
 filename = argv[0] if len(argv) > 0 else default_file
 # Loads an image
 src = cv.imread(cv.samples.findFile(filename), cv.IMREAD_GRAYSCALE)
 # Check if image is loaded fine
 if src is None:
 print ('Error opening image!')
 print ('Usage: hough_lines.py [image_name -- default ' + default_file + '] \n')
 return -1

使用 Canny 检测器检测图像边缘:

C++

 // Edge detection
 Canny(src, dst, 50, 200, 3);

Java

 // Edge detection
 Imgproc.Canny(src, dst, 50, 200, 3, false);

Pyhton

 # Edge detection
 dst = cv.Canny(src, 50, 200, None, 3)

现在,我们将应用 Hough 线性变换。我们将介绍如何使用 OpenCV 的两个函数来实现这一目的。

标准 Hough 线变换:

首先,应用变换:
C++

 // Standard Hough Line Transform
 vector<Vec2f> lines; // will hold the results of the detection
 HoughLines(dst, lines, 1, CV_PI/180, 150, 0, 0 ); // runs the actual detection

Java

 // Standard Hough Line Transform
 Mat lines = new Mat(); // will hold the results of the detection
 Imgproc.HoughLines(dst, lines, 1, Math.PI/180, 150); // runs the actual detection

Pyhton

 # Standard Hough Line Transform
 lines = cv.HoughLines(dst, 1, np.pi / 180, 150, None, 0, 0)
  • 使用以下参数
    • dst: 边缘检测器的输出。应该是灰度图像(但实际上是二值图像)
    • lines: 一个向量,用于存储检测到的线条的参数 (r,θ)
    • rho:参数 r 的分辨率(像素)。我们使用 1 像素。
    • θ: 参数 θ 的分辨率,单位为弧度。我们使用 1 度 (CV_PI/180)
    • threshold: 用于 "检测"一条直线的最小交点数
    • srn 和 stn:默认参数为零。更多信息请查看 OpenCV 参考资料。
      然后通过绘制线条显示结果。
      C++
 // Draw the lines
 for( size_t i = 0; i < lines.size(); i++ )
 {
 float rho = lines[i][0], theta = lines[i][1];
 Point pt1, pt2;
 double a = cos(theta), b = sin(theta);
 double x0 = a*rho, y0 = b*rho;
 pt1.x = cvRound(x0 + 1000*(-b));
 pt1.y = cvRound(y0 + 1000*(a));
 pt2.x = cvRound(x0 - 1000*(-b));
 pt2.y = cvRound(y0 - 1000*(a));
 line( cdst, pt1, pt2, Scalar(0,0,255), 3, LINE_AA);
 }

Java

 // Draw the lines
 for (int x = 0; x < lines.rows(); x++) {
 double rho = lines.get(x, 0)[0],
 theta = lines.get(x, 0)[1];
 double a = Math.cos(theta), b = Math.sin(theta);
 double x0 = a*rho, y0 = b*rho;
 Point pt1 = new Point(Math.round(x0 + 1000*(-b)), Math.round(y0 + 1000*(a)));
 Point pt2 = new Point(Math.round(x0 - 1000*(-b)), Math.round(y0 - 1000*(a)));
 Imgproc.line(cdst, pt1, pt2, new Scalar(0, 0, 255), 3, Imgproc.LINE_AA, 0);
 }

Pyhton

 # Draw the lines
 if lines is not None:
 for i in range(0, len(lines)):
 rho = lines[i][0][0]
 theta = lines[i][0][1]
 a = math.cos(theta)
 b = math.sin(theta)
 x0 = a * rho
 y0 = b * rho
 pt1 = (int(x0 + 1000*(-b)), int(y0 + 1000*(a)))
 pt2 = (int(x0 - 1000*(-b)), int(y0 - 1000*(a)))
 cv.line(cdst, pt1, pt2, (0,0,255), 3, cv.LINE_AA)

概率霍夫线性变换

首先应用变换:
C++

 // Probabilistic Line Transform
 vector<Vec4i> linesP; // will hold the results of the detection
 HoughLinesP(dst, linesP, 1, CV_PI/180, 50, 50, 10 ); // runs the actual detection

Java

 // Probabilistic Line Transform
 Mat linesP = new Mat(); // will hold the results of the detection
 Imgproc.HoughLinesP(dst, linesP, 1, Math.PI/180, 50, 50, 10); // runs the actual detection

Pyhton

 # Probabilistic Line Transform
 linesP = cv.HoughLinesP(dst, 1, np.pi / 180, 50, None, 50, 10)
  • 使用参数
    • dst: 边缘检测器的输出。应该是灰度图像(但实际上是二值图像)
    • lines: 一个向量,用于存储检测到的线条的参数(xstart,ystart,xend,yend)。
    • rho:参数 r 的分辨率,单位为像素。我们使用 1 像素。
    • θ: 参数 θ 的分辨率,单位为弧度。我们使用 1 度 (CV_PI/180)
    • threshold: 检测*"线的最小交点数
    • minLineLength: 能形成一条直线的最小点数。少于此点数的线条将被忽略。
    • maxLineGap:同一条直线上两个点之间的最大间距。
      然后通过画线显示结果。
      C++
 // Draw the lines
 for( size_t i = 0; i < linesP.size(); i++ )
 {
 Vec4i l = linesP[i];
 line( cdstP, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0,0,255), 3, LINE_AA);
 }

Java

 // Draw the lines
 for (int x = 0; x < linesP.rows(); x++) {
 double[] l = linesP.get(x, 0);
 Imgproc.line(cdstP, new Point(l[0], l[1]), new Point(l[2], l[3]), new Scalar(0, 0, 255), 3, Imgproc.LINE_AA, 0);
 }

Pyhton

 # Draw the lines
 if linesP is not None:
 for i in range(0, len(linesP)):
 l = linesP[i][0]
 cv.line(cdstP, (l[0], l[1]), (l[2], l[3]), (0,0,255), 3, cv.LINE_AA)

显示原始图像和检测到的线条:

C++

 // Show results
 imshow("Source", src);
 imshow("Detected Lines (in red) - Standard Hough Line Transform", cdst);
 imshow("Detected Lines (in red) - Probabilistic Line Transform", cdstP);

Java

 // Show results
 HighGui.imshow("Source", src);
 HighGui.imshow("Detected Lines (in red) - Standard Hough Line Transform", cdst);
 HighGui.imshow("Detected Lines (in red) - Probabilistic Line Transform", cdstP);

Pyhton

 # Show results
 cv.imshow("Source", src)
 cv.imshow("Detected Lines (in red) - Standard Hough Line Transform", cdst)
 cv.imshow("Detected Lines (in red) - Probabilistic Line Transform", cdstP)

等待用户退出程序

C++

 // Wait and Exit
 waitKey();
 return 0;

Java

 // Wait and Exit
 HighGui.waitKey();
 System.exit(0);

Pyhton

 # Wait and Exit
 cv.waitKey()
 return 0

结果

注释
下面的结果是使用我们在 "代码 "部分提到的略微高级的版本得到的。它仍然实现了与上面相同的功能,只是为阈值添加了 Trackbar。

使用输入图像,如数独图像。通过使用标准 Hough 线变换,我们可以得到以下结果:

在这里插入图片描述

使用概率 Hough 线变换得到的结果如下

在这里插入图片描述

您可能会注意到,当您改变阈值时,检测到的线条数量会有所不同。原因很明显: 如果阈值越高,检测到的线条数量就越少(因为需要更多的点才能检测到线条)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值