OpenCV开发笔记(四十九):红胖子8分钟带你深入了解轮廓识别(图文并茂+浅显易懂+程序源码)

若该文为原创文章,未经允许不得转载
原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客导航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/105843092
各位读者,知识无穷而人力有穷,要么改需求,要么找专业人士,要么自己研究

红胖子(红模仿)的博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结合等等)持续更新中...(点击传送门)

OpenCV开发专栏(点击传送门)

上一篇:OpenCV开发笔记(四十八):红胖子8分钟带你深入了解直方图均衡化(图文并茂+浅显易懂+程序源码)

下一篇:OpenCV开发笔记(五十):红胖子8分钟带你深入了解轮廓凸包(图文并茂+浅显易懂+程序源码)

 

前言

      红胖子来也!!!

      前面学了很多概念性的东西,本章开始开始做点实际的东西,车牌识别中,需要将车牌分离出来,一种方式是需要其轮廓,然后将其抠出来,再做其他操作。

 

Demo

 

轮廓

概述

      轮廓可以理解为是一系列点包围了一个区域,它将一些列边界包围起来,形成的一个区域。

      先通过滤波、阈值化的操作,然后寻找轮廓,定位到识别的物体的区域,这样可以将区域标记出来,抠图可以进而后续的二次处理。

原理

      (注意:轮廓查找的算法有很多,本篇章使用cv的函数findContours(),所以详解他的原理。)

识别过程如下:

查找轮廓函数原型

void findContours( InputOutputArray image,
                  OutputArrayOfArrays contours,
                  OutputArray hierarchy,
                  int mode,
                  int method,
                  Point offset = Point());
void findContours( InputOutputArray image,
                   OutputArrayOfArrays contours,
                   int mode,
                   int method,
                   Point offset = Point());
  • 参数一:InputOutputArray类型的image,一般为mat,输入图像,需要为单通道图像,图像的非0像素被视为1,0像素被保留为9,所以图像为二进制图像;
  • 参数二:OutputArrayOfArrays类型的contours,检测到的轮过,函数调用后,结果存储,每个轮廓存储为一个点向量,即用point类型的vector表示。
  • 参数三:OutputArray类型的hierarchy,可选的输出向量,包含图像的拓扑信息,其作为轮廓数量的表示,包含了许多元素。每个轮廓contours[i]对应4个hierarchy元素hierarchy[i][0]~ hierarchy[i][3],分别表示后一个轮廓、前一个轮廓、父林廓、内嵌轮廓的是索引编号。如果没有对应项,对应的hierarchy[i]值设为负数。
  • 参数四:int类型的mode, 轮廓检索模式,取值如下:

序号

枚举

描述

1

RETR_EXTERNAL

0

仅检索最外层轮廓。

2

RETR_LIST

1

检索所有轮廓,而不建立任何层次关系

3

RETR_CCOMP

2

检索所有轮廓并将其组织为两级层次结构。在顶端级别,组件有外部边界。在第二层,有洞的边界。如果在连接组件的孔内有另一个轮廓,则仍然被放在最高层。

4

RETR_TREE

3

检索所有轮廓并重建嵌套轮廓的完整层次结构

5

RETR_FLOODFILL

4

输入图像应该是连接的组件或填充功能的结果

  • 参数五:int类型的method,轮廓的近似办法,如下表:

序号

枚举

描述

1

CHAIN_APPROX_NONE

0

存储所有轮廓点。也就是说,任何2个后续点(x1,y1)和轮廓的(x2,y2)将是水平、垂直或对角线邻接,即,max(abs(x1-x2),abs(y2-y1))==1。

2

CHAIN_APPROX_SIMPLE

1

压缩水平、垂直和对角线线段,只保留其端点

3

CHAIN_APPROX_TC89_L1

2

应用TehChin89近似算法的一种风格

4

CHAIN_APPROX_TC89_KCOS

3

应用TehChin89近似算法的一种风格

  • 参数六:Point类型的offset,每个轮廓点的可选偏移量,有默认值Point()。瑞ROI图像中找出的轮廓,并要在整个图像中进行分析师,这个参数便可派上用场。

绘制轮廓函数原型

