【Qt&OpenCV 检测图像中的线/圆/轮廓 HoughLinesP/HoughCircles/findContours&drawContours】

简介: 【Qt&OpenCV 检测图像中的线/圆/轮廓 HoughLinesP/HoughCircles/findContours&drawContours】

前言

越来越多的开发人员选择基于开源的Qt框架与OpenCV来实现界面和算法,其原因不单单是无版权问题,更多是两个社区的发展蓬勃,可用来学习的资料与例程特别丰富。以下是关于利用Qt构建GUI并使用OpenCV中的HoughLinesP/HoughCircles/findContours&drawContours函数进行图像检测。

软件版本:Qt-5.12.0/OpenCV-4.5.3

平台:Windows10/11–64

一、函数介绍

1、HoughLinesP

函数原型

cv::HoughLinesP(InputArray image, OutputArray lines, double rho, double theta, int threshold, double minLineLength=0, double maxLineGap=0 )


参数解释:

image:输入图像,即源图像,需为8位的单通道二进制图像,可以将任意的源图载入进来后由函数修改成此格式后,再填在这里;

Iines:经过调用HoughLinesP函数后后存储了检测到的线条的输出矢量,每一条线由具有四个元素的矢量(x_1,y_1, x_2, y_2) 表示,其中,(x_1, y_1)和(x_2, y_2) 是是每个检测到的线段的结束点;

rho:以像素为单位的距离精度。另一种形容方式是直线搜索时的进步尺寸的单位半径;

theta:以弧度为单位的角度精度。另一种形容方式是直线搜索时的进步尺寸的单位角度;

threshold:累加平面的阈值参数,即识别某部分为图中的一条直线时它在累加平面中必须达到的值。大于阈值threshold的线段才可以被检测通过并返回到结果中;

minLineLength:有默认值0,表示最低线段的长度,比这个设定参数短的线段就不能被显现出来;

maxLineGap:有默认值0,允许将同一行点与点之间连接起来的最大的距离;

2、HoughCircles

函数原型


cv::HoughCircles(InputArray image,OutputArray circles, int method, double dp, double minDist, double param1=100,double param2=100, int minRadius=0, int maxRadius=0 )


参数解释:

image输入图像,即源图像,需为8位的灰度单通道图像;

circles:经过调用HoughCircles函数后此参数存储了检测到的圆的输出矢量,每个矢量由包含了3个元素的浮点矢量(x, y, radius)表示;

method:即使用的检测方法,目前OpenCV中就霍夫梯度法一种可以使用,它的标识符为CV_HOUGH_GRADIENT,在此参数处填这个标识符即可;

dp:用来检测圆心的累加器图像的分辨率于输入图像之比的倒数,且此参数允许创建一个比输入图像分辨率低的累加器。上述文字不好理解的话,来看例子吧。例如,如果dp= 1时,累加器和输入图像具有相同的分辨率。如果dp=2,累加器便有输入图像一半那么大的宽度和高度;

minDist:为霍夫变换检测到的圆的圆心之间的最小距离,即让我们的算法能明显区分的两个不同圆之间的最小距离。这个参数如果太小的话,多个相邻的圆可能被错误地检测成了一个重合的圆。反之,这个参数设置太大的话,某些圆就不能被检测出来了;

param1:有默认值100。它是第三个参数method设置的检测方法的对应的参数。对当前唯一的方法霍夫梯度法CV_HOUGH_GRADIENT,它表示传递给canny边缘检测算子的高阈值,而低阈值为高阈值的一半;

param2:也有默认值100。它是第三个参数method设置的检测方法的对应的参数。对当前唯一的方法霍夫梯度法CV_HOUGH_GRADIENT,它表示在检测阶段圆心的累加器阈值。它越小的话,就可以检测到更多根本不存在的圆,而它越大的话,能通过检测的圆就更加接近完美的圆形了;

minRadius:有默认值0,表示圆半径的最小值;

maxRadius:也有默认值0,表示圆半径的最大值;

3、findContours

函数原型:

