c++ 基于opencv 識(shí)別、定位二維碼
前言
因工作需要,需要定位圖片中的二維碼;我遂查閱了相關(guān)資料,也學(xué)習(xí)了opencv開源庫(kù)。通過一番努力,終于很好的實(shí)現(xiàn)了二維碼定位。本文將講解如何使用opencv定位二維碼。
定位二維碼不僅僅是為了識(shí)別二維碼;還可以通過二維碼對(duì)圖像進(jìn)行水平糾正以及相鄰區(qū)域定位。定位二維碼,不僅需要圖像處理相關(guān)知識(shí),還需要分析二維碼的特性,本文先從二維碼的特性講起。
1 二維碼特性
二維碼在設(shè)計(jì)之初就考慮到了識(shí)別問題,所以二維碼有一些特征是非常明顯的。
二維碼有三個(gè)“回“”字形圖案,這一點(diǎn)非常明顯。中間的一個(gè)點(diǎn)位于圖案的左上角,如果圖像偏轉(zhuǎn),也可以根據(jù)二維碼來糾正。

思考題:
為什么是三個(gè)點(diǎn),而不是一個(gè)、兩個(gè)或四個(gè)點(diǎn)。
一個(gè)點(diǎn):特征不明顯,不易定位。不易定位二維碼傾斜角度。
兩個(gè)點(diǎn):兩個(gè)點(diǎn)的次序無法確認(rèn),很難確定二維碼是否放正了。
四個(gè)點(diǎn):無法確定4個(gè)點(diǎn)的次序,從而無法確定二維碼是否放正了。
識(shí)別二維碼,就是識(shí)別二維碼的三個(gè)點(diǎn),逐步分析一下這三個(gè)點(diǎn)的特性
1 每個(gè)點(diǎn)有兩個(gè)輪廓。就是兩個(gè)口,大“口”內(nèi)部有一個(gè)小“口”,所以是兩個(gè)輪廓。
2 如果把這個(gè)“回”放到一個(gè)白色的背景下,從左到右,或從上到下畫一條線。這條線經(jīng)過的圖案黑白比例大約為:黑白比例為1:1:3:1:1。
3 如何找到左上角的頂點(diǎn)?這個(gè)頂點(diǎn)與其他兩個(gè)頂點(diǎn)的夾角為90度。
通過上面幾個(gè)步驟,就能識(shí)別出二維碼的三個(gè)頂點(diǎn),并且識(shí)別出左上角的頂點(diǎn)。
2 使用opencv識(shí)別二維碼
1)查找輪廓,篩選出三個(gè)二維碼頂點(diǎn)
opencv一個(gè)非常重要的函數(shù)就是查找輪廓,就是可以找到一個(gè)圖中的縮所有的輪廓,“回”字形圖案是一個(gè)非常的明顯的輪廓,很容易找到。
int QrParse::FindQrPoint(Mat& srcImg, vector<vector<Point>>& qrPoint) {
//彩色圖轉(zhuǎn)灰度圖
Mat src_gray;
cvtColor(srcImg, src_gray, CV_BGR2GRAY);
namedWindow("src_gray");
imshow("src_gray", src_gray);
//二值化
Mat threshold_output;
threshold(src_gray, threshold_output, 0, 255, THRESH_BINARY | THRESH_OTSU);
Mat threshold_output_copy = threshold_output.clone();
namedWindow("Threshold_output");
imshow("Threshold_output", threshold_output);
//調(diào)用查找輪廓函數(shù)
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
findContours(threshold_output, contours, hierarchy, CV_RETR_TREE, CHAIN_APPROX_NONE, Point(0, 0));
//通過黑色定位角作為父輪廓,有兩個(gè)子輪廓的特點(diǎn),篩選出三個(gè)定位角
int parentIdx = -1;
int ic = 0;
for (int i = 0; i < contours.size(); i++) {
if (hierarchy[i][2] != -1 && ic == 0) {
parentIdx = i;
ic++;
} else if (hierarchy[i][2] != -1) {
ic++;
} else if (hierarchy[i][2] == -1) {
ic = 0;
parentIdx = -1;
} {
bool isQr = QrParse::IsQrPoint(contours[parentIdx], threshold_output_copy);
//保存找到的三個(gè)黑色定位角
if (isQr)
qrPoint.push_back(contours[parentIdx]);
ic = 0;
parentIdx = -1;
}
}
return 0;
}
找到了兩個(gè)輪廓的圖元,需要進(jìn)一步分析是不是二維碼頂點(diǎn),用到如下函數(shù):
bool QrParse::IsQrPoint(vector<Point>& contour, Mat& img)
{
//最小大小限定
RotatedRect rotatedRect = minAreaRect(contour);
if (rotatedRect.size.height < 10 || rotatedRect.size.width < 10)
return false;
//將二維碼從整個(gè)圖上摳出來
cv::Mat cropImg = CropImage(img, rotatedRect);
int flag = i++;
//橫向黑白比例1:1:3:1:1
bool result = IsQrColorRate(cropImg, flag);
return result;
}
黑白比例判斷函數(shù):
//橫向和縱向黑白比例判斷
bool QrParse::IsQrColorRate(cv::Mat& image, int flag)
{
bool x = IsQrColorRateX(image, flag);
if (!x)
return false;
bool y = IsQrColorRateY(image, flag);
return y;
}
//橫向黑白比例判斷
bool QrParse::IsQrColorRateX(cv::Mat& image, int flag)
{
int nr = image.rows / 2;
int nc = image.cols * image.channels();
vector<int> vValueCount;
vector<uchar> vColor;
int count = 0;
uchar lastColor = 0;
uchar* data = image.ptr<uchar>(nr);
for (int i = 0; i < nc; i++)
{
vColor.push_back(data[i]);
uchar color = data[i];
if (color > 0)
color = 255;
if (i == 0)
{
lastColor = color;
count++;
}
else
{
if (lastColor != color)
{
vValueCount.push_back(count);
count = 0;
}
count++;
lastColor = color;
}
}
if (count != 0)
vValueCount.push_back(count);
if (vValueCount.size() < 5)
return false;
//橫向黑白比例1:1:3:1:1
int index = -1;
int maxCount = -1;
for (int i = 0; i < vValueCount.size(); i++)
{
if (i == 0)
{
index = i;
maxCount = vValueCount[i];
}
else
{
if (vValueCount[i] > maxCount)
{
index = i;
maxCount = vValueCount[i];
}
}
}
//左邊 右邊 都有兩個(gè)值,才行
if (index < 2)
return false;
if ((vValueCount.size() - index) < 3)
return false;
//黑白比例1:1:3:1:1
float rate = ((float)maxCount) / 3.00;
cout << "flag:" << flag << " ";
float rate2 = vValueCount[index - 2] / rate;
cout << rate2 << " ";
if (!IsQrRate(rate2))
return false;
rate2 = vValueCount[index - 1] / rate;
cout << rate2 << " ";
if (!IsQrRate(rate2))
return false;
rate2 = vValueCount[index + 1] / rate;
cout << rate2 << " ";
if (!IsQrRate(rate2))
return false;
rate2 = vValueCount[index + 2] / rate;
cout << rate2 << " ";
if (!IsQrRate(rate2))
return false;
return true;
}
//縱向黑白比例判斷 省略
bool QrParse::IsQrColorRateY(cv::Mat& image, int flag)
bool QrParse::IsQrRate(float rate)
{
//大概比例 不能太嚴(yán)格
return rate > 0.6 && rate < 1.9;
}
2)確定三個(gè)二維碼頂點(diǎn)的次序
通過如下原則確定左上角頂點(diǎn):二維碼左上角的頂點(diǎn)與其他兩個(gè)頂點(diǎn)的夾角為90度。
// pointDest存放調(diào)整后的三個(gè)點(diǎn),三個(gè)點(diǎn)的順序如下
// pt0----pt1
//
// pt2
bool QrParse::AdjustQrPoint(Point* pointSrc, Point* pointDest) {
bool clockwise;
int index1[3] = {
2,1,0
}
;
int index2[3] = {
0,2,1
}
;
int index3[3] = {
0,1,2
}
;
for (int i = 0; i < 3; i++) {
int *n = index1;
if(i==0)
n = index1; else if (i == 1)
n = index2; else
n = index3;
if (angle > 80 && angle < 99) {
pointDest[0] = pointSrc[n[2]];
if (clockwise) {
pointDest[1] = pointSrc[n[0]];
pointDest[2] = pointSrc[n[1]];
} else {
pointDest[1] = pointSrc[n[1]];
pointDest[2] = pointSrc[n[0]];
}
return true;
}
}
return true;
}
3)通過二維碼對(duì)圖片矯正。
圖片有可能是傾斜的,傾斜夾角可以通過pt0與pt1連線與水平線之間的夾角確定。二維碼的傾斜角度就是整個(gè)圖片的傾斜角度,從而可以對(duì)整個(gè)圖片進(jìn)行水平矯正。
//二維碼傾斜角度 Point hor(pointAdjust[0].x+300,pointAdjust[0].y); //水平線 double qrAngle = QrParse::Angle(pointAdjust[1], hor, pointAdjust[0], clockwise); //以二維碼左上角點(diǎn)為中心 旋轉(zhuǎn) Mat drawingRotation = Mat::zeros(Size(src.cols,src.rows), CV_8UC3); double rotationAngle = clockwise? -qrAngle:qrAngle; Mat affine_matrix = getRotationMatrix2D(pointAdjust[0], rotationAngle, 1.0);//求得旋轉(zhuǎn)矩陣 warpAffine(src, drawingRotation, affine_matrix, drawingRotation.size());
4)二維碼相鄰區(qū)域定位
一般情況下,二維碼在整個(gè)圖中的位置是確定的。識(shí)別出二維碼后,根據(jù)二維碼與其他圖的位置關(guān)系,可以很容易的定位別的圖元。

