C++ OpenCV實現二維碼檢測功能
前言
本文將使用OpenCV C++ 進行二維碼檢測。
一、二維碼檢測
首先我們要先將圖像進行預處理,通過灰度、濾波、二值化等操作提取出圖像輪廓。在這裡我還添加瞭形態學操作,消除噪點,有效將矩形區域連接起來。
Mat gray; cvtColor(src, gray, COLOR_BGR2GRAY); Mat blur; GaussianBlur(gray, blur, Size(3, 3), 0); Mat bin; threshold(blur, bin, 0, 255, THRESH_BINARY_INV | THRESH_OTSU); //通過Size(5,1)開運算消除邊緣毛刺 Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 1)); Mat open; morphologyEx(bin, open, MORPH_OPEN, kernel); //通過Size(21,1)閉運算能夠有效地將矩形區域連接 便於提取矩形區域 Mat kernel1 = getStructuringElement(MORPH_RECT, Size(21, 1)); Mat close; morphologyEx(open, close, MORPH_CLOSE, kernel1);
如圖為經過一系列圖像處理之後得到的效果。之後我們需要對該圖進行輪廓提取,找到二維碼所在的矩形區域。
//使用RETR_EXTERNAL找到最外輪廓 vector<vector<Point>>MaxContours; findContours(close, MaxContours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE); for (int i = 0; i < MaxContours.size(); i++) { Mat mask = Mat::zeros(src.size(), CV_8UC3); mask = Scalar::all(255); double area = contourArea(MaxContours[i]); //通過面積閾值找到二維碼所在矩形區域 if (area > 6000 && area < 100000) { //計算最小外接矩形 RotatedRect MaxRect = minAreaRect(MaxContours[i]); //計算最小外接矩形寬高比 double ratio = MaxRect.size.width / MaxRect.size.height; if (ratio > 0.8 && ratio < 1.2) { Rect MaxBox = MaxRect.boundingRect(); //rectangle(src, Rect(MaxBox.tl(), MaxBox.br()), Scalar(255, 0, 255), 2); //將矩形區域從原圖摳出來 Mat ROI = src(Rect(MaxBox.tl(), MaxBox.br())); ROI.copyTo(mask(MaxBox)); ROI_Rect.push_back(mask); } } }
由以下代碼段我們就可以很好的找出二維碼所在的矩形區域,並將這些區域摳出來保存以便進行下面的識別工作。
//找到二維碼所在的矩形區域 void Find_QR_Rect(Mat src, vector<Mat>&ROI_Rect) { Mat gray; cvtColor(src, gray, COLOR_BGR2GRAY); Mat blur; GaussianBlur(gray, blur, Size(3, 3), 0); Mat bin; threshold(blur, bin, 0, 255, THRESH_BINARY_INV | THRESH_OTSU); //通過Size(5,1)開運算消除邊緣毛刺 Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 1)); Mat open; morphologyEx(bin, open, MORPH_OPEN, kernel); //通過Size(21,1)閉運算能夠有效地將矩形區域連接 便於提取矩形區域 Mat kernel1 = getStructuringElement(MORPH_RECT, Size(21, 1)); Mat close; morphologyEx(open, close, MORPH_CLOSE, kernel1); //使用RETR_EXTERNAL找到最外輪廓 vector<vector<Point>>MaxContours; findContours(close, MaxContours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE); for (int i = 0; i < MaxContours.size(); i++) { Mat mask = Mat::zeros(src.size(), CV_8UC3); mask = Scalar::all(255); double area = contourArea(MaxContours[i]); //通過面積閾值找到二維碼所在矩形區域 if (area > 6000 && area < 100000) { //計算最小外接矩形 RotatedRect MaxRect = minAreaRect(MaxContours[i]); //計算最小外接矩形寬高比 double ratio = MaxRect.size.width / MaxRect.size.height; if (ratio > 0.8 && ratio < 1.2) { Rect MaxBox = MaxRect.boundingRect(); //rectangle(src, Rect(MaxBox.tl(), MaxBox.br()), Scalar(255, 0, 255), 2); //將矩形區域從原圖摳出來 Mat ROI = src(Rect(MaxBox.tl(), MaxBox.br())); ROI.copyTo(mask(MaxBox)); ROI_Rect.push_back(mask); } } } }
如圖所示,這是找到的二維碼矩形。這裡隻展示其中之一。
二、二維碼識別
通過findContours找到輪廓層級關系
//用於存儲檢測到的二維碼 vector<vector<Point>>QR_Rect; //遍歷所有找到的矩形區域 for (int i = 0; i < ROI_Rect.size(); i++) { Mat gray; cvtColor(ROI_Rect[i], gray, COLOR_BGR2GRAY); Mat bin; threshold(gray, bin, 0, 255, THRESH_BINARY_INV|THRESH_OTSU); //通過hierarchy、RETR_TREE找到輪廓之間的層級關系 vector<vector<Point>>contours; vector<Vec4i>hierarchy; findContours(bin, contours, hierarchy, RETR_TREE, CHAIN_APPROX_NONE); //父輪廓索引 int ParentIndex = -1; int cn = 0; //用於存儲二維碼矩形的三個“回” vector<Point>rect_points; for (int i = 0; i < contours.size(); i++) { //hierarchy[i][2] != -1 表示該輪廓有子輪廓 cn用於計數“回”中第幾個輪廓 if (hierarchy[i][2] != -1 && cn == 0) { ParentIndex = i; cn++; } else if (hierarchy[i][2] != -1 && cn == 1) { cn++; } else if (hierarchy[i][2] == -1) { //初始化 ParentIndex = -1; cn = 0; } //如果該輪廓存在子輪廓,且有2級子輪廓則認定找到‘回' if (hierarchy[i][2] != -1 && cn == 2) { drawContours(canvas, contours, ParentIndex, Scalar::all(255), -1); RotatedRect rect; rect = minAreaRect(contours[ParentIndex]); rect_points.push_back(rect.center); } } }
以上代碼段的整體思路為:首先經過圖像預處理進行輪廓檢測,
通過hierarchy、RETR_TREE找到輪廓之間的層級關系。根據hierarchy[i][2]是否為-1判斷該輪廓是否有子輪廓。若該輪廓存在子輪廓,則統計有幾個子輪廓。如果該輪廓存在子輪廓,且有2級子輪廓則認定找到‘回’ 。關於輪廓的層級關系,大傢可以自行百度查找資料,理解一下其中原理。
//對找到的矩形區域進行識別是否為二維碼 int Dectect_QR_Rect(Mat src,Mat &canvas,vector<Mat>&ROI_Rect) { //用於存儲檢測到的二維碼 vector<vector<Point>>QR_Rect; //遍歷所有找到的矩形區域 for (int i = 0; i < ROI_Rect.size(); i++) { Mat gray; cvtColor(ROI_Rect[i], gray, COLOR_BGR2GRAY); Mat bin; threshold(gray, bin, 0, 255, THRESH_BINARY_INV|THRESH_OTSU); //通過hierarchy、RETR_TREE找到輪廓之間的層級關系 vector<vector<Point>>contours; vector<Vec4i>hierarchy; findContours(bin, contours, hierarchy, RETR_TREE, CHAIN_APPROX_NONE); //父輪廓索引 int ParentIndex = -1; int cn = 0; //用於存儲二維碼矩形的三個“回” vector<Point>rect_points; for (int i = 0; i < contours.size(); i++) { //hierarchy[i][2] != -1 表示該輪廓有子輪廓 cn用於計數“回”中第幾個輪廓 if (hierarchy[i][2] != -1 && cn == 0) { ParentIndex = i; cn++; } else if (hierarchy[i][2] != -1 && cn == 1) { cn++; } else if (hierarchy[i][2] == -1) { //初始化 ParentIndex = -1; cn = 0; } //如果該輪廓存在子輪廓,且有2級子輪廓則認定找到‘回' if (hierarchy[i][2] != -1 && cn == 2) { drawContours(canvas, contours, ParentIndex, Scalar::all(255), -1); RotatedRect rect; rect = minAreaRect(contours[ParentIndex]); rect_points.push_back(rect.center); } } //將找到地‘回'連接起來 for (int i = 0; i < rect_points.size(); i++) { line(canvas, rect_points[i], rect_points[(i + 1) % rect_points.size()], Scalar::all(255), 5); } QR_Rect.push_back(rect_points); } return QR_Rect.size(); }
由以上代碼段,我們就可以識別出二維碼。效果如圖所示。
三、二維碼繪制
//框出二維碼所在位置 Mat gray; cvtColor(canvas, gray, COLOR_BGR2GRAY); vector<vector<Point>>contours; findContours(gray, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE); Point2f points[4]; for (int i = 0; i < contours.size(); i++) { RotatedRect rect = minAreaRect(contours[i]); rect.points(points); for (int j = 0; j < 4; j++) { line(src, points[j], points[(j + 1) % 4], Scalar(0, 255, 0), 2); } }
最終效果如圖所示。
四、源碼
#include<iostream> #include<opencv2/core.hpp> #include<opencv2/imgproc.hpp> #include<opencv2/highgui.hpp> using namespace std; using namespace cv; //找到二維碼所在的矩形區域 void Find_QR_Rect(Mat src, vector<Mat>&ROI_Rect) { Mat gray; cvtColor(src, gray, COLOR_BGR2GRAY); Mat blur; GaussianBlur(gray, blur, Size(3, 3), 0); Mat bin; threshold(blur, bin, 0, 255, THRESH_BINARY_INV | THRESH_OTSU); //通過Size(5,1)開運算消除邊緣毛刺 Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 1)); Mat open; morphologyEx(bin, open, MORPH_OPEN, kernel); //通過Size(21,1)閉運算能夠有效地將矩形區域連接 便於提取矩形區域 Mat kernel1 = getStructuringElement(MORPH_RECT, Size(21, 1)); Mat close; morphologyEx(open, close, MORPH_CLOSE, kernel1); //使用RETR_EXTERNAL找到最外輪廓 vector<vector<Point>>MaxContours; findContours(close, MaxContours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE); for (int i = 0; i < MaxContours.size(); i++) { Mat mask = Mat::zeros(src.size(), CV_8UC3); mask = Scalar::all(255); double area = contourArea(MaxContours[i]); //通過面積閾值找到二維碼所在矩形區域 if (area > 6000 && area < 100000) { //計算最小外接矩形 RotatedRect MaxRect = minAreaRect(MaxContours[i]); //計算最小外接矩形寬高比 double ratio = MaxRect.size.width / MaxRect.size.height; if (ratio > 0.8 && ratio < 1.2) { Rect MaxBox = MaxRect.boundingRect(); //rectangle(src, Rect(MaxBox.tl(), MaxBox.br()), Scalar(255, 0, 255), 2); //將矩形區域從原圖摳出來 Mat ROI = src(Rect(MaxBox.tl(), MaxBox.br())); ROI.copyTo(mask(MaxBox)); ROI_Rect.push_back(mask); } } } } //對找到的矩形區域進行識別是否為二維碼 int Dectect_QR_Rect(Mat src,Mat &canvas,vector<Mat>&ROI_Rect) { //用於存儲檢測到的二維碼 vector<vector<Point>>QR_Rect; //遍歷所有找到的矩形區域 for (int i = 0; i < ROI_Rect.size(); i++) { Mat gray; cvtColor(ROI_Rect[i], gray, COLOR_BGR2GRAY); Mat bin; threshold(gray, bin, 0, 255, THRESH_BINARY_INV|THRESH_OTSU); //通過hierarchy、RETR_TREE找到輪廓之間的層級關系 vector<vector<Point>>contours; vector<Vec4i>hierarchy; findContours(bin, contours, hierarchy, RETR_TREE, CHAIN_APPROX_NONE); //父輪廓索引 int ParentIndex = -1; int cn = 0; //用於存儲二維碼矩形的三個“回” vector<Point>rect_points; for (int i = 0; i < contours.size(); i++) { //hierarchy[i][2] != -1 表示該輪廓有子輪廓 cn用於計數“回”中第幾個輪廓 if (hierarchy[i][2] != -1 && cn == 0) { ParentIndex = i; cn++; } else if (hierarchy[i][2] != -1 && cn == 1) { cn++; } else if (hierarchy[i][2] == -1) { //初始化 ParentIndex = -1; cn = 0; } //如果該輪廓存在子輪廓,且有2級子輪廓則認定找到‘回' if (hierarchy[i][2] != -1 && cn == 2) { drawContours(canvas, contours, ParentIndex, Scalar::all(255), -1); RotatedRect rect; rect = minAreaRect(contours[ParentIndex]); rect_points.push_back(rect.center); } } //將找到地‘回'連接起來 for (int i = 0; i < rect_points.size(); i++) { line(canvas, rect_points[i], rect_points[(i + 1) % rect_points.size()], Scalar::all(255), 5); } QR_Rect.push_back(rect_points); } return QR_Rect.size(); } int main() { Mat src = imread("6.png"); if (src.empty()) { cout << "No image data!" << endl; system("pause"); return 0; } vector<Mat>ROI_Rect; Find_QR_Rect(src, ROI_Rect); Mat canvas = Mat::zeros(src.size(), src.type()); int flag = Dectect_QR_Rect(src, canvas, ROI_Rect); //imshow("canvas", canvas); if (flag <= 0) { cout << "Can not detect QR code!" << endl; system("pause"); return 0; } cout << "檢測到" << flag << "個二維碼。" << endl; //框出二維碼所在位置 Mat gray; cvtColor(canvas, gray, COLOR_BGR2GRAY); vector<vector<Point>>contours; findContours(gray, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE); Point2f points[4]; for (int i = 0; i < contours.size(); i++) { RotatedRect rect = minAreaRect(contours[i]); rect.points(points); for (int j = 0; j < 4; j++) { line(src, points[j], points[(j + 1) % 4], Scalar(0, 255, 0), 2); } } imshow("source", src); waitKey(0); destroyAllWindows(); system("pause"); return 0; }
總結
本文使用OpenCV C++進行二維碼檢測,關鍵步驟有以下幾點。
1、圖像預處理,篩選出二維碼所在的矩形區域,並將該區域摳出來進行後續的識別工作。
2、對篩選出的矩形區域進行輪廓檢測,判斷它們之前的層級關系,以此來識別二維碼。
3、最後根據檢測到的二維碼“回”字,將其繪制出來就可以瞭。
以上就是C++ OpenCV實現二維碼檢測功能的詳細內容,更多關於C++ OpenCV二維碼檢測的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- 使用c++實現OpenCV繪制圓端矩形
- OpenCV繪制圓端矩形的示例代碼
- OpenCV基於背景減除實現行人計數
- OpenCV輪廓檢測之boundingRect繪制矩形邊框
- C++ OpenCV實戰之圖像透視矯正