rm中,装甲板的识别在比赛中可谓是最基础的算法。而在各个开源框架中,该算法也可以说最为成熟。出于学习目的,之后将对比多个高校或网络代码(),尝试学习各个rm装甲板识别算法的优点和流程。


这次先是东南大学(SEU-SuperNova-CVRA)开源的视觉算法:

cv::Mat binBrightImg;
cvtColor(_roiImg, _grayImg, COLOR_BGR2GRAY, 1);
cv::threshold(_grayImg, binBrightImg, _param.brightness_threshold, 255, cv::THRESH_BINARY);

先将_roiImg转成灰度图,方便后续转成二值图。

之后再将上图转为二值图,放到binBrightImg中,这里的_param.brightness_threshold是阈值,应用的为cv::THRESH_BINARY的方法。个人认为这里的方法还可以用CV_THRESH_OTSU也就是大津算法,效率有待后续测试。

下面这段代码先不要看,等到下述中出现“请回到顶部”字样的时候观看

// 把一个3通道图像转换成3个单通道图像
split(_roiImg,channels);//分离色彩通道
//预处理删除己方装甲板颜色
if(_enemy_color==RED)
_grayImg=channels.at(2)-channels.at(0);//Get red-blue image;
else _grayImg=channels.at(0)-channels.at(2);//Get blue-red image;
————————————————
版权声明:本文为CSDN博主「Raring_Ringtail」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u010750137/article/details/96428059
cv::Mat element = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(3, 3));
dilate(binBrightImg, binBrightImg, element);

进行膨胀处理,将二值图中的灯条变粗。