cv::findContours(InputOutputArray image, OutputArrayOfArrays contours, OutputArray hierarchy,

int mode, int method, Point offset = Point());


参数解释:

image:单通道图像矩阵,可以是灰度图,但更常用的是二值图像,一般是经过Canny、拉普拉斯等边缘检测算子处理过的二值图像;

contours:contours定义为“vector contours”,是一个双重向量(向量内每个元素保存了一组由连续的Point构成的点的集合的向量),每一组点集就是一个轮廓,有多少轮廓,contours就有多少元素;

hierarchy:hierarchy定义为“vector hierarchy”,Vec4i的定义:typedef Vec<int, 4> Vec4i;(向量内每个元素都包含了4个int型变量),所以从定义上看,hierarchy是一个向量,向量内每个元素都是一个包含4个int型的数组。向量hierarchy内的元素和轮廓向量contours内的元素是一一对应的,向量的容量相同。hierarchy内每个元素的4个int型变量是hierarchy[i][0] ~ hierarchy[i][3],分别表示当前轮廓 i 的后一个轮廓、前一个轮廓、父轮廓和内嵌轮廓的编号索引。如果当前轮廓没有对应的后一个轮廓、前一个轮廓、父轮廓和内嵌轮廓,则相应的hierarchy[i][*]被置为-1。

mode:定义轮廓的检索模式,取值如下:


CV_RETR_EXTERNAL:只检测最外围轮廓,包含在外围轮廓内的内围轮廓被忽略;

CV_RETR_LIST:检测所有的轮廓,包括内围、外围轮廓,但是检测到的轮廓不建立等级关系,彼此之间独立,没有等级关系,这就意味着这个检索模式下不存在父轮廓或内嵌轮廓,所以hierarchy向量内所有元素的第3、第4个分量都会被置为-1,具体下文会讲到;

CV_RETR_CCOMP: 检测所有的轮廓,但所有轮廓只建立两个等级关系,外围为顶层,若外围内的内围轮廓还包含了其他的轮廓信息,则内围内的所有轮廓均归属于顶层;

CV_RETR_TREE: 检测所有轮廓,所有轮廓建立一个等级树结构。外层轮廓包含内层轮廓,内层轮廓还可以继续包含内嵌轮廓。


method:定义轮廓的近似方法,取值如下:


CV_CHAIN_APPROX_NONE:保存物体边界上所有连续的轮廓点到contours向量内;

CV_CHAIN_APPROX_SIMPLE:仅保存轮廓的拐点信息,把所有轮廓拐点处的点保存入contours向量内,拐点与拐点之间直线段上的信息点不予保留;

CV_CHAIN_APPROX_TC89_L1:使用teh-Chinl chain 近似算法;

CV_CHAIN_APPROX_TC89_KCOS:使用teh-Chinl chain 近似算法。


offset:Point偏移量,所有的轮廓信息相对于原始图像对应点的偏移量,相当于在每一个检测出的轮廓点上加上该偏移量,并且Point还可以是负值!

4、 drawContours

函数原型

cv::drawContours(InputOutputArray image, InputArrayOfArrays contours, int contourIdx, const Scalar & color, int thickness = 1, int lineType = LINE_8, InputArray hierarchy = noArray(), int maxLevel = INT_MAX, Point offset = Point())


参数解释:

image:目标图像

contours:输入的所有轮廓(每个轮廓以点集的方式存储)

contoursIdx:指定绘制轮廓的下标(若为负数,则绘制所有轮廓)

color:绘制轮廓的颜色

thickness:绘制轮廓的线的宽度(若为负数,则填充轮廓内部)

lineType:绘制轮廓的线型(4连通、8连通或者反锯齿)

hierarchy:关于层级的可选信息,仅用于当你想要绘制部分轮廓的时候

maxLevel:绘制轮廓的最大层级,若为0,则仅仅绘制指定的轮廓;若为1,则绘制该轮廓及其内嵌轮廓,若为2,则绘制该轮廓、其内嵌轮廓以及内嵌轮廓的内嵌轮廓,依次类推。该参数只有在有层级信息输入时才被考虑。

