基於opencv3.0和zbar下條形碼和二維碼的識別與解碼


其中對條碼與二維碼的識別分為以下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進行源碼克隆下載

如有問題請留言評論。轉載請注明出處,謝謝。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM