好久没更博客了, 借此机会复习一下之前学过的东西
话不多说, 先看要识别的图片
红色和蓝色的差不多, 只要调好hsv色域就行(创建滑动条), 本程序就以蓝色的为例
牌反光后无法识别的问题并没有解决
程序思路
- 进行二值化和多次腐蚀操作后找到牌的轮廓和所有点的轮廓, 利用sort()函数对轮廓进行从大到小的排序, 面积最大的轮廓为牌的轮廓, 其余的为孔的轮廓, 并绘制出牌的外边框(寻找最小旋转矩形)
- 对外边界的矩形顶点进行编号(编好号的顺序短边必定为03和12且03一直位于12的下方, 并以逆时针顺序排序), 利用已经编好号的四个顶点确定矩形平行于长边的中线L, 和编号0,3组成的短边M
- 将所有孔的轮廓转换为最小旋转矩形(为得到所有孔的中心点), 随意找一个点(我直接用的轮廓排序后的第一个点), 并找到与之最近的点, 分别判断两点到中线L的距离, 得到远离中线的点b和接近中线的点a
-
- 判断点a, b到短边M的距离, 若点a到M的距离小于点b到M的距离, 则箭头朝下, 反之朝上
- 根据当前数据进行三次判断才会显示最终结果, 否则显示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 }
最终效果
本程序遍历次数太多, 有些地方还不精简, 帧率提不上来
菜鸡一枚, 还请多多指教