首页 > 编程语言 >上交自瞄算法开源代码-装甲板识别功能分析

上交自瞄算法开源代码-装甲板识别功能分析

时间:2023-01-26 21:56:35浏览次数:71  
标签:矩形 函数 功能分析 博客 轮廓 灯条 上交 源代码 装甲

前言

开源代码github网址:GitHub - xinyang-go/SJTU-RM-CV-2019: 上海交通大学 RoboMaster 2019赛季 视觉代码

这里着重分析主函数main.cpp与装甲板识别部分的工程文件armer文件夹。

由于未完全分析,所以对于其中log.h、options.h中的函数并不清楚,在分析时会跳过这些内容。

该文章重点分析的是思路,部分函数中的数学逻辑、数据处理思路这里不详细解释(没能力)


所有思维导图通过 知犀知犀思维导图 - 知犀官网 (zhixi.com) 原创

image-20230126200546472


框架分析

首先看一下该文章要分析的部分

image-20230126200737472

头文件

这里我们只分析寻找装甲板的armor_finder.h和分类器classifier.h,而分类器其实是为寻找装甲板服务的

classifier.h

image-20230126205916974

  • load_conv_w/b:加载卷积层
    • 先以只读方式打开文件。如果打开失败则输出日志提示打开失败,然后将state设为false,返回一个未分配空间的result
    • 如果成功,就根据文件中的参数进行操作,通过emplace_back将数据添加到result中,最后return result
  • load_fc_w/b:加载全连接层
    • 基本思路与卷积层相同,不同的是:如果文件打开失败,返回一个现创建的1x1零向量而不是动态vector
  • relu:relu激活函数
    • 第一个relu用了三元表达式实现,后面的重载使用了这个relu方法
  • leaky_relu:relu函数的变形
    • image-20230126210319011
    • 用来防止训练过程中ReLu函数负半轴导致神经元死亡的问题
  • 有参构造
    • 这里用到了state,有参构造中会调用所有前面写的load_xxx_x方法来给私有属性赋值
    • 如果最终state为true,说明参数加载成功
  • calculate:
    • 在operator中使用
    • operator将图片RGB三通道值都转换到0-1之间,传给calculate计算
      1. 经过relu,平均池化,relu,平局池化,,relu后,将其压平到一维
      2. 通过wx+b算出y,使用relu,再算下一层,进行softmax
      3. 将最后得到的矩阵返回
  • operator
    1. 通道拆分
    2. 所有通道值/255,进行归一化
    3. 通过calculate进行CNN处理,得到代表概率的一维向量
    4. 找到最大概率的行,如果概率大于0.5,认为就是该类型
      如果最大概率小于0.5,认为匹配失败,返回0(筛)

armor_finder.h

这里面写了三个类,每个类都有着自己的属性与方法,最后功能都会集合到findArmorBox上

image-20230126210914047

  • 里面的灯条类和装甲板类都通过typedef给自己类型的vector容器起了别名。他们被广泛用于后续的函数中,借助引用来将函数处理后的数据储存起来
  • 自瞄类中很多属性、方法与装甲板识别功能无关,所以流程图中省略了

源码

  • classifier.cpp是为了实现classifier.h中的各项函数
  • armor_finder.cpp最关键的作用就是实现了有参构造和自瞄类的核心方法run
  • 其他.cpp文件都是在实现armor_finder.h中的某个或某些函数,他们大多都有一下规律:先创建一下属于自己的函数作为铺垫,然后再通过这些函数去实现armor_finder.h的函数

具体分析

函数调用情况

图中的几个函数是装甲板识别功能中极为重要的函数,也是接下来我们要分析的函数。图中展示了他们的调用关系方便理清逻辑