offset:可选的轮廓偏移参数,所有的轮廓将会进行指定的偏移


当thickness = FILLED,即使没有提供层级信息也可以正确处理带孔洞的连通域情况(分析轮廓时采用奇偶规则),但如果是单独检索的轮廓合并则可能会出现错误的情况,该情况下则需要分开处理。

二、演示

1、GUI

如上图创建Operator的QComboBox控件进行函数选择,Action的功能按钮QPushButton,对当前窗口进行检测,并输出状态信息。

2、代码实现

HoughLinesP/HoughCircles/findContours&drawContours实现代码:

geometryBtn的clicked()槽函数

void MainWindow::on_geometryBtn_clicked()
{
    std::size_t numView = ui->tabWidget->currentIndex() % 3;
    if (dispMat[numView]->empty())
    {
        outputInfo(2, tr("Please make sure the Mat exist!"));
        return;
    }

    if (dispMat[numView]->channels() == 3)
    {
        cv::cvtColor(*dispMat[numView], *dispMat[numView], cv::COLOR_RGB2GRAY);
    }

    tmpMat->zeros(dispMat[numView]->size(), \
                  dispMat[numView]->type());

    cv::GaussianBlur(*dispMat[numView], *dispMat[numView], cv::Size(3, 3), \
                     0, 0, cv::BORDER_DEFAULT);
    int operatorType = ui->geometryCombo->currentIndex(); // 0: line, 1: circle, 2: contours

    switch (operatorType)
    {
        case 0:
        {

            int cannyThresh = ui->cannyThreshSlider->value();
            int lineThresh = ui->lineThreshSlider->value();
            std::vector<cv::Vec4i> lines;
            cv::Canny(*dispMat[numView], *tmpMat, cannyThresh, \
                      cannyThresh * 2, 3);
            cv::HoughLinesP(*tmpMat, lines, 1, CV_PI/180, lineThresh, 50, 10);

            for (size_t i = 0; i < lines.size(); i++)
            {
                cv::Vec4i l = lines[i];
                cv::line(*tmpMat, cv::Point(l[0], l[1]), cv::Point(l[2], l[3]), \
                        cv::Scalar(0, 0, 255), 1, 1, 1);

            }
            outputInfo(1, tr("Lines done."));
            break;
        }

        case 1:
        {
            int circleThresh = ui->circleThreshSlider->value();
            std::vector<cv::Vec3f> circles;
            double startTime = static_cast<double>(cv::getTickCount());

            cv::HoughCircles(*dispMat[numView], circles, cv::HOUGH_GRADIENT, \
                             1, dispMat[numView]->rows / 16, \
                             circleThresh, circleThresh/2, \
                             0, 0);
            double timeCost = (static_cast<double>(cv::getTickCount()) - \
                               startTime) / cv::getTickFrequency();

            QString costTime = "Cost time: " + QString::number(timeCost);
            outputInfo(1, costTime);

            for (size_t i = 0; i < circles.size(); i++)
            {
                cv::Point center(cvRound(circles[i][0]), \
                                 cvRound(circles[i][1]));
                int radius = cvRound(circles[i][2]);
                cv::circle(*tmpMat, center, 3, \
                           cv::Scalar(0, 255, 0), -1, 8, 0);
                cv::circle(*tmpMat, center, radius, \
                           cv::Scalar(0, 0, 255), 3, 8, 0);
            }
            outputInfo(1, tr("Circles done."));
            break;
        }

        case 2:
        {
            cv::RNG rng(12345);
            int cannyThresh = ui->cannyThreshSlider->value();
            cv::Canny(*dispMat[numView], *tmpMat, cannyThresh, \
                      cannyThresh * 2, 3);
            std::vector<std::vector<cv::Point>> contours;
            std::vector<cv::Vec4i> hierarchy;
            cv::findContours(*tmpMat, contours, hierarchy, \
                             cv::RETR_TREE, \
                             cv::CHAIN_APPROX_SIMPLE, \
                             cv::Point(0, 0));
            std::vector<std::vector<cv::Point>> contoursPoly(contours.size());
            std::vector<cv::Rect> boundRect(contours.size());
            std::vector<cv::Point2f> center(contours.size());
            std::vector<float> radius(contours.size());

            for (size_t i = 0; i < contours.size(); i++)
            {
                cv::approxPolyDP(cv::Mat(contours[i]), contoursPoly[i], 3, true);
                boundRect[i] = cv::boundingRect(cv::Mat(contoursPoly[i]));
                minEnclosingCircle(contoursPoly[i], center[i], radius[i]);

            }

            for (size_t i = 0; i < contours.size(); i++)
            {
                cv::Scalar color = cv::Scalar(rng.uniform(0, 255), \
                                              rng.uniform(0, 255), \
                                              rng.uniform(0, 255));
                cv::drawContours(*tmpMat, contoursPoly, static_cast<int>(i), \
                                 color, 1, 8, \
                                 std::vector<cv::Vec4i>(), 0, \
                                 cv::Point());

                cv::rectangle(*tmpMat, boundRect[i].tl(), \
                              boundRect[i].br(), \
                              color, 2, 8, 0);
                cv::circle(*tmpMat, center[i], static_cast<int>(radius[i]), \
                                                                color, 2, 8, 0);
            }
            outputInfo(1, tr("Contours done."));

            break;
        }

    }

    if (ui->geometryChkBox->isChecked())
    {
        *dispMat[numView] = tmpMat->clone();
        cvtMatPixmap(dispMat, dispPixmap, numView);
    }
    else
    {
        if (tmpMat->channels() == 3)
        {
            QImage tmpImage = QImage(tmpMat->data, tmpMat->cols,tmpMat->rows, \
                         static_cast<int>(tmpMat->step), \
                         QImage::Format_RGB888);
            dispPixmap[numView]->setPixmap(QPixmap::fromImage(tmpImage.rgbSwapped()));
        }
        else
        {
            QImage tmpImage = QImage(tmpMat->data, tmpMat->cols,tmpMat->rows, \
                         static_cast<int>(tmpMat->step), \
                         QImage::Format_Grayscale8);
            dispPixmap[numView]->setPixmap(QPixmap::fromImage(tmpImage.rgbSwapped()));
        }

    }
}
void MainWindow::on_geometryBtn_clicked()
{
    std::size_t numView = ui->tabWidget->currentIndex() % 3;
    if (dispMat[numView]->empty())
    {
        outputInfo(2, tr("Please make sure the Mat exist!"));
        return;
    }

    if (dispMat[numView]->channels() == 3)
    {
        cv::cvtColor(*dispMat[numView], *dispMat[numView], cv::COLOR_RGB2GRAY);
    }

    tmpMat->zeros(dispMat[numView]->size(), \
                  dispMat[numView]->type());

    cv::GaussianBlur(*dispMat[numView], *dispMat[numView], cv::Size(3, 3), \
                     0, 0, cv::BORDER_DEFAULT);
    int operatorType = ui->geometryCombo->currentIndex(); // 0: line, 1: circle, 2: contours

    switch (operatorType)
    {
        case 0:
        {

            int cannyThresh = ui->cannyThreshSlider->value();
            int lineThresh = ui->lineThreshSlider->value();
            std::vector<cv::Vec4i> lines;
            cv::Canny(*dispMat[numView], *tmpMat, cannyThresh, \
                      cannyThresh * 2, 3);
            cv::HoughLinesP(*tmpMat, lines, 1, CV_PI/180, lineThresh, 50, 10);

            for (size_t i = 0; i < lines.size(); i++)
            {
                cv::Vec4i l = lines[i];
                cv::line(*tmpMat, cv::Point(l[0], l[1]), cv::Point(l[2], l[3]), \
                        cv::Scalar(0, 0, 255), 1, 1, 1);

            }
            outputInfo(1, tr("Lines done."));
            break;
        }

        case 1:
        {
            int circleThresh = ui->circleThreshSlider->value();
            std::vector<cv::Vec3f> circles;
            double startTime = static_cast<double>(cv::getTickCount());

            cv::HoughCircles(*dispMat[numView], circles, cv::HOUGH_GRADIENT, \
                             1, dispMat[numView]->rows / 16, \
                             circleThresh, circleThresh/2, \
                             0, 0);
            double timeCost = (static_cast<double>(cv::getTickCount()) - \
                               startTime) / cv::getTickFrequency();

            QString costTime = "Cost time: " + QString::number(timeCost);
            outputInfo(1, costTime);

            for (size_t i = 0; i < circles.size(); i++)
            {
                cv::Point center(cvRound(circles[i][0]), \
                                 cvRound(circles[i][1]));
                int radius = cvRound(circles[i][2]);
                cv::circle(*tmpMat, center, 3, \
                           cv::Scalar(0, 255, 0), -1, 8, 0);
                cv::circle(*tmpMat, center, radius, \
                           cv::Scalar(0, 0, 255), 3, 8, 0);
            }
            outputInfo(1, tr("Circles done."));
            break;
        }

        case 2:
        {
            cv::RNG rng(12345);
            int cannyThresh = ui->cannyThreshSlider->value();
            cv::Canny(*dispMat[numView], *tmpMat, cannyThresh, \
                      cannyThresh * 2, 3);
            std::vector<std::vector<cv::Point>> contours;
            std::vector<cv::Vec4i> hierarchy;
            cv::findContours(*tmpMat, contours, hierarchy, \
                             cv::RETR_TREE, \
                             cv::CHAIN_APPROX_SIMPLE, \
                             cv::Point(0, 0));
            std::vector<std::vector<cv::Point>> contoursPoly(contours.size());
            std::vector<cv::Rect> boundRect(contours.size());
            std::vector<cv::Point2f> center(contours.size());
            std::vector<float> radius(contours.size());

            for (size_t i = 0; i < contours.size(); i++)
            {
                cv::approxPolyDP(cv::Mat(contours[i]), contoursPoly[i], 3, true);
                boundRect[i] = cv::boundingRect(cv::Mat(contoursPoly[i]));
                minEnclosingCircle(contoursPoly[i], center[i], radius[i]);

            }

            for (size_t i = 0; i < contours.size(); i++)
            {
                cv::Scalar color = cv::Scalar(rng.uniform(0, 255), \
                                              rng.uniform(0, 255), \
                                              rng.uniform(0, 255));
                cv::drawContours(*tmpMat, contoursPoly, static_cast<int>(i), \
                                 color, 1, 8, \
                                 std::vector<cv::Vec4i>(), 0, \
                                 cv::Point());

                cv::rectangle(*tmpMat, boundRect[i].tl(), \
                              boundRect[i].br(), \
                              color, 2, 8, 0);
                cv::circle(*tmpMat, center[i], static_cast<int>(radius[i]), \
                                                                color, 2, 8, 0);
            }
            outputInfo(1, tr("Contours done."));

            break;
        }

    }

    if (ui->geometryChkBox->isChecked())
    {
        *dispMat[numView] = tmpMat->clone();
        cvtMatPixmap(dispMat, dispPixmap, numView);
    }
    else
    {
        if (tmpMat->channels() == 3)
        {
            QImage tmpImage = QImage(tmpMat->data, tmpMat->cols,tmpMat->rows, \
                         static_cast<int>(tmpMat->step), \
                         QImage::Format_RGB888);
            dispPixmap[numView]->setPixmap(QPixmap::fromImage(tmpImage.rgbSwapped()));
        }
        else
        {
            QImage tmpImage = QImage(tmpMat->data, tmpMat->cols,tmpMat->rows, \
                         static_cast<int>(tmpMat->step), \
                         QImage::Format_Grayscale8);
            dispPixmap[numView]->setPixmap(QPixmap::fromImage(tmpImage.rgbSwapped()));
        }

    }
}

