机甲大师:矩形框选算法(23/4/23已更新)
2023/4/17:由于大创的关系,我把这个算法整合进新项目中,所以同时修复了代码中的一些问题,比如全局对象问题,当然挺多没改的(代码是大一写的,现在看看真的是屎。)
效果如图:
——————————————————分割线—————————————————
本项目是一个基于Opencv与C++的项目,主要实现了装甲板灯条的识别,并用矩形动态框选
本项目是我RM视觉初学者时,结合队友的项目基础,一点点磨出来的,算是一个优化版本。
PS:这个版本矩形框不会跳动,很稳定,我优化了很久,但视频帧率有点低
整个项目我在初学时都标注了详尽的注释,可谓花了很多功夫。希望能帮到各位有需要的人吧。
个人额外提醒:注意项目中的 功能4:斜矩形框选 ,那个是优化的精髓。我研究了很久,可能是我当时Opencv基础较差的原因,可以让矩形框选保持稳定。
项目配置:
采用Opecv4以上版本:
#include <iostream> #include<vector> #include<opencv2/opencv.hpp> #include<opencv2/highgui/highgui_c.h> #include<imgproc/imgproc.hpp> #include<core/core.hpp> using namespace std; using namespace cv; class rec_recognition { public: int match_num; vector<RotatedRect>rect_match; vector<Point3i>level_end; Mat src; Mat src_end, dst; Mat GrayImg; Mat BinBrightImg; Mat BinGrayImg; Mat final; double max(double first, double secend); double min(double first, double secend); void pre_treatment(); void application(); int match(); private: int index = 0, maxlevel = 0; RotatedRect rect, leftrect, rightrect; Rect rect_first; Point2f currentCenter; double area[2]; double height = (leftrect.size.height + rightrect.size.height) / 2; }; int main(){ VideoCapture capture("/Users/nathanchen/Downloads/rec_recognition/rectangle.mp4"); //这里输入文件 rec_recognition apply; while (1){ capture >> apply.src; apply.pre_treatment(); apply.match(); if (apply.match_num == 1){ //如果match处匹配成功,那就返回1 apply.application(); //imshow("rec_recognition", apply.src_end); imshow("rec_recognition1", apply.src); } apply.rect_match.clear(); apply.level_end.clear(); waitKey(1000 / capture.get(CAP_PROP_FPS)); //每隔多少秒读下一帧 } return 0; } void rec_recognition::pre_treatment(){ //色彩通道处理 std::vector<Mat> channels;// 把一个3通道图像转换成3个单通道图像 split(src, channels);//分离色彩通道 //只留蓝色(可以修改成其他颜色)——————————————————————————————————————修改颜色 GrayImg = channels.at(0) - channels.at(2); //阈值处理 threshold(GrayImg, BinBrightImg, 55, 255, THRESH_BINARY); threshold(GrayImg, BinGrayImg, 25, 255, THRESH_BINARY); //形态学操作(膨胀) Mat element_d = getStructuringElement(MORPH_ELLIPSE, Size(2, 2));//形态学操作 dilate(BinBrightImg, BinBrightImg, element_d);//膨胀 //与操作(?????) BinBrightImg &= BinGrayImg; //形态学操作(膨胀与腐蚀) Mat element_d0 = getStructuringElement(MORPH_ELLIPSE, Size(3, 3)); Mat element_e0 = getStructuringElement(MORPH_ELLIPSE, Size(3, 3)); //再次腐蚀膨胀,为了让线条更连贯 dilate(BinBrightImg, BinBrightImg, element_d0); erode(BinBrightImg, final, element_e0); //--------------------------寻找矩形框架---------------------------------// vector<vector<Point>> contours; vector<Vec4i> hierarchy; Point2f vertex[4]; findContours(final, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(0, 0)); //筛选矩形 for (int i = 0; i < hierarchy.size(); ++i) { rect = minAreaRect(contours.at(i)); if (rect.size.width > rect.size.height) { swap(rect.size.width, rect.size.height); rect.angle += 90.0; } while (rect.angle >= 90.0)rect.angle -= 180.0; while (rect.angle <= -90.0)rect.angle += 180.0; if (rect.size.width * rect.size.height < 1000) { if (rect.size.width < rect.size.height / 2.5) { if (rect.angle < 45 || rect.angle>135) { rect_match.push_back(rect); //将匹配好的矩形放入数组(rect_match)中 } } } //测试:矩形框选灯条 /*rect_first = boundingRect(contours.at(i)); if (rect_first.width < rect_first.height) { rectangle(src, rect_first, Scalar(255, 0, 255), 2, LINE_8); }*/ } cout << rect_match.size() << endl; //清空数组,防止内存泄漏 contours.clear(); hierarchy.clear(); //imshow("Shlimazl1", final); } int rec_recognition::match(){ int level = 0; if (rect_match.size() < 2){ return match_num = 0; }else{ for (int j = 0; j < rect_match.size(); ++j){ for (int k = j + 1; k < rect_match.size(); ++k){ int level = 0; leftrect = rect_match[j]; rightrect = rect_match[k]; //左右矩形高匹配 if ((min(leftrect.size.height, rightrect.size.height) / max(leftrect.size.height, rightrect.size.height)) == 1) { level += 10; } else if ((min(leftrect.size.height, rightrect.size.height) / max(leftrect.size.height, rightrect.size.height)) > 0.95) { level += 8; } else if ((min(leftrect.size.height, rightrect.size.height) / max(leftrect.size.height, rightrect.size.height)) > 0.9) { level += 6; } else if ((min(leftrect.size.height, rightrect.size.height) / max(leftrect.size.height, rightrect.size.height)) > 0.85) { level += 4; } else if ((min(leftrect.size.height, rightrect.size.height) / max(leftrect.size.height, rightrect.size.height)) > 0.8) { level += 2; } //左右矩形角度匹配 if (leftrect.angle == rightrect.angle) { level += 10; } else if (abs(leftrect.angle - rightrect.angle) < 4) { level += 8; } else if (abs(leftrect.angle - rightrect.angle) < 8) { level += 6; } else if (abs(leftrect.angle - rightrect.angle) < 12) { level += 4; } else if (abs(leftrect.angle - rightrect.angle) < 16) { level += 2; } //左右矩形面积匹配 area[0] = leftrect.size.width * leftrect.size.height; area[1] = rightrect.size.width * rightrect.size.height; if (area[0] == area[1]) { level += 10; } else if (min(area[0], area[1]) * 1.2 > max(area[0], area[1])) { level += 8; } else if (min(area[0], area[1]) * 1.5 > max(area[0], area[1])) { level += 6; } else if (min(area[0], area[1]) * 1.8 > max(area[0], area[1])) { level += 4; } else if (min(area[0], area[1]) * 2.1 > max(area[0], area[1])) { level += 2; } //左右矩形中心的y值匹配 if (leftrect.center.y == rightrect.center.y) { level += 10; } else if (abs(leftrect.center.y - rightrect.center.y) < 0.05 * height) { level += 8; } else if (abs(leftrect.center.y - rightrect.center.y) < 0.1 * height) { level += 6; } else if (abs(leftrect.center.y - rightrect.center.y) < 0.15 * height) { level += 4; } else if (abs(leftrect.center.y - rightrect.center.y) < 0.2 * height) { level += 2; } //左右矩形中心的x值匹配 if ((abs(leftrect.center.x - rightrect.center.x) / 2.5 * height) == 1) { level += 10; } else if (1 > (abs(leftrect.center.x - rightrect.center.x) / 2.5 * height) > 0.9) { level += 8; } else if (0.9 > (abs(leftrect.center.x - rightrect.center.x) / 2.5 * height) > 0.8) { level += 6; } else if (0.8 > (abs(leftrect.center.x - rightrect.center.x) / 2.5 * height) > 0.7) { level += 4; } else if (0.7 > (abs(leftrect.center.x - rightrect.center.x) / 2.5 * height) > 0.6) { level += 2; } level_end.push_back(Point3i(j, k, level)); //level_end 为一个数组 , 为什么要用3D坐标系???秀技??? } } //level 的比较 int maxlevel = 0; for (int C = 0; C < level_end.size(); ++C) { if (level_end[C].z > maxlevel) { maxlevel = level_end[C].z; index = C; } } // rect_match[level_end[index] //首先我们筛选出优质的矩形对,这里面包含(左矩形的标号,右矩形的标号,权值),这里是一个三元数组,x为左矩形,y为右 //输出中心点坐标 currentCenter.x = (rect_match[level_end[index].x].center.x + rect_match[level_end[index].y].center.x) / 2; currentCenter.y = (rect_match[level_end[index].x].center.y + rect_match[level_end[index].y].center.y) / 2; //return currentCenter, rect_match[level_end[index].x].center, rect_match[level_end[index].y].center; return match_num = 1; } } void rec_recognition::application() { Point2f points1[4]; Point2f points2[4]; Point2f Leftc; Point2f Rightc; //注意:在视频中,左右矩形会交换 rect_match[level_end[index].y].points(points2); rect_match[level_end[index].x].points(points1); Leftc = rect_match[level_end[index].x].center; Rightc = rect_match[level_end[index].y].center; //功能1:输出中心点坐标 //cout << "//*********************************************" << endl; //cout << "(" << Leftc.x << "," << Leftc.y << ")" << endl; //cout << "(" << Rightc.x << "," << Rightc.y << ")" << endl; //cout << "*********************************************//" << endl; //功能2:画出中心点 //点型 //circle(apply.src, currentCenter, 2, Scalar(0, 0, 255), 2, 8, 0); //十字型 line(src, Point(currentCenter.x - 5, currentCenter.y), Point(currentCenter.x + 5, currentCenter.y), Scalar(0, 0, 255), 2, 8, 0); line(src, Point(currentCenter.x, currentCenter.y - 5), Point(currentCenter.x, currentCenter.y + 5), Scalar(0, 0, 255), 2, 8, 0); //功能3:正矩形框选 /* line(apply.src, Point(currentCenter.x + (Leftc.x - Rightc.x) / 2, currentCenter.y - (Leftc.x - Rightc.x) / 4), Point(currentCenter.x - (Leftc.x - Rightc.x) / 2, currentCenter.y - (Leftc.x - Rightc.x) / 4), Scalar(0, 0, 255), 2, 8); line(apply.src, Point(currentCenter.x + (Leftc.x - Rightc.x) / 2, currentCenter.y + (Leftc.x - Rightc.x) / 4), Point(currentCenter.x + (Leftc.x - Rightc.x) / 2, currentCenter.y - (Leftc.x - Rightc.x) / 4), Scalar(0, 0, 255), 2, LINE_8); line(apply.src, Point(currentCenter.x - (Leftc.x - Rightc.x) / 2, currentCenter.y + (Leftc.x - Rightc.x) / 4), Point(currentCenter.x - (Leftc.x - Rightc.x) / 2, currentCenter.y - (Leftc.x - Rightc.x) / 4), Scalar(0, 0, 255), 2, 8); line(apply.src, Point(currentCenter.x + (Leftc.x - Rightc.x) / 2, currentCenter.y + (Leftc.x - Rightc.x) / 4), Point(currentCenter.x - (Leftc.x - Rightc.x) / 2, currentCenter.y + (Leftc.x - Rightc.x) / 4), Scalar(0, 0, 255), 2, 8);*/ //功能4:斜矩形框选 //一开始points1[0]在右矩形,然后过一段时间它会跳到左边 if (points2[0].x <= points1[0].x) { //矩形的长 line(src, points1[2], points2[1], Scalar(0, 0, 255), 2, 8, 0); line(src, points1[3], points2[0], Scalar(0, 0, 255), 2, 8, 0); //矩形的宽 line(src, points1[2], points1[3], Scalar(0, 0, 255), 2, 8, 0); line(src, points2[1], points2[0], Scalar(0, 0, 255), 2, 8, 0); } else if (points2[0].x > points1[0].x) { //矩形的长 line(src, points1[0], points2[3], Scalar(0, 0, 255), 2, 8, 0); line(src, points1[1], points2[2], Scalar(0, 0, 255), 2, 8, 0); //矩形的宽 line(src, points1[0], points1[1], Scalar(0, 0, 255), 2, 8, 0); line(src, points2[2], points2[3], Scalar(0, 0, 255), 2, 8, 0); } //test(点测试) /* circle(apply.src, points1[0], 2, Scalar(0, 0, 255), 2, 8, 0); circle(apply.src, points1[1], 2, Scalar(0, 0, 255), 2, 8, 0); circle(apply.src, points2[0], 2, Scalar(255, 0, 0), 2, 8, 0); circle(apply.src, points2[1], 2, Scalar(255, 0, 0), 2, 8, 0);*/ //下一步的操作: //预测:重力加速度,重力为g, //做好预测 } double rec_recognition::max(double first, double second){ return first > second ? first : second; } double rec_recognition::min(double first, double second){ return first < second ? first : second; }