前言
雷达基站检测想了很久要不要写出来,鉴于没有什么有动力的事情去做了,所以我还是把博客写出来同时对代码在进行一次检查.
相机选择
实际比赛的过程中对于相机的选择很重要,本次选择的是大华相机USB3的接口,其分辨率为1280*1024,选择的镜头是6mm的,可以通过一组公式进行计算我们的视场角.
先求x轴的视场角:
S
=
4.8
∗
640
/
1000
=
3.072
m
m
S=4.8*640/1000=3.072mm
S=4.8∗640/1000=3.072mm
2
∗
θ
=
2
∗
arctan
(
3.072
/
6
)
=
2
∗
27.112
=
54.2
2*\theta= 2*\arctan(3.072/6)=2*27.112=54.2
2∗θ=2∗arctan(3.072/6)=2∗27.112=54.2
同理可以求出y的视场角
通过solidworks绘图可以看到红色的效果如下所示
代码思路
流程图
基站检测的思路很简单,首先通过运动物体检测检测出来所有的前景图像(这里采用的是背景减弱法的KNN),然后判断整辆车的颜色,然后寻找所有灯条和匹配的灯条,最后找到装甲板,并识别装甲板的数字ID,然后在与追踪进行融合.
前景提取
类似这样的 cv::Ptr<cv::BackgroundSubtractor> KNN
这里面的knn可以替换成 cv::Ptr<cv::BackgroundSubtractor> MOG2
.其实运动物体检测只有三个步骤,
首先要定义一个 Background Subtractor objects,然后创建一个 Background Subtractor objects,最后,通过apply更新背景模型.
int main()
{
cv::Mat frame;
cv::Ptr<cv::BackgroundSubtractor> KNN; //Background subtractor
cv::VideoCapture capBase; //创建VideoCapture对象
capBase.open("/home/demon/CLionProjects/jidi_radar/video/20200506T202230/Realtime_Base.avi");
if(!capBase.isOpened()){ //检查是否能正常打开视频文件
std::cout<<"fail to open Base video"<<std::endl;
}
KNN = cv::createBackgroundSubtractorKNN(); //创建 Background Subtractor objects
while(1)
{
capBase >> frame;
if (frame.empty())//如果某帧为空则退出循环
break;
cv::Mat mask_knn,src_gray,thresh_knn; //通过knn方法得到的掩码图像fgmask
cvtColor(frame, src_gray, cv::COLOR_BGR2GRAY);
KNN->apply(src_gray, mask_knn); //更新背景模型
threshold(mask_knn, thresh_knn, 50, 255, cv::THRESH_OTSU);//THRESH_BINARY,二值化
cv::Mat element = getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(3, 3));
cv::Mat element2 = getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(30, 30));
morphologyEx(thresh_knn, thresh_knn, cv::MORPH_ERODE, element);//腐蚀
morphologyEx(thresh_knn, thresh_knn, cv::MORPH_DILATE, element2);//膨胀
medianBlur(thresh_knn, thresh_knn, 9); //中值滤波
cv::imshow("dfdf",thresh_knn);
cv::waitKey(20);
}
}
颜色判断
对于颜色识别一般有两种方法,可以采用RGB中的三通道分量进行判断,还有的就是通过HSV的方式进行判断.
本次进行的颜色判断采用二者结合的方法,首先通过颜色的范围对他进行二值化,然后在对比RGB的值.
//输入图片
//判断颜色,输出1为红色,否则蓝色
int usingHSV(const cv::Mat bgr_roi)
{
//对轮廓颜色面积比例进行约束,首先对rgb进行约束,进而对hsv进行约束
cv::Mat hsv_roi, color_mask_roi1,color_mask_roi2;
cv::cvtColor(bgr_roi, hsv_roi, cv::COLOR_BGR2HSV);
cv::Mat hsv1, hsv2;
cv::inRange(hsv_roi, cv::Scalar(0, 43, 46), cv::Scalar(10, 255, 255), hsv1); //红色
cv::inRange(hsv_roi, cv::Scalar(156, 43, 46), cv::Scalar(180, 255, 255), hsv2);
color_mask_roi1 = hsv1 + hsv2;
cv::inRange(hsv_roi, cv::Scalar(100, 43, 46), cv::Scalar(124, 255, 255), color_mask_roi2);//蓝色
int correct_pxl1=0,correct_pxl2=0;
for (int j=0; j < color_mask_roi1.rows; j++)
{
auto *hsv_ptr1 = color_mask_roi1.ptr<uchar>(j);
auto *bgr_ptr1 = bgr_roi.ptr<uchar>(j);
for (int k = 0; k < color_mask_roi1.cols; k++)
{
auto hsv_val1 = hsv_ptr1[k]; //第j行,第k列
auto b1 = bgr_ptr1[k * 3];
auto r1 = bgr_ptr1[k * 3 + 2];
if (hsv_val1 && r1 - b1 > 0)
correct_pxl1++;
}
}
for (int j=0; j < color_mask_roi2.rows; j++)
{
auto *hsv_ptr2 = color_mask_roi2.ptr<uchar>(j);
auto *bgr_ptr2 = bgr_roi.ptr<uchar>(j);
for (int k = 0; k < color_mask_roi2.cols; k++)
{
auto hsv_val2 = hsv_ptr2[k]; //第j行,第k列
auto b2 = bgr_ptr2[k * 3];
auto r2 = bgr_ptr2[k * 3 + 2];
if(hsv_val2 && r2-b2 < 0)
correct_pxl2++;
}
}
// std::cout << "correct_pxl1:"<<correct_pxl1 << " _correct_pxl2:"<<correct_pxl2 << std::endl;
if(correct_pxl1 < 40 && correct_pxl2 < 40)
{ return -1;}
return correct_pxl1 > correct_pxl2;
}
数字识别
数字识别可以参考我的另外一篇文章.
由于该相机安装高度和角度与第一人称视角和其他视角有所区别,经过测试,在开源的数据集中以及第一视角提取的样本的基础上训练得到的模型,识别效果很差,做出了如下改进:
1.使用该相机拍摄车辆的运动数据,使用装甲板识别功能提取图片作为训练用样本集;
2.设立负样本集合,将环境下的背景图,灯光效果,影子效果等容易误识别的图片作为负样本集合;
3,训练前,将图片归一化为25*25左右大小,图片缩小以后效果变好;训练步骤比较简单,主要是数据集的制作和处理比较麻烦以及需要技巧;
多目标追踪
多目标追踪有很多种,比如KCF,CSRT,MIL,Boosting,MedianFlow,TLD,GOTURN,MOSSE等.
/**
* 多目标追踪器初始化
*/
void DetectVehicle::multiTrackInit(cv::Mat frame,Vehicles &track_targets)
{
std::string trackerType = "CSRT"; //KCF
multiTracker = cv::MultiTracker::create();
// initialize multitracker
for (int i = 0; i < track_targets.size(); i++)
multiTracker->add(cv::TrackerKCF::create(), frame, track_targets[i].VehicleRect);
std::cout << "start tracking" << std::endl;
}
在多目标的API里面,用add方法代替了init方法,给trackers传入每个ROI的跟踪器和边界框。
注意这里,传入的边界框数据类型是Rect2d,因为涉及到计算,所以需要double类型,需要转换一下。track_targets[i].VehicleRect是vector类型.
以下是进行绘制追踪框图
/**
* 目标追踪
*/
void DetectVehicle::trackVehicle(cv::Mat frame,Vehicles &track_targets)
{
cv::Mat img(frame.size() , CV_8UC1, cv::Scalar(0));
std::vector<cv::Rect2d> TargetsRects;
TargetsRects.clear();
if(!multiTracker->update(frame,TargetsRects)){
std::cout<<"追踪失败"<<std::endl;
}
static int contour_area;
// draw tracked objects
for(unsigned i=0; i<TargetsRects.size(); i++)
{
// 获取相较于追踪区域两倍长款的区域,用于重新搜索
cv::Rect2d bigger_rect;
bigger_rect.x = TargetsRects[i].x - TargetsRects[i].width / 2.0;
bigger_rect.y = TargetsRects[i].y - TargetsRects[i].height / 2.0;
bigger_rect.height = TargetsRects[i].height * 2;
bigger_rect.width = TargetsRects[i].width * 2;
bigger_rect &= cv::Rect2d(0, 0, 1280, 1024);
cv::Mat roi = frame(bigger_rect).clone();
cv::Mat roi_gray;
cv::cvtColor(roi, roi_gray, CV_RGB2GRAY);
cv::threshold(roi_gray, roi_gray, 180, 255, cv::THRESH_BINARY);
contour_area = cv::countNonZero(roi_gray); //返回不为零的像素数
if(abs(cv::countNonZero(roi_gray) - contour_area) > contour_area * 0.3){
std::cout<<"追踪失败aa"<<std::endl;
}
cv::putText(frame, intToString(track_targets[i].VehicleID),TargetsRects[i].tl(), cv::FONT_HERSHEY_COMPLEX, 1, cv::Scalar(0,0,255));
}
}
车辆的ID确定
即只要判断每个运动物体上面的哪一种id号最多,那么就可以得到该车辆的ID.
总结
至此,雷达基站检测的关键部分已经写完.他的作用就是, 最终基站在检测到基地附近敌我分布之后能够通过裁判系统得到所有机器人的信息,并生成威胁系数:比如首先集火血量低的车辆;识别友军集火目标辅助攻击;根据当前局势选择攻防策略;通过远距离预警补充哨兵视野;分析敌方行为动向等。