[QT5&OpenCV] 边缘检测、轮廓提取及轮廓跟踪

简介: [QT5&OpenCV] 边缘检测、轮廓提取及轮廓跟踪

一、读取图像

  读取图像见QT+opencv学习笔记(1)——图像点运算,这里不再赘述。

二、边缘检测

  边缘是指图像局部强度变化最显著的部分。边缘主要存在与目标与目标、目标与背景、区域与区域之间。图像强度的不连续性可分为:阶跃不连续,即图像强度在不连续处的两边的像素灰度值有显著的差异;线条不连续,即图像强度从一个值变化到另一个值,保持一较小行程后又回到原来的值。

   边缘检测算子检查每个像素的邻域并对灰度变换率进行量化,也包括方向的确定。大多数使用基于方向倒数掩模求卷积的方法。

   下面介绍几种常用的边缘检测算子。

Canny算子:

  Canny算子运用比较广泛。是在Sobel算子的基础上改进的。

  Canny算子的步骤是:

  1.先进行滤波降噪。

  2.计算梯度幅值和方向(进行Sobel算子计算)。

  3.非极大值抑制。

  4.滞后阈值。

  Canny边缘检测可通过Canny()函数来实现。Canny()函数的定义如下:

void Canny(InputArray image, OutputArray edges, double threshold1, double threshold2, int apertureSize, bool L2gradient )

  名字:Canny边缘检测算子

  描述:用于检测图像边缘轮廓

  参数:

  InputArray image :8位单通道输入图像

  OutputArray edges:输出图像,和输入图像的尺寸类型一致

  double threshold1:滞后阈值低阈值(用于边缘连接)

  double threshold2:滞后阈值高阈值(控制边缘初始段)

  int apertureSize:表示Sobel算子孔径大小,默认为3

  bool L2gradient:计算图像梯度幅值的标识,默认false

   Canny边缘检测主要代码如下:

Canny(grayImg, edgeImg, 30, 80);

   Canny边缘检测处理结果如下:

 

Sobel算子

   Sobel算子是一个主要用于边缘检测的离散微分算子,它结合了高斯平滑和微分求导,用于计算图像灰度函数的近似梯度。

   Sobel算子检测方法对灰度渐变和噪声较多的图像处理效果较好,对边缘定位不是很准确,图像的边缘不止一个像素。

   Sobel边缘检测可通过Sobel()函数来实现。Sobel()函数的定义如下:

void Sobel(InputArray src, OutputArray dst, int ddepth, int xorder, int yorder, int ksize=3, double scale=1, double delta=0, intborderType=BORDERDEFAULT)

  名字:Sobel边缘检测算子

  描述:用于检测图像边缘轮廓

  参数:

  InputArray src :输入图像

  OutputArray dst:输出图像,和输入图像的尺寸类型一致

  int ddepth:输出图像的深度

  int xorder:x方向上的差分阶数

  int yorder:y方向上的差分阶数

  int ksize=3:Sobel核的大小,取值1,3,5,7

  double scale=1:计算倒数时的缩放因子

  double delta=0:可选delta值

  intborderType=BORDERDEFAULT:边界模式,一般默认即可

   Sobel边缘检测主要代码如下:

//Sobel边缘检测
Mat x_edgeImg, y_edgeImg;
Mat abs_x_edgeImg, abs_y_edgeImg;
/*先对x方向进行边缘检测 */
//因为Sobel求出来的结果有正负,8位无符号表示不全,故用16位有符号表示
Sobel(grayImg,x_edgeImg, CV_16S, 1, 0, 3, 1, 1, BORDER_DEFAULT);
convertScaleAbs(x_edgeImg, abs_x_edgeImg);//将16位有符号转化为8位无符号
/*再对y方向进行边缘检测**/
Sobel(grayImg, y_edgeImg, CV_16S, 0, 1, 3, 1, 1, BORDER_DEFAULT);
convertScaleAbs(y_edgeImg, abs_y_edgeImg);
addWeighted(abs_x_edgeImg, 0.5, abs_y_edgeImg, 0.5, 0, edgeImg);

  Sobel边缘检测处理结果如下:


Laplacian算子

   Laplacian算子边缘检测是通过二阶倒数,二阶倒数比一阶倒数的好处是在与受到周围的干扰小,其不具有方向性,操作容易,且对于很多方向的图像处理好。

   Laplacian算子对噪声比较敏感,所以很少用该算子检测边缘,而是用来判断边缘像素视为与图像的明区还是暗区。

   Laplacian边缘检测可通过Laplacian()函数来实现。Laplacian()函数的定义如下:

void Laplacian(InputArray src, OutputArray dst, int ddepth, int ksize=1, double scale, double delta=0, int borderType=BORDER_DEFAULT )

  名字: Laplacian边缘检测算子

  描述:用于检测图像边缘轮廓

  参数:

  InputArray src :输入图像

  OutputArray dst:输出图像,和输入图像的尺寸类型一致

  int ddepth:输出图像的深度

  int ksize=1:用于计算二阶导数的滤波器孔径大小,须为正奇数,默认值为1

  double scale:可选比例因子,默认值为1

  double delta=0:可选delta值

  int borderType=BORDER_DEFAULT :边界模式,一般默认即可

  Laplacian边缘检测主要代码如下:

