OpenCV实战(14)——图像线条提取
创始人
2025-05-28 22:31:43
0

OpenCV实战(14)——图像线条提取

    • 0. 前言
    • 1. 检测图像轮廓
      • 1.1 图像轮廓
      • 1.2 使用 Canny 算子检测图像轮廓
    • 2. 使用霍夫变换检测图像中的线条
      • 2.1 线条的表示
      • 2.2 霍夫变换检测直线
      • 2.3 概率霍夫变换
      • 2.4 霍夫变换与概率霍夫变换对比
      • 2.5 霍夫变换检测圆
    • 3. 完整代码
    • 小结
    • 系列链接

0. 前言

基于内容的图像分析,需要从构成图像的像素集合中提取有意义的特征。轮廓、线条、区域等是基本的图像基元,可用于描述图像中包含的元素。本节将介绍如何提取重要的图像线条特征。

1. 检测图像轮廓

1.1 图像轮廓

图像轮廓包含了重要的视觉信息,可以用于描绘图像元素,因此,通常用于物体识别等计算机视觉任务。然而,简单的二值轮廓图有两个主要缺点:检测到的边缘过粗,使较小物体难以识别;更重要的是,我们通常不可能恰巧找到一个能够同时检测图像所有重要边缘并舍弃不重要边缘的阈值,Canny 算法用于解决两者之间的权衡问题。

1.2 使用 Canny 算子检测图像轮廓

Canny 算法在 OpenCV 中由 cv::Canny 函数实现,该算法需要指定两个阈值。

(1) 要将 Canny 算法应用于加载的图像,必须首先创建一个新的 cv::Mat 结构来存储结果并调用 cv::Canny 函数:

