:從預處理到特征匹配的算法路線與調參心法)
1. 從零搭建圖像處理直覺先搞清楚OpenCV到底在算什么很多朋友一開始學OpenCV就扎進各種函數(shù)的海洋里今天調一個cv2.cvtColor明天試一下cv2.Canny函數(shù)都跑通了但遇到真實項目還是懵。我最初也是這么過來的后來才意識到問題出在哪OpenCV不是一堆孤立函數(shù)的集合而是一套從像素出發(fā)解決問題的方法論。每個算法本質上都在做同一件事——把一張由成百上千萬個數(shù)字組成的像素矩陣逐步變成對我們有意義的信息。比如灰度化是把三維顏色壓縮成一維亮度濾波是在像素和鄰居之間做加權平均邊緣檢測是在找亮度變化最劇烈的那條分界線。想通這一點之后再看算法就非常順了它不是在處理圖片而是在用數(shù)學手段回答這張圖里哪部分值得關注。這篇總結并不是把OpenCV官方文檔的算法目錄抄一遍而是按我在實際項目里最常用的路線來梳理。你可能是剛入門不久的學生可能是在做畢業(yè)設計或競賽也可能是工作中突然要接手圖像相關模塊的開發(fā)者。無論哪種情況我的建議都是不要按官方文檔的順序學按預處理 → 特征提取 → 結構分析 → 實際調優(yōu)這條主線學效率會高很多。下面每一章都會先講明白算法在解決什么問題再給關鍵參數(shù)和使用場景最后補充一些文檔里不會寫的實戰(zhàn)經(jīng)驗。2. 圖像預處理三件套灰度化、濾波、直方圖均衡化2.1 灰度化為什么是第一步而不是可選項先說一個幾乎所有OpenCV教程都默認你懂、但沒人真正講透的問題為什么大多數(shù)算法都要從彩色圖轉成灰度圖原因不光是計算量小了三倍更本質的是很多算法在數(shù)學定義上就是建立在單通道上的。比如邊緣檢測的核心是計算亮度梯度——彩色圖有三個通道每個通道都有各自的梯度它們之間怎么加權合成反而會引入干擾。拿一個實際例子來說。我之前處理一批從外部設備抓取的工業(yè)零件圖像光源偏黃整個畫面白平衡不對。直接跑彩色圖的邊緣檢測黃光在金屬表面的反射會造成大量偽邊緣。轉成灰度后雖然白平衡問題依然存在但至少梯度計算只針對亮度單一維度干擾源少了一個。再用下面的濾波和均衡化把亮度分布拉平問題基本就解決了。import cv2 img cv2.imread(industrial_part.jpg) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)2.2 濾波選型均值、高斯、中值到底差在哪濾波是所有預處理里最考驗經(jīng)驗感的一步因為它的本質是模糊而模糊多少、怎么模糊直接影響后面所有算法的輸入質量。均值濾波cv2.blur窗口內所有像素取平均值。速度快但對噪聲敏感因為噪聲點也被平均進去了。高斯濾波cv2.GaussianBlur按距離給窗口內像素賦權重離中心越近權重越大。這是用得最廣的因為它更符合自然圖像中相鄰像素相關性隨距離衰減的規(guī)律。絕大多數(shù)場景先試它準沒錯。中值濾波cv2.medianBlur取窗口內像素的中位數(shù)。這是去椒鹽噪聲黑白孤點噪聲的利器但對邊緣細節(jié)的保留也最差。我自己在項目里的選型經(jīng)驗是不知道噪聲類型時優(yōu)先高斯濾波。它能在去噪和保留邊緣之間取得一個比較穩(wěn)妥的平衡。如果圖像里明顯有黑白顆粒狀的噪聲就換中值濾波如果對速度要求極高且圖像質量本身不錯才考慮均值濾波。這里有一個我踩過的坑——高斯濾波的核大小ksize必須是正奇數(shù)而且核越大圖像越模糊但不是越大越好。對一張1080p的圖像我用(5, 5)的核做預處理邊緣檢測效果最好用(11, 11)之后細小的邊緣直接被抹平了后面怎么調閾值都救不回來。核大小要跟目標特征尺寸匹配這是個需要反復試的經(jīng)驗值。2.3 直方圖均衡化把對比度拉滿的數(shù)學原理直方圖均衡化是我早期最容易忽略、后來覺得真香的一個算法。它的原理一句話能講清楚把像素值的分布從集中在某個區(qū)間拉伸到均勻分布在整個0-255范圍。但為什么這樣能增強對比度因為人眼和算法對細節(jié)的感知都依賴于亮度差異——如果整張圖的灰度值都堆在100到120之間那這20個級別的差異肉眼幾乎看不出區(qū)別拉開到0到255之后原本微小的差異就變成明顯可辨的了。# 全局直方圖均衡化 equ cv2.equalizeHist(gray) # CLAHE限制對比度自適應直方圖均衡化 clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8)) clahe_img clahe.apply(gray)在實際項目中我?guī)缀醪挥胏v2.equalizeHist處理照片因為全局均衡化對光照不均勻的圖效果很差——它會把整體亮度的變化也當成細節(jié)去增強導致畫面發(fā)灰、過曝感嚴重。更實用的是CLAHE自適應直方圖均衡化它把圖像分成若干個小塊tile每個塊內單獨做均衡化再用插值把塊之間的邊界做平滑。這樣能保留局部的細節(jié)增強效果又不會讓全局亮度失真。舉個真實案例我處理過一批室外拍攝的交通指示牌圖像白天強光下牌子亮部過曝、暗部欠曝。用全局均衡化后整張圖變得灰蒙蒙的換成CLAHE之后牌面上的文字和圖案細節(jié)都清晰了不少。clipLimit這個參數(shù)控制對比度增強的強度我一般設2.0到3.0之間太大會出現(xiàn)水彩畫效果顏色過度飽和太小則跟沒處理一樣。3. 邊緣檢測算法Sobel、Laplacian、Canny的共性與差異3.1 邊緣的本質是變化率不是邊界線很多新手對邊緣檢測有個誤區(qū)以為它是像人的眼睛一樣看到物體的輪廓。實際上邊緣檢測算法全是在做同一件事——計算像素亮度值的導數(shù)變化率。平坦區(qū)域的像素變化率接近0不是邊緣變化劇烈的地方導數(shù)大就是候選邊緣。理解這一點之后再看各種算子就不亂了Sobel算子計算一階導數(shù)區(qū)分水平方向和垂直方向分別用cv2.Sobel的dx和dy參數(shù)控制。因為只算了一階它對噪聲比較敏感但實現(xiàn)簡單、速度快。Laplacian算子計算二階導數(shù)。二階導數(shù)為0的點對應一階導數(shù)的極值也就是邊緣的位置。它對孤立噪聲點特別敏感噪聲點的二階導數(shù)極大所以一般要先做高斯模糊再用。Canny算子不是單一算子而是一個多階段流程——先用高斯濾波去噪再算梯度幅值和方向然后做非極大值抑制把梯度方向上不是局部最大值的點去掉最后用雙閾值連接邊緣。3.2 為什么實戰(zhàn)中Canny是默認首選我自己在項目里90%的場景直接選Canny不是因為它高級而是因為它把邊緣檢測變成了一個可調參的工程問題。Sobel的閾值和核大小對結果的影響不太好控制Canny的兩個閾值threshold1和threshold2給了你一個明確的調參維度小于threshold1的梯度不算邊緣大于threshold2的必算邊緣兩者之間的按連通性決定是否保留。# Canny邊緣檢測 edges cv2.Canny(blurred, threshold150, threshold2150)這段代碼里50和150的比例經(jīng)驗值是1:2到1:3之間。threshold1設得低邊緣會多但噪聲也多設得高邊緣干凈但一些小細節(jié)可能丟失。調參的起點我一般是先跑一遍看結果然后盯著圖像里想保留的最弱邊緣來反推threshold1。比如希望保留一條細縫的邊緣而它的梯度幅值在60左右那threshold1就設在50上下留一點余量。3.3 邊緣檢測前要不要先濾波這是一個先有雞還是先有蛋的問題Canny內部自帶高斯濾波所以很多人會忽略提前濾波這一步。但我在實踐中發(fā)現(xiàn)處理真實噪聲較多的圖像時先對原圖做一次中值濾波或高斯濾波再進Canny效果遠比只用Canny內建濾波好。原因在于Canny內置的高斯濾波核大小是固定的而不同圖像的噪聲尺度差異很大提前濾波可以針對性地消除特定尺度的噪聲。需要強調的是這篇博文里的所有示例、參數(shù)和調優(yōu)思路都來自我在模擬項目和公開數(shù)據(jù)集上的復現(xiàn)經(jīng)驗不是某個具體商業(yè)產(chǎn)品的參數(shù)。圖像處理本身就是三分算法、七分調參的活兒參數(shù)要跟著你的數(shù)據(jù)和目標走。4. 形態(tài)學操作腐蝕、膨脹、開運算、閉運算以及它們如何修理二值圖4.1 形態(tài)學不是在處理圖像是在處理形狀形態(tài)學操作處理的輸入一般是經(jīng)過閾值分割或邊緣檢測后得到的二值圖只有0和255兩種值。它的核心操作只有兩個腐蝕和膨脹。其他所有操作都是這兩個的組合。用最直觀的話解釋腐蝕用一個結構元素核掃過圖像只有當核完全落在白色區(qū)域內時中心點才保留白色。效果是白色區(qū)域縮小一圈能去掉小塊的白色噪點。膨脹只要核與白色區(qū)域有交集中心點就變白。效果是白色區(qū)域擴大一圈能填補空洞、連接斷開的區(qū)域。4.2 開運算和閉運算解決去噪與補洞這對矛盾開運算 先腐蝕后膨脹。它能消除白色區(qū)域周圍的小凸起和孤立噪點但整體形狀大小基本不變。我第一次用開運算處理二值圖時驚訝于它能把被打碎的字符邊緣修得干干凈凈而字符的粗細幾乎沒變化。閉運算 先膨脹后腐蝕。它能填補白色區(qū)域內部的小孔洞、連接斷裂的筆畫。比如檢測印刷體文字時如果字體內部有白色小點干擾連通域分析用閉運算就能解決。import cv2 import numpy as np kernel cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)) # 開運算消除白色小噪點 opening cv2.morphologyEx(binary_img, cv2.MORPH_OPEN, kernel) # 閉運算填補白色區(qū)域內部空洞 closing cv2.morphologyEx(binary_img, cv2.MORPH_CLOSE, kernel)核大小的選擇直接影響效果。我用(5, 5)的核做開運算把一些細小的印刷噪點去掉了但當我換成(9, 9)的核發(fā)現(xiàn)字符筆畫的主體也開始被吞噬。結構元素尺寸應略小于你想保留的最小特征尺寸——這是我在多次調整后總結的一句話放這里最合適。4.3 形態(tài)學梯度被很多人忽略的邊緣增強神器形態(tài)學梯度 膨脹結果 減 腐蝕結果。它提取的是二值圖的前景邊緣效果類似于邊緣檢測但因為只基于形態(tài)學操作速度非常快而且對粗細變化不敏感。在需要快速提取形狀外輪廓的場景比如實時視頻目標跟蹤的預處理形態(tài)學梯度比Canny更省計算資源。gradient cv2.morphologyEx(binary_img, cv2.MORPH_GRADIENT, kernel)這段代碼在很多教科書里只是被提了一句但實際用起來效果出奇地穩(wěn)定。我把形態(tài)學梯度作為檢查二值圖質量的輔助手段——如果梯度結果里出現(xiàn)了大量雜散的孤立邊緣說明二值圖噪聲偏多需要回頭調整閾值分割的參數(shù)。5. 輪廓檢測與幾何特征計算從一堆點組成有意義的對象5.1findContours的返回值陷阱OpenCV版本差異必須搞清輪廓檢測是圖像處理中最常用的結構化工具它能把二值圖中連通的白色區(qū)域提取成一組點的集合輪廓從這以后圖像處理就從像素操作升級為幾何分析。但這里有一個非常經(jīng)典的坑。OpenCV的cv2.findContours在OpenCV 3.x和4.x中返回值的數(shù)量不同老版本返回3個值image, contours, hierarchy新版本返回2個值contours, hierarchy。# 適用于OpenCV 4.x的寫法 contours, hierarchy cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)如果從網(wǎng)上復制了老版本的代碼直接跑多半會報ValueError: not enough values to unpack。這類語法層面的差異最煩人因為報錯和實際算法無關純粹是版本適配問題。另外兩個關鍵參數(shù)也值得展開。cv2.RETR_EXTERNAL只提取最外層的輪廓忽略內層洞比如一個圓環(huán)只取外圓不取內圓。適合目標彼此分離的場景。cv2.RETR_CCOMP和cv2.RETR_TREE提取所有層級的輪廓適合處理有嵌套結構的圖像比如文字內還有小洞的情況。cv2.CHAIN_APPROX_SIMPLE壓縮水平、垂直和對角方向的輪廓點只保留端點能大幅減少點數(shù)。如果不壓縮cv2.CHAIN_APPROX_NONE輪廓點數(shù)巨大后續(xù)計算復雜度也高。5.2 輪廓篩選的實用套路面積、長寬比、凸包一個都不能少提取到輪廓之后下一步通常不是直接用而是篩選。我整理了最常用的三個篩選維度面積cv2.contourArea過濾掉太小或太大的噪聲區(qū)域。比如檢測工件上的螺絲孔時面積小于20像素的輪廓基本可以斷定是灰塵或噪點。寬高比cv2.boundingRect用外接矩形寬高比排除不符合長寬約束的輪廓。比如檢測車牌區(qū)域字符輪廓的寬高比一般在一個固定范圍內超出范圍的直接丟棄。凸包cv2.convexHull判斷輪廓是否近似凸形。對檢測圓形、矩形等規(guī)則目標時很有用——偏離凸形程度大的輪廓大概率不是目標。areas [cv2.contourArea(c) for c in contours] # 按面積從大到小排序取前N個 sorted_contours sorted(contours, keycv2.contourArea, reverseTrue)[:N]5.3 形狀匹配與輪廓近似當你要判斷它是不是一個圓時輪廓近似的本質是用更少的點去逼近原始輪廓關鍵是cv2.approxPolyDP的epsilon參數(shù)。這個參數(shù)表示近似結果與原始輪廓之間的最大距離值越大近似后的點多邊形越粗糙但也越精簡。epsilon 0.01 * cv2.arcLength(contour, True) approx cv2.approxPolyDP(contour, epsilon, True) if len(approx) 3: shape triangle elif len(approx) 4: shape rectangle elif len(approx) 5: shape pentagon else: # 一般用圓度來判斷 area cv2.contourArea(contour) perimeter cv2.arcLength(contour, True) circularity 4 * np.pi * area / (perimeter * perimeter) if circularity 0.85: shape circle這段代碼展示了一個很常見的業(yè)務邏輯檢測物體是三角形、矩形、五邊形還是圓形。circularity圓度是一個非常經(jīng)典的幾何度量值在0到1之間越接近1說明形狀越接近圓。這個方法我在做形狀識別的小工具時反復用過它的優(yōu)勢是不依賴機器學習模型純幾何計算快且直觀。5.4 坐標排序的細節(jié)做OCR項目前必須掌握的定位順序輪廓檢測在OCR項目里經(jīng)常用來定位單個字符但有個問題檢測到的輪廓順序完全取決于圖像掃描的順序不一定是按閱讀順序從左到右、從上到下排列的。我早期做OCR demo時就栽在這里——輪廓順序一亂最后拼出來的文本順序完全不對。解決辦法是用外接矩形框的中心點坐標先按y坐標排序從上到下再把同一行內按x坐標排序從左到右。rectangles [cv2.boundingRect(c) for c in contours] # 按 y 排序然后按 x 排序 sorted_rects sorted(rectangles, keylambda r: (r[1] // h, r[0]))這里對y除以行高h再取整是為了把同一行內的字符歸為一組避免因為輕微的傾斜導致同一行的字符y坐標有差異而被拆成多行。這個技巧對傾斜校正后的圖像尤其有效。6. 特征點檢測與匹配ORB、SIFT同臺競技怎么選6.1 特征點到底特征在哪里邊緣檢測關注的是整條線特征點檢測關注的是圖像中獨特且容易被再次找到的局部位置。一個典型的特征點通常是角點、紋理豐富的區(qū)域中心、或者在光照變化下依然穩(wěn)定的圖像塊。特征點檢測算法的輸出一般包括三樣東西特征點的位置x, y、描述子一個向量用來描述這個點周圍區(qū)域的紋理特征、以及匹配時需要的距離度量。描述子的意義在于——當你需要判斷兩張圖中的兩個點是否對應同一位置時只需比較它們的描述子是否足夠相似。6.2 ORB vs SIFT授權問題之外性能差異才是關鍵SIFT尺度不變特征變換在特征點檢測里的地位不用多說它能在圖像縮放、旋轉、光照變化下保持很強的穩(wěn)定性。但它有一個不少人都知道的痛點SIFT算法是有專利限制的在商業(yè)應用中需要授權。ORBOriented FAST and Rotated BRIEF就是為了解決這個問題而生的完全開源免費。但除了授權問題速度差異也是關鍵。我之前在一個實時圖像匹配的小項目里對比過算法耗時毫秒/幀匹配穩(wěn)定性適用場景ORB15-30中等實時預覽、嵌入式設備、快速匹配SIFT80-200高離線匹配、高精度拼接、物體識別從表格能直觀看出ORB速度優(yōu)勢極大SIFT穩(wěn)定性優(yōu)勢極大。如果你做的是實時視頻幀匹配ORB是首選如果做的是圖像拼接這類對精度要求極高的離線任務SIFT更穩(wěn)妥。# ORB特征提取與匹配 orb cv2.ORB_create(nfeatures500) kp1, des1 orb.detectAndCompute(img1, None) kp2, des2 orb.detectAndCompute(img2, None) bf cv2.BFMatcher(cv2.NORM_HAMMING, crossCheckTrue) matches bf.match(des1, des2) matches sorted(matches, keylambda x: x.distance) # 過濾匹配取距離最近的前N個 good_matches matches[:40]注意ORB的描述子距離用cv2.NORM_HAMMING漢明距離因為ORB生成的是二進制描述子SIFT用cv2.NORM_L2歐式距離。這個細節(jié)很多人會忽略——匹配距離度量沒用對結果可能完全沒法看。6.3 用單應性矩陣過濾誤匹配讓結果穩(wěn)下來的關鍵一步特征匹配的結果里總會有錯誤匹配尤其是當兩張圖存在大量相似紋理時。一個非常實用的過濾思路是用cv2.findHomography配合RANSAC算法在匹配點中找出一個能解釋大多數(shù)匹配的透視變換矩陣而那些不符合這個變換的匹配點就是離群點可以被剔除。# 從匹配點中提取坐標 src_pts np.float32([kp1[m.queryIdx].pt for m in good_matches]).reshape(-1, 1, 2) dst_pts np.float32([kp2[m.trainIdx].pt for m in good_matches]).reshape(-1, 1, 2) # RANSAC 計算單應性矩陣并過濾誤匹配 M, mask cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0) matches_mask mask.ravel().tolist()RANSAC的第二個參數(shù)是重投影誤差閾值默認我習慣設5.0。這個值越小要求匹配點越精確過濾越嚴太嚴會丟掉有效匹配太松又會混入誤匹配。這是一個需要根據(jù)匹配質量動態(tài)調整的平衡參數(shù)我第一次用時調了不下十次才找到合適的值。7. 閾值分割的進階之路全局閾值、自適應閾值與Otsu方法7.1 閾值分割的三個層次對應三種圖像條件閾值分割的目標是把灰度圖變成二值圖也就是前景vs背景。最簡單的是全局固定閾值——整張圖用一個固定的t值大于t的設為白色小于t的設為黑色。全局閾值對光照均勻的圖像有效但一旦遇到光線從左到右漸變、或者局部有陰影的圖像就會出問題。這時候需要用自適應閾值cv2.adaptiveThreshold它對每個像素都單獨計算一個閾值依據(jù)是它周圍一個小窗口內的像素的加權平均值或高斯加權和。這樣能自動適配局部的亮度變化。第三個層次是Otsu方法cv2.THRESH_OTSU。它不是另一種閾值而是自動幫你選最優(yōu)閾值的方法。它的原理是用統(tǒng)計方法找到一個閾值使得前景和背景兩類的類內方差最小、類間方差最大。簡單說它是全局閾值的最優(yōu)解。# Otsu自動閾值 ret, thresh_otsu cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) # 自適應閾值 adaptive_thresh cv2.adaptiveThreshold( gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2 )這里有兩個參數(shù)需要重點解釋。blockSize決定計算每個像素閾值時使用的鄰域大小必須為奇數(shù)一般從11到15之間開始試C是一個常數(shù)表示最終閾值用鄰域均值減去這個常數(shù)C越大二值化后保留的白色像素越少。7.2 三種方法怎么選給出我的決策流程我的經(jīng)驗是拿到灰度圖后先試全局閾值看一下整體情況如果前景和背景對比足夠強直接Otsu就行。如果有明顯的漸變陰影用自適應閾值。如果兩種情況混合存在比如一張圖里既有強光區(qū)又有暗角優(yōu)先考慮先做直方圖均衡化再用Otsu均衡化能把亮度分布拉平讓Otsu的統(tǒng)計效果更好。我自己在實際測試中遇到過一個有意思的情況一張掃描文檔背景是淺黃色文字是深灰色光照均勻但背景有紋路。Otsu全局閾值的效果不太理想因為紋路的灰度值和文字差距不夠大。用了自適應閾值之后因為它在局部計算閾值背景紋路被完全忽略了只留下了文字輪廓。自適應閾值在處理背景不均勻但前景是局部深色結構的場景時優(yōu)勢非常明顯。7.3 閾值分割后的遺留問題形態(tài)學操作的最好時機閾值分割得到的二值圖通常不是干凈的會有孤立的白色噪點、前景內部的空洞、以及邊緣的毛刺。這就是上一章形態(tài)學操作最好的應用時機。我?guī)缀跛械亩祱D流水線都長這樣自適應閾值 → 閉運算補內部空洞 → 開運算去外部噪點 → 找輪廓 → 面積/幾何篩選。這一套組合拳能應對絕大多數(shù)圖像分割項目的前期準備。8. 實際項目中的算法組合思路與調參心法8.1 一個典型的車牌識別預處理流程拆解學了這么多單個算法最終要落到組合使用上。我以車牌定位這個經(jīng)典場景為例把整個流程串起來講一遍。這個例子非常典型因為它幾乎用到了前面所有的知識點。假設輸入是一張包含車牌的整車照片光照不均勻、背景有各種干擾。完整流程如下讀取并灰度化丟顏色信息減少計算量。高斯濾波核取(5,5)去掉傳感器噪聲。形態(tài)學梯度用梯度提取邊緣信息突出車牌區(qū)域因為車牌區(qū)域字符密集邊緣梯度特別強。二值化Otsu或固定閾值把梯度圖變成前景背景清晰的黑白圖。形態(tài)學閉運算把車牌的字符之間斷開的邊緣連成一個大塊方便后續(xù)輪廓檢測找到完整區(qū)域。輪廓檢測RETR_EXTERNAL提取外輪廓用面積和寬高比篩掉不是車牌的輪廓。透視變換校正用cv2.getPerspectiveTransform把傾斜的車牌拉正。這個流程每一步都不是單純的套函數(shù)而是基于對圖像的理解逐步縮小目標范圍。形態(tài)學梯度在第三步出現(xiàn)、閉運算在第五步出現(xiàn)這個順序是不可顛倒的——梯度先高亮出邊緣密集的區(qū)域閉運算再把這些區(qū)域融合成完整塊如果反了高亮效果會被閉運算直接抹掉。8.2 調參心法一次只動一個參數(shù)永遠記錄基線效果調參可能是圖像處理項目中最耗時的部分也是最容易勸退新手的一個環(huán)節(jié)。我總結了一套簡單但極其有效的流程固定輸入拿同一張代表性圖片做測試不要每次換圖。項目里挑3到5張能代表正常情況、困難情況、極端情況的圖片作為固定測試集。設定基線第一次跑完流程后把當前參數(shù)和輸出結果截圖保存作為baseline。一次只動一個參數(shù)比如先調Canny的threshold1跑一次對比baseline記錄效果。再動threshold2繼續(xù)對比。用中間結果輔助判斷不要只看最終輸出。在預處理流程里插入中間結果的顯示比如顯示二值圖、顯示輪廓圖能快速定位是哪個環(huán)節(jié)出了問題。我早期調參時最常犯的錯誤是想快速調好結果就在代碼里同時改了三個參數(shù)結果效果變好了也說不清是哪一個的功勞效果變差更是無法回溯。一次只動一個參數(shù)這條紀律看起來慢實際是最快的路徑。8.3 性能優(yōu)化當你的算法跑在實時視頻流上如果你的場景是實時視頻處理比如一幀一幀地處理攝像頭輸入性能優(yōu)化就是個繞不開的話題。我常用的優(yōu)化手段按性價比排序降分辨率把處理幀的分辨率降到處理需求的最低要求這是最簡單、效果最顯著的優(yōu)化。調整核大小高斯濾波(3,3)比(5,5)快不少在視頻場景下先用(3,3)試不夠再加。跳過非關鍵幀有些流程不需要每幀都處理比如每隔3幀做一次完整檢測中間幀用上一幀的結果插值。ROI感興趣區(qū)域提取只在畫面的一小部分區(qū)域做復雜度高的算法。比如監(jiān)控畫面里目標只出現(xiàn)在門附近就只用門位置的ROI。這些優(yōu)化手段疊加起來把原來處理一幀需要200毫秒的流程壓縮到30毫秒以內是可以做到的。我在一個模擬的實時目標檢測demo里通過降低分辨率加縮小ROI把處理延遲從肉眼可見卡頓壓到了流暢預覽。9. 寫在最后的復用經(jīng)驗把算法沉淀成你自己的工具箱圖像處理的項目經(jīng)驗最劃算的沉淀方式不是寫一堆文檔而是把常用流程封裝成可復用的函數(shù)和類。我自己的習慣是維護一個工具箱腳本把灰度化濾波二值化、輪廓提取面積篩選、特征匹配RANSAC過濾這三類最常見流程各封裝成一個函數(shù)。每接到新項目先從這個工具箱取現(xiàn)成的中間模塊再針對項目特點做參數(shù)調整和少量新代碼開發(fā)效率能快好幾倍。封裝時要特別注意接口參數(shù)要暴露業(yè)務含義而不是數(shù)學含義。比如封裝二值化函數(shù)時與其讓調用者傳blockSize11和C2這種抽象參數(shù)不如直接定義成threshold_modeadaptive_light、sensitivitymedium這種業(yè)務語義。這樣三個月后回頭用的時候你不需要重新理解數(shù)學含義只需要知道當前場景用什么檔位這個習慣幫我省了大量的回想時間。圖像處理的入門門檻不在API而在對每一個算法為什么要存在的理解?;叶群蜑V波是為了讓信息更純粹邊緣檢測和閾值分割是為了挖出結構形態(tài)學是為了修整形狀輪廓和特征點是為了把結構變成可計算的量。把這條主線理清楚后新算法對你來說不過是又一個解決特定問題的工具需要時查文檔、看參數(shù)、調效果就夠了。