//Laplacian边缘检测
Mat lapImg;
Laplacian(grayImg, lapImg, CV_16S, 5, 1, 0, BORDER_DEFAULT);
convertScaleAbs(lapImg, edgeImg);

   Laplacian边缘检测处理结果如下:


三、轮廓提取

  轮廓的提取,边缘检测就可以做到,不过得到的轮廓比较粗糙。

  图像轮廓的提取先对图像二值化,再通过findContours()函数提取轮廓,最后通过drawContours()函数将轮廓绘制出来。在将轮廓提取的结果使用imwrite函数保存到本地时,总是写不了,查了半天没找出问题,刚开始文件名为con.bmp,最后把文件名改成cont.bmp就好了,玄学。。。

  1、轮廓边缘关键点可通过findContours()函数来得到。findContours()函数的定义如下:

声明1:

findContours( InputOutputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method, Point offset=Point());

声明2:

findContours( InputOutputArray image, OutputArrayOfArrays contours, int mode, int method, Point offset=Point());

名字:轮廓查找函数

描述:用于查找边缘轮廓坐标点

参数:

第一个参数:image,8位单通道图像矩阵,可以是灰度图,但更常用的是二值图像,可以使从canny()得到的图像,也可以是threshhold()函数得到的图像。’

第二个参数:contours,定义为“vector contours”,是一个向量,并且是一个双重向量,向量内每个元素保存了一组由连续的Point点构成的点的集合的向量,每一组Point点集就是一个轮廓。

第三个参数:hierarchy,表示层数,定义为“vector hierarchy”,这一输出将是一个数组(通常仍是标准模板库向量),每条轮廓对应一个数组中一个值,数组中的每个值都是一个四元数组。

第四个参数:int型的mode,定义轮廓的检索模式:

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

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

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

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

第五个参数:int型的method,定义轮廓的近似方法:

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

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

drawContours()函数定义如下:

   2、轮廓边缘的绘制可通过drawContours()函数来实现。drawContours()函数的定义如下:

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

名字:轮廓查找函数

描述:用于查找边缘轮廓坐标点

参数:

第一个参数:image,表示目标图像,

第二个参数:contours,表示输入的轮廓组,每一组轮廓由点vector构成,

第三个参数:contourIdx,指明画第几个轮廓,如果该参数为负值(通常设为-1),则画全部轮廓,如果是一个正数,则对应的轮廓被绘制。

第四个参数;color,为轮廓的颜色,

第五个参数;thickness,为轮廓的线宽,如果为负值或CV_FILLED表示填充轮廓内部,

第六个参数;lineType,为线型,

第七个参数;为轮廓结构信息,

第八个参数;为maxLevel

 

3、轮廓提取主要代码如下:

Mat contImg = Mat ::zeros(grayImg.size(),CV_8UC3);//定义三通道轮廓提取图像
Mat binImg;
threshold(grayImg, binImg, 127, 255, THRESH_OTSU);//大津法进行图像二值化
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
//查找轮廓
findContours(binImg, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_NONE);
//绘制查找到的轮廓
drawContours(contImg, contours, -1, Scalar(0,255,0));

 

4、轮廓提取处理结果如下:

四、轮廓跟踪

   通常在进行边缘检测之后,需要通过边缘跟踪来将离散的边缘串接起来,常使用的方法边缘跟踪法。边缘跟踪又分为八邻域和四邻域两种。

  实现步骤:

  1、灰度化并进行Canny边缘检测;

  2、按照预先设定的跟踪方向(顺时针)进行边缘跟踪;

  3、每次跟踪的终止条件为:8邻域都不存在轮廓。

主要代码如下:

