基于opencv 識別、定位二維碼 (c++版)

前言?因工作需要,需要定位圖片中的二維碼;我遂查閱了相關資料,也學習了opencv開源庫。通過一番努力,終于很好的實現了二維碼定位。本文將講解如何使用opencv定位二維碼。

定位二維碼不僅僅是為了識別二維碼;還可以通過二維碼對圖像進行水平糾正以及相鄰區域定位。定位二維碼,不僅需要圖像處理相關知識,還需要分析二維碼的特性,本文先從二維碼的特性講起。

1 二維碼特性

二維碼在設計之初就考慮到了識別問題,所以二維碼有一些特征是非常明顯的。

二維碼有三個“回“”字形圖案,這一點非常明顯。中間的一個點位于圖案的左上角,如果圖像偏轉,也可以根據二維碼來糾正。

思考題:?為什么是三個點,而不是一個、兩個或四個點。

一個點:特征不明顯,不易定位。不易定位二維碼傾斜角度。

兩個點:兩個點的次序無法確認,很難確定二維碼是否放正了。

四個點:無法確定4個點的次序,從而無法確定二維碼是否放正了。

識別二維碼,就是識別二維碼的三個點,逐步分析一下這三個點的特性

1 每個點有兩個輪廓。就是兩個口,大“口”內部有一個小“口”,所以是兩個輪廓。

2 如果把這個“回”放到一個白色的背景下,從左到右,或從上到下畫一條線。這條線經過的圖案黑白比例大約為:黑白比例為1:1:3:1:1。

3 如何找到左上角的頂點?這個頂點與其他兩個頂點的夾角為90度。

通過上面幾個步驟,就能識別出二維碼的三個頂點,并且識別出左上角的頂點。

2 使用opencv識別二維碼

1) 查找輪廓,篩選出三個二維碼頂點

opencv一個非常重要的函數就是查找輪廓,就是可以找到一個圖中的縮所有的輪廓,“回”字形圖案是一個非常的明顯的輪廓,很容易找到。

1intQrParse::FindQrPoint(Mat& srcImg,vector>& qrPoint)2{3//彩色圖轉灰度圖4Mat src_gray;5cvtColor(srcImg, src_gray, CV_BGR2GRAY);6namedWindow("src_gray");7imshow("src_gray", src_gray);89//二值化10Mat threshold_output;11threshold(src_gray, threshold_output,0,255, THRESH_BINARY | THRESH_OTSU);12Mat threshold_output_copy = threshold_output.clone();13namedWindow("Threshold_output");14imshow("Threshold_output", threshold_output);1516//調用查找輪廓函數17vector > contours;18vector hierarchy;19findContours(threshold_output, contours, hierarchy, CV_RETR_TREE, CHAIN_APPROX_NONE, Point(0,0));2021//通過黑色定位角作為父輪廓,有兩個子輪廓的特點,篩選出三個定位角22intparentIdx =-1;23intic =0;2425for(inti =0; i < contours.size(); i++)26{27if(hierarchy[i][2] !=-1&& ic ==0)28{29parentIdx = i;30ic++;31}32elseif(hierarchy[i][2] !=-1)33{34ic++;35}36elseif(hierarchy[i][2] ==-1)37{38ic =0;39parentIdx =-1;40}4143//有兩個子輪廓才是二維碼的頂點44if(ic >=2)45{46boolisQr = QrParse::IsQrPoint(contours[parentIdx], threshold_output_copy);4748//保存找到的三個黑色定位角49if(isQr)50qrPoint.push_back(contours[parentIdx]);5152ic =0;53parentIdx =-1;54}55}5657return0;58}

找到了兩個輪廓的圖元,需要進一步分析是不是二維碼頂點,用到如下函數:

boolQrParse::IsQrPoint(vector& contour, Mat& img){//最小大小限定RotatedRect rotatedRect = minAreaRect(contour);if(rotatedRect.size.height <10|| rotatedRect.size.width <10)returnfalse;//將二維碼從整個圖上摳出來cv::Mat cropImg = CropImage(img, rotatedRect);intflag = i++;//橫向黑白比例1:1:3:1:1boolresult = IsQrColorRate(cropImg, flag);returnresult;}

黑白比例判斷函數:

1//橫向和縱向黑白比例判斷2boolQrParse::IsQrColorRate(cv::Mat& image,intflag)3{4boolx = IsQrColorRateX(image, flag);5if(!x)6returnfalse;7booly = IsQrColorRateY(image, flag);8returny;9}10//橫向黑白比例判斷11boolQrParse::IsQrColorRateX(cv::Mat& image,intflag)12{13intnr = image.rows /2;14intnc = image.cols * image.channels();1516vector vValueCount;17vector vColor;18intcount =0;19uchar lastColor =0;2021uchar* data = image.ptr(nr);22for(inti =0; i < nc; i++)23{24vColor.push_back(data[i]);25uchar color = data[i];26if(color >0)27color =255;2829if(i ==0)30{31lastColor = color;32count++;33}34else35{36if(lastColor != color)37{38vValueCount.push_back(count);39count =0;40}41count++;42lastColor = color;43}44}4546if(count !=0)47vValueCount.push_back(count);4849if(vValueCount.size() <5)50returnfalse;5152//橫向黑白比例1:1:3:1:153intindex =-1;54intmaxCount =-1;55for(inti =0; i < vValueCount.size(); i++)56{57if(i ==0)58{59index = i;60maxCount = vValueCount[i];61}62else63{64if(vValueCount[i] > maxCount)65{66index = i;67maxCount = vValueCount[i];68}69}70}7172//左邊 右邊 都有兩個值,才行73if(index <2)74returnfalse;75if((vValueCount.size() - index) <3)76returnfalse;7778//黑白比例1:1:3:1:179floatrate = ((float)maxCount) /3.00;8081cout<<"flag:"<< flag <<" ";8283floatrate2 = vValueCount[index -2] / rate;84cout<< rate2 <<" ";85if(!IsQrRate(rate2))86returnfalse;8788rate2 = vValueCount[index -1] / rate;89cout<< rate2 <<" ";90if(!IsQrRate(rate2))91returnfalse;9293rate2 = vValueCount[index +1] / rate;94cout<< rate2 <<" ";95if(!IsQrRate(rate2))96returnfalse;9798rate2 = vValueCount[index +2] / rate;99cout<< rate2 <<" ";100if(!IsQrRate(rate2))101returnfalse;102103returntrue;104}105//縱向黑白比例判斷 省略106boolQrParse::IsQrColorRateY(cv::Mat& image,intflag)

boolQrParse::IsQrRate(floatrate){//大概比例 不能太嚴格returnrate >0.6&& rate <1.9;}

2) 確定三個二維碼頂點的次序

通過如下原則確定左上角頂點:二維碼左上角的頂點與其他兩個頂點的夾角為90度。

1// pointDest存放調整后的三個點,三個點的順序如下2// pt0----pt13// 4// pt25boolQrParse::AdjustQrPoint(Point* pointSrc, Point* pointDest)6{7boolclockwise;8intindex1[3] = {2,1,0};9intindex2[3] = {0,2,1};10intindex3[3] = {0,1,2};1112for(inti =0; i <3; i++)13{14int*n = index1;15if(i==0)16n = index1;17elseif(i ==1)18n = index2;19else20n = index3;2122double angle = QrParse::Angle(pointSrc[n[0]], pointSrc[n[1]], pointSrc[n[2]], clockwise);23if(angle >80&& angle <99)24{25pointDest[0] = pointSrc[n[2]];26if(clockwise)27{28pointDest[1] = pointSrc[n[0]];29pointDest[2] = pointSrc[n[1]];30}31else32{33pointDest[1] = pointSrc[n[1]];34pointDest[2] = pointSrc[n[0]];35}36returntrue;37}38}39returntrue;40}

3)通過二維碼對圖片矯正。

圖片有可能是傾斜的,傾斜夾角可以通過pt0與pt1連線與水平線之間的夾角確定。二維碼的傾斜角度就是整個圖片的傾斜角度,從而可以對整個圖片進行水平矯正。

1//二維碼傾斜角度2Point hor(pointAdjust[0].x+300,pointAdjust[0].y);//水平線3doubleqrAngle =QrParse::Angle(pointAdjust[1], hor, pointAdjust[0], clockwise);45//以二維碼左上角點為中心 旋轉6Mat drawingRotation =Mat::zeros(Size(src.cols,src.rows), CV_8UC3);7doublerotationAngle = clockwise? -qrAngle:qrAngle;8Mat affine_matrix = getRotationMatrix2D(pointAdjust[0], rotationAngle,1.0);//求得旋轉矩陣9warpAffine(src, drawingRotation, affine_matrix, drawingRotation.size());

4)二維碼相鄰區域定位

一般情況下,二維碼在整個圖中的位置是確定的。識別出二維碼后,根據二維碼與其他圖的位置關系,可以很容易的定位別的圖元。

后記

作者通過查找大量資料,仔細研究了二維碼的特征,從而找到了識別二維碼的方法。網上也有許多識別二維碼的方法,但是不夠嚴謹。本文是將二維碼的多個特征相結合來識別,這樣更準確。這種識別方法已應用在公司的產品中,識別效果還是非常好的。

看我主頁簡介免費C++學習資源,視頻教程、職業規劃、面試詳解、學習路線、開發工具

每晚8點直播講解C++編程技術。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,247評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,520評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,362評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,805評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,541評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,896評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,887評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,062評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,608評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,356評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,555評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,077評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,769評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,175評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,489評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,289評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,516評論 2 379