总结

以上是关于利用Qt进行GUI构建并使用OpenCV中的 HoughLinesP/HoughCircles/findContours&drawContours函数进行图像检测。

 

目录
相关文章
|
2月前
|
算法 计算机视觉
基于qt的opencv实时图像处理框架FastCvLearn实战
本文介绍了一个基于Qt的OpenCV实时图像处理框架FastCvLearn,通过手撕代码的方式详细讲解了如何实现实时人脸马赛克等功能,并提供了结果展示和基础知识回顾。
基于qt的opencv实时图像处理框架FastCvLearn实战
|
2月前
|
文字识别 计算机视觉 开发者
基于QT的OCR和opencv融合框架FastOCRLearn实战
本文介绍了在Qt环境下结合OpenCV库构建OCR识别系统的实战方法,通过FastOCRLearn项目,读者可以学习Tesseract OCR的编译配置和在Windows平台下的实践步骤,文章提供了技术资源链接,帮助开发者理解并实现OCR技术。
124 9
基于QT的OCR和opencv融合框架FastOCRLearn实战
|
1月前
|
计算机视觉
Opencv学习笔记(三):图像二值化函数cv2.threshold函数详解
这篇文章详细介绍了OpenCV库中的图像二值化函数`cv2.threshold`,包括二值化的概念、常见的阈值类型、函数的参数说明以及通过代码实例展示了如何应用该函数进行图像二值化处理,并展示了运行结果。
291 0
Opencv学习笔记(三):图像二值化函数cv2.threshold函数详解
|
2月前
|
算法 计算机视觉
opencv图像形态学
图像形态学是一种基于数学形态学的图像处理技术,它主要用于分析和修改图像的形状和结构。
47 4
|
2月前
|
存储 计算机视觉
Opencv的基本操作(一)图像的读取显示存储及几何图形的绘制
本文介绍了使用OpenCV进行图像读取、显示和存储的基本操作,以及如何绘制直线、圆形、矩形和文本等几何图形的方法。
Opencv的基本操作(一)图像的读取显示存储及几何图形的绘制
|
2月前
|
计算机视觉
基于QT的opencv插件框架qtCvFrameLearn实战
这篇文章详细介绍了如何基于Qt框架开发一个名为qtCvFrameLearn的OpenCV插件,包括项目配置、插件加载、Qt与OpenCV图像转换,以及通过各个插件学习OpenCV函数的使用,如仿射变换、卡通效果、腐蚀、旋转和锐化等。
43 10
|
2月前
|
机器学习/深度学习 Java 计算机视觉
opencv4.5.5+qt5.15.2+vtk9.1+mingw81_64编译记录
本文记录了使用mingw81_64编译OpenCV 4.5.5、Qt 5.15.2、VTK 9.1的详细过程,包括编译结果截图、编译步骤、遇到的问题及其解决方案,以及相关参考链接。文中还提到了如何编译boost源码为静态库,并提供了测试代码示例。
opencv4.5.5+qt5.15.2+vtk9.1+mingw81_64编译记录
WK
|
3月前
|
编解码 计算机视觉 Python
如何在OpenCV中进行图像转换
在OpenCV中,图像转换涉及颜色空间变换、大小调整及类型转换等操作。常用函数如`cvtColor`可实现BGR到RGB、灰度图或HSV的转换;`resize`则用于调整图像分辨率。此外,通过`astype`或`convertScaleAbs`可改变图像数据类型。对于复杂的几何变换,如仿射或透视变换,则可利用`warpAffine`和`warpPerspective`函数实现。这些技术为图像处理提供了强大的工具。
WK
105 1
|
3月前
|
算法 计算机视觉 Python
python利用opencv进行相机标定获取参数,并根据畸变参数修正图像附有全部代码(流畅无痛版)
该文章详细介绍了使用Python和OpenCV进行相机标定以获取畸变参数,并提供了修正图像畸变的全部代码,包括生成棋盘图、拍摄标定图像、标定过程和畸变矫正等步骤。
python利用opencv进行相机标定获取参数,并根据畸变参数修正图像附有全部代码(流畅无痛版)
|
5月前
|
算法 计算机视觉
【Qt&OpenCV 图像的感兴趣区域ROI】
【Qt&OpenCV 图像的感兴趣区域ROI】
160 1