RM视觉学习

问题:将两个装甲板的同侧俩灯条识别成装甲板

解决思路:

  1. 找出图片中所有灯条
  2. 根据长宽比,面积大小,凸度来筛选灯条
  3. 合适匹配
  4. 配对灯条作为候选装甲板,然后提取中间的图案判断是否是数字来进行确定

用红蓝通道相减得到的差作为识别用的灰度图

 

对于图像中红色的物体来说,其rgb分量中r的值最大,g和b在理想情况下应该是0,同理蓝色物体的b分量应该最大。

如果识别红色物体可以直接用r通道-b通道。由于在低曝光下只有灯条是有颜色的,两通道相减后,其他区域的部分会因为r和b的值差不多而被减去,而蓝色灯条部分由于r通道比b通道的值小,相减后就归0了,也就是剩下的灰度图只留下了红色灯条。

// 把一个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;

得到灰度图后需要阈值化处理得到二值图,之后可以进行膨胀处理让图像中的轮廓更明显。

Mat binBrightImg;
//阈值化
threshold(_grayImg, binBrightImg,_param.brightness_threshold
              , 255, cv::THRESH_BINARY);
Mat element = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(3, 3));//选元
//膨胀
dilate(binBrightImg, binBrightImg, element);

找轮廓,这步是整个算法中最耗时的部分,如果预处理做的好,可以极大地减少找轮廓中花费的时间。

vector<vector<Point>> lightContours;
//找轮廓
findContours(binBrightImg.clone(), lightContours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

找到轮廓后开始遍历轮廓提取灯条

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;
          //因为颜色通道相减后己方灯条直接过滤,不需要判断颜色了,可以直接将灯条保存
          lightInfos.push_back(LightDescriptor(lightRec));
      }
//没找到灯条就返回没找到
if(lightInfos.empty())
{
	return _flag = ARMOR_NO;
}

对灯条进行匹配筛选

      //按灯条中心x从小到大排序
sort(lightInfos.begin(), lightInfos.end(), [](const LightDescriptor& ld1, const LightDescriptor& ld2)
      {//Lambda函数,作为sort的cmp函数
	return ld1.center.x < ld2.center.x;
});
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];
		/*
		*	Works for 2-3 meters situation
		*	morphologically similar: // parallel 
						 // similar height
		*/
              //角差
              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;
              //左右灯条中心点x的差值
              float xDiff = abs(leftLight.center.x - rightLight.center.x);
              //x差比率
              float xDiff_ratio = xDiff / meanLen;
              //相距距离与灯条长度比值
              float ratio = dis / meanLen;
              //筛选
              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;
		float rotationScore = -(ratiOff * ratiOff + yOff * yOff);
              //得到匹配的装甲板
              ArmorDescriptor armor(leftLight, rightLight, armorType, channels.at(1), rotationScore, _param);

		_armors.emplace_back(armor);
		break;
	}
}
//没匹配到装甲板则返回没找到
if(_armors.empty())
{
	return _flag = ARMOR_NO;
}

对找到的装甲板进行筛选

//delete the fake armors
   _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;
	}
}

判断是不是装甲板我用的是模板匹配的方法,模板匹配特别适合待识别图片不会变化的场景,选择合适的模板可以得到很高的准确率并且花费时间远小于svm等机器学习方法。

//模板匹配 根据装甲板中心的图案判断是不是装甲板
bool ArmorDescriptor::isArmorPattern(std::vector<cv::Mat> &small,
                                     std::vector<cv::Mat> &big ,
                                     LastenemyType &lastEnemy)
{
    //若需要判断装甲中间数字
#ifdef IS_ARMOR
    vector<pair<int,double>> score;
    map<int,double> mp;
    Mat regulatedImg=frontImg;
	
    for(int i=0;i<8;i++){
        //载入模板,模板是在初始化的时候载入ArmorDetector类,
        //因为ArmorDescriptor与其非同类需要间接导入
        Mat tepl=small[i];
        Mat tepl1=big[i];
        //模板匹配得到位置,这里没用
        cv::Point matchLoc;
        //模板匹配得分
        double value;
		//匹配小装甲
        value = TemplateMatch(regulatedImg, tepl, matchLoc, CV_TM_CCOEFF_NORMED);
        mp[i+1]=value;
        score.push_back(make_pair(i+1,value));
		//匹配大装甲
        value = TemplateMatch(regulatedImg, tepl1, matchLoc, CV_TM_CCOEFF_NORMED);
        mp[i+11]=value;
        score.push_back(make_pair(i+11,value));
    }
    //对该装甲与所有模板匹配后的得分进行排序
    sort(score.begin(),score.end(), [](const pair<int,double> &a, const pair<int,double> &b)
    {
        return a.second > b.second;
    });
    //装甲中心位置
    cv::Point2f c=(vertex[0]+vertex[1]+vertex[2]+vertex[3])/4;
	//装甲数字即为得分最高的那个
    int resultNum=score[0].first;
    //得分太低认为没识别到数字
    if(score[0].second<0.6)
    {
        if(//与上次识别到的装甲板位置差不多,且丢失次数不超过一定值
                std::abs(std::abs(lastEnemy.center.x)-std::abs(c.x))<10&&
                std::abs(std::abs(lastEnemy.center.y)-std::abs(c.y))<10&&
                lastEnemy.lostTimes<100
                )
        {//认为该装甲的数字与上次相同
            lastEnemy.lostTimes++;
            lastEnemy.center=c;
            enemy_num=lastEnemy.num;
            return true;
        }
        else
        {//认为不是装甲
            return false;
        }
    }
    //当装甲板识别为小装甲,而得到的号码为11、22……时,说明把大装甲识别为了小装甲。
    if(type==SMALL_ARMOR )
    {
        if(resultNum>10)
        {
            type=BIG_ARMOR;
        }
    }
    enemy_num=resultNum%10;
    lastEnemy.num=enemy_num;
    lastEnemy.center=c;
    lastEnemy.lostTimes=0;
    return true;
#endif
    //若不需要判断匹配的装甲板中的数字则整个函数直接返回true
#ifndef IS_ARMOR
    return true;
#endif
}

 

  • 1
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值