void 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() );
  • 参数一:InputOutputArray类型的image,目标图像,填 Mat 类的对象即可 。
  • 参数二:InputArrayOfArrays类型的contours,所有的输入轮廓 。 每个
    轮廓存储为 一个点 向 量 ,即用 pOlnt 类型 的 vector 表示。
  • 参数三:int类型的contourIdx,轮廓绘制的指示变量 。 如果其为负值,
    则绘制所有轮廓 。
  • 参数四:Scalar类型的color,轮廓的颜色
  • 参数五:int类型的thickness,轮廓线条的粗细度,有默认值1。如果其为负值(如thickness=cv_filled ),便会绘制在轮廓的内部。可选为FlLLED宏(OpenCV2版为CV_FILLED)。
  • 参数六:int类型的lineType,线条的类型,有默认值8。取值类型如表:

  • 参数七:InputArray类型的hierarchy,可选的层次结构信息,有默认值noArray();
  • 参数八:int类型的maxLevel,表示用于绘制轮廓的最大等级,有默认值INT_MAX;
  • 参数九:Point类型的offset,可选的轮廓偏移参数,用指定的偏移且offset=(dx,dy)偏移需要绘制的轮廓,有默认值Point();

 

分割车牌的步骤

步骤一:滤波

      (略,本篇章主要是轮廓,车牌识别),为了避免一些简单的噪声,在这里使用了自适应流行滤波器进行滤波。

      参照博文《OpenCV开发笔记(三十五):红胖子8分钟带你深入了解ximgproc扩展模块中的自适应流行滤波器(图文并茂+浅显易懂+程序源码)

步骤二:阈值化

      使用合适的方式进行过滤,在这里先转成灰度图,然后使用基础阈值化,自己调整阈值化,达到一个合适的值。

参照博文《OpenCV开发笔记(二十八):带你学习图像识别之阈值化

步骤三:寻找轮廓

      使用轮过查找,查找出轮廓,得到轮廓信息,是本篇所关注的。

步骤四:分割轮廓

      得到轮廓后,对图像进行分割(抠图),其实轮廓已经得出了基本的图像轮廓信息,抠图然后把该部分最外层轮廓抠出来就是数字和字母了还有一些杂质。

      可以分为两步,先抠出来整张图的这个部分(一整张图大小,非抠图部分为黑色),然后在进行一次裁剪,得到实际图像的位置,去掉多余的部分(其实就是ROI感兴趣的区域,此处不在赘述),如下图:

步骤五:识别

      (略,识别部分很多方式:模版、特征点、深度学习等等,后续写到实际开发部分详解后会与此处进行关联)。

 

Demo源码

