基于opencv华为杯识别红蓝牌箭头方向

好久没更博客了, 借此机会复习一下之前学过的东西

话不多说, 先看要识别的图片

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 红色和蓝色的差不多, 只要调好hsv色域就行(创建滑动条), 本程序就以蓝色的为例

牌反光后无法识别的问题并没有解决

程序思路

  1. 进行二值化和多次腐蚀操作后找到牌的轮廓和所有点的轮廓, 利用sort()函数对轮廓进行从大到小的排序, 面积最大的轮廓为牌的轮廓, 其余的为孔的轮廓, 并绘制出牌的外边框(寻找最小旋转矩形)
  2. 对外边界的矩形顶点进行编号(编好号的顺序短边必定为03和12且03一直位于12的下方, 并以逆时针顺序排序), 利用已经编好号的四个顶点确定矩形平行于长边的中线L, 和编号0,3组成的短边M 
  3. 将所有孔的轮廓转换为最小旋转矩形(为得到所有孔的中心点), 随意找一个点(我直接用的轮廓排序后的第一个点), 并找到与之最近的点, 分别判断两点到中线L的距离, 得到远离中线的点b和接近中线的点a
  4.  

     


     

     

  5. 判断点a, b到短边M的距离, 若点a到M的距离小于点b到M的距离, 则箭头朝下, 反之朝上
  6. 根据当前数据进行三次判断才会显示最终结果, 否则显示none

