OpenCV机读卡识别
簡單介紹
編寫一個基于OpenCV的小程序,用于識別下圖所示機讀卡。
步驟回顧
圖像處理
圖像二值化
圖像識別離不開圖像的處理。用相機拍攝的機讀卡基本都是三通道的彩色圖像,而這里需要用到的處理方法就是“圖像二值化”,整個圖像呈現出明顯的只有黑和白的視覺效果,便于進行圖像分割。現將圖片先轉換為灰度圖,然后再進行二值化。
Mat loadImage(char *path) {Mat src = imread(path);if (src.empty()){cout << "IMAGE LOAD FAILED!" << endl;exit(0);}Mat gray, binary;cvtColor(src, gray, CV_BGR2GRAY);adaptiveThreshold(gray, binary, 255, 0, 1, 101, 10);return binary; }處理效果:
ROI分割
想要獲得數字信息和選擇題答案,就要定位到ROI(region of interest,感興趣區域)將其單獨分割出來。顯而易見,圖1的ROI即矩形框框住的區域,也是整個圖像中連通域最大的區域。獲得最大連通域的方法如下:
Mat gaintComponent(Mat src) {//查找連通域vector<vector<Point>>contours;findContours(src, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);//最大連通域vector<Point>maxContour;double maxArea = 0.0;for (size_t i = 0; i < contours.size(); i++){double area = cv::contourArea(contours[i]);if (area > maxArea){maxArea = area;maxContour = contours[i];}}//轉換為矩形框(boundingbox)Rect maxRect = boundingRect(maxContour);Mat result = src(maxRect);return result; }分割結果:
選擇題部分分割
根據圖3的分割效果將圖像反色,再次提取最大連通域即可定位到選擇題方框,接著再次反色讓背景變成黑色。然后對得到的圖像進行簡單的裁剪,去掉上方的分割線,降低識別誤差。
ps:二值圖中白色為1,黑色為0。處理二值圖圖像時計算機只看得見白色,所以需要反色操作。
分割效果:
數字部分分割
//定位選擇題上方數字信息 Mat roiImg = gaintComponent(src); Mat infoBox = roiImg(Rect(0, 10, roiImg.cols, roiImg.rows / 4 - 10)); vector<vector<cv::Point>> contours, numBox; cv::findContours(infoBox, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); for (size_t i = 0; i < contours.size(); i++) {double area = cv::contourArea(contours[i]);if (area > 100)numBox.push_back(contours[i]); } vector<Mat>num; for (size_t i = 0; i < numBox.size(); i++) {cv::Rect rect = cv::boundingRect(numBox[i]);cv::Mat result = infoBox(rect);num.push_back(result); }該代碼的問題:對于上方數字的分割用的是固定值的方法,只適用于圖1大小的機讀卡圖片,不能自適應的調整分割區域
分割效果:
識別
選擇題識別
根據圖4的分割效果,可以明顯的看出涂抹了選項的選項部分連通域明顯大于未涂抹部分,所以這里可以很輕易的得到40道選擇題的涂抹區域的相對位置。將圖4分割為10x20的矩陣,每個位置對于一個區域信息,從而得到涂抹結果。
代碼實現:
//定位涂抹區間連通域vector<vector<cv::Point>> contours, answer;cv::findContours(choiceBox, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);for (size_t i = 0; i < contours.size(); i++){double area = cv::contourArea(contours[i]);if (area > 20 && area < 60)answer.push_back(contours[i]);}//存儲涂抹中心點vector<Moments> mu(answer.size());for (int i = 0; i < answer.size(); i++)mu[i] = moments(answer[i], false);// 計算中心矩:vector<Point2f> mc(answer.size());for (int i = 0; i < answer.size(); i++)mc[i] = Point2f(mu[i].m10 / mu[i].m00, mu[i].m01 / mu[i].m00);//得到答案(使用選擇題模板,涂抹部分的答案用小寫字母標識)char answers[10][20] = {{ ' ','A','B','C','D',' ','A','B','C','D', ' ','A','B','C','D', ' ','A','B','C','D' },{ ' ','A','B','C','D',' ','A','B','C','D', ' ','A','B','C','D', ' ','A','B','C','D' },{ ' ','A','B','C','D',' ','A','B','C','D', ' ','A','B','C','D', ' ','A','B','C','D' },{ ' ','A','B','C','D',' ','A','B','C','D', ' ','A','B','C','D', ' ','A','B','C','D' },{ ' ','A','B','C','D',' ','A','B','C','D', ' ','A','B','C','D', ' ','A','B','C','D' },{ ' ','A','B','C','D',' ','A','B','C','D', ' ','A','B','C','D', ' ','A','B','C','D' },{ ' ','A','B','C','D',' ','A','B','C','D', ' ','A','B','C','D', ' ','A','B','C','D' },{ ' ','A','B','C','D',' ','A','B','C','D', ' ','A','B','C','D', ' ','A','B','C','D' },{ ' ','A','B','C','D',' ','A','B','C','D', ' ','A','B','C','D', ' ','A','B','C','D' },{ ' ','A','B','C','D',' ','A','B','C','D', ' ','A','B','C','D', ' ','A','B','C','D' } };int x = choiceBox.cols / 20;int y = choiceBox.rows / 10;for (size_t i = 0; i < mc.size(); i++){int x_index = mc[i].x / x;int y_index = mc[i].y / y;answers[y_index][x_index] += 32;}for (int i = 0; i < 10; i++){for (int j = 0; j < 20; j++){if (answers[i][j] >= 'a'&&answers[i][j] <= 'd')cout << answers[i][j] << " ";}cout << endl;}數字識別
數字識別相較于選擇題識別更加復雜一點,需要進行模板匹配,當然也可以用機器學習的相關方法實現。這里我使用的是較為簡單一點的模板識別。這是我自己做的一個匹配模板:
按照選擇題識別的方法,將0~9的數字放在一個圖像上,通過OpenCV提供的匹配函數定位識別區域中心的位置。為了使識別更精準,現將待匹配的數字圖像提取其最大連通域進行匹配。下面是代碼實現的過程:
//method: //CV_TM_SQDIFF =0, //CV_TM_SQDIFF_NORMED =1, //CV_TM_CCORR =2, //CV_TM_CCORR_NORMED =3, //CV_TM_CCOEFF =4, //CV_TM_CCOEFF_NORMED =5 int numMatch(Mat src, int method) {//模板簡單處理Mat model = imread("model.jpg", CV_BGR2GRAY);resize(model, model, Size(src.cols * 10, src.rows));//調整模板至便于匹配的大小//取數字部分連通域,排除干擾因素src = gaintComponent(~src);src = ~src;//單獨識別1,因為1的連通域最小,長寬比大if (src.rows / src.cols > 2)return 1;//帶識別圖像處理(單通道變三通道)cv::Mat three_ch = Mat::zeros(src.rows, src.cols, CV_8UC3);vector<Mat>channels;for (int i = 0; i < 3; i++)channels.push_back(src);merge(channels, three_ch);//用matchTemplate()函數匹配數字Mat result;matchTemplate(model, three_ch, result, method);normalize(result, result, 0, 1, NORM_MINMAX, -1, Mat());//通過匹配的區域的中心點坐標定位數字double minVal, maxVal;Point minLoc, maxLoc;minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc, Mat());int ans = (minLoc.x + three_ch.cols / 2) / (model.cols / 10);return ans; }運行結果
轉載于:https://www.cnblogs.com/linzijie1998/p/11031965.html
總結
以上是生活随笔為你收集整理的OpenCV机读卡识别的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【EasyWechat】自动回复
- 下一篇: 7-7 狐狸和兔子** (10 分)