Mat edgeImg,trackImg; 
//Canny边缘检测
Canny(grayImg, edgeImg, 50, 100);
vector<Point> edge_t;
vector<vector<Point>> edges;
//边缘跟踪
EdgeTracking(edgeImg,edge_t,edges,trackImg);
void Dialog::EdgeTracking(Mat& Edge,vector<Point>& edge_t,vector<vector<Point>>& edges,Mat& trace_edge_color)
{
    // 8 neighbors
    const Point directions[8] = { { 0, 1 }, {1,1}, { 1, 0 }, { 1, -1 }, { 0, -1 },  { -1, -1 }, { -1, 0 },{ -1, 1 } };
    int i, j, counts = 0, curr_d = 0;
    for (i = 1; i < Edge.rows - 1; i++)
        for (j = 1; j < Edge.cols - 1; j++)
        {
            // 起始点及当前点
            //Point s_pt = Point(i, j);
            Point b_pt = Point(i, j);
            Point c_pt = Point(i, j);
            // 如果当前点为前景点
            if (255 == Edge.at<uchar>(c_pt.x, c_pt.y))
            {
                edge_t.clear();
                bool tra_flag = false;
                // 存入
                edge_t.push_back(c_pt);
                Edge.at<uchar>(c_pt.x, c_pt.y) = 0;    // 用过的点直接给设置为0
                // 进行跟踪
                while (!tra_flag)
                {
                    // 循环八次
                    for (counts = 0; counts < 8; counts++)
                    {
                        // 防止索引出界
                        if (curr_d >= 8)
                        {
                            curr_d -= 8;
                        }
                        if (curr_d < 0)
                        {
                            curr_d += 8;
                        }
                        // 当前点坐标
                        // 跟踪的过程,应该是个连续的过程,需要不停的更新搜索的root点
                        c_pt = Point(b_pt.x + directions[curr_d].x, b_pt.y + directions[curr_d].y);
                        // 边界判断
                        if ((c_pt.x > 0) && (c_pt.x < Edge.cols - 1) &&
                            (c_pt.y > 0) && (c_pt.y < Edge.rows - 1))
                        {
                            // 如果存在边缘
                            if (255 == Edge.at<uchar>(c_pt.x, c_pt.y))
                            {
                                curr_d -= 2;   // 更新当前方向
                                edge_t.push_back(c_pt);
                                Edge.at<uchar>(c_pt.x, c_pt.y) = 0;
                                // 更新b_pt:跟踪的root点
                                b_pt.x = c_pt.x;
                                b_pt.y = c_pt.y;
                                //cout << c_pt.x << " " << c_pt.y << endl;
                                break;   // 跳出for循环
                            }
                        }
                        curr_d++;
                    }   // end for
                    // 跟踪的终止条件:如果8邻域都不存在边缘
                    if (8 == counts )
                    {
                        // 清零
                        curr_d = 0;
                        tra_flag = true;
                        edges.push_back(edge_t);
                        break;
                    }
                }  // end if
            }  // end while
        }
    // 显示一下
    Mat trace_edge = Mat::zeros(Edge.rows, Edge.cols, CV_8UC1);
    //Mat trace_edge_color;
    cvtColor(trace_edge, trace_edge_color, CV_GRAY2BGR);
    for (i = 0; i < edges.size(); i++)
    {
        Scalar color = Scalar(rand()%255, rand()%255, rand()%255);
        // 过滤掉较小的边缘
        if (edges[i].size() > 5)
        {
            for (j = 0; j < edges[i].size(); j++)
            {
                trace_edge_color.at<Vec3b>(edges[i][j].x, edges[i][j].y)[0] = color[0];
                trace_edge_color.at<Vec3b>(edges[i][j].x, edges[i][j].y)[1] = color[1];
                trace_edge_color.at<Vec3b>(edges[i][j].x, edges[i][j].y)[2] = color[2];
            }
        }
    }
}

  轮廓跟踪处理结果如下:

参考:https://blog.csdn.net/minghui_/article/details/80501436

 


戳戳小手帮忙点个免费的赞和关注吧,嘿嘿。


目录
相关文章
|
25天前
|
计算机视觉 数据格式
使用opencv在Qt控件上播放mp4文件
使用opencv在Qt控件上播放mp4文件
29 2
|
1月前
|
算法 计算机视觉
OpenCV(二十六):边缘检测(二)
OpenCV(二十六):边缘检测(二)
22 0
|
1月前
|
计算机视觉
OpenCV(二十五):边缘检测(一)
OpenCV(二十五):边缘检测(一)
26 0
|
3月前
|
机器学习/深度学习 API 计算机视觉
Qt5 和 OpenCV4 计算机视觉项目:6~9
Qt5 和 OpenCV4 计算机视觉项目:6~9
101 0
|
4月前
|
算法 计算机视觉 Python
OpenCV中Canny边缘检测和霍夫变换的讲解与实战应用(附Python源码)
OpenCV中Canny边缘检测和霍夫变换的讲解与实战应用(附Python源码)
76 0
|
3月前
|
机器学习/深度学习 存储 文字识别
Qt5 和 OpenCV4 计算机视觉项目:1~5(5)
Qt5 和 OpenCV4 计算机视觉项目:1~5(5)
44 0
|
3月前
|
计算机视觉
OpenCV3 和 Qt5 计算机视觉:1~5
OpenCV3 和 Qt5 计算机视觉:1~5
28 0
|
3月前
|
存储 Linux 计算机视觉
Qt5 和 OpenCV4 计算机视觉项目:1~5(1)
Qt5 和 OpenCV4 计算机视觉项目:1~5(1)
60 0
|
1月前
|
机器学习/深度学习 API vr&ar
Qt, OpenCV与OpenGL协同作战:图像处理与三维图形界面的完美结合
Qt, OpenCV与OpenGL协同作战:图像处理与三维图形界面的完美结合
113 4
|
1月前
|
算法 C++ 计算机视觉
Opencv(C++)学习系列---Laplacian拉普拉斯边缘检测算法
Opencv(C++)学习系列---Laplacian拉普拉斯边缘检测算法

推荐镜像

更多