程序

  1 #include <iostream>
  2 #include <opencv2/opencv.hpp>
  3 #include <vector>
  4 
  5 using namespace cv;
  6 using namespace std;
  7 
  8 Mat src, hsv, n_erode, edge, element;
  9 
 10 int h_max = 179, s_max = 255, v_max = 255;
 11 int h_min = 90, s_min = 110, v_min = 110;
 12 
 13 typedef struct line_normal{
 14     double line_a;
 15     double line_b;
 16     double line_c;
 17 }Normalline;
 18 
 19 //滑动条回调函数
 20 void on_callback(int, void*);
 21 
 22 //获取直线一般式的ABC
 23 void get_line_normal(Point point1, Point point2, Normalline &n_line);
 24 
 25 //点到直线的距离
 26 double get_pointline_distance(Point n_point, Normalline n_line);
 27 
 28 //用于sort的比较函数
 29 static inline bool ContoursSortFun(vector<cv::Point> contourone,vector<cv::Point> contourtwo)
 30 {
 31     return (contourone.size() > contourtwo.size());
 32 }
 33 
 34 int main() {
 35     VideoCapture video(0);
 36     char key;
 37     //判断方向结果累加
 38     int up_count = 0, down_count = 0;
 39     //中线一般式
 40     Normalline midline = {0,0,0};
 41     //边界线一般式
 42     Normalline borderline = {0, 0, 0};
 43 
 44     while(1){
 45         video >> src;
 46         //文字定义
 47         string text;
 48         int baseline;
 49         Size text_size = getTextSize(text, FONT_HERSHEY_PLAIN, 2,  2, &baseline);
 50         Point origin((src.cols - text_size.width) / 2, text_size.height);
 51 
 52         //进行颜色转换
 53         cvtColor(src, hsv, COLOR_BGR2HSV);
 54 
 55         //创建滑动条
 56         createTrackbar("H最大值", "二值图", &h_max, 179, on_callback);
 57         createTrackbar("H最小值", "二值图", &h_min, 179, on_callback);
 58         createTrackbar("S最大值", "二值图", &s_max, 255, on_callback);
 59         createTrackbar("S最小值", "二值图", &s_min, 255, on_callback);
 60         createTrackbar("V最大值", "二值图", &v_max, 255, on_callback);
 61         createTrackbar("V最小值", "二值图", &v_min, 255, on_callback);
 62 
 63         on_callback(0, 0);
 64         /************************寻找轮廓************************/
 65         vector<vector<Point>>contours;
 66         vector<Vec4i> hierarchy;
 67 
 68         //轮廓内所有的孔
 69         vector<RotatedRect> hole;
 70         //找到所有轮廓
 71         findContours(n_erode, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);
 72         if(contours.empty() || hierarchy.empty() || contours.size() == 0 || hierarchy.size() == 0) {
 73             waitKey(CAP_PROP_FPS);
 74             continue;
 75         }
 76         //对所有轮廓进行排序(最大的那个轮廓为外轮廓)
 77         std::sort(contours.begin(), contours.end(), ContoursSortFun);
 78 
 79         //找到最大轮廓的最小旋转矩形
 80         RotatedRect rrect;
 81         rrect = minAreaRect(contours[0]);
 82 
 83         //读取最小外接矩形的4个顶点
 84         Point2f  points[4];
 85         rrect.points(points);
 86         //绘制矩形
 87         for(int j = 0; j < 4; ++j){
 88             line(src, points[j], points[(j + 1) % 4], Scalar(255, 0, 0), 2, 8, 0);
 89         }
 90         /****************************给外边界矩形进行编号****************************/
 91         //绘制顶点
 92         for(int i = 0; i < 4; ++i){
 93             circle(src, points[i], 5, Scalar(0, 0, 255), 2);
 94         }
 95         //计算边长比较出长边,一次while只运行一次, 对顶点进行编号
 96         double border = 0;
 97         int num1 = 0, num2 = 0, num3 = 0, num4 = 0; //储存点在数据中的序号
 98         for (int j = 0; j < 4; j++) {
 99             //a2+b2方法比较长度
100             double border1 = pow((points[j % 4].x - points[(j + 1) % 4].x), 2) + pow((points[j % 4].y - points[(j + 1) % 4].y), 2);
101             //比较前两次,后两次长边
102             if (border <= border1) {
103                 if (j <= 1) {
104                     num1 = j % 4;
105                     num2 = (j + 1) % 4;
106                     border = border1;
107                 }
108                 else{
109                     num3 = j % 4;
110                     num4 = (j + 1) % 4;
111                     border = border1;
112                 }
113             }
114         }
115         //获取短边边界
116         get_line_normal(points[num1], points[num4], borderline);
117         Point mid1 = Point ((points[num1].x + points[num4].x) / 2, (points[num1].y + points[num4].y) / 2);
118         Point mid2 = Point ((points[num2].x + points[num3].x) / 2, (points[num2].y + points[num3].y) / 2);
119         line(src, mid1, mid2, Scalar(128,0,0), 2, 8);
120         //获取中线信息
121         get_line_normal(mid1, mid2, midline);
122         /****************************寻找轮廓内的点****************************/
123         //所有孔化为最小旋转矩形(contours[0]为牌的外轮廓)
124         for (int i = 1; i < contours.size(); ++i) {
125             hole.push_back(minAreaRect(contours[i]));
126         }
127         if(hole.empty() || hole.size() == 0) {
128             waitKey(CAP_PROP_FPS);
129             continue;
130         }
131         int point_dis = 1000;
132         int inner1 = 0, inner2 = 0;
133         //找到距离最小的两个点
134         for(int j = 0; j < hole.size(); ++j){
135             int tmp_dis = pow(hole[inner1].center.x - hole[j].center.x, 2) + pow(hole[inner1].center.y - hole[j].center.y, 2);
136             if(tmp_dis < point_dis && inner1 != j){
137                 point_dis = tmp_dis;
138                 inner2 = j;
139             }
140         }
141 
142         //计算内部点到直线的距离
143         double pointd1 = get_pointline_distance(hole[inner1].center, midline);
144         double pointd2 = get_pointline_distance(hole[inner2].center, midline);
145 
146         //保证点2一定是离中线最远的点
147         if(pointd1 < pointd2){
148             int t = inner1;
149             inner1 = inner2;
150             inner2 = t;
151         }
152 
153         //圈出距离最小的两个点
154         circle(src, hole[inner1].center, 5, Scalar(255, 255, 0), 2);
155         circle(src, hole[inner2].center, 5, Scalar(0, 255, 255), 2);
156 
157         /***************找到远离中线的点和近中线的点到某一短边的距离,并进行比较****************/
158         int innerd1 = (int)get_pointline_distance(hole[inner1].center, borderline);
159         int innerd2 = (int)get_pointline_distance(hole[inner2].center, borderline);
160         if(innerd1 > innerd2) {up_count = 0;text = "down"; down_count++;}
161         else {down_count = 0;text = "up"; up_count++;}
162 
163         //累计三次结果一样才会显示方向
164         if(up_count < 3 && down_count < 3) {
165             text = "none";
166         }
167 
168         putText(src, text, origin, FONT_HERSHEY_PLAIN, 2, Scalar(0, 255, 0), 2, 8);
169         cout << CAP_PROP_FPS << endl;
170         imshow("效果图", src);
171         key = (char) waitKey(CAP_PROP_FPS);
172         if(key == 27) break;
173     }
174     return 0;
175 }
176 
177 //滑动条回调函数
178 void on_callback(int, void*){
179     inRange(hsv, Scalar(h_min, s_min, v_min), Scalar(h_max, s_max, v_max), hsv);
180     //获取自定义内核
181     element = getStructuringElement(MORPH_ELLIPSE,Size(3, 3));
182 
183     erode(hsv, n_erode, element);
184     erode(n_erode, n_erode, element);
185     erode(n_erode, n_erode, element);
186 
187     imshow("腐蚀膨胀图", n_erode);
188     imshow("二值图", hsv);
189 }
190 
191 //获取直线一般式的ABC
192 void get_line_normal(Point point1, Point point2, Normalline &n_line){
193     n_line.line_a = point2.y - point1.y;
194     n_line.line_b = point1.x - point2.x;
195     n_line.line_c = point2.x * point1.y - point1.x * point2.y;
196 }
197 
198 //点到直线的距离
199 double get_pointline_distance(Point n_point, Normalline n_line){
200     return fabs((n_line.line_a * n_point.x + n_line.line_b * n_point.y + n_line.line_c)/
201     sqrt(pow(n_line.line_a, 2) + pow(n_line.line_b, 2)));
202 }

 

最终效果

 

 

 

 

 

 

 本程序遍历次数太多, 有些地方还不精简, 帧率提不上来

菜鸡一枚, 还请多多指教

 

posted @ 2020-12-05 21:24  上帝的绵羊  阅读(891)  评论(0编辑  收藏  举报