:從輪廓分析到透視校正實(shí)戰(zhàn))
簡(jiǎn)介本資源是一份面向C開發(fā)者與計(jì)算機(jī)視覺初學(xué)者的OpenCV二維碼定位圖案檢測(cè)實(shí)踐項(xiàng)目聚焦于二維碼四角特征結(jié)構(gòu)1:1:3:1:1比例同心方框的圖像識(shí)別與坐標(biāo)定位適用于掃碼系統(tǒng)開發(fā)、工業(yè)視覺定位等實(shí)際場(chǎng)景。壓縮包共8個(gè)文件含2張測(cè)試圖像png/jpg、1個(gè)核心實(shí)現(xiàn)源碼main.cpp、1個(gè)Visual Studio工程配置vcxproj、1個(gè)解決方案文件sln及配套編譯過(guò)濾器與用戶配置文件整體體積782KB結(jié)構(gòu)完整開箱即可編譯運(yùn)行。目前已有3322人學(xué)習(xí)下載資源提供可直接調(diào)試的端到端檢測(cè)流程從灰度化、Canny邊緣提取、輪廓篩選到定位圖案匹配與綠色邊界框繪制代碼注釋清晰關(guān)鍵步驟如輪廓比例驗(yàn)證、角點(diǎn)計(jì)算邏輯均有明確實(shí)現(xiàn)便于理解底層原理并快速遷移至多碼識(shí)別或角度校正等進(jìn)階任務(wù)。1. 項(xiàng)目概述從“識(shí)別二維碼”到“定位圖案檢測(cè)”用OpenCV做二維碼定位圖案檢測(cè)說(shuō)白了就是在一張圖里把二維碼三個(gè)角上的“回”字黑框方塊找出來(lái)。這個(gè)需求我第一次遇到是在AGV機(jī)器人導(dǎo)航項(xiàng)目里工業(yè)掃碼槍太貴普通攝像頭又需要自己知道二維碼在圖像的哪個(gè)位置。后來(lái)發(fā)現(xiàn)很多場(chǎng)景都需要這套能力需要把傾斜、透視變形的二維碼拉正再交給解碼庫(kù)需要從視頻流里實(shí)時(shí)追蹤二維碼位置或者只是想把二維碼圖片做預(yù)處理提高識(shí)別率。如果你也在用C寫二維碼識(shí)別、定位、糾偏或者想在MISC類的圖片題里快速定位并拉正二維碼這篇文章應(yīng)該能幫你省掉好幾天調(diào)試時(shí)間。定位圖案檢測(cè)和真正的“二維碼解碼”是兩回事。解碼庫(kù)比如OpenCV自帶的QRCodeDetector能直接返回內(nèi)容和四個(gè)角點(diǎn)但有時(shí)候只寫一行detectAndDecode真的夠用嗎多數(shù)情況下不夠。尤其是二維碼只占畫面很小一塊、背景雜亂、光照不均或者存在旋轉(zhuǎn)畸變時(shí)官方接口經(jīng)常返回空結(jié)果。自己做定位圖案檢測(cè)就可以在解碼前把二維碼先“提”出來(lái)把識(shí)別問(wèn)題降維成“一個(gè)干凈正面的標(biāo)準(zhǔn)二維碼”。這篇文章我會(huì)完整拆解檢測(cè)原理、C實(shí)現(xiàn)步驟、關(guān)鍵代碼和調(diào)參經(jīng)驗(yàn)所有內(nèi)容基于OpenCV 4.x和C11以上環(huán)境。1.1 什么時(shí)候需要自己檢測(cè)定位圖案先說(shuō)說(shuō)我為什么不用現(xiàn)成的decode直接干活。OpenCV的QRCodeDetector方便是真方便但它是一個(gè)黑盒只能給你解碼文本和四個(gè)點(diǎn)中間過(guò)程不可控。實(shí)際項(xiàng)目里有三類需求它滿足不了。一類是定位需求。比如AGV小車需要通過(guò)攝像頭尋找二維碼作為地標(biāo)這時(shí)候更關(guān)心“二維碼在圖像哪個(gè)位置、偏轉(zhuǎn)了多少度”而不是二維碼內(nèi)容。二維碼定位圖案就是天然的視覺標(biāo)靶三個(gè)角點(diǎn)可以精確解算出目標(biāo)位姿配合相機(jī)標(biāo)定甚至能做三維定位。另一類是圖像預(yù)處理需求。拍攝角度傾斜導(dǎo)致透視變形時(shí)解碼庫(kù)可能失敗但如果我們自己能找到三個(gè)定位圖案就能先做透視校正把二維碼變成正面視角再去解碼。第三類是特殊圖像處理需求比如低分辨率二維碼、部分遮擋、強(qiáng)反光這時(shí)候官方檢測(cè)常常失效自己實(shí)現(xiàn)的方案可以通過(guò)調(diào)整預(yù)處理參數(shù)來(lái)救回來(lái)。所以做定位圖案檢測(cè)不是重復(fù)造輪子而是把二維碼識(shí)別的關(guān)鍵環(huán)節(jié)握在自己手里。1.2 這次我們打算怎么做整體思路分四步預(yù)處理圖像、查找輪廓、根據(jù)層級(jí)關(guān)系篩選像“定位圖案”的候選框、最后排序并輸出三個(gè)角點(diǎn)。后面每一步我都會(huì)給出C代碼和解釋并附上我在真實(shí)項(xiàng)目里踩過(guò)的坑。代碼不會(huì)很長(zhǎng)但每個(gè)參數(shù)為什么這么設(shè)我會(huì)盡量講清楚。2. 定位圖案的特征分析與檢測(cè)方案選型2.1 Finder Pattern的結(jié)構(gòu)與三種檢測(cè)方案對(duì)比二維碼的定位圖案英文叫Finder Pattern一共三個(gè)分別位于左上、右上、左下。它的結(jié)構(gòu)是同心嵌套的方形最外層是黑色邊框中間一圈白色最里面是黑色方塊。按二維碼模塊單位來(lái)算就是1個(gè)模塊寬的黑色外框、1個(gè)模塊寬的白色間隙、3個(gè)模塊寬的中心黑塊再加上另一側(cè)同樣的白間黑邊整體是7x7模塊。用圖像處理的視角看特征非常明顯三層輪廓嵌套而且每一層都接近正方形。檢測(cè)方案常見的有三種方案原理優(yōu)點(diǎn)缺點(diǎn)模板匹配制作Finder Pattern模板在全圖滑窗匹配實(shí)現(xiàn)簡(jiǎn)單對(duì)旋轉(zhuǎn)、尺度變化敏感速度慢Hough直線/圓檢測(cè)檢測(cè)同心正方形邊緣能處理旋轉(zhuǎn)參數(shù)多、易誤檢受背景干擾大輪廓層級(jí)分析用findContours提取輪廓再利用父子嵌套關(guān)系篩選旋轉(zhuǎn)和尺度不變結(jié)構(gòu)邏輯清晰需要理解輪廓層級(jí)代碼稍復(fù)雜我最終選了輪廓層級(jí)分析。定位圖案最穩(wěn)定的特征不是像素值而是“輪廓嵌套”這種拓?fù)浣Y(jié)構(gòu)。只要二維碼沒有嚴(yán)重?fù)p壞三層嵌套關(guān)系一定存在不依賴亮度、對(duì)比度的絕對(duì)值。2.2 為什么優(yōu)先選擇輪廓層級(jí)模板匹配最大的問(wèn)題是旋轉(zhuǎn)。Finder Pattern轉(zhuǎn)45度之后矩形模板就匹配不上了。雖然可以生成多角度模板但工程復(fù)雜度立刻上去了。Hough直線檢測(cè)則需要在邊緣圖上調(diào)一堆參數(shù)找四條邊組合成回字形很容易把背景里的書脊、窗框也算進(jìn)去。輪廓層級(jí)分析的優(yōu)勢(shì)是兩個(gè)第一天然支持任意旋轉(zhuǎn)和縮放。findContours提取的是輪廓點(diǎn)的集合不管方形轉(zhuǎn)了30度還是縮小到20像素輪廓的形狀和嵌套關(guān)系都還在。配合minAreaRect做幾何校驗(yàn)就能拿到帶旋轉(zhuǎn)角度的最小外接矩形這正是后面透視校正需要的信息。第二誤檢率可控。一張圖片里可能有很多方形物體但“外層輪廓有子輪廓、子輪廓還有孫輪廓”這種嚴(yán)格嵌套的方形結(jié)構(gòu)并不常見。用層級(jí)篩選能過(guò)濾掉大部分背景干擾剩下的再用面積比、長(zhǎng)寬比做二次確認(rèn)準(zhǔn)確率非常高。2.3 預(yù)處理不要讓光照毀掉檢測(cè)在輪廓分析之前圖像預(yù)處理決定成敗。我這里的經(jīng)驗(yàn)是大部分檢測(cè)失敗都不是算法問(wèn)題而是二值化之后定位圖案的黑色區(qū)域斷開了。常見的處理流程是轉(zhuǎn)灰度、去噪、二值化。轉(zhuǎn)灰度用cvtColor就行。去噪推薦用高斯模糊核大小5x5或3x3太大的核會(huì)把細(xì)小的黑白邊界抹掉。二值化有兩種選擇全局閾值OTSU適合背景和前景對(duì)比明顯的圖像自適應(yīng)閾值適合光照不均的場(chǎng)景。我一般會(huì)用adaptiveThreshold作為默認(rèn)選項(xiàng)因?yàn)閷?shí)拍照片里光照不勻是常態(tài)。很多人會(huì)在這步用直方圖均衡化增強(qiáng)對(duì)比度。但要注意OpenCV的equalizeHist沒有掩膜參數(shù)不能直接指定只增強(qiáng)目標(biāo)區(qū)域。網(wǎng)上搜到的“equalizeHist 掩膜”基本都是自己算直方圖再對(duì)指定區(qū)域做映射。實(shí)測(cè)下來(lái)我更推薦用createCLAHE做自適應(yīng)直方圖均衡它對(duì)整幅圖的光照不均勻處理更自然不容易把普通背景也增強(qiáng)出偽輪廓來(lái)。特別是二維碼處于高反光表面時(shí)CLAHE比全局直方圖均衡穩(wěn)定得多。3. C實(shí)現(xiàn)流程拿到三個(gè)定位角點(diǎn)3.1 環(huán)境搭建要點(diǎn)我推薦用VSCode寫代碼CMake配置工程。OpenCV安裝包建議選和編譯器匹配的版本W(wǎng)indows下如果用的是MinGW就下MinGW版本別下MSVC版本混用Linux下直接apt安裝或者從源碼編譯都行。CMakeLists.txt最簡(jiǎn)配置如下cmake_minimum_required(VERSION 3.10) project(qr_finder) set(CMAKE_CXX_STANDARD 11) find_package(OpenCV REQUIRED) include_directories(${OpenCV_INCLUDE_DIRS}) add_executable(qr_finder main.cpp) target_link_libraries(qr_finder ${OpenCV_LIBS})注意一個(gè)坑OpenCV 4.x中findContours的接口不接受const輸入圖像所以二值化結(jié)果要存成普通Mat。另外如果你用的是老教程里的CV_前綴常量在OpenCV 4里編譯會(huì)報(bào)錯(cuò)需要改成COLOR_、THRESH_這種新命名。3.2 圖像預(yù)處理與二值化代碼預(yù)處理代碼不長(zhǎng)但參數(shù)影響很大。#include opencv2/opencv.hpp #include vector #include cmath using namespace cv; using namespace std; Mat preprocess(Mat src) { Mat gray, blur, bin; cvtColor(src, gray, COLOR_BGR2GRAY); // 中值濾波或高斯濾波都可以二維碼噪聲多時(shí)優(yōu)先用中值 medianBlur(gray, blur, 5); // 光照不均場(chǎng)景用自適應(yīng)閾值干凈圖片用 OTSU // adaptiveThreshold(blur, bin, 255, ADAPTIVE_THRESH_GAUSSIAN_C, // THRESH_BINARY_INV, 15, 10); threshold(blur, bin, 0, 255, THRESH_BINARY_INV | THRESH_OTSU); return bin; }之所以用THRESH_BINARY_INV是因?yàn)閒indContours默認(rèn)找白色前景而二維碼的黑色模塊是我們要檢測(cè)的目標(biāo)反相之后黑色區(qū)域變成白色更容易處理。自適應(yīng)閾值里的blockSize和C值需要根據(jù)圖像分辨率調(diào)整。blockSize太小會(huì)把背景噪聲也變成前景太大又會(huì)丟失局部對(duì)比度。我一般先從15開始調(diào)圖像分辨率高就適當(dāng)增加到25或31。C值控制偏差閾值默認(rèn)5到15。如果檢測(cè)到的輪廓斷裂把C值調(diào)小一點(diǎn)。3.3 輪廓提取與層級(jí)關(guān)系篩選核心調(diào)用是findContours必須使用RETR_TREE模式這樣才能拿到完整的父子嵌套關(guān)系。CHAIN_APPROX_SIMPLE可以壓縮輪廓點(diǎn)減少計(jì)算量精度足夠。vectorvectorPoint contours; vectorVec4i hierarchy; findContours(bin, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);hierarchy每一行四個(gè)整數(shù)分別表示同層級(jí)下一條輪廓、同層級(jí)上一條輪廓、第一個(gè)子輪廓、父輪廓的索引。對(duì)于定位圖案來(lái)說(shuō)最外層黑色外框的輪廓會(huì)有子輪廓白色環(huán)的內(nèi)邊界這個(gè)子輪廓又會(huì)有孫輪廓中心黑塊的外邊界。所以判斷標(biāo)準(zhǔn)就是當(dāng)前輪廓有子輪廓且子輪廓還有子輪廓。我把篩選寫成一個(gè)lambda函數(shù)放在一個(gè)遍歷循環(huán)里int parentIndex -1; vectorint candidateIndices; for (int i 0; i (int)contours.size(); i) { int child hierarchy[i][2]; if (child 0) continue; int grandChild hierarchy[child][2]; if (grandChild 0) continue; if (isFinderPatternShape(contours[i])) { candidateIndices.push_back(i); } }這是最核心的一層過(guò)濾能把絕大多數(shù)不相關(guān)的輪廓全部排除掉。注意這里找的是“最外層”輪廓也就是黑色外框后面計(jì)算也用這個(gè)輪廓。3.4 幾何校驗(yàn)過(guò)濾候選框找到嵌套三層輪廓之后還要確認(rèn)三個(gè)方塊的形狀和面積比例符合要求。我用三個(gè)校驗(yàn)條件最小外接矩形接近正方形長(zhǎng)寬比不能超過(guò)1.2。輪廓面積和最小外接矩形面積的比值不能太小說(shuō)明輪廓不是畸形鋸齒狀。中心子輪廓和父輪廓的面積比要與理論值吻合。Finder Pattern整體7x7模塊中心黑塊約占9/49約0.18白色環(huán)約占16/49約0.33。所以子輪廓面積比應(yīng)該落在0.1到0.4之間。代碼如下bool isFinderPatternShape(const vectorPoint contour) { double area contourArea(contour); if (area 100) return false; // 太小的不算 RotatedRect rect minAreaRect(contour); float w rect.size.width; float h rect.size.height; if (w 1 || h 1) return false; float whRatio max(w, h) / min(w, h); if (whRatio 1.2f) return false; double rectArea (double)w * h; double fillRatio area / rectArea; if (fillRatio 0.7) return false; return true; }這一步過(guò)濾完候選通常只剩幾個(gè)。如果仍然多出干擾項(xiàng)可以再做一次“候選點(diǎn)兩兩距離篩選”最終三個(gè)定位圖案之間的距離應(yīng)該大于每個(gè)定位圖案自身尺寸且三個(gè)點(diǎn)大致構(gòu)成等腰直角三角形。這個(gè)后處理我在下面排序時(shí)一起做。3.5 排序與透視變換三個(gè)定位圖案沒有順序需要把它們區(qū)分成左上、右上、左下。我用的是“距離最遠(yuǎn)兩點(diǎn)確定斜邊剩下的是直角點(diǎn)”的方法。因?yàn)槿齻€(gè)定位圖案中右上和左下之間的對(duì)角線距離一定最大剩下的那個(gè)點(diǎn)就是左上。然后是分辨右上和左下直接比較剩下兩點(diǎn)的x坐標(biāo)x大的是右上x小的是左下。這里有個(gè)隱含前提圖像沒有鏡像翻轉(zhuǎn)。如果相機(jī)是正面的這個(gè)前提成立。排序代碼void sortFinderPoints(vectorPoint2f points) { // points里是三個(gè)角點(diǎn) int idxA 0, idxB 1, idxC 2; // 找到距離最大的兩個(gè)點(diǎn)記為右上和左下 Point2f p0 points[idxA], p1 points[idxB], p2 points[idxC]; float d01 norm(p0 - p1); float d12 norm(p1 - p2); float d20 norm(p2 - p0); Point2f rightTop, leftBottom, leftTop; if (d01 d12 d01 d20) { leftTop points[2]; rightTop points[0]; leftBottom points[1]; } else if (d12 d01 d12 d20) { leftTop points[0]; rightTop points[1]; leftBottom points[2]; } else { leftTop points[1]; rightTop points[2]; leftBottom points[0]; } // 右上點(diǎn)x應(yīng)該大于左下點(diǎn)x if (rightTop.x leftBottom.x) { swap(rightTop, leftBottom); } points[0] leftTop; points[1] rightTop; points[2] leftBottom; }拿到三個(gè)點(diǎn)后就能計(jì)算第四個(gè)角點(diǎn)構(gòu)造透視變換矩陣Point2f leftTop points[0]; Point2f rightTop points[1]; Point2f leftBottom points[2]; Point2f rightBottom rightTop leftBottom - leftTop; vectorPoint2f srcQuad {leftTop, rightTop, rightBottom, leftBottom}; vectorPoint2f dstQuad {Point2f(0, 0), Point2f(200, 0), Point2f(200, 200), Point2f(0, 200)}; Mat trans getPerspectiveTransform(srcQuad.data(), dstQuad.data()); Mat warp; warpPerspective(src, warp, trans, Size(200, 200));這樣得到的warp就是一個(gè)200x200的正面二維碼圖后面就可以直接喂給解碼庫(kù)了。尺寸為什么選200因?yàn)榇蠖鄶?shù)掃碼場(chǎng)景下200像素寬度足夠包含一個(gè)標(biāo)準(zhǔn)QRCode的模塊又能控制計(jì)算量。如果二維碼模塊特別密可以設(shè)成300或400。4. 把檢測(cè)結(jié)果接入識(shí)別鏈路4.1 用透視變換得到正向碼圖透視變換是定位圖案檢測(cè)的下游核心功能。剛才我們已經(jīng)用三個(gè)定位點(diǎn)求出第四個(gè)點(diǎn)組成一個(gè)四邊形。用getPerspectiveTransform計(jì)算圖像到這個(gè)四邊形區(qū)域的單應(yīng)矩陣再warpPerspective把原圖映射成正面視圖。這里的意義不只是“擺正”。透視變換會(huì)把斜視造成的梯形變形矯正成正方形相當(dāng)于把相機(jī)從側(cè)面拍變成正面拍碼率和解碼成功率會(huì)大幅提升。很多OpenCV官方QRCodeDetector在二維碼傾斜超過(guò)40度時(shí)就會(huì)解碼失敗但先做透視校正就沒事。我可以分享一個(gè)調(diào)試技巧在warpPerspective之后不要直接拿這個(gè)圖取解碼先用imshow看一眼。如果標(biāo)志區(qū)域是正的、黑白分明說(shuō)明定位點(diǎn)排序和坐標(biāo)計(jì)算沒錯(cuò)如果出現(xiàn)鏡像或錯(cuò)位多半是四個(gè)角的順序沒搞對(duì)。4.2 配合OpenCV QRCodeDetector解碼雖然我們自己做的是定位圖案檢測(cè)最后還是要接一個(gè)解碼器來(lái)完成內(nèi)容解析。OpenCV 4.x自帶QRCodeDetector直接對(duì)校正后的圖調(diào)用QRCodeDetector detector; string info detector.detectAndDecode(warp); if (!info.empty()) { cout decode: info endl; } else { // 可以嘗試對(duì)原圖直接解碼 string info2 detector.detectAndDecode(src); cout decode original: info2 endl; }官方解碼器對(duì)正向、清晰、尺寸適中的二維碼識(shí)別率很高。所以把檢測(cè)和校正交給自己的代碼把解碼交給官方庫(kù)是目前比較合理的分工。如果你在乎性能可以在檢測(cè)到定位圖案后直接緩存透視變換矩陣下一幀用上一幀的矩陣做跟蹤避免每一幀都找輪廓。4.3 實(shí)測(cè)效果與優(yōu)化建議我用一張包含三個(gè)旋轉(zhuǎn)二維碼的測(cè)試圖跑過(guò)定位圖案檢測(cè)耗時(shí)大約15毫秒其中大部分時(shí)間花在輪廓提取上透視變換后交給QRCodeDetector解碼三個(gè)碼全部成功。如果是600x800以上的圖像建議先把圖像縮小到合理尺寸再檢測(cè)能顯著降低findContours耗時(shí)。性能調(diào)優(yōu)有兩條路一是縮小檢測(cè)尺寸定位圖案相對(duì)位置不會(huì)變只是坐標(biāo)要乘回縮放系數(shù)二是ROI剪裁如果上一幀已經(jīng)檢出二維碼下一幀只在上次位置附近擴(kuò)大一個(gè)范圍搜索速度會(huì)快很多。5. 常見問(wèn)題與排查技巧實(shí)錄5.1 二值化后定位圖案斷裂怎么辦這是最常見的問(wèn)題通常表現(xiàn)為三個(gè)定位圖案的黑色外框斷開導(dǎo)致輪廓嵌套關(guān)系不完整。首選方案是調(diào)整自適應(yīng)閾值的blockSize和C值。blockSize越大對(duì)小區(qū)域光照變化越不敏感但也會(huì)抹掉細(xì)節(jié)C值需要配合實(shí)驗(yàn)。第二種方案是形態(tài)學(xué)操作。對(duì)二值圖做一次閉運(yùn)算用getStructuringElement生成3x3或5x5的矩形核能把小裂縫補(bǔ)上。注意不要閉太大否則兩個(gè)相近的定位圖案會(huì)連在一起。Mat kernel getStructuringElement(MORPH_RECT, Size(3, 3)); morphologyEx(bin, bin, MORPH_CLOSE, kernel);5.2 輪廓層級(jí)篩選總是多出很多噪點(diǎn)當(dāng)背景里有很多黑白嵌套的圖案時(shí)比如條紋、同心圓、多個(gè)二維碼候選會(huì)明顯增多。這時(shí)候需要加后置空間約束三個(gè)定位圖案兩兩之間的距離應(yīng)大于每個(gè)圖案自身尺寸的2倍且三個(gè)候選點(diǎn)形成的三角形接近等腰直角三角形。我一般會(huì)做一個(gè)兩層循環(huán)遍歷候選點(diǎn)用這三個(gè)條件排出最可能的一組。如果誤檢仍然很多可以把幾何校驗(yàn)里的fillRatio閾值調(diào)到0.8甚至0.85。多試幾組參數(shù)直到誤檢消失。注意這里的“輪廓面積占最小外接矩形面積比”對(duì)角上有缺口的輪廓很敏感能有效淘汰殘缺候選框。5.3 小圖和模糊圖的處理很多MISC二維碼題里二維碼只占幾十個(gè)像素直接檢測(cè)輪廓幾乎必掛。我的土辦法是先用圖像金字塔把圖片放大2到3倍或者用resize配合INTER_CUBIC插值放大再做一個(gè)銳化。處理后定位圖案的輪廓層級(jí)就清楚多了。模糊圖要先提升清晰度再檢測(cè)不能跳過(guò)??梢杂酶咚逛J化或者unsharp mask。OpenCV沒有直接的unsharp函數(shù)但可以這樣寫Mat blurred, sharp; GaussianBlur(src, blurred, Size(0, 0), 3); addWeighted(src, 1.5, blurred, -0.5, 0, sharp);這是很經(jīng)典的非銳化掩膜公式對(duì)低對(duì)比度小二維碼有效。但要注意如果原圖本身噪聲很大銳化會(huì)讓噪聲更明顯這時(shí)候先做一次中值濾波。5.4 定位點(diǎn)對(duì)上了但解碼失敗這個(gè)問(wèn)題90%出在透視變換后圖像方向不對(duì)。QRCode的幾何結(jié)構(gòu)是固定的左上、右上、左下三個(gè)定位圖案一旦順序錯(cuò)亂校正圖就會(huì)變成鏡像或旋轉(zhuǎn)90度解碼自然全掛。所以我總會(huì)在排完序之后加一個(gè)斷言左上點(diǎn)x小于右上點(diǎn)x左上點(diǎn)y小于左下點(diǎn)y不滿足就交換。另外解碼失敗也可能是因?yàn)樾U龍D邊緣殘留背景干擾了解碼器的模塊采樣。我的辦法是在校正圖上做一次裁剪從邊緣去掉幾個(gè)像素只保留碼區(qū)int border 8; Mat roi warp(Rect(border, border, warp.cols - 2 * border, warp.rows - 2 * border));5.5 常見問(wèn)題速查表問(wèn)題現(xiàn)象可能原因解決方案三個(gè)定位點(diǎn)找不到光照不均勻二值化后斷裂換自適應(yīng)閾值做閉運(yùn)算或先CLAHE增強(qiáng)找到十幾個(gè)候選框背景干擾多增加面積比閾值加三角形空間約束有候選框但排序亂點(diǎn)間距離計(jì)算有誤確認(rèn)先找最大距離再比較x坐標(biāo)透視校正后圖案翻轉(zhuǎn)點(diǎn)順序不對(duì)加排序斷言檢查右上左下判斷條件解碼失敗但圖看起來(lái)正常分辨率不夠或邊緣有干擾放大校正圖裁剪邊緣銳化去噪檢測(cè)速度太慢findContours處理全圖縮小圖像、ROI跟蹤、限制輪廓點(diǎn)數(shù)6. 寫在后面一點(diǎn)調(diào)試上的個(gè)人體會(huì)二維碼定位圖案檢測(cè)這件事原理說(shuō)穿了不值錢真正值錢的是各種邊界情況的處理。我調(diào)試的時(shí)候最常干的一件事就是把二值化窗口、面積比閾值、自適應(yīng)閾值的C值全部做成滑動(dòng)條在視頻流里實(shí)時(shí)調(diào)參調(diào)到一個(gè)極端光照?qǐng)鼍耙材芊€(wěn)定檢出再固化參數(shù)寫進(jìn)代碼。這個(gè)土辦法比看文檔管用得多。最后再分享一個(gè)小技巧如果三個(gè)定位圖案中有一個(gè)因?yàn)檎趽鯖]找到不要急著放棄整體檢測(cè)。你可以根據(jù)另外兩個(gè)定位圖案的相對(duì)位置預(yù)測(cè)第三個(gè)點(diǎn)大概在哪塊區(qū)域把預(yù)測(cè)區(qū)域放大再做一次局部搜索。很多情況下遮擋只是擋住了局部并沒有完全毀掉定位圖案的輪廓結(jié)構(gòu)。這種“先猜后驗(yàn)證”的思路在AGV導(dǎo)航、工業(yè)抓取這種追求穩(wěn)定性的場(chǎng)景里非常實(shí)用。希望這篇文章能讓你少踩幾個(gè)坑順利把二維碼定位圖案檢測(cè)跑通。本文還有配套的精品資源點(diǎn)擊獲取