void OpenCVManager::testFindContours()
{
    QString fileName1 =
            "E:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/5.jpg";
    cv::Mat srcMat = cv::imread(fileName1.toStdString());
    cv::Mat dstMat;
    int width = 300;
    int height = 200;

    cv::resize(srcMat, srcMat, cv::Size(width, height));

    cv::String windowName = _windowTitle.toStdString();
    cvui::init(windowName);

    cv::Mat windowMat = cv::Mat(cv::Size(srcMat.cols * 2,
                                         srcMat.rows * 5),
                                srcMat.type());
    int sigmaS = 100;
    int sigmaR = 1.0;

    int thresh = 215;
    int maxval = 255;

    while(true)
    {
        // 刷新全图黑色
        windowMat = cv::Scalar(0, 0, 0);

        // 原图复制
        cv::Mat mat = windowMat(cv::Range(srcMat.rows * 0, srcMat.rows * 1),
                                cv::Range(srcMat.cols * 0, srcMat.cols * 1));
        cv::addWeighted(mat, 0.0f, srcMat, 1.0f, 0.0f, mat);

        cv::Mat tempMat;
        {
            {
                cvui::printf(windowMat, 75 + width * 1, 40 + height * 0, "sigmaS");
                cvui::trackbar(windowMat, 75 + width * 1, 50 + height * 0, 165, &sigmaS, 101, 10000);
                cvui::printf(windowMat, 75 + width * 1, 90 + height * 0, "sigmaR");
                cvui::trackbar(windowMat, 75 + width * 1, 100, 165 + height * 0, &sigmaR, 1, 100);

                // 使用自适应流形应用高维滤波。
                cv::Ptr<cv::ximgproc::AdaptiveManifoldFilter> pAdaptiveManifoldFilter
                        = cv::ximgproc::createAMFilter(sigmaS/100.0f, sigmaR/100.0f, true);
                pAdaptiveManifoldFilter->filter(srcMat, tempMat);
                // 效果图copy
                mat = windowMat(cv::Range(srcMat.rows * 1, srcMat.rows * 2),
                                cv::Range(srcMat.cols * 0, srcMat.cols * 1));
                cv::addWeighted(mat, 0.0f, tempMat, 1.0f, 0.0f, mat);
            }

            //  转为灰度图像
            cv::cvtColor(tempMat, tempMat, cv::COLOR_BGR2GRAY);

            {
                // 调整阈值化的参数thresh
                cvui::printf(windowMat, 75 + width * 1, 20 + height * 1, "thresh");
                cvui::trackbar(windowMat, 75 + width * 1, 40 + height * 1, 165, &thresh, 0, 255);
                // 调整阈值化的参数maxval
                cvui::printf(windowMat, 75 + width * 1, 80 + height * 1, "maxval");
                cvui::trackbar(windowMat, 75 + width * 1, 100 + height * 1, 165, &maxval, 0, 255);

                // 阈值化
                cv::threshold(tempMat, tempMat, thresh, maxval, cv::THRESH_BINARY);
                // 效果图copy
                mat = windowMat(cv::Range(srcMat.rows * 2, srcMat.rows * 3),
                                cv::Range(srcMat.cols * 0, srcMat.cols * 1));

                //  转还图像
                cv::Mat grayMat;
                cv::cvtColor(tempMat, grayMat, cv::COLOR_GRAY2BGR);
                cv::addWeighted(mat, 0.0f, grayMat, 1.0f, 0.0f, mat);
            }

            // 寻找轮廓
            {
                qDebug() << __FILE__ << __LINE__;
                std::vector<std::vector<cv::Point>> contours;
                std::vector<cv::Vec4i> hierarchy;
                // 查找轮廓
                cv::findContours(tempMat, contours, hierarchy, cv::RETR_CCOMP, cv::CHAIN_APPROX_SIMPLE);
                // 遍历所有顶层轮廓,并绘制出来
                dstMat = srcMat.clone();
                cv::Mat emptyMat = srcMat.clone();
                emptyMat = cv::Scalar(0,0,0);
                qDebug() << __FILE__ << __LINE__;
                // 轮廓contours[i]对应4个hierarchy元素hierarchy[i][0]~ hierarchy[i][3],
                // hierarchy[i][0]表示后一个轮廓的索引编号
                // hierarchy[i][1]前一个轮廓的索引编号
                // hierarchy[i][2]父轮廓的索引编号
                // hierarchy[i][3]内嵌轮廓的索引编号
                for(int index = 0; index >=0; index = hierarchy[index][0])
                {
                    if(hierarchy.size() <= 0)
                    {
                        break;
                    }
                    cv::Scalar color;
                    if(index < hierarchy.size() / 3)
                    {
                        color = cv::Scalar(250 / (hierarchy.size() / 3) * index, 255, 255);
                    }else if(index < hierarchy.size() / 3 * 2)
                    {
                        color = cv::Scalar(255, 250 / (hierarchy.size() / 3) * (index - hierarchy.size() / 3), 255);
                    }else
                    {
                        color = cv::Scalar(255, 255, 250 /
                                           (hierarchy.size() / 3 == 0? 1 : hierarchy.size() / 3) * (index - hierarchy.size() / 3 * 2));
                    }
                    // 绘制轮廓里面的第几个
                    cv::drawContours(dstMat, contours, index, color, CV_FILLED, 8, hierarchy);
                    cv::drawContours(emptyMat, contours, index, color, CV_FILLED, 8, hierarchy);
                    qDebug() << __FILE__ << __LINE__ << "index =" << index << "total =" << hierarchy.size();

                }
                // 效果图copy
                mat = windowMat(cv::Range(srcMat.rows * 3, srcMat.rows * 4),
                                cv::Range(srcMat.cols * 0, srcMat.cols * 1));
                cv::addWeighted(mat, 0.0f, emptyMat, 1.0f, 0.0f, mat);
                // 效果图copy
                mat = windowMat(cv::Range(srcMat.rows * 4, srcMat.rows * 5),
                                cv::Range(srcMat.cols * 0, srcMat.cols * 1));
                cv::addWeighted(mat, 0.0f, dstMat, 1.0f, 0.0f, mat);
            }
        }

        // 更新
        cvui::update();
        // 显示
        cv::imshow(windowName, windowMat);
        // esc键退出
        if(cv::waitKey(25) == 27)
        {
            break;
        }
    }
}

 

工程模板:对应版本号v1.44.0

      对应版本号v1.44.0

 

上一篇:OpenCV开发笔记(四十八):红胖子8分钟带你深入了解直方图均衡化(图文并茂+浅显易懂+程序源码)

下一篇:OpenCV开发笔记(五十):红胖子8分钟带你深入了解轮廓凸包(图文并茂+浅显易懂+程序源码)

 

原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客导航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/105843092

©️2020 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值