前言?因工作需要喇潘,需要定位圖片中的二維碼体斩;我遂查閱了相關資料,也學習了opencv開源庫颖低。通過一番努力絮吵,終于很好的實現(xiàn)了二維碼定位。本文將講解如何使用opencv定位二維碼忱屑。
定位二維碼不僅僅是為了識別二維碼蹬敲;還可以通過二維碼對圖像進行水平糾正以及相鄰區(qū)域定位。定位二維碼莺戒,不僅需要圖像處理相關知識伴嗡,還需要分析二維碼的特性,本文先從二維碼的特性講起从铲。
1 二維碼特性
二維碼在設計之初就考慮到了識別問題瘪校,所以二維碼有一些特征是非常明顯的。
二維碼有三個“回“”字形圖案名段,這一點非常明顯阱扬。中間的一個點位于圖案的左上角,如果圖像偏轉伸辟,也可以根據(jù)二維碼來糾正麻惶。
思考題:?為什么是三個點,而不是一個信夫、兩個或四個點窃蹋。
一個點:特征不明顯,不易定位忙迁。不易定位二維碼傾斜角度脐彩。
兩個點:兩個點的次序無法確認,很難確定二維碼是否放正了姊扔。
四個點:無法確定4個點的次序惠奸,從而無法確定二維碼是否放正了。
識別二維碼恰梢,就是識別二維碼的三個點佛南,逐步分析一下這三個點的特性
1 每個點有兩個輪廓梗掰。就是兩個口,大“口”內(nèi)部有一個小“口”嗅回,所以是兩個輪廓及穗。
2 如果把這個“回”放到一個白色的背景下,從左到右绵载,或從上到下畫一條線埂陆。這條線經(jīng)過的圖案黑白比例大約為:黑白比例為1:1:3:1:1。
3 如何找到左上角的頂點娃豹?這個頂點與其他兩個頂點的夾角為90度焚虱。
通過上面幾個步驟,就能識別出二維碼的三個頂點懂版,并且識別出左上角的頂點鹃栽。
2 使用opencv識別二維碼
1) 查找輪廓,篩選出三個二維碼頂點
opencv一個非常重要的函數(shù)就是查找輪廓,就是可以找到一個圖中的縮所有的輪廓躯畴,“回”字形圖案是一個非常的明顯的輪廓民鼓,很容易找到。
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//調(diào)用查找輪廓函數(shù)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}
找到了兩個輪廓的圖元,需要進一步分析是不是二維碼頂點倡鲸,用到如下函數(shù):
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;}
黑白比例判斷函數(shù):
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存放調(diào)整后的三個點峭状,三個點的順序如下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)二維碼相鄰區(qū)域定位
一般情況下胆敞,二維碼在整個圖中的位置是確定的。識別出二維碼后杂伟,根據(jù)二維碼與其他圖的位置關系移层,可以很容易的定位別的圖元。
后記
作者通過查找大量資料赫粥,仔細研究了二維碼的特征观话,從而找到了識別二維碼的方法。網(wǎng)上也有許多識別二維碼的方法越平,但是不夠嚴謹频蛔。本文是將二維碼的多個特征相結合來識別灵迫,這樣更準確。這種識別方法已應用在公司的產(chǎn)品中晦溪,識別效果還是非常好的瀑粥。
看我主頁簡介免費C++學習資源,視頻教程三圆、職業(yè)規(guī)劃狞换、面試詳解、學習路線舟肉、開發(fā)工具
每晚8點直播講解C++編程技術哀澈。