其中對條碼與二維碼的識別分為以下4個步驟
1. 利用opencv和Zbar(或者Zxing)對標准的條形碼圖片(即沒有多余背景干擾,且圖片沒有傾斜)進行解碼,將解碼信息顯示出來,並與原始信息對比。
2. 利用opencv和Zbar(或者Zxing)對標准的QR二維碼圖片(即沒有多余背景干擾,且圖片沒有傾斜)進行解碼,將解碼信息顯示出來,並與原始信息對比。
3. 對非標准條形碼,進行定位,然后用Zbar(或者Zxing)解碼顯示。
4. 對非標准的QR二維碼圖片,進行定位,然后用Zbar(或者Zxing)解碼顯示。
1. 利用opencv和Zbar(或者Zxing)對標准的條形碼圖片(即沒有多余背景干擾,且圖片沒有傾斜)進行解碼,將解碼信息顯示出來,並與原始信息對比。
2. 利用opencv和Zbar(或者Zxing)對標准的QR二維碼圖片(即沒有多余背景干擾,且圖片沒有傾斜)進行解碼,將解碼信息顯示出來,並與原始信息對比。
這兩部對於zbar可以一並操作。
操作步驟主要分為兩部分:A.原圖進行灰度轉化,B.送入Zbar掃描儀進行掃描(調用ImageScanner)
源碼如下:
1 /****************************************************** 2 函數名稱: Dis_Barcode 3 函數功能: 識別條形碼和二維碼 4 傳入參數: 5 返 回 值: 6 建立時間: 2018-05-19 7 修改時間: 8 建 立 人: 9 修 改 人: 10 其它說明:這里是借鑒其他人的代碼: 11 原文鏈接:https://www.cnblogs.com/dengxiaojun/p/5278679.html 12 以下代碼是經過改動的 13 ******************************************************/ 14 void MyClass::Dis_code(Mat image){ 15 Mat imageGray; // 所轉化成的灰度圖像 16 //定義一個掃描儀 17 ImageScanner scanner; 18 scanner.set_config(ZBAR_NONE, ZBAR_CFG_ENABLE, 1); 19 20 cvtColor(image, imageGray, CV_RGB2GRAY); 21 imshow("灰度圖", imageGray); 22 // 獲取所攝取圖像的長和寬 23 int width = imageGray.cols; 24 int height = imageGray.rows; 25 // 在Zbar中進行掃描時候,需要將OpenCV中的Mat類型轉換為(uchar *)類型,raw中存放的是圖像的地址;對應的圖像需要轉成Zbar中對應的圖像zbar::Image 26 uchar *raw = (uchar *)imageGray.data; 27 Image imageZbar(width, height, "Y800", raw, width * height); 28 // 掃描相應的圖像imageZbar(imageZbar是zbar::Image類型,存儲着讀入的圖像) 29 scanner.scan(imageZbar); //掃描條碼 30 Image::SymbolIterator symbol = imageZbar.symbol_begin(); 31 if (imageZbar.symbol_begin() == imageZbar.symbol_end()) 32 { 33 cout << "查詢條碼失敗,請檢查圖片!" << endl; 34 } 35 for (; symbol != imageZbar.symbol_end(); ++symbol) 36 { 37 cout << "類型:" << endl << symbol->get_type_name() << endl << endl; 38 cout << "條碼:" << endl << symbol->get_data() << endl << endl; 39 } 40 41 waitKey(); // 等待按下esc鍵,若需要延時1s則改用waitKey(1000); 42 43 // 將圖像中的數據置為0 44 imageZbar.set_data(NULL, 0); 45 system("pause"); 46 }
結果如下:
條形碼識別:
二維碼識別:
3. 對非標准條形碼,進行定位,然后用Zbar(或者Zxing)解碼顯示
在條形碼的識別上,根據條形碼的特性,我們只關心x軸上的形態。通過x軸的寬度進行確定條碼的大小,y軸根據實際提取進行區分
處理的目標:
A.消去非碼的其他物體圖形
B.划定條碼的范圍
C.提取圖片的ROI區域(即條碼區域)
總體分為:
灰度處理-》高斯平滑-》Sobel x—y梯度差-》均值濾波-》二值化-》閉運算-》腐蝕膨脹-》獲取ROI
1 /****************************************************** 2 函數名稱: Run 3 函數功能: 開始 4 傳入參數: 5 返 回 值: 6 建立時間: 2018-05-19 7 修改時間: 8 建 立 人: 9 修 改 人: 10 其它說明: 11 ******************************************************/ 12 void MyClass::Run(){ 13 Mat image; 14 image = getGray(srcimage);//獲取灰度圖 15 image = getGass(image);//高斯平滑濾波 16 image = getSobel(image);//Sobel x—y梯度差 17 image = getBlur(image);//均值濾波除高頻噪聲 18 image = getThold(image);//二值化 19 image = getBys(image);//閉運算 20 image = getErode(image);//腐蝕 21 image = getDilate(image);//膨脹 22 image = getRect(image, srcimage);//獲取ROI 23 imshow("最后的圖", image); 24 Dis_code(image); 25 waitKey(); 26 }
灰度處理(消除顏色干擾)
1 /****************************************************** 2 函數名稱: getGray 3 函數功能: 灰度處理 4 傳入參數: Mat image 5 返 回 值: 6 建立時間: 2018-05-19 7 修改時間: 8 建 立 人: 9 修 改 人: 10 其它說明: 11 ******************************************************/ 12 Mat MyClass::getGray(Mat image, bool show){//show默認false 待定參數法 13 Mat cimage; 14 cvtColor(image, cimage, CV_RGBA2GRAY); 15 if (show) 16 imshow("灰度圖", cimage); 17 return cimage; 18 }
處理結果:
高斯濾波處理(消除高斯噪聲)
1 /****************************************************** 2 函數名稱: getGass 3 函數功能: 高斯濾波處理 4 傳入參數: Mat image 5 返 回 值: 6 建立時間: 2018-05-19 7 修改時間: 8 建 立 人: 9 修 改 人: 10 其它說明: 11 ******************************************************/ 12 Mat MyClass::getGass(Mat image, bool show){ 13 Mat cimage; 14 GaussianBlur(image, cimage, Size(3, 3), 0); 15 if (show) 16 imshow("高斯濾波圖", cimage); 17 return cimage; 18 }
處理結果:
Sobel x-y差處理(只考慮x軸,消除y軸不必要信息)
1 /****************************************************** 2 函數名稱: getSobel 3 函數功能: Sobel處理 4 傳入參數: Mat image 5 返 回 值: 6 建立時間: 2018-05-19 7 修改時間: 8 建 立 人: 9 修 改 人: 10 其它說明: 11 ******************************************************/ 12 Mat MyClass::getSobel(Mat image, bool show){ 13 Mat cimageX16s, cimageY16s, imageSobelX, imageSobelY, out; 14 Sobel(image, cimageX16s, CV_16S, 1, 0, 3, 1, 0, 4); 15 Sobel(image, cimageY16s, CV_16S, 0, 1, 3, 1, 0, 4); 16 convertScaleAbs(cimageX16s, imageSobelX, 1, 0); 17 convertScaleAbs(cimageY16s, imageSobelY, 1, 0); 18 out = imageSobelX - imageSobelY; 19 if (show) 20 imshow("Sobelx-y差 圖", out); 21 return out; 22 }
處理結果:
均值濾波處理(消除高頻噪聲)
1 /****************************************************** 2 函數名稱: getBlur 3 函數功能: 均值濾波處理 4 傳入參數: Mat image 5 返 回 值: 6 建立時間: 2018-05-19 7 修改時間: 8 建 立 人: 9 修 改 人: 10 其它說明: 11 ******************************************************/ 12 Mat MyClass::getBlur(Mat image, bool show){ 13 Mat cimage; 14 blur(image, cimage, Size(3, 3)); 15 if (show) 16 imshow("均值濾波圖", cimage); 17 return cimage; 18 }
處理結果:
二值化處理(使圖像中數據量大為減少,從而能凸顯出目標的輪廓)
1 /****************************************************** 2 函數名稱: getThold 3 函數功能: 二值化處理 4 傳入參數: Mat image 5 返 回 值: 6 建立時間: 2018-05-19 7 修改時間: 8 建 立 人: 9 修 改 人: 10 其它說明: 11 ******************************************************/ 12 Mat MyClass::getThold(Mat image, bool show){ 13 Mat cimage; 14 threshold(image, cimage, 112, 255, CV_THRESH_BINARY); 15 if (show) 16 imshow("二值化圖", cimage); 17 return cimage; 18 }
處理結果:
閉運算處理(擴大軸之間的間隙)
1 /****************************************************** 2 函數名稱: getBys 3 函數功能: 閉運算處理 4 傳入參數: Mat image 5 返 回 值: 6 建立時間: 2018-05-19 7 修改時間: 8 建 立 人: 9 修 改 人: 10 其它說明: 11 ******************************************************/ 12 Mat MyClass::getBys(Mat image, bool show){ 13 morphologyEx(image, image, MORPH_CLOSE, element); 14 if (show) 15 imshow("閉運算圖", image); 16 return image; 17 }
處理結果:
腐蝕膨脹(消去干擾點和合並條碼區域)
1 /****************************************************** 2 函數名稱: getErode 3 函數功能: 腐蝕處理 4 傳入參數: Mat image 5 返 回 值: 6 建立時間: 2018-05-19 7 修改時間: 8 建 立 人: 9 修 改 人: 10 其它說明: 11 ******************************************************/ 12 Mat MyClass::getErode(Mat image, bool show){ 13 //Mat cimage; 14 erode(image, image, element); 15 if (show) 16 imshow("腐蝕圖", image); 17 return image; 18 } 19 /****************************************************** 20 函數名稱: getDilate 21 函數功能: 膨脹處理 22 傳入參數: Mat image 23 返 回 值: 24 建立時間: 2018-05-19 25 修改時間: 26 建 立 人: 27 修 改 人: 28 其它說明: 29 ******************************************************/ 30 Mat MyClass::getDilate(Mat image, bool show){ 31 for (int i = 0; i < 3; i++) 32 dilate(image, image, element); 33 if (show) 34 imshow("膨脹圖", image); 35 return image; 36 }
處理結果:
獲取ROI(為Zbar處理作預處理)
1 /****************************************************** 2 函數名稱: getRect 3 函數功能: 獲取碼的區域 4 傳入參數: Mat image, Mat simage原圖 5 返 回 值: 6 建立時間: 2018-05-19 7 修改時間: 8 建 立 人: 9 修 改 人: 10 其它說明:借鑒其他人進行改進 11 ******************************************************/ 12 Mat MyClass::getRect(Mat image, Mat simage, bool show){ 13 vector<vector<Point>> contours; 14 vector<Vec4i> hiera; 15 Mat cimage; 16 findContours(image, contours, hiera, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); 17 vector<float>contourArea; 18 for (int i = 0; i < contours.size(); i++) 19 { 20 contourArea.push_back(cv::contourArea(contours[i])); 21 } 22 //找出面積最大的輪廓 23 double maxValue; Point maxLoc; 24 minMaxLoc(contourArea, NULL, &maxValue, NULL, &maxLoc); 25 //計算面積最大的輪廓的最小的外包矩形 26 RotatedRect minRect = minAreaRect(contours[maxLoc.x]); 27 //為了防止找錯,要檢查這個矩形的偏斜角度不能超標 28 //如果超標,那就是沒找到 29 if (minRect.angle<2.0) 30 { 31 //找到了矩形的角度,但是這是一個旋轉矩形,所以還要重新獲得一個外包最小矩形 32 Rect myRect = boundingRect(contours[maxLoc.x]); 33 //把這個矩形在源圖像中畫出來 34 //rectangle(srcImage,myRect,Scalar(0,255,255),3,LINE_AA); 35 //看看顯示效果,找的對不對 36 //imshow(windowNameString,srcImage); 37 //將掃描的圖像裁剪下來,並保存為相應的結果,保留一些X方向的邊界,所以對rect進行一定的擴張 38 myRect.x = myRect.x - (myRect.width / 20); 39 myRect.width = myRect.width*1.1; 40 Mat resultImage = Mat(srcimage, myRect); 41 return resultImage; 42 } 43 44 for (int i = 0; i<contours.size(); i++) 45 { 46 Rect rect = boundingRect((Mat)contours[i]); 47 //cimage = simage(rect); 48 rectangle(simage, rect, Scalar(0), 2); 49 if (show) 50 imshow("轉變圖", simage); 51 } 52 return simage; 53 }
處理結果:
最后識別處理結果:
4. 對非標准的QR二維碼圖片,進行定位,然后用Zbar(或者Zxing)解碼顯示。
這里主要參考https://blog.csdn.net/nick123chao/article/details/77573675的博客。不過該博客的處理沒有考慮多個識別點時的情況:
例圖:
本文主要處理去除干擾的識別點的方向進行研究解決。根據二維碼特性:
我們只要找到90°±Δx的角,且夾角兩邊為最小的邊即可。
找到三個點后,我們需要對齊做旋轉處理,旋轉的角度如下:
其中處理的步驟分為:
灰度處理-》邊緣檢測-》特征輪廓檢測-》提取特征點-》排除干擾點-》繪制直角三角形-》糾正旋轉-》提取ROI-》識別
這里先給效果,后展示代碼
邊緣檢測:
特征輪廓檢測
提取特征點-》排除干擾點-》繪制直角三角形
糾正旋轉
提取ROI
識別
源碼如下:
1 /****************************************************** 2 函數名稱: QrRun 3 函數功能: 開始 4 傳入參數: 5 返 回 值: 6 建立時間: 2018-05-19 7 修改時間: 8 建 立 人: 9 修 改 人: 10 其它說明: 11 ******************************************************/ 12 void MyClass::QrRun(){ 13 RNG rng(12345); 14 //imshow("原圖", srcimage); 15 Mat src_all = srcimage.clone(); 16 Mat src_gray; 17 //灰度處理 18 src_gray = getBlur(getGray(srcimage)); 19 20 Scalar color = Scalar(1, 1, 255); 21 Mat threshold_output; 22 vector<vector<Point> > contours, contours2; 23 vector<Vec4i> hierarchy; 24 Mat drawing = Mat::zeros(srcimage.size(), CV_8UC3); 25 Mat drawing2 = Mat::zeros(srcimage.size(), CV_8UC3); 26 Mat drawingAllContours = Mat::zeros(srcimage.size(), CV_8UC3); 27 28 threshold_output = getThold(src_gray); 29 30 findContours(threshold_output, contours, hierarchy, CV_RETR_TREE, CHAIN_APPROX_NONE, Point(0, 0)); 31 32 int c = 0, ic = 0, k = 0, area = 0; 33 // 邊緣檢測 34 //通過黑色定位角作為父輪廓,有兩個子輪廓的特點,篩選出三個定位角 35 int parentIdx = -1; 36 for (int i = 0; i< contours.size(); i++) 37 { 38 //畫出所以輪廓圖 39 drawContours(drawingAllContours, contours, parentIdx, CV_RGB(255, 255, 255), 1, 8); 40 if (hierarchy[i][2] != -1 && ic == 0) 41 { 42 parentIdx = i; 43 ic++; 44 } 45 else if (hierarchy[i][2] != -1) 46 { 47 ic++; 48 } 49 else if (hierarchy[i][2] == -1) 50 { 51 ic = 0; 52 parentIdx = -1; 53 } 54 //特征輪廓檢測 - 》 55 //有兩個子輪廓 56 if (ic >= 2) 57 { 58 //保存找到的三個黑色定位角 59 contours2.push_back(contours[parentIdx]); 60 //畫出三個黑色定位角的輪廓 61 drawContours(drawing, contours, parentIdx, CV_RGB(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)), 1, 8); 62 ic = 0; 63 parentIdx = -1; 64 } 65 } 66 //提取特征點 67 //填充的方式畫出黑色定位角的輪廓 68 for (int i = 0; i<contours2.size(); i++) 69 drawContours(drawing2, contours2, i, CV_RGB(rng.uniform(100, 255), rng.uniform(100, 255), rng.uniform(100, 255)), -1, 4, hierarchy[k][2], 0, Point()); 70 71 //獲取定位角的中心坐標 72 vector<Point> pointfind; 73 for (int i = 0; i<contours2.size(); i++) 74 { 75 pointfind.push_back(Center_cal(contours2, i)); 76 } 77 //排除干擾點 78 Mat dst; 79 Point point[3]; double angle; Mat rot_mat; 80 ///選擇合適的點-核心篩選 81 if (pointfind.size()>3){ 82 double lengthA = 10000000000000000, lengthB = 10000000000000000000; 83 for (int i = 0; i < pointfind.size(); i++){ 84 for (int j = 0; j < pointfind.size(); j++){ 85 for (int k = 0; k < pointfind.size(); k++){ 86 if (i != j&&j != k&&i != k){ 87 double dxa, dxb,dya,dyb; 88 double k1, k2, wa, wb; 89 dxa = pointfind[i].x - pointfind[j].x; 90 dxb = pointfind[i].x - pointfind[k].x; 91 dya = pointfind[i].y - pointfind[j].y; 92 dyb = pointfind[i].y - pointfind[k].y; 93 if (dxa == 0 || dxb == 0)continue; 94 k1 = dya/dxa; 95 k2 = dyb/dxb ; 96 wa = sqrt(pow(dya, 2) + pow(dya, 2)); 97 wb = sqrt(pow(dyb, 2) + pow(dxb, 2)); 98 double anglea = abs(atan(k1) * 180 / CV_PI) + abs(atan(k2) * 180 / CV_PI); 99 if (int(anglea)>=85&&int(anglea)<=95&&wa<=lengthA&&wb<=lengthB){ 100 lengthA = wa; 101 lengthB = wb; 102 point[0] = pointfind[i]; 103 point[1] = pointfind[j]; 104 point[2] = pointfind[k]; 105 } 106 } 107 } 108 } 109 } 110 } 111 else{ 112 for (int i = 0; i < 3; i++){ 113 point[i] = pointfind[i]; 114 } 115 } 116 //繪制直角三角形 117 //計算輪廓的面積,計算定位角的面積,從而計算出邊長 118 area = contourArea(contours2[0]); 119 int area_side = cvRound(sqrt(double(area))); 120 for (int i = 0; i < 3; i++){ 121 line(drawing2, point[i], point[(i + 1)%3], color, area_side / 2, 8); 122 } 123 124 //糾正旋轉 125 //判斷是否正對 126 if (!IsCorrect(point)){ 127 //進入修正環節 128 double angle; Mat rot_mat; 129 int start = 0; 130 for (int i = 0; i < 3; i++){ 131 double k1, k2,kk; 132 k1 = (point[i].y - point[(i + 1) % 3].y) / (point[i].x - point[(i + 1) % 3].x); 133 k2 = (point[i].y - point[(i + 2) % 3].y) / (point[i].x - point[(i + 2) % 3].x); 134 kk = k1*k2; 135 if (k1*k2 <0) 136 start = i; 137 } 138 double ax, ay, bx, by; 139 ax = point[(start + 1) % 3].x; 140 ay = point[(start + 1) % 3].y; 141 bx = point[(start + 2) % 3].x; 142 by = point[(start + 2) % 3].y; 143 Point2f center(abs(ax - bx) / 2, abs(ay -by)/ 2); 144 double dy = ay - by; 145 double dx = ax - bx; 146 double k3 = dy / dx; 147 angle =atan(k3) * 180 / CV_PI;//轉化角度 148 rot_mat = getRotationMatrix2D(center, angle, 1.0); 149 150 warpAffine(src_all, dst, rot_mat, src_all.size(), 1, 0, 0);//旋轉原圖查看 151 warpAffine(drawing2, drawing2, rot_mat, src_all.size(), 1, 0, 0);//旋轉連線圖 152 warpAffine(src_all, src_all, rot_mat, src_all.size(), 1, 0, 0);//旋轉原圖 153 154 namedWindow("Dst"); 155 imshow("Dst", dst); 156 } 157 158 namedWindow("DrawingAllContours"); 159 imshow("DrawingAllContours", drawingAllContours); 160 161 namedWindow("Drawing2"); 162 imshow("Drawing2", drawing2); 163 164 namedWindow("Drawing"); 165 imshow("Drawing", drawing); 166 167 //提取ROI 168 //接下來要框出這整個二維碼 169 Mat gray_all, threshold_output_all; 170 vector<vector<Point> > contours_all; 171 vector<Vec4i> hierarchy_all; 172 cvtColor(drawing2, gray_all, CV_BGR2GRAY); 173 174 175 threshold(gray_all, threshold_output_all, 45, 255, THRESH_BINARY); 176 findContours(threshold_output_all, contours_all, hierarchy_all, RETR_EXTERNAL, CHAIN_APPROX_NONE, Point(0, 0));//RETR_EXTERNAL表示只尋找最外層輪廓 177 178 179 Point2f fourPoint2f[4]; 180 //求最小包圍矩形 181 RotatedRect rectPoint = minAreaRect(contours_all[1]);//pointfind.size()-3 182 183 //將rectPoint變量中存儲的坐標值放到 fourPoint的數組中 184 rectPoint.points(fourPoint2f); 185 186 int maxx = 0, maxy = 0, minx = 100000, miny = 100000; 187 for (int i = 0; i < 4; i++) 188 { 189 if (maxx < fourPoint2f[i].x)maxx = fourPoint2f[i].x; 190 if (maxy < fourPoint2f[i].y)maxy = fourPoint2f[i].y; 191 if (minx > fourPoint2f[i].x)minx = fourPoint2f[i].x; 192 if (miny > fourPoint2f[i].y)miny = fourPoint2f[i].y; 193 line(src_all, fourPoint2f[i % 4], fourPoint2f[(i + 1) % 4] 194 , Scalar(0), 3); 195 } 196 namedWindow("Src_all"); 197 ///邊際處理 198 int set_inter = 5; 199 while (true) 200 { 201 minx -= set_inter; 202 miny -= set_inter; 203 maxx += set_inter; 204 maxy += set_inter; 205 if (maxx > srcimage.size().width || maxy > srcimage.size().height || minx < 0 || miny < 0){ 206 minx += set_inter; 207 miny += set_inter; 208 maxx -= set_inter; 209 maxy -= set_inter; 210 set_inter--; 211 } 212 else 213 { 214 break; 215 } 216 } 217 imshow("Src_all", src_all(Rect(minx, miny, maxx - minx, maxy - miny)));//ROI 218 Mat fout = src_all(Rect(minx, miny, maxx - minx, maxy - miny));//ROI 219 220 //識別 221 Dis_code(fout); 222 223 waitKey(0); 224 destroyAllWindows(); 225 }
由於在解碼上是采用其他人的方法,存在解碼問題。(到時候有機會自己再寫下)
ps:目前的zbar不支持中文識別,但是zxing可以。所以借鑒本文的需要改進下識別的模塊即可。
本文的不足之處:
這里還做了測試,對於旋轉180°以上的二維碼圖片存在可能無法識別的問題。以及碼眼為非正方形的也無法識別。
如需要源碼請轉移至碼雲:https://gitee.com/cjqbaba/MediaTest/tree/Code_Find進行源碼克隆下載
如有問題請留言評論。轉載請注明出處,謝謝。