【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函数进行图像检测。

 

目录
相关文章
|
4天前
|
机器学习/深度学习 XML 计算机视觉
OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉和机器学习库,它提供了大量的函数和工具,用于处理图像和视频数据。
OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉和机器学习库,它提供了大量的函数和工具,用于处理图像和视频数据。
|
28天前
|
算法 计算机视觉
【Qt&OpenCV 图像的感兴趣区域ROI】
【Qt&OpenCV 图像的感兴趣区域ROI】
24 1
|
28天前
|
运维 算法 计算机视觉
【Qt&OpenCV 图像的模板匹配 matchTemplate/minMaxLoc】
【Qt&OpenCV 图像的模板匹配 matchTemplate/minMaxLoc】
20 1
|
12天前
|
机器学习/深度学习 人工智能 计算机视觉
好的资源-----打卡机+Arm+Qt+OpenCV嵌入式项目-基于人脸识别的考勤系统-----B站神经网络与深度学习,商城
好的资源-----打卡机+Arm+Qt+OpenCV嵌入式项目-基于人脸识别的考勤系统-----B站神经网络与深度学习,商城
|
1月前
|
计算机视觉
OpenCV中图像算术操作与逻辑操作
OpenCV中图像算术操作与逻辑操作
37 1
|
1月前
|
计算机视觉
OpenCV图像二值化
OpenCV图像二值化
|
1月前
|
存储 Cloud Native Linux
OpenCV图像翻转和旋转
OpenCV图像翻转和旋转
|
1月前
|
存储 Cloud Native Linux
OpenCV鼠标操作(画红色方框截取图像)
OpenCV鼠标操作(画红色方框截取图像)
|
1月前
|
计算机视觉
OpencV图像几何形状绘制
OpencV图像几何形状绘制
|
1月前
|
计算机视觉
OpenCV图像像素值统计
OpenCV图像像素值统计