C++?OpenCV實現(xiàn)二維碼檢測功能
前言
本文將使用OpenCV C++ 進(jìn)行二維碼檢測。
一、二維碼檢測

首先我們要先將圖像進(jìn)行預(yù)處理,通過灰度、濾波、二值化等操作提取出圖像輪廓。在這里我還添加了形態(tài)學(xué)操作,消除噪點,有效將矩形區(qū)域連接起來。
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)閉運算能夠有效地將矩形區(qū)域連接 便于提取矩形區(qū)域 Mat kernel1 = getStructuringElement(MORPH_RECT, Size(21, 1)); Mat close; morphologyEx(open, close, MORPH_CLOSE, kernel1);

如圖為經(jīng)過一系列圖像處理之后得到的效果。之后我們需要對該圖進(jìn)行輪廓提取,找到二維碼所在的矩形區(qū)域。
//使用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]);
//通過面積閾值找到二維碼所在矩形區(qū)域
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);
//將矩形區(qū)域從原圖摳出來
Mat ROI = src(Rect(MaxBox.tl(), MaxBox.br()));
ROI.copyTo(mask(MaxBox));
ROI_Rect.push_back(mask);
}
}
}
由以下代碼段我們就可以很好的找出二維碼所在的矩形區(qū)域,并將這些區(qū)域摳出來保存以便進(jìn)行下面的識別工作。
//找到二維碼所在的矩形區(qū)域
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)閉運算能夠有效地將矩形區(qū)域連接 便于提取矩形區(qū)域
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]);
//通過面積閾值找到二維碼所在矩形區(qū)域
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);
//將矩形區(qū)域從原圖摳出來
Mat ROI = src(Rect(MaxBox.tl(), MaxBox.br()));
ROI.copyTo(mask(MaxBox));
ROI_Rect.push_back(mask);
}
}
}
}

如圖所示,這是找到的二維碼矩形。這里只展示其中之一。
二、二維碼識別
通過findContours找到輪廓層級關(guān)系
//用于存儲檢測到的二維碼
vector<vector<Point>>QR_Rect;
//遍歷所有找到的矩形區(qū)域
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找到輪廓之間的層級關(guān)系
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用于計數(shù)“回”中第幾個輪廓
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級子輪廓則認(rèn)定找到‘回'
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);
}
}
}
以上代碼段的整體思路為:首先經(jīng)過圖像預(yù)處理進(jìn)行輪廓檢測,
通過hierarchy、RETR_TREE找到輪廓之間的層級關(guān)系。根據(jù)hierarchy[i][2]是否為-1判斷該輪廓是否有子輪廓。若該輪廓存在子輪廓,則統(tǒng)計有幾個子輪廓。如果該輪廓存在子輪廓,且有2級子輪廓則認(rèn)定找到‘回’ 。關(guān)于輪廓的層級關(guān)系,大家可以自行百度查找資料,理解一下其中原理。
//對找到的矩形區(qū)域進(jìn)行識別是否為二維碼
int Dectect_QR_Rect(Mat src,Mat &canvas,vector<Mat>&ROI_Rect)
{
//用于存儲檢測到的二維碼
vector<vector<Point>>QR_Rect;
//遍歷所有找到的矩形區(qū)域
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找到輪廓之間的層級關(guān)系
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用于計數(shù)“回”中第幾個輪廓
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級子輪廓則認(rèn)定找到‘回'
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;
//找到二維碼所在的矩形區(qū)域
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)閉運算能夠有效地將矩形區(qū)域連接 便于提取矩形區(qū)域
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]);
//通過面積閾值找到二維碼所在矩形區(qū)域
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);
//將矩形區(qū)域從原圖摳出來
Mat ROI = src(Rect(MaxBox.tl(), MaxBox.br()));
ROI.copyTo(mask(MaxBox));
ROI_Rect.push_back(mask);
}
}
}
}
//對找到的矩形區(qū)域進(jìn)行識別是否為二維碼
int Dectect_QR_Rect(Mat src,Mat &canvas,vector<Mat>&ROI_Rect)
{
//用于存儲檢測到的二維碼
vector<vector<Point>>QR_Rect;
//遍歷所有找到的矩形區(qū)域
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找到輪廓之間的層級關(guān)系
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用于計數(shù)“回”中第幾個輪廓
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級子輪廓則認(rèn)定找到‘回'
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;
}
總結(jié)
本文使用OpenCV C++進(jìn)行二維碼檢測,關(guān)鍵步驟有以下幾點。
1、圖像預(yù)處理,篩選出二維碼所在的矩形區(qū)域,并將該區(qū)域摳出來進(jìn)行后續(xù)的識別工作。
2、對篩選出的矩形區(qū)域進(jìn)行輪廓檢測,判斷它們之前的層級關(guān)系,以此來識別二維碼。
3、最后根據(jù)檢測到的二維碼“回”字,將其繪制出來就可以了。
以上就是C++ OpenCV實現(xiàn)二維碼檢測功能的詳細(xì)內(nèi)容,更多關(guān)于C++ OpenCV二維碼檢測的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
在std::thread中創(chuàng)建并管理QEventLoop的全面解析
QEventLoop的工作原理可以簡單地理解為一個無限循環(huán),它會不斷地檢查是否有新的事件需要處理,如果有,就將事件從事件隊列中取出,然后找到相應(yīng)的事件處理器進(jìn)行處理,這篇文章主要介紹了在std::thread中創(chuàng)建并管理QEventLoop的全面指南,需要的朋友可以參考下2023-06-06
C語言用棧實現(xiàn)十進(jìn)制轉(zhuǎn)換為二進(jìn)制的方法示例
這篇文章主要介紹了C語言用棧實現(xiàn)十進(jìn)制轉(zhuǎn)換為二進(jìn)制的方法,結(jié)合實例形式分析了C語言棧的定義及進(jìn)制轉(zhuǎn)換使用技巧,需要的朋友可以參考下2017-06-06
深入線性時間復(fù)雜度求數(shù)組中第K大數(shù)的方法詳解
本篇文章是對線性時間復(fù)雜度求數(shù)組中第K大數(shù)的方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05
C語言實現(xiàn)linux網(wǎng)卡連接檢測的方法
這篇文章主要為大家詳細(xì)介紹了C語言實現(xiàn)linux網(wǎng)卡連接檢測的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-06-06