image-20230126204359032

  1. main.cpp中调用run来实现自瞄功能
  2. run中当状态为SEARCHING_STATE时,会使用装甲板识别的功能。这时候他会先判断是否能从图片中搜索到装甲板、搜索到的是否与上次搜索到的是同一目标,只有判断成功才会进行下一步tracker对象的创建(进入追踪状态的基础)。而这个判断功能就交给stateSearchingTarget来处理
  3. stateSearchingTarget对上面两个问题做出判断,其中第一个问题:”是否能从图片中搜索到装甲板“就交给了findArmorBox来处理。他会告诉程序能否搜索到,如果搜索到了他还会把装甲板的信息传递过来,以便进行第二个问题的判断。后面装甲板的信息还会传递到run中用来追踪
  4. findArmorBox主要进行一下操作:寻找灯条,通过灯条得到候选装甲板,通过分类器来筛选。上面三步分别交给了图中的三个函数

这是对整个装甲板识别流程的简要说明,详细的内容会在下文一一阐述


main.cpp

刨除掉其他功能,其实关于寻找装甲板的功能就是通过run体现的


自瞄主函数run()

run中对于不同的机器状态会有不同的处理,这里我们只分析搜索状态下的流程,流程图如下:

image-20230126204714508

  • anti_switch_cnt防止乱切目标计数器在装甲板识别中未使用,所以不进行讨论
  • run中关于装甲板识别的内容其实只有stateSearchingTarget,所以这里只放出流程图,对于具体细节实现不讨论

我们根据函数调用情况图了解到是findArmorBox调用了findLightBlobs,但是为了逻辑的通畅,我们先分析灯条识别功能,不再按照情况图从下往上分析


灯条识别

image-20230126211600228

函数

  • lw_rate:旋转矩形的长宽比
    • 利用三元运算符,保证输出的比值>1
  • areaRatio:轮廓面积和其最小外接矩形面积比
    • 其中的Point类以前也用过,就是用来记录坐标点的。将轮廓的所有坐标点保存到vector容器中,这时候就可以认为这个容器代表了这个轮廓
    • 直接调用contourArea得到轮廓面积
  • isValidLightBlob:判断轮廓是否为一个灯条
    • 分析判断条件
      • A:旋转矩形的长宽比在1.2与10之间
      • B:最小外接矩形面积<50 并且 轮廓面积和其最小外接矩形面积之比>0.4
      • C:最小外接矩形面积>=50 并且 轮廓面积和其最小外接矩形面积之比>0.6
      • A && ( B || C)
    • 判断方式的原理
      • A是对灯条长宽比的基本数据限定,灯条什么角度都基本上是个长方形,而且比值不可能超过10
      • 轮廓面积和其最小外接矩形面积之比 其实就是灯条识别图像与外接矩形的契合度
        • 面积<50说明灯条比较远,那么杂光的干扰会大一些,灯条外形特征不明显,契合度就会相对较低
        • 同理C为较近的情况,契合度理应更高,所以要求的最小值就会高一些
  • get_blob_color:判断灯条的颜色
    • 整体思路是通过比较图片中红蓝的比例来判断灯条颜色(只看红蓝是因为灯条只可能是这两个颜色)
  • isSameBlob:判断两个灯条区域是否为同一个灯条
    • 如果两个灯条外接矩形的中心点坐标满足:(x坐标差值的平方+y坐标差值的平方)<9,则认为是同一个灯条
  • imagePreProcess:开闭运算
    • 其中用的都是3x5的矩阵卷积核
    • 依次对图像进行腐蚀,膨胀,膨胀,腐蚀

findLightBlobs

通道拆分

  1. 通道拆分为RGB三个,根据敌人的颜色选择使用哪个颜色的通道
  2. 根据敌人颜色设置light_threshold(二值化的阈值)的值

二值化处理

  • 方式一:对图像进行二值化处理,大于阈值(light_threshold)部分设置为255,小于部分设置为0,如果处理后图像为空返回false.对处理后图片进行一次开闭运算(imagePreProcess函数)

  • 方式二:对图像以140为阈值进行一次二值化处理,然后一次开闭运算

