基本概念
霍夫变换(Hough Transform)是一种用于检测图像中特定形状(如直线、圆、椭圆等)的技术。在OpenCV中,霍夫变换主要用于检测直线和圆形。这里我们将详细介绍如何使用OpenCV中的霍夫变换来检测直线。
霍夫变换(Hough Transform)是一种在图像中检测特定形状的方法,最常用的就是检测直线。霍夫变换的核心思想是将图像空间中的边缘点映射到参数空间中的点,然后寻找这些点在参数空间中的集中区域,以此来检测图像中的直线或其他形状。
霍夫变换原理
霍夫变换的核心思想是从图像空间到参数空间的映射。对于直线检测,霍夫变换将图像空间中的点映射到极坐标系下的参数空间。一条直线可以用极坐标系中的两个参数表示:距离 𝜌和角度 𝜃。
霍夫变换基本原理
霍夫变换通常分为两种类型:标准霍夫变换(Standard Hough Transform)和概率霍夫变换(Probabilistic Hough Transform)。
标准霍夫变换
标准霍夫变换将图像中的每一个边缘点投影到参数空间中的一个曲线或曲面,然后寻找这些曲线或曲面的交点。对于直线,参数空间是一个二维空间,通常用极坐标系表示,其中一个维度表示距离原点的距离(ρ),另一个维度表示角度(θ)。
概率霍夫变换
概率霍夫变换是对标准霍夫变换的一种改进,它通过随机选取边缘点来减少计算量,同时仍然能够检测出直线。概率霍夫变换通常用于处理大图像或需要快速处理的情况。
霍夫变换的主要步骤如下:
1.初始化参数空间:建立一个二维数组,对应于所有可能的 (𝜌,𝜃)组合。
2.投票过程:对于图像空间中的每个点,计算所有可能的 (𝜌,𝜃)对,并在对应的参数空间中增加计数。
3.检测峰值:在参数空间中寻找局部最大值,这些峰值对应于图像空间中的直线。
OpenCV中的霍夫变换直线检测
在OpenCV中,可以使用标准霍夫变换 HoughLines
或概率霍夫变换 HoughLinesP
函数来实现霍夫变换直线检测。前者用于检测无穷长的直线,后者用于检测有限长度的线段。
1. 使用HoughLines函数
HoughLines 函数是OpenCV中用于检测图像中的直线的一个重要函数。该函数利用霍夫变换来查找图像中的直线。
功能描述
HoughLines 函数通过对图像中的边缘点进行霍夫变换,在参数空间中找到那些累加器值高于给定阈值的位置,从而检测出直线。该函数返回一个包含直线参数的向量 Vec2f,其中每个元素表示一条直线,由两个值组成:直线到原点的距离(ρ)和直线的角度(θ)。
cpp
void HoughLines(
InputArray image,
OutputArray lines,
double rho,
double theta,
int threshold,
double srn = 0,
double stn = 0
);
参数说明
•image:输入图像,通常是边缘检测后的图像(如Canny边缘检测的结果)。此图像应为单通道的8位灰度图像。 输入图像,通常为二值化后的边缘图像。
•lines:输出直线,是一个包含直线参数的向量 Vec2f。每条直线由两个参数表示:距离原点的最近距离(ρ)和角度(θ)。输出的直线集,每条直线由(𝜌,𝜃)表示。
•rho:距离分辨率,即在参数空间中的步长。通常选择为 1 或更小的值,以获得更高的精度。距离分辨率,单位为像素。
•theta:角度分辨率,通常以弧度表示。可以选择为 CV_PI / 180 来获得每度一个角度步长。角度分辨率,单位为弧度。
•threshold:累加器阈值,只有当累加器中的值大于等于这个阈值时,才认为找到了一条直线。较高的阈值可以减少误检,但也可能导致漏检。阈值,只有当计数器大于此阈值时,才认为存在一条直线。
•srn:标准霍夫变换中的累加器行数。通常情况下不需要修改,默认为 0。
•stn:标准霍夫变换中的累加器列数。通常情况下不需要修改,默认为 0。
srn 和 stn: 分别是𝜌和𝜃的累积计数器的分辨率。
示例代码1
下面是一个详细的示例代码,展示如何使用 HoughLines 函数检测图像中的直线:
cpp
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
void detectLinesWithHough(const Mat &edges, vector<Vec2f> &lines, double rho, double theta, int threshold)
{
HoughLines(edges, lines, rho, theta, threshold);
}
int main(int argc, char** argv)
{
/*if (argc != 2) {
cout << "Usage: ./HoughLines <Image Path>" << endl;
return -1;
}*/
// 加载图像
Mat img = imread("283.png", IMREAD_GRAYSCALE);
if (!img.data)
{
cout << "Error opening image" << endl;
return -1;
}
// 边缘检测
Mat edges;
Canny(img, edges, 50, 150);
// 初始化输出向量
vector<Vec2f> lines;
// 使用霍夫变换检测直线
detectLinesWithHough(edges, lines, 1, CV_PI / 180, 50);
// 在原图上绘制检测到的直线
Mat result = img.clone();
for (size_t i = 0; i < lines.size(); i++)
{
float rho = lines[i][0], theta = lines[i][1];
double cos_theta = cos(theta), sin_theta = sin(theta);
double x0 = rho * cos_theta, y0 = rho * sin_theta;
Point pt1, pt2;
double alpha = 1000; // 控制直线的长度
pt1.x = cvRound(x0 + alpha*(-cos_theta));
pt1.y = cvRound(y0 + alpha*(-sin_theta));
pt2.x = cvRound(x0 - alpha*(-cos_theta));
pt2.y = cvRound(y0 - alpha*(-sin_theta));
line(result, pt1, pt2, Scalar(255, 0, 0), 3, LINE_AA);
}
// 显示结果
namedWindow("Original Image", WINDOW_NORMAL);
imshow("Original Image", img);
namedWindow("Edges", WINDOW_NORMAL);
imshow("Edges", edges);
namedWindow("Detected Lines", WINDOW_NORMAL);
imshow("Detected Lines", result);
waitKey(0);
destroyAllWindows();
return 0;
}
代码解释
1. 加载图像:使用 imread 函数以灰度模式加载图像。
2. 边缘检测:使用Canny边缘检测算法检测图像的边缘。
3. 初始化输出向量:创建一个新的向量来存储检测到的直线信息。
4. 执行霍夫变换:使用 HoughLines 函数检测图像中的直线。
5. 绘制直线:在原图上绘制检测到的直线。
6. 显示结果:使用 imshow 函数显示原始图像、边缘检测结果以及检测到的直线。
参数详解
•rho:距离分辨率,表示在参数空间中的步长。选择较小的 rho 值可以获得更高的精度,但也会增加计算量。
•theta:角度分辨率,通常以弧度表示。选择较小的 theta 值可以获得更高的精度,但也会增加计算量。通常选择 CV_PI / 180 表示每度一个角度步长。
•threshold:累加器阈值,只有当累加器中的值大于等于这个阈值时,才认为找到了一条直线。较高的阈值可以减少误检,但也可能导致漏检。
•srn 和 stn:这两个参数用于控制累加器的大小,通常情况下不需要修改,默认为 0。
应用场景
•图像分割:检测图像中的直线可以帮助分割图像中的不同区域。
•文档分析:在文档扫描或OCR处理中,检测文档中的直线可以帮助校正倾斜或扭曲的文档。
•交通标志检测:在智能交通系统中,检测道路标志中的直线可以帮助识别交通标志。
•机器人导航:在机器人视觉系统中,检测环境中的直线可以帮助机器人识别障碍物和路径。
注意事项
•边缘检测:在进行霍夫变换之前,通常需要先对图像进行边缘检测,以减少计算量并提高检测精度。
•参数选择:rho、theta 和 threshold 等参数的选择会影响检测结果。较小的 rho 和 theta 值可以提高检测精度,但也会增加计算量;较大的 threshold 值可以减少误检,但也可能导致漏检。
•性能考虑:标准霍夫变换计算量较大,适用于小图像或对速度要求不高的场景;概率霍夫变换则适用于大图像或需要快速处理的场景。
通过这些示例和详细解释,你应该能够理解如何在OpenCV中使用C++实现霍夫变换直线检测。如果有任何问题或需要进一步的帮助,请随时提问。
运行结果1
示例代码2
以下是一个完整的C++示例程序,展示了如何使用HoughLines函数来检测图像中的直线:
cpp
步骤一:包含必要的头文件
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
步骤二:加载图像并进行预处理
int main(int argc, char** argv)
{
// 加载图像
Mat src = imread("path_to_your_image.jpg", IMREAD_GRAYSCALE);
if (src.empty())
{
cout << "Error: Image cannot be loaded!" << endl;
return -1;
}
// 进行Canny边缘检测
Mat edges;
Canny(src, edges, 50, 150);
步骤三:应用霍夫变换直线检测
// 定义存储检测到的直线的容器
vector<Vec2f> lines;
// 使用HoughLines函数检测直线
HoughLines(edges, lines, 1, CV_PI / 180, 100); // 参数分别为:输入图像、输出直线、距离分辨率、角度分辨率、阈值
步骤四:绘制检测到的直线
// 创建一个彩色图像来绘制直线
Mat dst;
cvtColor(src, dst, COLOR_GRAY2BGR);
// 遍历检测到的每条直线并绘制
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(dst, pt1, pt2, Scalar(0, 0, 255), 3, LINE_AA);
}
步骤五:显示结果
// 显示原始图像和检测到的直线
namedWindow("Source Image", WINDOW_AUTOSIZE);
imshow("Source Image", src);
namedWindow("Detected Lines", WINDOW_AUTOSIZE);
imshow("Detected Lines", dst);
waitKey(0);
return 0;
}
参数解释
rho: 直线到原点的距离。
theta: 直线与水平线的夹角。
a 和 b: 极坐标系中的方向余弦。
x0 和 y0: 直线到原点的垂足坐标。
pt1 和 pt2: 用于绘制直线的两个端点。
注意事项
Canny边缘检测: 在应用HoughLines之前,通常需要先进行边缘检测。这里我们使用了Canny边缘检测器。
参数调整: HoughLines函数中的参数(如距离分辨率、角度分辨率和阈值)可以根据具体需求进行调整,以获得更好的检测效果。
直线绘制: HoughLines函数返回的直线是以极坐标形式表示的,需要将其转换为笛卡尔坐标系下的两点来绘制。
通过上述步骤,你可以使用OpenCV的HoughLines函数来检测图像中的直线,并将检测结果绘制在原始图像上。这种方法适用于检测图像中的无穷长直线,对于检测短直线或线段,可以考虑使用HoughLinesP函数。
完整代码
cpp
//步骤一:包含必要的头文件
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
//步骤二:加载图像并进行预处理
int main(int argc, char** argv)
{
// 加载图像
Mat src = imread("02.png", IMREAD_GRAYSCALE);
if (src.empty())
{
cout << "Error: Image cannot be loaded!" << endl;
return -1;
}
// 进行Canny边缘检测
Mat edges;
Canny(src, edges, 50, 150);
//步骤三:应用霍夫变换直线检测
// 定义存储检测到的直线的容器
vector<Vec2f> lines;
// 使用HoughLines函数检测直线
HoughLines(edges, lines, 1, CV_PI / 180, 100); // 参数分别为:输入图像、输出直线、距离分辨率、角度分辨率、阈值
//步骤四:绘制检测到的直线
// 创建一个彩色图像来绘制直线
Mat dst;
cvtColor(src, dst, COLOR_GRAY2BGR);
// 遍历检测到的每条直线并绘制
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(dst, pt1, pt2, Scalar(0, 0, 255), 3, LINE_AA);
}
//步骤五:显示结果
// 显示原始图像和检测到的直线
namedWindow("Source Image", WINDOW_NORMAL);
imshow("Source Image", src);
namedWindow("Detected Lines", WINDOW_NORMAL);
imshow("Detected Lines", dst);
waitKey(0);
return 0;
}
运行结果2
2. 使用HoughLinesP函数
HoughLinesP 函数是OpenCV中用于检测图像中直线段的一个函数,它是概率霍夫变换(Probabilistic Hough Transform)的一个实现。与标准霍夫变换相比,概率霍夫变换通过随机选择边缘点来减少计算量,同时仍能有效地检测直线。
功能描述
HoughLinesP 函数通过对图像中的边缘点进行概率霍夫变换,在参数空间中找到那些累加器值高于给定阈值的位置,并且根据 minLineLength 和 maxLineGap 参数过滤掉不符合条件的线段,从而检测出直线段。
cpp
void HoughLinesP(
InputArray image,
OutputArray lines,
double rho,
double theta,
int threshold,
double minLineLength = 0,
double maxLineGap = 0
);
参数说明
•image:输入图像,通常是边缘检测后的图像(如Canny边缘检测的结果)。此图像应为单通道的8位灰度图像。输入图像,通常为二值化后的边缘图像。
•lines:输出直线段,是一个包含直线段参数的向量 Vec4i。每个直线段由四个值表示:起始点的x坐标、起始点的y坐标、终止点的x坐标和终止点的y坐标。输出的直线集,每条直线由起点和终点坐标表示。
•rho:距离分辨率,即在参数空间中的步长。通常选择为 1 或更小的值,以获得更高的精度。距离分辨率,单位为像素。
•theta:角度分辨率,通常以弧度表示。可以选择为 CV_PI / 180 来获得每度一个角度步长。角度分辨率,单位为弧度。
•threshold:累加器阈值,只有当累加器中的值大于等于这个阈值时,才认为找到了一条直线段。较高的阈值可以减少误检,但也可能导致漏检。阈值,只有当计数器大于此阈值时,才认为存在一条直线。
•minLineLength:最小线段长度,只有比这个值长的线段才会被保留。最小线段长度,小于此长度的线段将被忽略。
•maxLineGap:两个线段间的最大允许间隙,如果两个线段之间的距离小于这个值,则认为它们属于同一直线。最大允许的线段间隙,小于此间隙的线段将被视为一条直线。
示例代码3
cpp
下面是一个基于C++的OpenCV示例程序,展示如何使用HoughLinesP函数来检测图像中的直线。
步骤一:包含必要的头文件
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
步骤二:加载图像并进行预处理
int main(int argc, char** argv)
{
Mat src = imread("path_to_your_image.jpg", IMREAD_GRAYSCALE);
if (src.empty())
{
cout << "Error: Image cannot be loaded!" << endl;
return -1;
}
// Canny边缘检测
Mat edges;
Canny(src, edges, 50, 150);
步骤三:应用霍夫变换直线检测
vector<Vec4i> lines; // 存储检测到的直线
HoughLinesP(edges, lines, 1, CV_PI / 180, 50, 50, 10); // CV_PI / 180 表示1度的角度分辨率
// 绘制检测到的直线
Mat dst = Mat::zeros(src.size(), CV_8UC3);
for (size_t i = 0; i < lines.size(); i++)
{
Vec4i l = lines[i];
line(dst, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0, 0, 255), 3, LINE_AA);
}
步骤四:显示结果
namedWindow("Source Image", WINDOW_AUTOSIZE);
imshow("Source Image", src);
namedWindow("Detected Lines", WINDOW_AUTOSIZE);
imshow("Detected Lines", dst);
waitKey(0);
return 0;
}
注意事项
预处理: 在应用霍夫变换之前,通常需要对图像进行边缘检测,如使用Canny边缘检测器。
参数调整: rho 和 theta 的分辨率、阈值 threshold、最小线段长度 minLineLength 和最大线段间隙 maxLineGap 都需要根据具体应用场景调整,以获得最佳的检测效果。
性能: 霍夫变换在检测复杂场景中的直线时可能会比较耗时,尤其是当参数空间很大时。可以通过适当调整参数来优化性能。
通过上述步骤,你可以使用OpenCV的HoughLinesP函数来检测图像中的直线,并绘制出检测结果。
完整代码
cpp
//下面是一个基于C++的OpenCV示例程序,展示如何使用HoughLinesP函数来检测图像中的直线。
//步骤一:包含必要的头文件
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
//步骤二:加载图像并进行预处理
int main(int argc, char** argv)
{
Mat src = imread("23.png", IMREAD_GRAYSCALE);
if (src.empty())
{
cout << "Error: Image cannot be loaded!" << endl;
return -1;
}
// Canny边缘检测
Mat edges;
Canny(src, edges, 50, 150);
//步骤三:应用霍夫变换直线检测
vector<Vec4i> lines; // 存储检测到的直线
HoughLinesP(edges, lines, 1, CV_PI / 180, 50, 50, 10); // CV_PI / 180 表示1度的角度分辨率
// 绘制检测到的直线
Mat dst = Mat::zeros(src.size(), CV_8UC3);
for (size_t i = 0; i < lines.size(); i++)
{
Vec4i l = lines[i];
line(dst, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0, 0, 255), 3, LINE_AA);
}
//步骤四:显示结果
namedWindow("Source Image", WINDOW_NORMAL);
imshow("Source Image", src);
namedWindow("Detected Lines", WINDOW_NORMAL);
imshow("Detected Lines", dst);
waitKey(0);
return 0;
}
运行结果3
示例代码4
cpp
下面是一个详细的示例代码,展示如何使用 HoughLinesP 函数检测图像中的直线段:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
void detectLinesWithHoughP(const Mat &edges, vector<Vec4i> &lines, double rho, double theta, int threshold, double minLineLength, double maxLineGap) {
HoughLinesP(edges, lines, rho, theta, threshold, minLineLength, maxLineGap);
}
int main(int argc, char** argv) {
if (argc != 2) {
cout << "Usage: ./HoughLinesP <Image Path>" << endl;
return -1;
}
// 加载图像
Mat img = imread(argv[1], IMREAD_GRAYSCALE);
if (!img.data) {
cout << "Error opening image" << endl;
return -1;
}
// 边缘检测
Mat edges;
Canny(img, edges, 50, 150);
// 初始化输出向量
vector<Vec4i> lines;
// 使用概率霍夫变换检测直线段
detectLinesWithHoughP(edges, lines, 1, CV_PI/180, 50, 50, 10);
// 在原图上绘制检测到的直线段
Mat result = img.clone();
for (size_t i = 0; i < lines.size(); i++) {
Vec4i l = lines[i];
line(result, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(255, 0, 0), 3, LINE_AA);
}
// 显示结果
imshow("Original Image", img);
imshow("Edges", edges);
imshow("Detected Line Segments", result);
waitKey(0);
destroyAllWindows();
return 0;
}
代码解释
1. 加载图像:使用 imread 函数以灰度模式加载图像。
2. 边缘检测:使用Canny边缘检测算法检测图像的边缘。
3. 初始化输出向量:创建一个新的向量来存储检测到的直线段信息。
4. 执行概率霍夫变换:使用 HoughLinesP 函数检测图像中的直线段。
5. 绘制直线段:在原图上绘制检测到的直线段。
6. 显示结果:使用 imshow 函数显示原始图像、边缘检测结果以及检测到的直线段。
参数详解
•rho:距离分辨率,表示在参数空间中的步长。选择较小的 rho 值可以获得更高的精度,但也会增加计算量。
•theta:角度分辨率,通常以弧度表示。选择较小的 theta 值可以获得更高的精度,但也会增加计算量。通常选择 CV_PI / 180 表示每度一个角度步长。
•threshold:累加器阈值,只有当累加器中的值大于等于这个阈值时,才认为找到了一条直线段。较高的阈值可以减少误检,但也可能导致漏检。
•minLineLength:最小线段长度,只有比这个值长的线段才会被保留。这有助于去除短的、可能是噪声的线段。
•maxLineGap:两个线段间的最大允许间隙,如果两个线段之间的距离小于这个值,则认为它们属于同一直线。这有助于合并接近的线段。
应用场景
•图像分割:检测图像中的直线段可以帮助分割图像中的不同区域。•文档分析:在文档扫描或OCR处理中,检测文档中的直线段可以帮助校正倾斜或扭曲的文档。
•交通标志检测:在智能交通系统中,检测道路标志中的直线段可以帮助识别交通标志。
•机器人导航:在机器人视觉系统中,检测环境中的直线段可以帮助机器人识别障碍物和路径。
注意事项
•边缘检测:在进行概率霍夫变换之前,通常需要先对图像进行边缘检测,以减少计算量并提高检测精度。
•参数选择:rho、theta、threshold、minLineLength 和 maxLineGap 等参数的选择会影响检测结果。较小的 rho 和 theta 值可以提高检测精度,但也会增加计算量;较高的 threshold 值可以减少误检,但也可能导致漏检;较大的 minLineLength 可以去除短的线段,但可能会丢失细节;较小的 maxLineGap 可以帮助合并接近的线段,但也可能会合并不应合并的线段。
•性能考虑:概率霍夫变换相比于标准霍夫变换更适合处理大图像或需要快速处理的情况。
通过这些示例和详细解释,你应该能够理解如何在OpenCV中使用C++实现概率霍夫变换直线段检测。
运行结果4
示例代码5
cpp
/* This is a standalone program. Pass an image name as the first parameter
of the program. Switch between standard and probabilistic Hough transform
by changing "#if 1" to "#if 0" and back */
#include <opencv2/opencv.hpp>
#include <iostream>
#include <cv.h>
#include <highgui.h>
#include <math.h>
using namespace cv;
using namespace std;
int main(int argc, char** argv)
{
Mat src, dst, color_dst;
if (!(src = imread("56.jpeg", 0)).data)
return -1;
Canny(src, dst, 50, 200, 3);
cvtColor(dst, color_dst, CV_GRAY2BGR);
#if 0
vector<Vec2f> lines;
HoughLines(dst, lines, 1, CV_PI / 180, 100);
for (size_t i = 0; i < lines.size(); i++)
{
float rho = lines[i][0];
float theta = lines[i][1];
double a = cos(theta), b = sin(theta);
double x0 = a*rho, y0 = b*rho;
Point pt1(cvRound(x0 + 1000 * (-b)),
cvRound(y0 + 1000 * (a)));
Point pt2(cvRound(x0 - 1000 * (-b)),
cvRound(y0 - 1000 * (a)));
line(color_dst, pt1, pt2, Scalar(0, 0, 255), 3, 8);
}
#else
vector<Vec4i> lines;
HoughLinesP(dst, lines, 1, CV_PI / 180, 80, 30, 10);
for (size_t i = 0; i < lines.size(); i++)
{
line(color_dst, Point(lines[i][0], lines[i][1]),
Point(lines[i][2], lines[i][3]), Scalar(0, 0, 255), 3, 8);
}
#endif
//namedWindow("Source", 1);
namedWindow("Source", WINDOW_NORMAL);
imshow("Source", src);
namedWindow("Detected Lines", WINDOW_NORMAL);
//namedWindow("Detected Lines", 1);
imshow("Detected Lines", color_dst);
waitKey(0);
return 0;
}