vector<vector<Point>> lightContours;
cv::findContours(binBrightImg.clone(), lightContours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

寻找轮廓,将找到的轮廓放置在lightcountours中,而这一步据网上说是相对费时的一段时间,其很大程度决定于前文的预处理情况。

之后是一个大循环来检测轮廓中的灯条。具体操作作为注释

for(const auto& contour : lightContours)//对每个轮廓都进行处理
{
//得到轮廓的面积//
float lightContourArea = contourArea(contour); //筛选掉噪声//
if(contour.size() <= 5 ||
lightContourArea < _param.light_min_area) continue;
//椭圆拟合生成相应的旋转矩形(注:只是用矩形去拟合灯条,方便获取灯条的长宽比等)
RotatedRect lightRec = fitEllipse(contour); adjustRec(lightRec, ANGLE_TO_UP);
//筛选出需要的灯条//
if(lightRec.size.width / lightRec.size.height > _param.light_max_ratio ||
lightContourArea / lightRec.size.area() < _param.light_contour_min_solidity)
continue; //扩展矩形的长宽//
lightRec.size.width *= _param.light_color_detect_extend_ratio;
lightRec.size.height *= _param.light_color_detect_extend_ratio;
//获取矩形的外接矩形框//
Rect lightRect = lightRec.boundingRect();
const Rect srcBound(Point(0, 0), _roiImg.size());
//上面定义的lightRect为灯条的外接矩形,与检测区域srcBound交集。但实际上我不明白这一步有什么用。如果是为了排除检测区域外的灯条,可是轮廓就是提取自一开始的roiImg的吧。//
//此处我一开始没反应过来是为了做什么,经过查找才明白:&=操作是将两个矩阵的交集再存放到lightRect中。相类似的操作还有|(并集),+point(a,b)(平移一个point的向量(a,b)位移),+size(a,b)(长和宽分别加上a和b)//
lightRect &= srcBound; //以后的操作(一直到#ifdef)都是为了颜色处理,特别是关于识别灯条是蓝色还是红色//
//但由于操作十分繁琐,所以我参考了网上来自江达小记的做法,请回到顶部//
Mat lightImg = _roiImg(lightRect);
Mat lightMask = Mat::zeros(lightRect.size(), CV_8UC1);
Point2f lightVertexArray[4];
lightRec.points(lightVertexArray);
std::vector<Point> lightVertex;
for(int i = 0; i < 4; i++)
{
lightVertex.emplace_back(Point(lightVertexArray[i].x - lightRect.tl().x,
lightVertexArray[i].y - lightRect.tl().y));
}
fillConvexPoly(lightMask, lightVertex, 255); if(lightImg.size().area() <= 0 || lightMask.size().area() <= 0) continue;
cv::dilate(lightMask, lightMask, element);
const Scalar meanVal = mean(lightImg, lightMask); if(((_enemy_color == BLUE) && (meanVal[BLUE] - meanVal[RED] > 20.0)) || (_enemy_color == RED && meanVal[RED] - meanVal[BLUE] > 20.0))
{
lightInfos.push_back(LightDescriptor(lightRec));
}
//若使用了两通道值相减的方法,则这里可以直接将结果保存下来。注意放入的是lightrec而不是lightrect的外接矩形信息
//lightInfos.push_back(LightDescriptor(lightRec));

到这里,已经将各可能的灯条放入到lightInfos中去了,可是我们的目的是找到装甲板,所以下面的代码将会在这方面展开:

		//检查是否检测到灯条//
if(lightInfos.empty())
{
return _flag = ARMOR_NO;
}
}//循环结束 {
//用到了C++11的lambda(可简单看作函数对象),设置了ld1和ld2两个参数,依照灯条中心的x坐标从左到右(opencv的坐标轴为横x竖y)。center为point2f类型的。//
sort(lightInfos.begin(), lightInfos.end(), [](const LightDescriptor& ld1, const LightDescriptor& ld2)
{
return ld1.center.x < ld2.center.x;
});
//设一个长为lightInfos.size(),值都为-1的数组//
vector<int> minRightIndices(lightInfos.size(), -1); //遍历每一种组合//
for(size_t i = 0; i < lightInfos.size(); i++)
{
for(size_t j = i + 1; (j < lightInfos.size()); j++)
{
const LightDescriptor& leftLight = lightInfos[i];
const LightDescriptor& rightLight = lightInfos[j]; //计算左灯和右灯的角度差//
float angleDiff_ = abs(leftLight.angle - rightLight.angle);
//计算左灯和右灯的长度差之比(越相近该值越小)//
float LenDiff_ratio = abs(leftLight.length - rightLight.length) / max(leftLight.length, rightLight.length);
//通过阈值筛选灯条//
if(angleDiff_ > _param.light_max_angle_diff_ ||
LenDiff_ratio > _param.light_max_height_diff_ratio_)
{
continue;
} /*
* proper location: // y value of light bar close enough
* // ratio of length and width is proper
*/
//计算左右灯条中心距离//
float dis = cvex::distance(leftLight.center, rightLight.center);
//计算左右灯条长度的均值//
float meanLen = (leftLight.length + rightLight.length) / 2;
//灯条y的差//
float yDiff = abs(leftLight.center.y - rightLight.center.y);
//y差值的比率//
float yDiff_ratio = yDiff / meanLen;
//同前//
float xDiff = abs(leftLight.center.x - rightLight.center.x);
float xDiff_ratio = xDiff / meanLen;
//灯条的距离与长度的比值(也就是嫌疑装甲板长和宽的比值)//
float ratio = dis / meanLen;
//对上面各量筛选,如果y差太大(y最好越相近越好),或者x差的太小,又或者装甲板长宽比不合适就排除掉。//
if(yDiff_ratio > _param.light_max_y_diff_ratio_ ||
xDiff_ratio < _param.light_min_x_diff_ratio_ ||
ratio > _param.armor_max_aspect_ratio_ ||
ratio < _param.armor_min_aspect_ratio_)
{
continue;
} // calculate pairs' info
//通过长宽比来确定是大的还是小的装甲板//
int armorType = ratio > _param.armor_big_armor_ratio ? BIG_ARMOR : SMALL_ARMOR;
// calculate the rotation score
float ratiOff = (armorType == BIG_ARMOR) ? max(_param.armor_big_armor_ratio - ratio, float(0)) : max(_param.armor_small_armor_ratio - ratio, float(0));
float yOff = yDiff / meanLen;
//应该是rotationScore越接近0越好,看后续用处//
float rotationScore = -(ratiOff * ratiOff + yOff * yOff); //生成相应的装甲板//
ArmorDescriptor armor(leftLight, rightLight, armorType, _grayImg, rotationScore, _param);
//将获得的嫌疑装甲板放到armors中去//
_armors.emplace_back(armor);
break;
}
} //此处删除debug内容// //没找到的话。。//
if(_armors.empty())
{
return _flag = ARMOR_NO;
}
} //此处删除调试信息// //delete the fake armors
_armors.erase(remove_if(_armors.begin(), _armors.end(), [](ArmorDescriptor& i)
{
//这里就是识别装甲板的算法了//
return !(i.isArmorPattern());
}), _armors.end()); //江达小记版本//
_armors.erase(remove_if(_armors.begin(), _armors.end(), [this](ArmorDescriptor& i)
{//lamdba函数判断是不是装甲板,将装甲板中心的图片提取后让识别函数去识别,识别可以用svm或者模板匹配等
return 0==(i.isArmorPattern(_small_Armor_template,_big_Armor_template,lastEnemy));
} ), _armors.end()); //没有一个是装甲板的情况//
if(_armors.empty())
{
_targetArmor.clear(); //看是目标丢失还是没识别出来//
if(_flag == ARMOR_LOCAL)
{
//cout << "Tracking lost" << endl;
return _flag = ARMOR_LOST;
}
else
{
//cout << "No armor pattern detected." << endl;
return _flag = ARMOR_NO;
}
} //calculate the final score
for(auto & armor : _armors)
{
armor.finalScore = armor.sizeScore + armor.distScore + armor.rotationScore;
} //choose the one with highest score, store it on _targetArmor
std::sort(_armors.begin(), _armors.end(), [](const ArmorDescriptor & a, const ArmorDescriptor & b)
{
return a.finalScore > b.finalScore;
});
_targetArmor = _armors[0]; //update the flag status
_trackCnt++;

