圖像透視校正實(shí)踐)
1. 表計(jì)讀數(shù)識別為什么必須加校正這一環(huán)做表計(jì)讀數(shù)識別的朋友應(yīng)該都有過這種經(jīng)歷算法模型在干凈的樣張上識別準(zhǔn)確率挺高一到現(xiàn)場實(shí)拍圖就全線崩潰。我第一版做字符分割時(shí)就踩過這個(gè)坑——采集器抓拍的表計(jì)圖像往往帶有明顯的旋轉(zhuǎn)和透視傾斜直接按固定區(qū)域切字符邊界全歪分割出來的數(shù)字殘缺不全識別率連五成都不到。后來才想明白讀數(shù)識別的前置條件不是這張圖里有表盤而是表盤必須正對相機(jī)刻度方向是標(biāo)準(zhǔn)的”?,F(xiàn)場安裝的攝像槍很難做到完美的正面拍攝角度一偏整個(gè)數(shù)字區(qū)域的幾何結(jié)構(gòu)就變了。所以第二篇我們要做的事情就是通過SURF特征匹配把任意角度、任意透視變形的表計(jì)圖像校正到和標(biāo)準(zhǔn)模板一致的位置。這個(gè)環(huán)節(jié)在整套表計(jì)讀數(shù)識別流程里承上啟下——上承接第一篇對表盤的定位與裁剪下為后續(xù)的刻度檢測、字符分割和讀數(shù)識別提供一張規(guī)矩的圖像。校正做得好不好直接影響后面每一步的容錯(cuò)率。我用的開發(fā)環(huán)境是 VS2015 搭配 OpenCV整套流程全部用 C 實(shí)現(xiàn)。之所以在環(huán)境選擇上多費(fèi)了些周折是因?yàn)?SURF 在 OpenCV 里的歸屬比較特殊——它不在主模塊里而是放在擴(kuò)展模塊 opencv_contrib 的 xfeatures2d 命名空間中。版本匹配、動(dòng)態(tài)庫鏈接、模塊加載每一步都可能浪費(fèi)半天時(shí)間。這篇文章會把環(huán)境搭建、特征提取參數(shù)調(diào)節(jié)、匹配策略選擇、單應(yīng)性矩陣求解與透視變換完整走一遍最后附上我在實(shí)際圖像上遇到的問題和調(diào)參經(jīng)驗(yàn)供準(zhǔn)備做同類項(xiàng)目的朋友參考。2. VS2015與OpenCV的版本匹配SURF模塊引入的兼容性取舍先交代環(huán)境細(xì)節(jié)。我本機(jī)是 Windows 10IDE 是 VS2015編譯器工具集對應(yīng) vc14。OpenCV 這里有個(gè)很容易踩的坑官方發(fā)布的預(yù)編譯庫Windows 版只提供 vc14 和 vc15 兩個(gè)工具集版本其中 vc14 對應(yīng)的就是 VS2015vc15 對應(yīng) VS2017。如果你機(jī)器上裝了 VS2019直接下載 vc15 的庫也能用因?yàn)?VS 本身向后兼容但 VS2015 用戶就必須老老實(shí)實(shí)選 vc14 版本否則鏈接階段會報(bào)一大堆 LNK2038 之類的運(yùn)行時(shí)庫不匹配錯(cuò)誤。關(guān)于 OpenCV 的具體版本號我建議選 3.4.x 系列不要選 4.x也不建議選更老的 2.x。原因有三點(diǎn)2.x 的 API 風(fēng)格比較老很多函數(shù)名和 3.x/4.x 不統(tǒng)一網(wǎng)上資料也以新版本為主排查問題不方便。4.x 雖然 API 和 3.x 基本一致但部分舊模塊位置有調(diào)整SURF 依然在 contrib 里可很多第三方博客針對的是 3.x 的路徑配置照抄容易出錯(cuò)。3.4.x 處于一個(gè)穩(wěn)定性較高的狀態(tài)SURF、SIFT 這些非自由算法依然可以通過 contrib 正常調(diào)用社區(qū)資料也最豐富。我在項(xiàng)目里選用的是 OpenCV 3.4.16這也是 3.4 系列最后一個(gè)版本。下載的時(shí)候需要注意擴(kuò)展包和解壓路徑有講究。下載頁面通常提供兩個(gè)壓縮包一個(gè)是標(biāo)準(zhǔn)包 opencv-3.4.16-vc14_vc15.exe另一個(gè)是擴(kuò)展包 opencv_contrib-3.4.16.zip。標(biāo)準(zhǔn)包解壓到 D:\opencv3416擴(kuò)展包不用單獨(dú)解壓直接保留 zip 文件即可因?yàn)?CMake 編譯的時(shí)候只需要指定它的路徑而不是先解壓到某個(gè)固定目錄。等等這里有個(gè)問題如果你下載的是官方預(yù)編譯的 Windows 庫SURF 是已經(jīng)編譯好的直接在鏈接器里加上 opencv_xfeatures2d3416.lib 就能用。但如果你的 OpenCV 是從源碼自己編譯的就需要先配置 OpenCV 的 CMake 工程把 OPENCV_EXTRA_MODULES_PATH 指向擴(kuò)展包路徑然后重新生成解決方案。兩種方式我都試過。官方預(yù)編譯的方式最簡單但有個(gè)隱患——預(yù)編譯庫的 contrib 模塊默認(rèn)是開啟狀態(tài)可實(shí)際使用中SURF 需要額外的第三方庫支持如果 install 目錄里的 include 文件不齊全編譯時(shí)找不到 features2d 頭文件會很麻煩。我后來實(shí)際部署時(shí)選擇了自己編譯因?yàn)轫?xiàng)目需要同時(shí)啟用一些 contrib 下的私有模塊而預(yù)編譯庫只開放了默認(rèn)子集。自己編譯時(shí)在 CMake 里需要打開 BUILD_EXAMPLES 和 BUILD_opencv_xfeatures2d同時(shí)確認(rèn) OPENCV_ENABLE_NONFREE 設(shè)置為 ON。這里特別提醒一下SURF 和 SIFT 屬于有專利限制的算法OpenCV 官方默認(rèn)編譯時(shí)是不開啟的必須在 CMake 配置階段顯式把 OPENCV_ENABLE_NONFREE 打開否則雖然特征提取函數(shù)能編譯通過運(yùn)行時(shí)會拋異常提示 This algorithm is patented and is excluded in this build。編譯耗時(shí)大概要一個(gè)多小時(shí)建議直接用 Release 和 Debug 兩套配置都編譯一遍避免后面調(diào)試時(shí)發(fā)現(xiàn) Debug 庫缺失又要重新來。我一開始只編了 Release后來程序崩了想用 Debug 查才發(fā)現(xiàn)沒有對應(yīng)的 dll又等了將近四十分鐘白白浪費(fèi)時(shí)間。環(huán)境配置部分include 目錄和 lib 目錄的設(shè)置不展開細(xì)說了有一點(diǎn)值得單獨(dú)提醒鏈接器附加依賴項(xiàng)里不要一股腦把 lib 全加進(jìn)去至少需要以下幾個(gè)opencv_world3416.lib或你編譯時(shí)生成的 opencv_world 庫文件名opencv_xfeatures2d3416.libopencv_features2d3416.libopencv_core3416.libopencv_imgproc3416.libopencv_calib3d3416.lib鏈接順序無所謂VS 不像老式 GCC 那樣對庫順序敏感。運(yùn)行時(shí)需要把對應(yīng)的 opencv_world3416.dll 和 opencv_xfeatures2d3416.dll 放到 exe 同目錄否則啟動(dòng)時(shí)報(bào)找不到 dll這是最常見的發(fā)布環(huán)境問題。3. 模板圖像選擇與SURF特征點(diǎn)提取的細(xì)節(jié)處理3.1 模板圖的準(zhǔn)備邏輯校正的本質(zhì)是把待識別圖像變換到模板圖的視角下所以模板圖的質(zhì)量直接決定校正的上限。我的經(jīng)驗(yàn)是模板圖必須滿足三個(gè)條件表盤完整清晰邊緣沒有被裁切。表盤中心盡量位于圖像中心長邊與圖像邊緣平行也就是標(biāo)準(zhǔn)的正面正對視角。表盤本體在畫面中占比盡量大不要把無關(guān)背景大量帶進(jìn)來。你可以用一張已經(jīng)貼墻安裝、且相機(jī)正對表計(jì)拍攝的照片作為模板也可以用廠家提供的標(biāo)準(zhǔn)表計(jì)渲染圖。我個(gè)人更推薦用真實(shí)拍攝并人工轉(zhuǎn)正后的照片作為模板因?yàn)?SURF 特征點(diǎn)對光照變化有一定魯棒性但紋理細(xì)節(jié)與真實(shí)表的差距過大時(shí)匹配效果會明顯下降。比如我用的是某款智能電表的正面照片表盤上有液晶屏、單位標(biāo)簽、條碼區(qū)、品牌標(biāo)識這些都能提供豐富的角點(diǎn)和紋理特征SURF 才能找到足夠多的可區(qū)分性關(guān)鍵點(diǎn)。順帶說一下模板圖分辨率不宜過低。建議長邊至少 800 像素以上否則特征點(diǎn)數(shù)量不夠后面的匹配容易失敗。我用的模板是 1280x960 的灰度圖特征點(diǎn)提取數(shù)量默認(rèn)在 400 左右效果比較理想。3.2 SURF檢測器的基本使用方式OpenCV 里 SURF 的調(diào)用方式很簡單但參數(shù)命名和版本間有差異。在 OpenCV 3.4.16 的 xfeatures2d 命名空間下標(biāo)準(zhǔn)寫法是#include opencv2/features2d.hpp #include opencv2/xfeatures2d.hpp #include opencv2/xfeatures2d/nonfree.hpp cv::Ptrcv::xfeatures2d::SURF detector cv::xfeatures2d::SURF::create(400); std::vectorcv::KeyPoint keypoints_tpl, keypoints_query; cv::Mat descriptors_tpl, descriptors_query; detector-detectAndCompute(templateImg, cv::Mat(), keypoints_tpl, descriptors_tpl); detector-detectAndCompute(queryImg, cv::Mat(), keypoints_query, descriptors_query);這里的 create(400) 其實(shí)是給的是 Hessian 閾值。SURF 算法通過計(jì)算圖像每個(gè)像素點(diǎn)的 Hessian 矩陣行列式來尋找斑點(diǎn)狀特征閾值越高檢測到的特征點(diǎn)越少但每個(gè)點(diǎn)的穩(wěn)定性越強(qiáng)。閾值 400 是個(gè)比較中庸的取值適合大多數(shù)場景。模板圖和查詢圖建議使用同一閾值避免兩個(gè)圖的特征點(diǎn)數(shù)量差異過大導(dǎo)致匹配失衡。3.3 Hessian閾值與特征點(diǎn)數(shù)量的聯(lián)動(dòng)關(guān)系實(shí)際調(diào)參時(shí)我發(fā)現(xiàn)Hessian 閾值對結(jié)果的影響遠(yuǎn)比想象中敏感。閾值設(shè)得過高比如 1000 以上模板圖和實(shí)拍圖可能各自只剩幾十個(gè)特征點(diǎn)匹配時(shí)正確匹配對數(shù)量不夠RANSAC 求單應(yīng)性矩陣時(shí)容易出現(xiàn)退化情況。閾值設(shè)得過低比如 100特征點(diǎn)密密麻麻匹配計(jì)算量暴漲而且容易出現(xiàn)誤匹配反而降低模型精度。我在實(shí)際項(xiàng)目中采用了一個(gè)簡單的自適應(yīng)策略先用 400 這個(gè)默認(rèn)閾值提取特征點(diǎn)如果匹配率低于某個(gè)閾值比如正確匹配對少于 20 對就把 Hessian 閾值調(diào)低到 200 重新提取再不行降到 100。反之如果特征點(diǎn)數(shù)量超過 1500 且匹配很慢就上調(diào)到 600。項(xiàng)目里大多數(shù)表計(jì)圖像用 300~400 之間的閾值都能穩(wěn)定工作。另外SURF 默認(rèn)檢測的是帶方向的特征點(diǎn)即每個(gè)關(guān)鍵點(diǎn)會計(jì)算一個(gè)主方向。這個(gè)方向的作用是讓描述子具備旋轉(zhuǎn)不變性。如果你的表計(jì)圖像本身傾斜角度不會很大比如只差 5~10 度可以把 create 時(shí)的參數(shù)改為不計(jì)算方向加速明顯。但本項(xiàng)目的核心需求恰恰就是矯正大角度傾斜所以方向信息必須保留不能為了性能犧牲旋轉(zhuǎn)適應(yīng)性。3.4 描述子與匹配前的數(shù)據(jù)預(yù)處理detectAndCompute 函數(shù)返回的描述子矩陣類型是 CV_32F行數(shù)為關(guān)鍵點(diǎn)數(shù)量列數(shù)為描述子維度。SURF 默認(rèn)描述子維度是 64也有擴(kuò)展的 128 維版本需要在 create 時(shí)設(shè)置 extended 參數(shù)。我一開始想當(dāng)然用了 128 維以為描述子越長匹配越準(zhǔn)實(shí)際測試后發(fā)現(xiàn) 64 維在表計(jì)圖像上已經(jīng)足夠而且 128 維占用內(nèi)存翻倍匹配速度慢不少。特征點(diǎn)的區(qū)分度取決于表計(jì)紋理的獨(dú)特性而不是盲目增加描述子長度。還有一個(gè)小細(xì)節(jié)如果輸入圖像是彩色圖detectAndCompute 默認(rèn)會先轉(zhuǎn)成灰度再處理。不過為了控制變量我習(xí)慣在調(diào)用前手動(dòng) cvtColor把原圖統(tǒng)一轉(zhuǎn)灰度再做一次高斯濾波降噪這樣可以避免不同色彩空間帶來的匹配結(jié)果不穩(wěn)定。特別是表計(jì)拍攝環(huán)境中常有 LED 顯示屏或強(qiáng)反光彩色信息反而會引入額外的光照干擾。4. 特征匹配策略KNN、距離比例篩選與交叉驗(yàn)證4.1 為什么不用簡單的暴力匹配拿到兩組特征描述子之后第一步是把模板圖中的每個(gè)特征點(diǎn)到查詢圖中找最相似的特征點(diǎn)。最直觀的方法是暴力枚舉也就是 Brute-Force Matching對每個(gè)模板特征點(diǎn)逐一計(jì)算和所有查詢特征點(diǎn)的歐氏距離取最小距離對應(yīng)的點(diǎn)作為匹配點(diǎn)。這種做法在特征點(diǎn)數(shù)量少、圖像內(nèi)容簡單時(shí)沒問題但在表計(jì)圖像上畫面里的刻度線、數(shù)字、條碼區(qū)域非常密集很多局部紋理看起來幾乎一樣暴力匹配很容易給某個(gè)特征點(diǎn)找到一個(gè)錯(cuò)誤但距離偏小的假匹配。直接拿這種匹配結(jié)果去做單應(yīng)性估計(jì)RANSAC 雖然能剔除一部分外點(diǎn)但外點(diǎn)比例太高時(shí)RANSAC 的內(nèi)點(diǎn)率算不出來模型直接崩掉。所以標(biāo)準(zhǔn)做法是先做 KNN 匹配取 k2。也就是對每個(gè)模板特征點(diǎn)找到查詢圖里距離最近的前兩個(gè)候選點(diǎn)然后比較最近距離和次近距離的比值。如果最近距離明顯小于次近距離說明這個(gè)匹配是獨(dú)一無二的可信度高如果兩者差不多說明該特征點(diǎn)在查詢圖里有多個(gè)相似區(qū)域匹配結(jié)果不可靠直接丟掉。這個(gè)思路用生活化的話說就是如果一個(gè)人在一群人中只有一個(gè)長得很像的朋友那他很容易被認(rèn)出來如果他在人群里有一堆孿生兄弟那你認(rèn)出他的把握就要打折扣。4.2 距離比例系數(shù)的選擇與實(shí)現(xiàn)OpenCV 里實(shí)現(xiàn) KNN 匹配和比例篩選的代碼如下cv::BFMatcher matcher(cv::NORM_L2); std::vectorstd::vectorcv::DMatch knn_matches; matcher.knnMatch(descriptors_tpl, descriptors_query, knn_matches, 2); const float ratio_thresh 0.8f; std::vectorcv::DMatch good_matches; for (size_t i 0; i knn_matches.size(); i) { if (knn_matches[i][0].distance ratio_thresh * knn_matches[i][1].distance) { good_matches.push_back(knn_matches[i][0]); } }距離比例系數(shù) ratio_thresh 是 Lowe 在 SIFT 論文里提出的方法經(jīng)典取值為 0.8。意思是最近距離必須小于次近距離的 80%才被認(rèn)為是有效匹配。這個(gè)值在 SIFT 上表現(xiàn)好換到 SURF 上也大致適用。我實(shí)測在表計(jì)圖像上0.8 的篩選力度比較適中大約能濾掉 40%~60% 的不可靠匹配剩下的匹配對正確率明顯提高。如果你發(fā)現(xiàn) good_matches 數(shù)量太多可以調(diào)小 ratio_thresh 到 0.7匹配對數(shù)會顯著減少但每個(gè)匹配的置信度更高。反之如果 good_matches 太少說明圖像差異大可以放寬到 0.9。注意這個(gè)參數(shù)不能無腦往 1.0 靠否則等于退化成直接取最近鄰誤匹配會大量涌入。4.3 交叉驗(yàn)證我推薦額外加的篩選層只用比例篩選還不夠。有一次我在一個(gè)反光嚴(yán)重的表計(jì)圖像上測試比例篩選后 good_matches 有 80 多對看起來一切正常但后續(xù) RANSAC 的 inlier 比例只有 40%明顯不對。后來查了一下發(fā)現(xiàn)相當(dāng)一部分錯(cuò)誤匹配呈現(xiàn)出對稱性錯(cuò)誤——模板圖的 A 點(diǎn)在查詢圖里匹配到 B 點(diǎn)而查詢圖的 B 點(diǎn)在模板圖里匹配到的卻是 C 點(diǎn)雙方不是互認(rèn)的。最簡單的解決辦法是加交叉驗(yàn)證也就是正向匹配和反向匹配同時(shí)進(jìn)行從模板到查詢做一次匹配再從查詢到模板做一次匹配只保留兩個(gè)方向都認(rèn)為對方是最優(yōu)匹配的特征點(diǎn)對。這個(gè)策略代碼上要增加一次 knnMatch 調(diào)用和一組集合判斷但效果立竿見影。我加上交叉驗(yàn)證后后續(xù) RANSAC 的 inlier 比例普遍能從 50% 提升到 75% 以上。4.4 均勻化特征點(diǎn)分布防止局部區(qū)域主導(dǎo)匹配還有一個(gè)比較隱蔽的問題。表計(jì)圖像的左上角如果有一塊密密麻麻的條碼區(qū)特征點(diǎn)會大量集中在這個(gè)區(qū)域?qū)е缕ヅ鋵σ苍言谶@里單應(yīng)性矩陣被這些局部點(diǎn)牽著鼻子走而表盤其他區(qū)域的特征點(diǎn)幾乎沒有參與計(jì)算。這樣求出來的變換矩陣對整體圖像不友好可能出現(xiàn)表盤中心區(qū)域校正得還行但邊緣區(qū)域扭曲明顯。針對這個(gè)問題我寫了一個(gè)簡單的均勻化邏輯把圖像劃分為 4x4 網(wǎng)格對每個(gè)網(wǎng)格內(nèi)返回的 good_matches 數(shù)量設(shè)上限。超過上限的網(wǎng)格只保留距離比例最小的前 N 個(gè)匹配這樣保證每個(gè)區(qū)域的匹配點(diǎn)都有貢獻(xiàn)。這個(gè)操作對提高單應(yīng)性矩陣的全局魯棒性幫助很大尤其是當(dāng)拍攝角度比較大的時(shí)候強(qiáng)烈建議加上。5. 單應(yīng)性矩陣求解與透視變換理論到代碼的銜接5.1 單應(yīng)性矩陣是什么為什么它能完成校正先解釋基礎(chǔ)概念。單應(yīng)性矩陣Homography Matrix是一個(gè) 3x3 矩陣描述的是同一個(gè)平面在兩個(gè)不同視角下的投影映射關(guān)系。表計(jì)的表盤是一個(gè)近似平面所以從任意角度拍攝到的表計(jì)圖像和標(biāo)準(zhǔn)模板圖之間可以由一個(gè)單應(yīng)性矩陣完成映射。數(shù)學(xué)上假設(shè)模板圖上的一個(gè)點(diǎn)為 (u, v)查詢圖上對應(yīng)點(diǎn)為 (x, y)那么映射關(guān)系為[ s \cdot \begin{bmatrix} x \ y \ 1 \end{bmatrix} H \cdot \begin{bmatrix} u \ v \ 1 \end{bmatrix} ]其中 s 是尺度因子。展開后H 矩陣的 8 個(gè)自由度足以描述旋轉(zhuǎn)、縮放、平移和透視變形。OpenCV 的 findHomography 函數(shù)就是從一組匹配點(diǎn)對中求解這 8 個(gè)參數(shù)。最少需要 4 對匹配點(diǎn)即可求解但實(shí)際使用時(shí)匹配點(diǎn)對中難免有錯(cuò)誤點(diǎn)所以必須配合 RANSAC 或 LMEDS 這類魯棒估計(jì)算法來剔除外點(diǎn)。5.2 findHomography 的參數(shù)選擇RANSAC 還是 LMEDSOpenCV 的 findHomography 提供了幾個(gè)計(jì)算方法其中實(shí)際項(xiàng)目里常用的是 RANSAC 和 LMEDS。兩者的區(qū)別在于對錯(cuò)誤數(shù)據(jù)外點(diǎn)的容忍方式和計(jì)算效率。RANSAC 算法的思路是隨機(jī)抽取 4 對匹配點(diǎn)計(jì)算一個(gè)候選 H然后用這個(gè) H 去測試所有匹配點(diǎn)統(tǒng)計(jì)滿足誤差閾值的點(diǎn)內(nèi)點(diǎn)數(shù)量反復(fù)迭代多次取內(nèi)點(diǎn)數(shù)量最多的那個(gè) H。LMEDS 則是最小化內(nèi)點(diǎn)的中位誤差它在數(shù)據(jù)包含較多外點(diǎn)時(shí)也能有不錯(cuò)的估計(jì)但計(jì)算量更大而且對誤差閾值的設(shè)置不像 RANSAC 那么直觀。我在項(xiàng)目里使用 RANSAC并設(shè)置了兩個(gè)關(guān)鍵參數(shù)閾值 ransacReprojThreshold默認(rèn)取 3.0單位是像素。這個(gè)值表示某匹配點(diǎn)經(jīng)過 H 變換后的位置和實(shí)際位置的距離小于該值時(shí)視為內(nèi)點(diǎn)。值越小篩選越嚴(yán)格。最大迭代次數(shù)OpenCV 默認(rèn)是 2000 次一般夠用。特征點(diǎn)數(shù)量很大時(shí)可以適當(dāng)降低減少耗時(shí)。代碼實(shí)現(xiàn)如下cv::Mat H cv::findHomography(points_tpl, points_query, cv::RANSAC, 3.0);這里要特別留意輸入點(diǎn)的類型。points_tpl 和 points_query 必須是 std::vector cv::Point2f 而且順序要一一對應(yīng)即 points_tpl[i] 和 points_query[i] 是同一條匹配對的兩個(gè)端點(diǎn)。我之前犯過一個(gè)低級錯(cuò)誤把模板點(diǎn)和查詢點(diǎn)搞反了結(jié)果 H 矩陣求出來是反的透視變換后的圖像直接被翻轉(zhuǎn)排查了很久才發(fā)現(xiàn)問題。5.3 從 DMatch 提取點(diǎn)坐標(biāo)的正確姿勢KNN 匹配得到的是 DMatch 結(jié)構(gòu)體數(shù)組每個(gè) DMatch 包含 queryIdx查詢圖特征點(diǎn)索引、trainIdx模板圖特征點(diǎn)索引和 distance。注意這里的query和train指的是調(diào)用 knnMatch 時(shí)兩個(gè)描述子的傳入順序而不是圖像語義上的查詢圖或模板圖。我的代碼里以模板圖作為第一個(gè)參數(shù)所以模板圖的特征點(diǎn)對應(yīng) trainIdx查詢圖的特征點(diǎn)對應(yīng) queryIdx。提取坐標(biāo)時(shí)一定要搞清楚這個(gè)對應(yīng)關(guān)系否則又是一處隱性翻車點(diǎn)。std::vectorcv::Point2f points_tpl, points_query; for (const cv::DMatch m : good_matches) { points_tpl.push_back(keypoints_tpl[m.trainIdx].pt); points_query.push_back(keypoints_query[m.queryIdx].pt); }5.4 透視變換與圖像尺寸的決策求解得到 H 矩陣之后用 warpPerspective 函數(shù)完成變換cv::Mat correctedImg; cv::warpPerspective(queryImg, correctedImg, H, templateImg.size());這里的關(guān)鍵是輸出尺寸的選擇。我直接把 templateImg.size() 作為輸出尺寸這樣校正后的圖像和模板圖在空間上一一對應(yīng)后續(xù)如果要做模板匹配或者區(qū)域比對坐標(biāo)可以直接復(fù)用。相比之下如果輸出尺寸設(shè)成 queryImg.size()校正后的圖像雖然變正了但尺寸還是原圖的坐標(biāo)體系和模板不同后續(xù)操作要多一層坐標(biāo)換算沒必要。warpPerspective 的插值方式默認(rèn)是 INTER_LINEAR對于表計(jì)讀數(shù)識別場景線性插值夠用。如果你的表計(jì)圖像分辨率很高放大時(shí)想保留更清晰的邊緣可以試試 INTER_CUBIC但速度會略慢。邊界模式一般默認(rèn) BORDER_CONSTANT填充黑色方便觀察校正后的圖像區(qū)域。5.5 校正效果自檢怎么判斷變換矩陣是否可信拿到 H 之后不要急著往下走先做一個(gè)簡單的可視化自檢把 good_matches 中的匹配點(diǎn)在模板圖、查詢圖上畫出來同時(shí)把 transform 后的圖顯示出來肉眼觀察表計(jì)是否轉(zhuǎn)正、刻度方向是否水平、長寬比例是否自然。這一步雖然土但非常有效。另外推薦一個(gè)數(shù)值化的校驗(yàn)方法計(jì)算 H 矩陣的條件數(shù)或者檢查 H 矩陣中某些奇異值是否過大。如果 H 矩陣數(shù)值不穩(wěn)定說明求解過程出現(xiàn)了退化情況這時(shí)候即使圖像看起來好像轉(zhuǎn)正了也可能是巧合后續(xù)識別步驟很容易出錯(cuò)。更嚴(yán)格的做法是隨機(jī)抽取一部分匹配點(diǎn)做交叉驗(yàn)證看穩(wěn)定匹配比例是否超過 60%。6. 實(shí)戰(zhàn)中的問題排查記錄與調(diào)參技巧6.1 真實(shí)場景一表計(jì)反光導(dǎo)致特征點(diǎn)丟失第一個(gè)讓我頭疼的場景是夜間或強(qiáng)光下的反光表計(jì)。這類表計(jì)的玻璃面板會反射出環(huán)境光源形成一個(gè)亮斑區(qū)SURF 在亮斑附近提取的特征點(diǎn)質(zhì)量很差而且方向描述子容易受到高光干擾。我的解決方案是在做特征提取前先對圖像進(jìn)行一次自適應(yīng)直方圖均衡化CLAHE把局部對比度增強(qiáng)同時(shí)限制光照不均勻造成的梯度偏移。這個(gè)預(yù)處理只影響特征點(diǎn)提取不對原圖做永久修改。實(shí)測下來匹配對數(shù)從 30 對提高到 70 多對效果好得明顯。6.2 真實(shí)場景二鏡頭畸變導(dǎo)致邊緣匹配錯(cuò)位某些廣角攝像頭存在明顯的桶形畸變靠近畫面邊緣的表計(jì)會發(fā)生輕微的彎曲變形。SURF 特征點(diǎn)不受這種低頻形變的影響太大但單應(yīng)性矩陣只能描述平面投影變換不能描述非線性畸變導(dǎo)致邊緣部分的匹配殘差較大。這種情況我建議在特征匹配前先調(diào)用 getOptimalNewCameraMatrix 和 undistort 對圖像做畸變校正或者在采集端直接使用長焦鏡頭減少廣角畸變。后一種方式往往更省事成本也低。6.3 真實(shí)場景三匹配對數(shù)不足怎么兜底無論怎么優(yōu)化總會遇到某些表計(jì)圖像因?yàn)檎趽趸驑O端角度導(dǎo)致匹配對數(shù)不足 10 對。這時(shí)候強(qiáng)行求單應(yīng)性矩陣是危險(xiǎn)的我采用了一個(gè)分級降級策略先嘗試 SURF RANSAC。匹配對數(shù)不足時(shí)改用 ORB 特征再做一次匹配ORB 對光照變化不如 SURF但在一些紋理簡單的表計(jì)上反而更好使。如果還是不足就退化為邊緣直線檢測通過找表盤矩形邊緣估算旋轉(zhuǎn)角做簡單的仿射校正。這個(gè)降級策略保證了流程在絕大多數(shù)情況下不會完全中斷。雖然第三步精度不如特征匹配但至少后續(xù)步驟能拿到一個(gè)可用的初值不會因?yàn)橐粋€(gè)異常圖導(dǎo)致整條識別鏈路卡死。6.4 耗時(shí)優(yōu)化實(shí)時(shí)性不夠怎么辦SURF 的計(jì)算量比 ORB 大得多在 1280x960 的圖上特征提取加匹配大約需要 150~300 毫秒具體取決于 CPU 性能。對于離線批量識別場景這個(gè)速度可以接受但如果要做實(shí)時(shí)視頻流需要做兩處優(yōu)化把模板圖的特征點(diǎn)和描述子在程序啟動(dòng)時(shí)預(yù)先計(jì)算一次后續(xù)每一幀只用檢測查詢圖并匹配省掉模板圖檢測的時(shí)間。查詢圖在進(jìn)入特征提取前先做一次降采樣。比如把長邊降到 640 像素。SURF 對尺度變化不敏感降采樣后的特征點(diǎn)數(shù)量雖然減少但匹配質(zhì)量不會下降太多。實(shí)測降采樣到 640 后一幀的處理時(shí)間能控制在 80 毫秒左右基本能達(dá)到 12 FPS 的準(zhǔn)實(shí)時(shí)效果。6.5 一個(gè)容易忽略的小細(xì)節(jié)dll 運(yùn)行環(huán)境最后提一個(gè)容易被發(fā)布環(huán)節(jié)坑到的地方。VS2015 編譯的程序默認(rèn)依賴 vc14 運(yùn)行庫如果目標(biāo)機(jī)器上沒有安裝對應(yīng)版本的 Visual C Redistributable程序啟動(dòng)會直接報(bào)程序無法正常啟動(dòng)。我當(dāng)時(shí)把程序部署到車間工控機(jī)上就踩了這個(gè)坑工控機(jī)上只裝了 VS2013 的運(yùn)行庫結(jié)果跑不起來。后來在發(fā)布包里附帶了 vc_redist.x64.exe并在部署腳本里自動(dòng)安裝問題才解決。類似的還有 OpenCV 的 dll 依賴。建議在最終發(fā)布時(shí)把用到的 opencv_world3416.dll 和 opencv_xfeatures2d3416.dll 直接復(fù)制到 exe 同目錄不要依賴系統(tǒng)的 PATH 環(huán)境變量工控機(jī)環(huán)境往往很干凈沒有任何配好的環(huán)境變量。7. 模板匹配之外校正后的圖像還能做什么圖像校正完成后后續(xù)的讀數(shù)識別步驟就變得非常簡單。由于校正后的圖像與模板圖尺寸一致、視角一致你可以直接按模板圖里手工標(biāo)注好的數(shù)字區(qū)域坐標(biāo)去裁剪字符然后用模板匹配、OCR 或者輕量級 CNN 做數(shù)字分類。這一整套流程下來識別的穩(wěn)定性比直接拿傾斜圖硬識別高出不少。我個(gè)人在實(shí)際項(xiàng)目中的體會是校正這一環(huán)在整個(gè)表計(jì)讀數(shù)識別系統(tǒng)里的重要性被很多人低估了。不少團(tuán)隊(duì)一上來就搞深度學(xué)習(xí)模型指望網(wǎng)絡(luò)能自動(dòng)學(xué)到旋轉(zhuǎn)不變性但工程上的魯棒性往往來自前端幾何校正的扎實(shí)程度。數(shù)據(jù)增強(qiáng)雖然能在訓(xùn)練時(shí)引入各種角度但推理時(shí)如果輸入圖像角度過偏模型的表現(xiàn)仍然不理想而幾何校正正好把這個(gè)問題從源頭解決掉。另外SURF 校正方案對模板圖的依賴比較強(qiáng)如果實(shí)際場景中表計(jì)型號很多每種型號都需要準(zhǔn)備一張模板圖。建議把模板圖特征點(diǎn)離線緩存成二進(jìn)制文件運(yùn)行時(shí)按型號加載避免每次啟動(dòng)都重新提取特征。這個(gè)緩存文件通常只有幾 MB加載速度在毫秒級幾乎不影響啟動(dòng)時(shí)間。如果你在項(xiàng)目里也打算走表計(jì)讀數(shù)識別這條路先別急著上高端模型把 SURF 校正這塊打磨扎實(shí)后面每一步都會省心很多。