matchLights
本函数将灯条两两匹配,条件判断和传入conTain()判断是否为装甲板将符合的装甲板放入candidateArmors容器
这个部分主要是对灯条的两两配对,主要通过物理特征
// 将旋转矩形从左到右排序
sort(candidateLights.begin(), candidateLights.end(),
[](RotatedRect& a1, RotatedRect& a2) {
return a1.center.x < a2.center.x; });
sort函数是一种快速排序算法,其参数要求传入指针和函数指针,写法较为固定
这里的函数写法名为 “lambda表达式” 也可以叫匿名函数,是一种C++引入的新特性
通过这个方法,我们就可以不用再写一个函数,简化代码
下面则是对每两个灯条进行匹配:
Light lightI = candidateLights[i];
Point2f centerI = lightI.center; // 灯条1中心点
for (size_t j = i + 1; j < candidateLights.size(); j++)
{
Light lightJ = candidateLights[j];
Point2f centerJ = lightJ.center; // 灯条2中心点
double armorWidth = POINT_DIST(centerI,centerJ) - (lightI.width + lightJ.width)/2.0; // 计算装甲板宽度
double armorHeight = (lightI.height + lightJ.height) / 2.0; // 计算装甲板高度
double armor_ij_ratio = lightI.height / lightJ.height; // 计算两灯条高度比例
double armorAngle = atan2((centerI.y - centerJ.y),fabs(centerI.x - centerJ.x))/CV_PI*180.0; // 计算装甲板倾斜角度
// 宽高比筛选条件
bool small_wh_ratio_ok = armor_small_min_wh_ratio < armorWidth/armorHeight && armorWidth/armorHeight < armor_small_max_wh_ratio;
bool big_wh_ratio_ok = armor_big_min_wh_ratio < armorWidth/armorHeight && armorWidth/armorHeight < armor_big_max_wh_ratio;
bool wh_ratio_ok = small_wh_ratio_ok || big_wh_ratio_ok;
// 左右灯条角度差筛选条件
bool angle_offset_ok = fabs(lightI.angle - lightJ.angle) < armor_max_offset_angle;
// 左右亮灯条中心点高度差筛选条件
bool height_offset_ok = fabs(lightI.center.y - lightJ.center.y) / armorHeight < armor_height_offset;
// 左右灯条的高度比
bool ij_ratio_ok = armor_ij_min_ratio < armor_ij_ratio && armor_ij_ratio < armor_ij_max_ratio;
// 候选装甲板角度筛选条件
bool angle_ok = fabs(armorAngle) < armor_max_angle;
// 条件集合
bool is_like_Armor = wh_ratio_ok && angle_offset_ok && height_offset_ok && ij_ratio_ok && angle_ok;
// 判断为装甲板
if (is_like_Armor)
{
Point2f armorCenter = (centerI + centerJ) / 2.0;
RotatedRect armor_rrect = RotatedRect(armorCenter,
Size2f(armorWidth,armorHeight),
-armorAngle);
// 装甲板4点坐标 (从左下角开始逆时针)
Point2f pt4[4] = { lightI.bottom, lightJ.bottom, lightJ.top, lightI.top };
// conTain()判断两个灯条是否包含了其他灯条
if (!conTain(armor_rrect,candidateLights,i,j))
{
Armor armor(armor_rrect);
for(int index = 0; index < 4; index++)
armor.armor_pt4[index] = pt4[index];
// 判断大小装甲板
if(small_wh_ratio_ok)
armor.type = SMALL;
else
armor.type = BIG;
// 判断装甲板的 id
preImplement(armor);
// 将符合要求的装甲板放入 candidateArmors 容器
candidateArmors.emplace_back(armor);
#ifdef DRAW_ARMORS_RRT
//cout<<"LightI_angle : "<<lightI.angle<<" LightJ_angle : "<<lightJ.angle<<" "<<fabs(lightI.angle - lightJ.angle)<<endl;
//cout<<"armorAngle : "<<armorAngle * 180 / CV_PI <<endl;
//cout<<" w/h : "<<armorWidth/armorHeight<<endl;
//cout<<"height-offset: "<<fabs(lightI.height - lightJ.height) / armorHeight<<endl;
//cout<<" height-ratio: "<<armor_ij_ratio<<endl;
Mat showSrc = _src.clone();
Point2f vertice_armors[4];
armor.points(vertice_armors);
for (int m = 0; m < 4; m++)
{
line(showSrc, vertice_armors[m], vertice_armors[(m + 1) % 4], CV_RGB(0, 255, 255),2,LINE_8);
}
//circle(showSrc,armorCenter,15,Scalar(0,255,255),-1);
// namedWindow("showSrc",WINDOW_NORMAL);
imshow("showSrc", showSrc);
putText(showSrc,to_string(armorAngle),armor.armor_pt4[3],FONT_HERSHEY_COMPLEX,1.0,Scalar(0,255,255),2,8);
#endif //DRAW_ARMORS_RRT
}
}
}
这里物理特征匹配不进行讲解,请自行观看
这里需要注意的就是装甲板的角点顺序,从左下角开始逆时钟进行计算
如图:

请记住装甲板的角点顺序
这里是对装甲板数字的一个存储函数,请看:
// 判断装甲板的 id
preImplement(armor);
// 功能: 获取传入装甲板的数字ROI,放置在numROIs容器内
void ArmorDetector::preImplement(Armor& armor)
{
Mat numDst;
Mat num;
// Light length in image
const int light_length = 14; //大致为高的一半
// Image size after warp
const int warp_height = 30;
const int small_armor_width = 32;//为48/3*2
const int large_armor_width = 44;//约为70/3*2
// Number ROI size
const cv::Size roi_size(22, 30);
const int top_light_y = (warp_height - light_length) / 2;
const int bottom_light_y = top_light_y + light_length;
//std::cout<<"type:"<<armor.type<<std::endl;
// 大小装甲板的宽度不同
const int warp_width = armor.type == SMALL ? small_armor_width : large_armor_width;
cv::Point2f target_vertices[4] = {
cv::Point(0, bottom_light_y),
cv::Point(warp_width, bottom_light_y),
cv::Point(warp_width, top_light_y),
cv::Point(0, top_light_y),
};
const Mat& rotation_matrix = cv::getPerspectiveTransform(armor.armor_pt4, target_vertices);
cv::warpPerspective(_src, numDst, rotation_matrix, cv::Size(warp_width, warp_height));
// Get ROI
numDst = numDst(cv::Rect(cv::Point((warp_width - roi_size.width) / 2, 0), roi_size));
Dnn_Detect.img_processing(numDst, numROIs);
}
这里会将认为是装甲板的中心部分进行剪裁缩放,然后存放到 numROIs 容器中,以便后续进行数字识别
warpPerspective 是OpenCV库中用于执行透视变换的函数之一。透视变换可以将图像从一个透视投影转换为另一个透视投影,实现图像的旋转、缩放、平移等操作,我们通过这个函数来剪裁数字部分
关于Dnn_Detect,这个是一个封装好的推理类,在后续chooseTarget中的会进行简单介绍