// 应用 Canny 算法
cv::Mat contours;
cv::Canny(image,    // 灰度图像contours,   // 输出图像125,        // 低阈值350);       // 高阈值

(2) 在测试图像上调用 Canny 算法,检测结果如下:

Canny 算子检测结果

由于算法正常结果表示非零像素的轮廓,为了获得上示图像,我们必须反转黑白值。Canny 算子通常基于 Sobel 算子,但也可以使用其他梯度算子,关键思想是使用两个不同的阈值来确定哪些像素属于轮廓。
选择的低阈值应包括所有被认为属于重要图像轮廓的边缘像素。例如,使用指定的低阈值,并将其应用于 Sobel 算子可以得到如下边缘图:

使用较低阈值得到的边缘图
如上图所示,道路的边缘非常明显。然而,由于使用了较低阈值,因此检测到较多的边缘。第二个阈值的作用是定义属于重要轮廓的边缘,它能够排除被视为异常值的边缘。例如,使用高阈值对应的 Sobel 边缘图如下:

使用较高阈值得到的边缘图
上图中包含了较多破碎边缘,但其中可见的边缘均属于重要轮廓。
Canny 算法将以上两个边缘图组合在一起,以生成最佳轮廓图。其仅保留存在连续边缘路径的低阈值边缘图的边缘点,并将这些边缘点链接到属于高阈值边缘图中的边缘。因此,保留了高阈值图中的所有边缘点,同时去除了低阈值图中所有孤立的边缘点链。这是一种良好的折中解决方案,只要指定合适的阈值,就可以获得高质量的轮廓。这种基于使用两个阈值来获得二值图的策略称为滞后阈值,可用于需要从阈值操作中获得二值图的应用,但这需要以更高的计算复杂度为代价。
此外,Canny 算法还使用了其他能够提高边缘图质量的策略,在应用滞后阈值之前,移除所有梯度幅值在梯度方向上不是最大值的边缘点,得到合适边缘。由于梯度方向总是垂直于边缘,因此,方向梯度的局部最大值对应于轮廓强度最大的点。

2. 使用霍夫变换检测图像中的线条

2.1 线条的表示

在物理世界中,平面和线性结构非常常见,因此,在图像中经常可以看到直线,直线特征在物体识别和图像理解中起着重要作用。霍夫变换是一种经典算法,常用于检测图像中的特定特征。它最初用于检测图像中的线条,但也可以扩展到检测其他简单的图像结构。使用霍夫变换,使用以下等式表示线条:
ρ=xcos(θ)+ysin(θ)ρ=xcos(\theta)+ysin(\theta) ρ=xcos(θ)+ysin(θ)
ρρρ 参数是直线与图像原点(左上角)之间的距离,θθθ 是直线与直线的夹角(采用弧度制)。采用这种表示,图像中线条的角 θθθ 介于 000 到 πππ 之间,而半径 ρρρ 的最大值等于图像对角线长度。例如,使用以下线条:

线条示例

在上图中,垂直线(线 1 )的 θθθ 角等于 000,而水平线(线 5 )的 θθθ 值等于 π/2π/2π/2,线 3 的角度 θθθ 等于 π/4π/4π/4,线 4 的角度大约为 0.7π0.7π0.7π。为了能够用 θθθ 在 [0,π][0, π][0,π] 区间内表示所有可能的直线,可以将半径值设为负数,例如,线 2 的 θθθ 值等于 0.8π0.8π0.8π,ρρρ 为负值。

2.2 霍夫变换检测直线

OpenCV 提供了两种用于线检测的霍夫变换实现,基本方法是 cv::HoughLines,它的输入是包含一组点(由非零像素表示)的二值图(其中一些点对齐形成线),通常可以使用 Canny 算子获得的边缘图作为输入。cv::HoughLines 函数的输出是 cv::Vec2f 元素的向量,每个元素都是一对浮点值,表示检测到的线的参数 (ρ,θ)(ρ, θ)(ρ,θ)。

(1) 首先应用 Canny 算子得到图像轮廓:

// 应用 Canny 算法
cv::Mat contours;
cv::Canny(image,    // 灰度图像contours,   // 输出图像125,        // 低阈值350);       // 高阈值

(2) 使用霍夫变换检测线,第 3 个参数和第 4 个参数用于设置线搜索的步长,在以下示例代码中,函数以 1 为步长搜索所有可能半径的线,并以 π/180π/180π/180 为步长搜索所有可能的角度,我们将在下一节中解释最后一个参数的作用。使用以上参数值调用 cv::HoughLines,可以在示例图像上检测到 15 条线:

// 霍夫变换
std::vector lines;
cv::HoughLines(contours, lines, 1, PI/180, 130);

(3) 为了可视化检测的结果,可以在原始图像上绘制这些线。但是,需要注意的是,该算法是检测图像中的线而不是线段,因为算法没有给出每条线的端点。因此,绘制的直线会穿过整个图像。为此,对于垂直方向的线,我们计算其与图像水平边界(第一行和最后一行)的交点,并在这两个点之间绘制一条线,对于水平方向的线进行类似的处理(使用第一列和最后一列)。使用 cv::line 函数绘制线条,即使点坐标超出图像范围,此函数也能正常工作。因此,无需检查计算出的交点是否落在图像内。最后,通过迭代线向量绘制所有线:

// 绘制检测结果
cv::Mat result(contours.rows, contours.cols, CV_8U, cv::Scalar(255));
image.copyTo(result);
std::cout << "Lines detected: " << lines.size() << std::endl;
std::vector::const_iterator it = lines.begin();
while (it!=lines.end()) {float rho = (*it)[0];float theta = (*it)[1];if (theta < PI/4. || theta > 3.*PI/4.) {    // 竖线// 直线与图像第一行交点cv::Point pt1(rho/cos(theta), 0);// 直线与图像最后一行交点cv::Point pt2((rho-result.rows*sin(theta))/cos(theta), result.rows);cv::line(result, pt1, pt2, cv::Scalar(255), 1);} else {        // 横线// 直线与图像第一列交点cv::Point pt1(0, rho/sin(theta));// 直线与图像最后一列交点cv::Point pt2(result.cols, (rho-result.cols*cos(theta))/sin(theta));cv::line(result, pt1, pt2, cv::Scalar(255), 1);}std::cout << "line: (" << rho << "," << theta << ")" << std::endl;++it;
}

执行以上程序可以得到如下结果:

霍夫变换检测结果

如上图所示,霍夫变换仅用于寻找图像中边缘像素的对齐方式。偶然的像素对齐,或当多条线通过相同像素对齐时,都可能可能会产生一些错误检测。

2.3 概率霍夫变换

为了克服霍夫变换的问题,并能够检测线段(即具有端点),已经提出了霍夫变换的改进版本,即概率霍夫变换,在 OpenCV 中使用 cv::HoughLinesP 函数实现。

(1) 使用 cv::HoughLinesP 函数创建封装函数参数的 LineFinder 类:

class LineFinder {private:// 原始图像cv::Mat img;// 检测到的线段的端点向量std::vector lines;// 累加器参数double deltaRho;double deltaTheta;// 最小得票数int minVote;// 线段最小长度double minLength;// 线段最大允许间隙double maxGap;public:// 默认参数LineFinder() : deltaRho(1), deltaTheta(PI/180), minVote(10), minLength(0.), maxGap(0.) {}

(2) 创建对应的 setter 方法:

// 参数设置
void setAccResolution(double dRho, double dTheta) {deltaRho = dRho;deltaTheta = dTheta;
}
void setMinVote(int minv) {minVote = minv;
}
void setLineLengthAndGap(double length, double gap) {minLength = length;maxGap = gap;
}

(3) 使用上述方法,创建霍夫线段检测方法 findLines

// 应用概率霍夫转换
std::vector findLines(cv::Mat& binary) {lines.clear();cv::HoughLinesP(binary, lines, deltaRho, deltaTheta, minVote, minLength, maxGap);return lines;
}

(4) findLines 方法返回一个 cv::Vec4i 向量,每个向量包含检测到的线段的起点和终点坐标。然后使用方法 drawDetectedLines 在图像上绘制检测到的线条:

// 绘制检测到的线段
void drawDetectedLines(cv::Mat& image, cv::Scalar color=cv::Scalar(255, 255, 255)) {std::vector::const_iterator it2 = lines.begin();while (it2!=lines.end()) {cv::Point pt1((*it2)[0], (*it2)[1]);cv::Point pt2((*it2)[2], (*it2)[3]);cv::line(image, pt1, pt2, color);++it2;}
}

(5) 接下来,按以下顺序检测线条:

// 创建 LineFinder 实例
LineFinder ld;
// 设置概率霍夫变换
ld.setLineLengthAndGap(100, 20);
ld.setMinVote(60);
// 直线检测
std::vector li = ld.findLines(contours);
ld.drawDetectedLines(image);
cv::namedWindow("Lines with HoughP");
cv::imshow("Lines with HoughP", image);

代码执行结果如下所示:

概率霍夫变换检测结果

2.4 霍夫变换与概率霍夫变换对比

霍夫变换的目标是在二值图像中找到通过足够数量点的所有线,变换通过考虑输入二值图中的每个单独像素点并识别通过它的所有可能的线来完成。当同一条直线经过多个点时,表示这条直线足够重要,可以考虑保留。
霍夫变换使用二维累加器来计算给定线被识别的次数,累加器的大小由所采用指定 (ρ,θ)(ρ, θ)(ρ,θ) 搜索步长定义。为了说明变换过程,我们首先创建一个 180x200 的矩阵(对应 θθθ 的步长为 π/180π/180π/180,ρρρ 的步长为 1):

// 创建霍夫累加器
cv::Mat acc(200, 180, CV_8U, cv::Scalar(0));

累加器是不同 (ρ,θ)(ρ, θ)(ρ,θ) 值的映射,因此,该矩阵的每一项对应于一个特定的线。假设在坐标 (50, 30) 处有一个点,那么可以通过遍历所有可能的 θθθ 角(步长为 π/180π/180π/180 )来识别通过该点的所有线,并且计算相应的(四舍五入的) ρρρ 值:

// 选择一个点
int x=50, y=30;
// 循环所有角
for (int i=0; i<180; i++) {double theta = i*PI/180.;double rho = x*std::cos(theta)+y*std::sin(theta);int j = static_cast(rho+100.5);std::cout << i << ", " << j << std::endl;acc.at(j, i)++;
}

计算出的 (ρ,θ)(ρ, θ)(ρ,θ) 对相对应的累加器的项递增,项表示通过图像的一个点的所有线,换句话说,每个点都投票给一组可能的候选线。如果我们将累加器显示为图像,可以得到以下结果:

霍夫变换累加器

以上曲线表示通过所考虑点的所有线的集合。如果我们用点 (30, 10) 执行相同的操作,可以得到以下累加器:

霍夫变换累加器

如上图所示,两条结果曲线在相交于一点:对应于通过这两个点的线的交点。累加器对应的项计数加 2,表示有两点通过这条线。如果对二值映射的所有点重复此过程,那么沿给定线对齐的点将令累加器的公共项持续增加。最后,只需要最终的结果累加器中识别局部最大值,以检测图像中的线(即点对齐)。cv::HoughLines 函数中指定的最后一个参数对应于一条线必须收到的最小投票数才能被视为检测到。例如,我们将这个值降低到 50

cv::HoughLines(test,lines,1,PI/180,50);

使用以上的代码,将获得更多直线,如下图所示:

霍夫变换直线检测

概率霍夫变换对基本算法进行了改进。首先,在二值图中以随机顺序选择点扫描图像,而不是逐行进行扫描。每当累加器的项达到指定的最小值时,就会沿相应的线扫描图像,并删除所有通过它的点,即使它们尚未投票。这种扫描还决定了将被接受的段的长度,因此,算法定义了两个附加参数,一个是要接受的线段的最小长度,另一个是允许形成连续段的最大像素间隙,这个额外的步骤增加了算法的复杂性,但由于在扫描过程中减少了点的数量,因此总的算法时间运行时间相差无几。
霍夫变换也可用于检测其他几何实体。事实上,任何可以由参数方程表示的实体都可以由霍夫变换检测。

2.5 霍夫变换检测圆

圆对应的参数方程如下:
r2=(x−x0)2+(y−y0)2r^2=(x-x_0)^2+(y-y_0)^2 r2=(x−x0​)2+(y−y0​)2
该方程包括三个参数(圆半径和中心坐标),这表示我们需要一个三维累加器。然而,霍夫变换通常随着其累加器维数的增加而导致性能下降。实际上,在这种情况下,每个点都会增加累加器的大量项,因此,局部峰值的准确定位变得十分困难。为了克服这个问题,已经提出了多种改进策略,OpenCV 中实现的霍夫圆检测使用两次传递的策略,在第一次传递时,使用二维累加器来查找候选的圆圈位置。由于圆周上点的梯度指向半径方向,因此对于每个点,仅沿梯度方向递增累加器中的项(基于预定义的最小和最大半径值),一旦检测到可能的圆心(即已收到预定义数量的投票),就会在第二次传递时构建候选半径的一维直方图,此直方图中的峰值对应于检测到的圆的半径。
实现上述策略的 cv::HoughCircles 函数集成了 Canny 检测和 Hough 变换:

cv::GaussianBlur(image, image, cv::Size(5, 5), 1.5);
std::vector circles;
cv::HoughCircles(image, circles, cv::HOUGH_GRADIENT,2,          // 累加器尺寸 (图像尺寸/2)20,         // 两个圆之间的最小距离200,        // 高阈值 Canny 60,         // 最小投票数15, 50);    // 能接受的最大最小圆半径

需要注意的是,在调用 cv::HoughCircles 函数之前平滑图像可以减少导致错误圆检测的图像噪声。检测结果由 cv::Vec3f 实例的向量给出,前两个值是圆心,第三个值是半径;CV_HOUGH_GRADIENT 参数对应于二次圆检测方法;第 4 个参数定义累加器分辨率,它是一个分频因子;例如,指定值 2 则累加器大小为图像大小的一半;第 5 个参数是两个检测到的圆之间的最小像素距离,第 6 个参数对应于 Canny 边缘检测器的高阈值,Canny 检测器的低阈值被设置为该参数值的一半;第 7 个参数是中心位置在第一次传递期间必须获得的最小票数,票数达到时才能被视为第二次传递的候选圆。最后两个参数是要检测的圆的最小和最大半径值。
一旦获得检测到的圆的向量,就可以迭代向量并调用 cv::circle 绘图函数在图像上绘制这些圆:

std::vector::const_iterator itc = circles.begin();
while (itc!=circles.end()) {cv::circle(image,cv::Point((*itc)[0], (*itc)[1]),    // 圆心(*itc)[2],                          // 半径cv::Scalar(255),                    // 颜色2);                                 // 粗细++itc;
}

执行霍夫圆检测可以得到以下结果:

请添加图片描述

3. 完整代码

头文件 (linefinder.h) 完整代码如下:

#if !defined LINEF
#define LINEF#include 
#include 
#define PI 3.1415926class LineFinder {private:// 原始图像cv::Mat img;// 检测到的线段的端点向量std::vector lines;// 累加器参数double deltaRho;double deltaTheta;// 最小得票数int minVote;// 线段最小长度double minLength;// 线段最大允许间隙double maxGap;public:// 默认参数LineFinder() : deltaRho(1), deltaTheta(PI/180), minVote(10), minLength(0.), maxGap(0.) {}// 参数设置void setAccResolution(double dRho, double dTheta) {deltaRho = dRho;deltaTheta = dTheta;}void setMinVote(int minv) {minVote = minv;}void setLineLengthAndGap(double length, double gap) {minLength = length;maxGap = gap;}// 应用概率霍夫转换std::vector findLines(cv::Mat& binary) {lines.clear();cv::HoughLinesP(binary, lines, deltaRho, deltaTheta, minVote, minLength, maxGap);return lines;}// 绘制检测到的线段void drawDetectedLines(cv::Mat& image, cv::Scalar color=cv::Scalar(255, 255, 255)) {std::vector::const_iterator it2 = lines.begin();while (it2!=lines.end()) {cv::Point pt1((*it2)[0], (*it2)[1]);cv::Point pt2((*it2)[2], (*it2)[3]);cv::line(image, pt1, pt2, color);++it2;}}// 消除方向不等于方向输入矩阵中指定方向的线段std::vector removeLinesOfInconsistentOrientations(const cv::Mat& orientations,double percentage, double delta) {std::vector::iterator it = lines.begin();while (it!=lines.end()) {int x1 = (*it)[0];int y1 = (*it)[1];int x2 = (*it)[2];int y2 = (*it)[3];// 线段方向 +90 度以获得平行线double ori1 = atan2(static_cast(y1-y2), static_cast(x1-x2)) + PI/2;if (ori1>PI) ori1 = ori1 - 2*PI;double ori2 = atan2(static_cast(y2-y1), static_cast(x2-x1)) + PI/2;if (ori2>PI) ori2 = ori2 - 2*PI;cv::LineIterator lit(orientations, cv::Point(x1, y1), cv::Point(x2, y2));int i, count;for (i=0, count=0; ifloat ori = *(reinterpret_cast(*lit));if (std::min(fabs(ori-ori1), fabs(ori1-ori2))count++;}}double consistency = count / static_cast(i);if(consistency(*it)[0] = (*it)[1] = (*it)[2] = (*it)[3] = 0;}++it;}return lines;}
};#endif

头文件 (edgedetector.h) 完整代码如下:

#if !defined SOBELEDGES
#define SOBELEDGES
#define PI 3.1415926#include 
#include class EdgeDetector {private:// 原始图像cv::Mat img;// 16 位整数类型图像cv::Mat sobel;// Sobel 核大小int aperture;// Sobel 范数cv::Mat sobelMagnitude;// Sobel 方向cv::Mat sobelOrientation;public:EdgeDetector() : aperture(3) {}// 设定核大小void setAperture(int a) {aperture = a;}// 获取核大小int getAperture() const {return aperture;}// 计算 Sobelvoid computeSobel(const cv::Mat& image) {cv::Mat sobelX;cv::Mat sobelY;cv::Sobel(image, sobelX, CV_32F, 1, 0, aperture);cv::Sobel(image, sobelY, CV_32F, 0, 1, aperture);cv::cartToPolar(sobelX, sobelY, sobelMagnitude, sobelOrientation);}void computeSobel(const cv::Mat& image, cv::Mat& sobelX, cv::Mat& sobelY) {cv::Sobel(image, sobelX, CV_32F, 1, 0, aperture);cv::Sobel(image, sobelY, CV_32F, 0, 1, aperture);cv::cartToPolar(sobelX, sobelY, sobelMagnitude, sobelOrientation);}cv::Mat getMagnitude() {return sobelMagnitude;}cv::Mat getOrientation() {return sobelOrientation;}// 获取阈值二值图cv::Mat getBinaryMap(double threshold) {cv::Mat bin;cv::threshold(sobelMagnitude, bin, threshold, 255, cv::THRESH_BINARY_INV);return bin;}// 获取 Sobel 的二值图像cv::Mat getSobelImage() {cv::Mat bin;double minval, maxval;cv::minMaxLoc(sobelMagnitude, &minval, &maxval);sobelMagnitude.convertTo(bin, CV_8U, 255/maxval);return bin;}// 获取Sobel方向的CV_8U图像cv::Mat getSobelOrientationImage() {cv::Mat bin;sobelOrientation.convertTo(bin, CV_8U, 90/PI);return bin;}
};#endif

主文件 (contours.cpp) 完整代码如下所示:

#include 
#include 
#include 
#include 
#include 
#include "linefinder.h"
#include "edgedetector.h"#define PI 3.1415926int main() {// 读取输入图像cv::Mat image = cv::imread("road.jpg", 0);if (!image.data) return 0;cv::namedWindow("Original Image");cv::imshow("Original Image", image);// 计算 SobelEdgeDetector ed;ed.computeSobel(image);cv::namedWindow("Sobel (orientation)");cv::imshow("Sobel (orientation)", ed.getSobelOrientationImage());cv::imwrite("ori.png", ed.getSobelOrientationImage());// 低阈值 Sobelcv::namedWindow("Sobel (low threshold)");cv::imshow("Sobel (low threshold)", ed.getBinaryMap(125));// 高阈值 Sobelcv::namedWindow("Sobel (high threshold)");cv::imshow("Sobel (high threshold)", ed.getBinaryMap(350));// 应用 Canny 算法cv::Mat contours;cv::Canny(image,    // 灰度图像contours,   // 输出图像125,        // 低阈值350);       // 高阈值cv::namedWindow("Canny Contours");cv::imshow("Canny Contours", 255-contours);// 创建测试图像cv::Mat test(400, 400, CV_8U, cv::Scalar(255));cv::line(test,cv::Point(200,0),cv::Point(400,400),cv::Scalar(0));cv::line(test,cv::Point(0,100),cv::Point(400,400),cv::Scalar(0));cv::line(test,cv::Point(0,400),cv::Point(400,0),cv::Scalar(0));cv::line(test,cv::Point(400,0),cv::Point(0,400),cv::Scalar(0));cv::line(test,cv::Point(200,0),cv::Point(200,400),cv::Scalar(0));cv::line(test,cv::Point(0,200),cv::Point(400,200),cv::Scalar(0));cv::namedWindow("Test Image");cv::imshow("Test Image", test);cv::imwrite("test.png", test);// 霍夫变换std::vector lines;cv::HoughLines(contours, lines, 1, PI/180, 80);// 绘制检测结果cv::Mat result(contours.rows, contours.cols, CV_8U, cv::Scalar(255));image.copyTo(result);std::cout << "Lines detected: " << lines.size() << std::endl;std::vector::const_iterator it = lines.begin();while (it!=lines.end()) {float rho = (*it)[0];float theta = (*it)[1];if (theta < PI/4. || theta > 3.*PI/4.) {    // 竖线// 直线与图像第一行交点cv::Point pt1(rho/cos(theta), 0);// 直线与图像最后一行交点cv::Point pt2((rho-result.rows*sin(theta))/cos(theta), result.rows);cv::line(result, pt1, pt2, cv::Scalar(255), 1);} else {        // 横线// 直线与图像第一列交点cv::Point pt1(0, rho/sin(theta));// 直线与图像最后一列交点cv::Point pt2(result.cols, (rho-result.cols*cos(theta))/sin(theta));cv::line(result, pt1, pt2, cv::Scalar(255), 1);}std::cout << "line: (" << rho << "," << theta << ")" << std::endl;++it;}cv::namedWindow("Lines with Hough");cv::imshow("Lines with Hough", result);// 创建 LineFinder 实例LineFinder ld;// 设置概率霍夫变换ld.setLineLengthAndGap(100, 20);ld.setMinVote(60);// 直线检测std::vector li = ld.findLines(contours);ld.drawDetectedLines(image);cv::namedWindow("Lines with HoughP");cv::imshow("Lines with HoughP", image);std::vector::const_iterator it2 = li.begin();while (it2 != li.end()) {std::cout << "(" << (*it2)[0] << ", " << (*it2)[1] << ") - (" << (*it2)[2] << ", " << (*it2)[3] << ")" << std::endl;++it2;}// 消除不一致直线ld.removeLinesOfInconsistentOrientations(ed.getOrientation(), 0.4, 0.1);image = cv::imread("road.jpg", 0);ld.drawDetectedLines(image);cv::namedWindow("Detected Lines (2)");cv::imshow("Detected Lines (2)", image);// 创建霍夫累加器cv::Mat acc(200, 180, CV_8U, cv::Scalar(0));// 选择一个点int x=50, y=30;// 循环所有角for (int i=0; i<180; i++) {double theta = i*PI/180.;double rho = x*std::cos(theta)+y*std::sin(theta);int j = static_cast(rho+100.5);std::cout << i << ", " << j << std::endl;acc.at(j, i)++;}// 绘制坐标轴cv::line(acc, cv::Point(0, 0), cv::Point(0, acc.rows-1), 255);cv::line(acc, cv::Point(acc.cols-1, acc.rows-1), cv::Point(0, acc.rows-1), 255);cv::imwrite("hough1.png", 255-(acc*100));// 选择第二个点x=30, y=10;// 循环所有角for (int i=0; i<180; i++) {double theta = i*PI/180.;double rho = x*std::cos(theta)+y*std::sin(theta);int j = static_cast(rho+100.5);std::cout << i << ", " << j << std::endl;acc.at(j, i)++;}cv::namedWindow("Hough Accumulator");cv::imshow("Hough Accumulator", acc*100);cv::imwrite("hough2.png", 255-(acc*100));// 检测圆image = cv::imread("example.png", 0);cv::GaussianBlur(image, image, cv::Size(5, 5), 1.5);std::vector circles;cv::HoughCircles(image, circles, cv::HOUGH_GRADIENT,2,          // 累加器尺寸 (图像尺寸/2)20,         // 两个圆之间的最小距离200,        // 高阈值 Canny 60,         // 最小投票数15, 50);    // 能接受的最大最小圆半径std::cout << "Circles: " << circles.size() << std::endl;// 绘制圆圈image = cv::imread("example.png", 0);std::vector::const_iterator itc = circles.begin();while (itc!=circles.end()) {cv::circle(image,cv::Point((*itc)[0], (*itc)[1]),    // 圆心(*itc)[2],                          // 半径cv::Scalar(255),                    // 颜色2);                                 // 粗细++itc;}cv::namedWindow("Detected Circles");cv::imshow("Detected Circles", image);cv::waitKey();return 0;
}

小结

轮廓检测指在图像中,忽略背景和目标内部的纹理以及噪声干扰的影响实现目标轮廓提取的过程。轮廓检测是目标检测、形状分析、目标识别和目标跟踪等技术的重要基础。本节,我们介绍了如何使用轮廓检测算子 Canny 检测目标轮廓,同时讲解了霍夫变换及其变体概率霍夫变换,可以用于检测任何能够用参数方程表达的形状。

系列链接

OpenCV实战(1)——OpenCV与图像处理基础
OpenCV实战(2)——OpenCV核心数据结构
OpenCV实战(3)——图像感兴趣区域
OpenCV实战(4)——像素操作
OpenCV实战(5)——图像运算详解
OpenCV实战(6)——OpenCV策略设计模式
OpenCV实战(7)——OpenCV色彩空间转换
OpenCV实战(8)——直方图详解
OpenCV实战(9)——基于反向投影直方图检测图像内容
OpenCV实战(10)——积分图像详解
OpenCV实战(11)——形态学变换详解
OpenCV实战(12)——图像滤波详解
OpenCV实战(13)——高通滤波器及其应用

上一篇:Crontab命令

下一篇: VFD工作原理

相关内容

热门资讯

企业党建工作总结 企业党建工作总结  时间乘着年轮循序往前,一段时间的工作已经结束了,回顾坚强走过的这段时间,取得的成...
基层党建工作制度 基层党建工作制度  基层党建工作制度(精选14篇)  在现实社会中,制度使用的情况越来越多,制度具有...
工会经费的账务处理 工会经费的账务处理  企业会计准则中,长期股权投资与可供出售金融资产作长  金为两类资产,采用了不同...
高一下学期期末个人小结 高一下学期期末个人小结  一、总结的释义  1.总地归结。  2.对某一阶段的工作、学习或思想中的经...
党支部谈心谈话内容记录 党支部谈心谈话内容记录  党支部谈心谈话内容记录(精选8篇)  党组织领导班子成员之间、班子成员和党...
党性分析材料 党性分析材料  一、中国共产党的介绍  中国共产党,创建于1921年7月23日,1921年中国共产党...
小学校长优秀事迹材料 小学校长优秀事迹材料小学校长优秀事迹材料说起xx,熟悉的老师们都叫他“xx市最年轻校长”、“xx市最...
感动人物优秀事迹 感动人物优秀事迹6篇  在学习、工作乃至生活中,大家或多或少都会用到过事迹吧,事迹具有触发力大、感染...
告知函 告知函范文_____先生:我方三月十日致贵方函谅悉,故在函中曾强调我方第816号订单交货之重要,务必...
道教佛教儒教的三者区别 道教佛教儒教的三者区别人神好清,而心扰之。人心好静,而欲牵之。常能遣其欲,而心自静,澄其心,而神自清...
关于乡村医生主要事迹 关于乡村医生主要事迹  关于乡村医生主要事迹(精选34篇)  医生党员要更加努力提高自己的思想政治觉...
廖俊波同志先进事迹 廖俊波同志先进事迹  廖俊波同志生前是福建省南平市委常委、南平市人民政府副市长、党组成员,武夷新区党...
2022最新组织生活会个人发... 2022最新组织生活会个人发言材料范文  一、组织生活会的基本内容  (1)学习党的基本理论,不断地...
作风建设方面自我剖析材料 作风建设方面自我剖析材料  作风建设方面自我剖析材料(精选10篇)  作风建设,是一个政党自我进化、...
教师个人事迹材料 教师个人事迹材料(通用27篇)  在生活、工作和学习中,要用到事迹的情况还是蛮多的,事迹以先进对象的...
圣诞节短剧:《天路历程》 圣诞节短剧:《天路历程》天 路 历 程天路客在十字架下脱落罪的重担后,走上了天路,在这天路历程中,他...
网格个人先进事迹材料 网格个人先进事迹材料范文(精选3篇)  在日复一日的学习、工作或生活中,要用到事迹材料的地方还是很多...
入职证明模板 入职证明模板  一、什么是工作证明  工作证明是指我国公民在日常生产生活经营活动中的一种证明文件,一...
马来西亚进军 马来西亚进军最最最详细的马来西亚10日自助游攻略!!!-总述1、 我们回来啦!总述2、第一天-上海杭...
党的100周年心得体会300... 党的100周年心得体会300字  一、中国共产党建党日  中国共产党于1921年7月23日成立后,在...