专栏首页行走的机械人【opencv实践】你确定真的了解寻找轮廓函数吗?【RM大符识别】

【opencv实践】你确定真的了解寻找轮廓函数吗?【RM大符识别】

前几天师兄跟我讲了一下opencv的findContours()函数识别大符,感觉真的是妙啊!自己学的时候马马虎虎,就导致很多细节都没有领悟到,今天给大家分享一下。

大家看完如果觉得不能很好的理解,就等有时间了动手复制粘贴一遍代码,就一定能懂了。

还是和前面几篇文章一样,我们要找个小项目实践一下。就以RoboMaster比赛的大符识别这个小项目为例好了。首先,先给大家介绍一下这个小项目:

大家仅看这个封面也是可以的,这是一个不停在转的轮盘,上面有两种不同的红色的标识,我们需要识别的是封面右上方的那种标识的中心框,识别效果图如下:

要识别出上图蓝色所标的矩形框,其实有很多方法(图像处理从来都是仁者见仁智者见智妙招无穷),但利用findContours()函数可以很完美的解决这个问题。我们一步步来。

理论分析

观察图像

观察分析图像是必不可少的,甚至你对图像理解的好变已经成功了一大部分。

首先我们肯定可以看出,我们需要识别的目标颜色是很鲜艳突出的红色,所以讲红色扣出来是很容易想到的。

那如何识别那个矩形框呢?我们可以看到,左下角的红色里面包裹这三块黑色,而右上角的红色里面仅包含着一块黑色。这就是我们来识别的依据了!

但为什么要以此为依据呢?看了下文findContours()函数的内容,你就知道了。

findContours()函数

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

先看一下它的参数:

1@image:输入原图像,为8位单通道图像。

2@contours:检测到的轮廓,函数调用后的运行结构存在这里,每个轮廓存储为一个点向量,即用point类型的vector表示。

3@hierarchy:可选的输出向量,包含图像的拓扑信息。其作为轮廓数量的表示,包含了许多元素。每个轮廓contours[i]对应4个hierarchy元素hierarchy[i][0]~hierarchy[i][3],分别表示后一个轮廓,前一个轮廓,内嵌轮廓,父轮廓的索引编号。如果没有对应项,对应的hierarchy[i]值设置为负数。

4@mode:轮廓检索模式,取值如下图:

5@method:为轮廓的近似办法,取值如下图:

6@offset:每个轮廓点的可选偏移量,有默认值Point(),对ROI图像中找出的轮廓,并要在整个图像中进行分析时,这个参数便可排上用场。

其中第三个参数是我们需要重点关注的,它是我们解决这个问题的依据:

如何理解呢?我们以下图为例:

我们的findContours()函数会将上图中的黑色边框找出来,并依次标号为1~7。我们可以说边框1为边框3的前一个轮廓,也就是contours[3]的hierarchy[3][1] = 1。

同理,我们可以认为边框2的父轮廓为边框1,则contours[2]的hiearchy[2][3] = 1。

同样,边框6,7的父轮廓为边框5,只不过当我们返回边框5的内嵌轮廓(子轮廓)时,只能返回6,7其中之一。

编程思路

到此理解了findContours()函数,我们再回顾一下我们要处理的图像:

结合上面关于findContours()函数的介绍,我们可以先将红色区域扣出来,然后寻找边框,之后我们只需找出那个仅含一个子轮廓的轮廓,就是我们要找的红色区域。而该轮廓的子轮廓,就是我们的目标target了:

编程实现

整体框架搭建

#include <iostream>
#include "opencv2/opencv.hpp"
using namespace cv;
using namespace std;
int main()
{
  VideoCapture capture("大符.mp4"); //读入视频
  Mat frame, srcImage;
  Point2i center; //定义矩形中心
  while (1)
  {  
    capture >> frame;//读入帧
    resize(frame, srcImage, Size(frame.cols / 3, frame.rows / 3));//转换大小(原视频太大了)
    center = markred(srcImage);  //自定义函数进行识别
    imshow("效果图", srcImage);
    cout << center << endl; //打印目标坐标
    if (waitKey(30) >= 0) //按任意键退出
      break;
  }
  return 0;
}

上面函数就是完成读取视频操作了,其中用到了一个自定义的函数

markred(srcImage);

该自定义函数就包含了我们所有的处理操作了。

下文所介绍的,就都是该自定义函数的内容了!

步骤一:扣图

#include <iostream>
#include "opencv2/opencv.hpp"
using namespace cv;
using namespace std;
int main()
{
  VideoCapture capture("大符.mp4"); //读入视频
  Mat frame, srcImage;
  Point2i center; //定义矩形中心
  while (1)
  {  
    capture >> frame;//读入帧
    resize(frame, srcImage, Size(frame.cols / 3, frame.rows / 3));//转换大小(原视频太大了)
    center = markred(srcImage);  //自定义函数进行识别
    imshow("效果图", srcImage);
    cout << center << endl; //打印目标坐标
    if (waitKey(30) >= 0) //按任意键退出
      break;
  }
  return 0;
}

我们首先将RGB颜色空间转换为HSV颜色空间,因为扣颜色的话HSV颜色空间更直观:

由上图可以看到红色的HSV空间域的红色区间有两个:【156,180】以及【0,10】,因此我们分别扣出后进行add()函数合并为一个。效果图如下:

详细有关HSV的我们就不讲了,大家可以看这篇CSDN:

https://blog.csdn.net/ColdWindHA/article/details/82080176?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

步骤二:闭操作去小黑洞

这就是常规的图像处理操作啦,主要是为了防止白色的边框有断开的地方。

Mat dstImage;
Mat element = getStructuringElement(MORPH_RECT,Size(5, 5));
morphologyEx(HsvImage, dstImage, MORPH_OPEN, element);