使用两个不同的二值化阈值同时进行灯条提取,减少环境光照对二值化这个操作的影响。
同时剔除重复的灯条,剔除冗余计算,即对两次找出来的灯条取交集。

取交集

  1. 找两次处理的轮廓,检索模式为CV_RETR_CCOMP;定义轮廓的近似方法为CV_CHAIN_APPROX_NONE
    • CV_RETR_CCOMP: 检测所有的轮廓,但所有轮廓只建立两个等级关系,外围为顶层,若外围内的内围轮廓还包含了其他的轮廓信息,则内围内的所有轮廓均归属于顶层
    • CV_CHAIN_APPROX_NONE:保存物体边界上所有连续的轮廓点到contours向量内
  2. 分别给两个灯条容器对象(利用了typedef std::vector\<LightBlob> LightBlobs)添加内容:最小外接矩形,轮廓面积和最小外接矩阵面积之比,灯条的颜色
  3. 最后通过一系列取交集操作判断是否找到灯条(灯条数大于等于2),同时灯条的数据都存在了light_blobs这个LightBlobs类型的vectorr中

与灯条匹配成装甲板

image-20230126213256736

matchArmorBoxes

  1. 清空ArmorBox类型的vector容器
  2. 读取light_blobs(里面存储着灯条的数据)的内容,这里的嵌套for循环是为了将所有灯条进行两两配对处理
  3. 先判断两个灯条是否匹配,不配对则继续
  4. 将两个灯条的矩形一个作为左矩形一个作为右矩形
  5. 通过一系列计算处理,如果一系列数据限制条件都通过,则将这个候选装甲板的坐标,宽高,两个相应的灯条数据,敌人颜色都存入armor_boxes这个容器中

findArmorBox

  1. 创建light_blobs,armor_boxes两个容器,初始化box对象
  2. 调用findLightBlobs函数,将找到的所有可能的灯条存到bight_blobs容器中
  3. 调用matchArmorBoxes函数,得到装甲板候选区
  4. 使用分类器classifier对装甲板候选去进行筛选(概率小于0.5的都会被筛选掉)
  5. 按照优先级对装甲板进行排序,选择优先级最大的作为结果(id不能为0),如果结果是空,返回false。如果分类器不可用就默认选择候选区第一个区域作为目标

上交的灯条识别与自己灯条识别的区别

C++学习记录

C++新知识

数值计算库Eigen

参考:

Matrix: (16条消息) Eigen教程2----MatrixXd和VectorXd的用法_MaybeTnT的博客-CSDN博客_eigen::matrixxd

.colise: (16条消息) Eigen初学相关介绍_小白要努力的博客-CSDN博客_colwise函数

maxCoeff()与索引:(16条消息) Eigen库使用之矩阵的最大/小值及其位置_Chen-Sh的博客-CSDN博客_maxcoeff

  • Marix类:在Eigen,所有的矩阵和向量都是Matrix模板类的对象,Vector只是一种特殊的矩阵(一行或者一列)

  • MatrixXd:Matrix的一种构造函数,X代表动态,d代表double类型,也就是说生成一个类型的double类型的动态大小的矩阵

    • Matrix3f意味着生成一个3x3的类型为float的矩阵
    • MatrixXd f(row, col)意味着f是一个类型的double的row*col矩阵(注意不是int类型,int是MatrixXi)
  • VectorXd:与MatrixXd同理,生成一个类型的double类型的动态大小的向量

  • .colwise:Mat.colwise()理解为分别去看矩阵的每一列,然后再作用maxCoeff()函数,即求每一列的最大值。

    需要注意的是,colwise返回的是一个行向量(列方向降维),rowwise返回的是一个列向量(行方向降维)。

  • image-20230126103711287

    • 先通过calculate方法得到矩阵result
    • 设置最小值的索引
    • 通过.maxCoeff计算矩阵中的最大值最小值,这时得到了最大值的坐标
    • 如果最大值>0.5返回行坐标,反之返回0

容器知识

参考:

emplace_back:(16条消息) C++的emplace_back函数介绍_Jason_Lee155的博客-CSDN博客_c++ emplace_back

  • emplace_back:传统对vector进行尾插入都是使用push_back,但是其中有很多冗余的计算。而C++11引入了emplace_back函数,它通过完美转发实现了在vector中插入时直接在容器内构造对象,省略了创建临时对象的操作

文件读写函数

  • fscanf:按“格式字符串”所指定的格式,从“文件类型指针”所指向的文件的当前位置读取数据,然后按“输入项地址表列”的顺序,将读取来的数据存入指定的内存单元中。
    • fscanf(fp,"%d,%f",&i,&t)为例,意为从指针fp所指向的文件中以%d,%f格式读取两个值,分别存储在地址&i,&t的内存中

OOP知识

参考:

=default:(16条消息) C++ =default_c++ default_TABE_的博客-CSDN博客

转换运算符:关于c ++:“ operator bool()const”是什么意思 | 码农家园 (codenong.com)

构造函数冒号语法:(16条消息) C++子类的构造函数后面加:冒号的作用_lusirking的博客-CSDN博客_c++ 构造函数 冒号

  • default 函数。程序员只需在函数声明后加上=default,就可将该函数声明为 default 函数,编译器将为显式声明的default函数自动生成函数体。
  • operator bool()const:当使用时可以将对象转化为bool类型
  • 该源码为第二种使用场景:对类成员进行初始化。正常写法也可以做到,但是这样会更加简便

其他

参考:

typedef:(16条消息) C++ typedef详解_jupeiii的博客-CSDN博客_c++ typedef

enum:(16条消息) C++枚举enum使用详解_赵大宝字的博客-CSDN博客_c++ enum

extern:(7条消息) C++中的extern_老胡写代码的博客-CSDN博客_c++ extern

auto类型:(7条消息) C++ auto类型总结_emper丶z的博客-CSDN博客_c++ auto

.erase:C++中erase函数的使用,可以用来删除内存擦除 - 初见不如不相见 - 博客园 (cnblogs.com)

  • fmax,fmin是c语言中用来快速比较两数大小的函数,会返回两个浮点数中更x的那个
  • auto类型:可以在声明变量时自动推断被声明变量的类型,更适用于类型冗长复杂、变量使用范围专一时,使程序更清晰易读
  • .erase:可以用来更新(删除内容)迭代器

OpenCV新知识

参考:

轮廓拟合函数:(7条消息) opencv--轮廓拟合函数 boundingRect(),minAreaRect(),minEnclosingCircle(),fitEllipse(),fitLine()_Haohao fighting!的博客-CSDN博客_boundingrect()

getStructuringElement():(7条消息) getStructuringElement函数以及开、闭、腐蚀、膨胀原理讲解_SerendipityMIT的博客-CSDN博客

  • RotatedRect是一个存储平面上旋转矩形的类,通常用来存储最小外包矩形函数minAreaRect( )和椭圆拟合函数fitEllipse( )返回的结果。以前给灯条找最小外接矩阵的时候用过一次
    • .boundingRect():轮廓拟合函数中的一个,能够返回包围轮廓的矩形的边界信息。
  • getStructuringElement():返回一个结构元素(卷积核)。第一个参数会决定卷积核的形状,源码中imagePreProcess方法使用的MORPH_RECT会使函数返回矩形卷积核

收获

  1. 如果一个函数作用是信息处理,那么一般返回值为bool,表示这个函数是否成功处理了数据。处理后的数据不作为返回值,而是通过参数中的引用直接存放到变量(容器)中。就比如findLightBlobs这个函数,通过容器是否为空来作为bool类型的返回,通过函数得到的灯条信息在函数体中已经存放到了容器中(容器参数利用了引用)。

标签:矩形,函数,功能分析,博客,轮廓,灯条,上交,源代码,装甲
From: https://www.cnblogs.com/zaughtercode/p/17068290.html

相关文章