后記
作者通過查找大量資料,仔細(xì)研究了二維碼的特征,從而找到了識(shí)別二維碼的方法。網(wǎng)上也有許多識(shí)別二維碼的方法,但是不夠嚴(yán)謹(jǐn)。本文是將二維碼的多個(gè)特征相結(jié)合來識(shí)別,這樣更準(zhǔn)確。這種識(shí)別方法已應(yīng)用在公司的產(chǎn)品中,識(shí)別效果還是非常好的。
以上就是c++ 基于opencv 識(shí)別、定位二維碼的詳細(xì)內(nèi)容,更多關(guān)于c++ opencv 識(shí)別、定位二維碼的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解VisualS tudio Code開發(fā)Arm嵌入式Linux應(yīng)用
本文介紹如何在 Visual Studio Code 中使用 Yocto Project 生成的 Linux SDK,并針對(duì) Arm 處理器進(jìn)行 C/C++ 應(yīng)用交叉編譯和調(diào)試,感興趣的朋友跟隨小編一起看看吧2021-04-04
C語言中函數(shù)棧幀的創(chuàng)建和銷毀的深層分析
在C語言中,每一個(gè)正在運(yùn)行的函數(shù)都有一個(gè)棧幀與其對(duì)應(yīng),棧幀中存儲(chǔ)的是該函數(shù)的返回地址和局部變量。從邏輯上講,棧幀就是一個(gè)函數(shù)執(zhí)行的環(huán)境:函數(shù)參數(shù)、函數(shù)的局部變量、函數(shù)執(zhí)行完后返回到哪里等等2022-04-04
C++實(shí)現(xiàn)在文本中找出某個(gè)單詞的位置信息
本文給大家分享的是使用C++實(shí)現(xiàn)在文本中找出某個(gè)單詞的位置信息,就是給出此單詞所在的行和列,有需要的小伙伴可以參考下。2016-02-02
C++的get()函數(shù)與getline()函數(shù)使用詳解
這篇文章主要介紹了C++的get()函數(shù)與getline()函數(shù)使用詳解,是C++入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-09-09