步骤三:寻找边界

这里就是重头戏了!

vector<vector<Point>>contours;//轮廓数组
  vector<Vec4i>hierarchy; //一个参数
  Point2i center; //用来存放找到的目标的中心坐标
  //提取所有轮廓并建立网状轮廓结构
  findContours(dstImage, contours, hierarchy, RETR_TREE, CHAIN_APPROX_NONE, Point(0, 0));

我们首先定义了一个轮廓数组contours,是vector<vector<Point>>类型的,findContours函数检测到的轮廓都会存放到contours里。

然后定义了vector<Vec4i>hierarchy,这是我们要传给findContours函数的,用来存放每个轮廓contours[i]对应的4个hierarchy元素

hierarchy[i][0]~hierarchy[i][3]。

然后便是运行findContours函数啦。

int contour[20] = { 0 };
  for (int i = 0; i < contours.size(); i++)//遍历检测的所有轮廓
  {
    if (hierarchy[i][3] != -1) //有内嵌轮廓,说明是一个父轮廓
    {
      contour[hierarchy[i][3]]++; //对该父轮廓进行记录
    }
  }

然后我们定义了一个20个单位长的0数组contour[20]。然后我们遍历所有上一步的检测到的轮廓,当某一轮廓的hierarchy[i][3]不等于-1时,也就是说明该轮廓有父轮廓,也就是说明该轮廓为一个内嵌轮廓。

这时,我们将数组

contour[hierarchy[i][3]]自增1。

这里是在做啥呢?

上图中,蓝色框是我们检测出来的父轮廓,轮廓1里面有一个黑洞,也就是包含一个内嵌轮廓,而2中没有内嵌轮廓,3中有三个内嵌轮廓。

而我们要检测的就是轮廓1的内嵌轮廓。但opencv中没有直接数父轮廓里所包含内嵌轮廓个数的函数。怎么办呢?

我们就检测子轮廓(内嵌轮廓),检测到一个子轮廓,就将其父轮廓对应的数组元素加1。然后看父轮廓对应数组元素的值就知道该父轮廓包含几个子轮廓了。

 for (int j = 0; j < contours.size(); j++)//再次遍历所有轮廓
  {
    if (contour[j] == 1) //如果某轮廓对应数组的值为1,说明只要一个内嵌轮廓    
    {
      int num = hierarchy[j][2]; //记录该轮廓的内嵌轮廓
      RotatedRect box = minAreaRect(contours[num]); //包含该轮廓所有点
      Point2f vertex[4];
      box.points(vertex);//将左下角,左上角,右上角,右下角存入点集
      for (int i = 0; i < 4; i++)
      {
        line(srcImage, vertex[i], vertex[(i + 1) % 4], Scalar(255, 0, 0), 4, LINE_AA); //画线
      }
      center = (vertex[0] + vertex[2]) / 2; //返回中心坐标
      putText(srcImage, "target", vertex[0], FONT_HERSHEY_SIMPLEX, 1.0, Scalar(255, 255, 0));//打印字体
    }
  }

然后上面的程序就是筛选出我们想要的目标轮廓并画出来,再返回其坐标了。处理结果如下:

好了,到此我们就完成了。你感觉到findContours函数的妙处了吗?

本文分享自微信公众号 - Opencv视觉实践(gh_31e12b1be0e0),作者:是周旋哎

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-04-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 【通信专栏】STM32单片机/SPI通信

    SPI 接口主要应用在 EEPROM, FLASH,实时时钟, AD 转换器,还有数字信号处理器和数字信号解码器之间。SPI,是一种高速的,全双工,同步的通信总...

    周旋
  • 【opencv实践】仿射变换和透视变换

    上面这副图就是我们今天要处理的了,我们想把它从拍照视角变成鸟瞰图,这是机器人导航中的常用手段,以便在该平面上进行规划和导航。

    周旋
  • 【通信专栏】一:STM32串口通信(usart)

    在开始学写STM32串口通信的代码实现前,首先先了解一下两块芯片之间通信的分类,按照数据传输方式可以分为

    周旋
  • opencv 9 -- 轮廓 层次结构

    使用函数 cv2.findContours 来查找轮廓, 我们需要传入一个参数:轮廓提取模式(Contour_Retrieval_Mode)。 我们总是...

    wust小吴
  • OpenCV玩九宫格数独(一):九宫格图片中提取数字

    在一年之前,我曾用 C++ 尝试过 opencv 解数独,但由于当时水平有限,未能完成。最近我打算改用 Python 来完成。本文先说第一步,图片中数字的提取。

    刘潇龙
  • CVPR2020:Deep Snake 用于实时实例分割

    1) 提出了一种基于学习的用于实时实例分割的蛇算法,介绍了用于轮廓学习的圆形卷积。

    小白学视觉
  • 使用Octave来学习Machine Learning(二)

    前言 上一篇我们介绍了 Octave 的一些基本情况,大家对 Octave 应该已经有了一个基本的了解,我相信看这篇文章的朋友已经在自己的电脑中安装好 Ocat...

    小之丶
  • ZOJ 3309 Search New Posts 解题报告

    题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3309

    owent
  • vBulletin vB_Api_Hook->decodeArguments RCE 分析

    介绍: vBulletin是一个国外著名的商业论坛程序。前几天因官网被黑,而被爆出一个命令执行漏洞。 我们最早获得的一篇分析是在Pastie上的http://p...

    安恒信息
  • myeclipse去除jsp校验错误

    选择你的工程右击菜单 >> 点击MyEclipse菜单项的Exclude From Validation,如果你的工程里还 有其它需要它检验其合法性建议可以针对...

    用户1220053

扫码关注云+社区

领取腾讯云代金券