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()
)等后續有機會再准備。