而识别装甲板算法(也就是原文中的i.isArmorPattern())等后续有机会再准备。

东南大学RM装甲板识别算法详解的更多相关文章

  1. 机器学习经典算法详解及Python实现--基于SMO的SVM分类器

    原文:http://blog.csdn.net/suipingsp/article/details/41645779 支持向量机基本上是最好的有监督学习算法,因其英文名为support vector  ...

  2. 第三十一节,目标检测算法之 Faster R-CNN算法详解

    Ren, Shaoqing, et al. “Faster R-CNN: Towards real-time object detection with region proposal network ...

  3. 第二十九节,目标检测算法之R-CNN算法详解

    Girshick, Ross, et al. “Rich feature hierarchies for accurate object detection and semantic segmenta ...

  4. HanLP中人名识别分析详解

    HanLP中人名识别分析详解 在看源码之前,先看几遍论文<基于角色标注的中国人名自动识别研究> 关于命名识别的一些问题,可参考下列一些issue: l ·名字识别的问题 #387 l ·机 ...

  5. HanLP中的人名识别分析详解

    在看源码之前,先看几遍论文<基于角色标注的中国人名自动识别研究> 关于命名识别的一些问题,可参考下列一些issue: u u名字识别的问题 #387 u u机构名识别错误 u u关于层叠H ...

  6. BM算法  Boyer-Moore高质量实现代码详解与算法详解

    Boyer-Moore高质量实现代码详解与算法详解 鉴于我见到对算法本身分析非常透彻的文章以及实现的非常精巧的文章,所以就转载了,本文的贡献在于将两者结合起来,方便大家了解代码实现! 算法详解转自:h ...

  7. kmp算法详解

    转自:http://blog.csdn.net/ddupd/article/details/19899263 KMP算法详解 KMP算法简介: KMP算法是一种高效的字符串匹配算法,关于字符串匹配最简 ...

  8. [转] KMP算法详解

    转载自:http://www.matrix67.com/blog/archives/115 KMP算法详解 如果机房马上要关门了,或者你急着要和MM约会,请直接跳到第六个自然段.    我们这里说的K ...

  9. 【转】AC算法详解

    原文转自:http://blog.csdn.net/joylnwang/article/details/6793192 AC算法是Alfred V.Aho(<编译原理>(龙书)的作者),和 ...

随机推荐

  1. 分享一个腾讯域名拦截检测api

    接口地址:https://api.oioweb.cn/api/ymjc.php 返回格式:json 请求方式:get 调用示例:https://api.oioweb.cn/api/ymjc.php?u ...

  2. The sequence and de novo assembly of the giant panda genome.ppt

    sequencing:使用二代测序原因:高通量,短序列 不用长序列原因: 1.算法错误率高 2.长序列测序将嵌合体基因错误积累.嵌合体基因:通过重组由来源与功能不同的基因序列剪接而形成的杂合基因 se ...

  3. 网站爬取-案例一:猫眼电影TOP100

    今天有小朋友说想看一下猫眼TOP100的爬取数据,要TOP100的名单,让我给发过去,其实很简单,先来看下目标网站: 建议大家都用谷歌浏览器: 这是我们要抓取的内容,100个数据,很少 我们看一下页面 ...

  4. SpringBoot开发二十-Redis入门以及Spring整合Redis

    安装 Redis,熟悉 Redis 的命令以及整合Redis,在Spring 中使用Redis. 代码实现 Redis 内置了 16 个库,索引是 0-15 ,默认选择第 0 个 Redis 的常用命 ...

  5. 4)date中的Ymd格式问题

    以下是详细的参数: format 字符 说明 返回值例子日 --- ---d 月份中的第几天,有前导零的 2 位数字 01 到 31D 星期中的第几天,文本表示,3 个字母 Mon 到 Sunj 月份 ...

  6. python3下scrapy爬虫(第四卷:初步抓取网页内容之抓取网页里的指定数据延展方法)

    上卷中我运用创建HtmlXPathSelector 对象进行抓取数据: 现在咱们再试一下其他的方法,先试一下我得最爱XPATH 看下结果: 直接打印出结果了 我现在就正常拼下路径 只求打印结果: 现在 ...

  7. SPA(单页面web应用)和MPA(多页面web应用)的区别

    转:https://blog.csdn.net/amaniz/article/details/79203562 vue多页面应用开发请参见: https://github.com/amunamuna/ ...

  8. Mysql错误问题:ERROR 1005 (HY000): Can't create table 'crm_1.tbl_client' (errno: 150)

    MySQL外键创建条件: 1.两个表必须是InnoDB数据引擎2.外键表的外键字段必须是主键3.字段类型必须一致 创建表时创建外键: create table tbl_client(userName ...

  9. <JZOJ1329>旅行

    贪心大水题 #include<cstdio> #include<iostream> #include<cstring> #include<algorithm& ...

  10. 自定义一个简单的SegmentedControl

    先大概看下我们想实现简单的效果 源码 // // DSegmentedControl.swift // IOS学习之自定义UISegmentedControl // // Created by din ...