算法詳解:從梯度算子到Canny與輪廓提取)
1. 邊緣檢測(cè)到底在檢測(cè)什么為什么梯度是最合適的數(shù)學(xué)工具1.1 灰度突變邊緣在人眼與數(shù)學(xué)之間的統(tǒng)一表達(dá)先問一個(gè)看起來有點(diǎn)傻的問題人眼是怎么看出這里有一條邊的你看一張黑白棋盤圖黑色格子和白色格子交界的地方明暗反差最大你就覺得那里有一條清晰的線。如果換成兩塊幾乎一樣的淺灰色交界處就很難感知到邊緣。這個(gè)直覺放到圖像里本質(zhì)就是一件事像素的灰度值在某個(gè)方向上發(fā)生了劇烈的變化。所謂邊緣不是某個(gè)像素本身有什么特殊屬性而是它和周圍鄰居之間的亮度差異足夠大。一張平滑漸變的天藍(lán)色天空照片里相鄰像素灰度差很小所以整張圖幾乎沒有邊緣一張紋理復(fù)雜的毛毯照片里相鄰像素灰度差忽大忽小邊緣就密密麻麻。把圖像理解成一個(gè)函數(shù) f(x, y)x 和 y 是像素坐標(biāo)f 是該點(diǎn)的灰度值。那么灰度劇烈變化在數(shù)學(xué)上對(duì)應(yīng)的概念就是導(dǎo)數(shù)一維函數(shù)里 f(x) 表示 f 隨 x 變化的速度到了二維圖像里x 和 y 兩個(gè)方向都有變化速度這就引出了梯度。所以邊緣檢測(cè)的第一性原理就一句話哪里有突變哪里就是邊緣而梯度天生就是衡量突變的工具。理解這一點(diǎn)后面所有算子的設(shè)計(jì)你都能自己推導(dǎo)出來而不是死記一堆核矩陣。1.2 梯度向量幅值給強(qiáng)度方向給走勢(shì)二維函數(shù) f(x, y) 在某一點(diǎn)的梯度是一個(gè)向量?f (?f/?x, ?f/?y)這個(gè)向量包含兩個(gè)信息方向指向灰度上升最快的方向長(zhǎng)度模長(zhǎng)反映灰度變化的速率。平時(shí)我們用兩個(gè)導(dǎo)出量梯度幅值|?f| sqrt(Gx2 Gy2)衡量該點(diǎn)作為邊緣的強(qiáng)度。梯度方向θ atan2(Gy, Gx)指示邊緣的走向嚴(yán)格說邊緣方向與梯度方向垂直。我自己的理解方式是用山坡打比方。灰度是海拔像素坐標(biāo)是經(jīng)緯度。你在山坡上走最陡的方向就是梯度方向最陡的程度就是梯度幅值。如果某片區(qū)域坡度很大它在地圖上看就更像一條山脊線或陡坎對(duì)應(yīng)圖像里的邊緣如果坡度平緩圖像里就是大片平滑區(qū)域。明白了梯度是向量就能解釋很多后來的現(xiàn)象。比如 Sobel 算子為什么要分別算 Gx 和 Gy 兩個(gè)圖Canny 的非極大值抑制為什么需要利用梯度方向而不是只看幅值。方向信息不是錦上添花它是后續(xù)所有精修步驟的靈魂。2. 離散圖像的導(dǎo)數(shù)怎么算Sobel、Scharr、Prewitt 算子背后的差分與加權(quán)2.1 差分近似為什么取中心差分而不是前后差分問題來了圖像是離散的像素網(wǎng)格f(x, y) 根本沒有解析表達(dá)式你沒法真正求導(dǎo)。那就只能用差分去近似導(dǎo)數(shù)。最樸素的想法是前向差分 f(x1) - f(x)或者后向差分 f(x) - f(x-1)。這兩者都行但有一個(gè)實(shí)際問題如果圖像里有噪聲單個(gè)像素的異常值會(huì)直接污染差分結(jié)果讓邊緣圖變得毛躁。于是出現(xiàn)了中心差分?f/?x ≈ f(x1) - f(x-1)也就是看左右各隔一個(gè)像素的差。它的好處有兩個(gè)一是比前向/后向差分多了一點(diǎn)平均的效果能略微抵抗噪聲二是在數(shù)學(xué)上中心差分對(duì)線性漸變區(qū)域的估計(jì)誤差更小定位更準(zhǔn)。注意中心差分的模板 [-1, 0, 1]它天然就是 3x3 卷積核的一維形式。為什么是 3x3 而不是 5x5 或 1x3這是精度和定位的權(quán)衡。窗口越大平滑噪聲的能力越強(qiáng)但邊緣定位越模糊。3x3 是在兩者之間最常見的折中點(diǎn)它運(yùn)算量小定位偏差也在可接受范圍內(nèi)所以初級(jí)算子普遍選它。2.2 卷積核里的系數(shù)為什么是 1、2、1Sobel 算子的 Gx 長(zhǎng)這樣[-1 0 1 -2 0 2 -1 0 1]很多人記完就忘或者干脆 opencv 一行代碼調(diào)用從沒想過這個(gè)核是怎么組合出來的。我拆開給你看會(huì)驚喜地發(fā)現(xiàn)它就是一個(gè)平滑核和一個(gè)差分核的卷積組合。先看水平方向的差分核 [-1, 0, 1]它負(fù)責(zé)在 x 方向做中心差分。但如果只用這一行去卷積垂直方向沒有任何平滑結(jié)果對(duì)上下相鄰像素的噪聲非常敏感。所以 Sobel 在垂直方向疊加了一個(gè)平滑核 [1, 2, 1]?。這個(gè)平滑核就是在垂直方向上給靠近中間行的像素更大的權(quán)重中間行權(quán)重 2上下兩行權(quán)重 1。整體效果相當(dāng)于先沿垂直方向做加權(quán)平均再沿水平方向求差分。為什么平滑核偏偏是 1、2、1 而不是 1、1、1這和二項(xiàng)式系數(shù)有關(guān)。1-2-1 是展開 (ab)2 的系數(shù)它是離散高斯核最簡(jiǎn)單的近似。說白了Sobel 在做差分之前先用一個(gè)迷你高斯核在另一個(gè)方向上平滑了一下從而在抑制噪聲和保留邊緣之間取得平衡。你去翻 Scharr 算子的核會(huì)發(fā)現(xiàn)它把平滑系數(shù)換成了 3、10、3本質(zhì)還是平滑加權(quán)差分的組合只是權(quán)重更接近連續(xù)高斯的性質(zhì)對(duì)旋轉(zhuǎn)的對(duì)稱性更好。我們完全可以手工驗(yàn)證一個(gè)巧妙的性質(zhì)Sobel 核等于兩個(gè)一維向量的外積Gx [-1 0 1] ? [1 2 1]?其中 ? 表示外積。也就是說 Sobel 是可分離的OpenCV 實(shí)際計(jì)算時(shí)也是分成兩步走先平滑再差分這比直接一個(gè) 3x3 的卷積要少幾次乘法速度更快。理解這個(gè)拆分后面你想設(shè)計(jì)自己的邊緣算子比如 5x5 或 7x7 的變體思路就通了一邊選一個(gè)平滑核一邊選一個(gè)差分核外積拼起來。2.3 Scharr 與 Prewitt在旋轉(zhuǎn)對(duì)稱性上的取舍Prewitt 核是 Sobel 的簡(jiǎn)化版平滑權(quán)重全部是 1[-1 0 1 -1 0 1 -1 0 1]它的優(yōu)點(diǎn)是計(jì)算更簡(jiǎn)單缺點(diǎn)是平滑性不如 Sobel對(duì)噪聲更敏感而且對(duì)角邊緣的響應(yīng)不均勻。實(shí)際項(xiàng)目里很少單獨(dú)用它多數(shù)時(shí)候它被當(dāng)作教學(xué)案例讓你理解差分平滑的基本框架。Scharr 核是在 Sobel 基礎(chǔ)上改進(jìn)的版本[-3 0 3 -10 0 10 -3 0 3]它的核心動(dòng)機(jī)是解決旋轉(zhuǎn)對(duì)稱性問題。Sobel 在 45 度和 90 度方向的梯度幅值響應(yīng)存在差異對(duì)斜向邊緣的檢出強(qiáng)度不如水平垂直邊緣。Scharr 調(diào)大了中心行權(quán)重讓梯度幅值對(duì)方向不那么敏感實(shí)測(cè)下來斜向邊緣的連續(xù)性確實(shí)更好。我的建議是如果你做傳統(tǒng)的 Sobel 演示用 Sobel 就夠如果對(duì)邊緣方向一致性有要求比如要做后續(xù)的 Hough 直線檢測(cè)、圓形檢測(cè)優(yōu)先考慮 Scharr。代價(jià)只是多幾次整數(shù)乘法幾乎可以忽略。3. Canny 的雙重篩選噪聲抑制、非極大值抑制與滯后閾值背后的數(shù)學(xué)直覺3.1 高斯卷積先把非線性噪聲攤平再算梯度直接把 Sobel 的結(jié)果拿來做閾值你會(huì)發(fā)現(xiàn)兩個(gè)問題邊緣線很粗明明是一根細(xì)線卻檢測(cè)出兩三像素寬的一條帶噪點(diǎn)很多圖像稍有紋理就滿屏小亮點(diǎn)。這兩個(gè)問題讓梯度幅值過閾值變成了一種很粗糙的策略。Canny 的思路是做兩件事細(xì)化和篩選。但無論哪件事前提都是先把噪聲壓低。高斯濾波處理圖像本質(zhì)上是用一個(gè)加權(quán)窗口對(duì)每個(gè)像素鄰域做加權(quán)平均權(quán)重服從高斯分布。高斯核的數(shù)學(xué)形式是 G(x, y) exp(-(x2y2)/(2σ2))σ 越大鄰域內(nèi)遠(yuǎn)處像素的權(quán)重越高平滑效果越強(qiáng)但邊緣也被抹得越平。所以 σ 不是越大越好。在 Canny 里高斯濾波有兩個(gè)作用一是把傳感器噪點(diǎn)攤平避免它們?cè)诤罄m(xù)梯度計(jì)算中被放大二是讓梯度計(jì)算更穩(wěn)定。注意一個(gè)容易誤解的點(diǎn)先做高斯再做 Sobel等效于拿高斯偏導(dǎo)數(shù)核直接做卷積。從數(shù)學(xué)上看這非常漂亮因?yàn)楦咚箤?dǎo)數(shù)的形狀像一個(gè)平滑的邊緣敏感濾波器這正是 Canny 在原論文里用的做法。實(shí)操中我常用高斯核尺寸 5x5 配 σ≈1.4處理普通照片時(shí)這是個(gè)穩(wěn)妥的起點(diǎn)。太小的核比如 3x3 壓不住噪聲太大的核比如 9x9 會(huì)把細(xì)小邊緣直接抹掉。3.2 沿梯度方向找局部極大非極大值抑制在做什么非極大值抑制Non-Maximum SuppressionNMS這個(gè)名字聽著嚇人道理其實(shí)很簡(jiǎn)單梯度幅值高的像素通常形成一條帶但真正理想的邊緣應(yīng)該只有一個(gè)像素寬。為了把帶壓縮成線我們需要對(duì)每個(gè)像素檢查它是不是沿著梯度方向也就是垂直邊緣的方向上局部最大的那一個(gè)如果是保留如果不是置零。為什么是沿梯度方向檢查因?yàn)樘荻确较蚴腔叶茸兓顒×业姆较蜻吘墐蓚?cè)的像素梯度幅值通常呈現(xiàn)中間高、兩邊低的形狀。你從邊緣切一刀看灰度剖面它很像一條鐘形曲線峰值正好在邊緣中心。NMS 就是找到曲線的峰值。一個(gè)很常見的坑是插值。梯度方向是任意的角度不可能每次剛好穿過相鄰像素的中心。嚴(yán)格做法是線性插值取相鄰兩個(gè)像素的幅值做比較。OpenCV 內(nèi)部已經(jīng)處理好了這件事但你知道了原理就知道它的輸出為什么是同邊緣位置只有單一像素。NMS 做完之后圖像變成了一堆候選邊緣點(diǎn)這些點(diǎn)上都含有局部最大梯度幅值但仍有很多是噪聲產(chǎn)生的假響應(yīng)于是來到了最后一道坎雙閾值。3.3 雙閾值滯后連接高閾值找種子低閾值接斷口如果只用單閾值選高了邊緣會(huì)斷成一截一截選低了噪聲全進(jìn)來。Canny 的經(jīng)典做法是雙閾值滯后化先設(shè)一個(gè)高閾值和一個(gè)低閾值高閾值用來找確定是邊緣的強(qiáng)邊緣點(diǎn)低閾值用來找可能是邊緣的弱邊緣點(diǎn)。規(guī)則是梯度幅值 高閾值強(qiáng)邊緣保留。梯度幅值 低閾值丟棄。介于兩者之間弱邊緣只有在與某個(gè)強(qiáng)邊緣相連時(shí)才保留。這里的關(guān)鍵是滯后兩個(gè)字。判斷一個(gè)弱邊緣點(diǎn)是否保留不是看它自身有多強(qiáng)而是看它是否和強(qiáng)邊緣連通。這樣做的數(shù)學(xué)和物理動(dòng)機(jī)是真實(shí)邊緣很少是一段孤立的強(qiáng)信號(hào)更多是中間強(qiáng)、兩端漸弱的連續(xù)曲線。如果只保留高閾值的點(diǎn)邊緣會(huì)斷如果把低閾值點(diǎn)全保留噪聲會(huì)混進(jìn)來。滯后連接相當(dāng)于用高閾值確定種子再用低閾值沿著種子把斷裂的線補(bǔ)上。于是有了一條非常實(shí)用的調(diào)參規(guī)律低閾值大概取高閾值的 1/2 到 1/3 之間比如 Canny 高低閾值設(shè)為 50 和 150。低閾值太高斷口補(bǔ)不上低閾值太低噪聲又回來了。這段話在無數(shù)的教程里被簡(jiǎn)單帶過但當(dāng)你親眼看到同一張圖在 (30, 90) 和 (50, 150) 下的巨大差異你才真正理解雙閾值的意義。4. 邊緣到輪廓的最后一公里二值化、閉運(yùn)算與輪廓層級(jí)4.1 邊緣點(diǎn)不一定是輪廓線很多初學(xué)者會(huì)把 Canny 的輸出直接丟給 findContours然后發(fā)現(xiàn)結(jié)果和自己想的不一樣。原因在于邊緣和輪廓是兩個(gè)層面的東西。邊緣是梯度層面上的像素點(diǎn)集合它可能不閉合可能是斷斷續(xù)續(xù)的兩條線可能包含內(nèi)部紋理的邊緣輪廓是語(yǔ)義層面上的對(duì)象邊界它是一條閉合曲線把前景和背景分離開。要讓邊緣變成輪廓通常要做三步處理把邊緣圖變成二值圖Canny 輸出本身就是 0/255 的二值圖天然滿足條件。用形態(tài)學(xué)操作把斷口閉合比如閉運(yùn)算膨脹后腐蝕。再交給 findContours 提取連通區(qū)域的邊界。閉運(yùn)算為什么要先膨脹再腐蝕膨脹會(huì)長(zhǎng)胖白色區(qū)域把細(xì)小的缺口堵上腐蝕再把它縮回去保持原來大致的大小。這個(gè)先擴(kuò)后縮的組合對(duì)補(bǔ)斷口非常有效。我在實(shí)測(cè)里發(fā)現(xiàn)Canny 檢出的邊緣圖如果斷裂寬度不超過 2~3 像素用 3x3 或 5x5 的閉運(yùn)算核基本都能接回來缺口太寬就別硬堵了調(diào)整 Canny 參數(shù)才是正路。4.2 findContours 的層級(jí)邏輯findContours 接收二值圖后會(huì)按照拓?fù)潢P(guān)系提取輪廓不止是外邊界還包括孔洞的邊界。OpenCV 提供幾種層級(jí)模式RETR_EXTERNAL只取最外層輪廓忽略孔洞。RETR_LIST所有輪廓全部提取不做層級(jí)關(guān)聯(lián)。RETR_CCOMP兩層結(jié)構(gòu)外層輪廓和內(nèi)層洞分開。RETR_TREE完整層級(jí)樹輪廓之間的父子關(guān)系一目了然。我通常先用 RETR_TREE 打印一下各輪廓的層級(jí)關(guān)系搞清楚結(jié)構(gòu)后再?zèng)Q定要不要只取外層。比如要檢測(cè)畫面里所有完整的工件往往 RETR_EXTERNAL 就夠但如果要區(qū)分工件和工件上的孔就得用 RETR_CCOMP 或者 RETR_TREE。一個(gè)常見的翻車點(diǎn)是圖像邊緣處有半截物體輪廓算法會(huì)把它當(dāng)成完整輪廓圈起來處理時(shí)要么先裁剪掉邊緣區(qū)域要么用輪廓面積和形狀做篩選。輪廓輸出后最常用的過濾手段是按面積排序和篩選cv2.contourArea可以算出每個(gè)輪廓包圍的像素面積設(shè)置一個(gè)最小面積閾值就能把由噪點(diǎn)產(chǎn)生的小輪廓過濾掉。這里有個(gè)細(xì)節(jié)輪廓面積不是簡(jiǎn)單的白色像素?cái)?shù)量而是輪廓內(nèi)部區(qū)域面積對(duì)不規(guī)則形狀來說二者差別很明顯。4.3 完整的代碼串起整條鏈路下面這段代碼用 Python OpenCV 把讀圖 → 灰度 → 高斯模糊 → Canny → 閉運(yùn)算 → 輪廓提取 → 篩選畫出完整跑一遍我每次做邊緣檢測(cè) demo 都從這套模板開始改import cv2 import numpy as np def edge_to_contour(image_path, min_area200): # 讀圖并轉(zhuǎn)灰度 img cv2.imread(image_path) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 高斯平滑降低噪聲對(duì)梯度的干擾 blurred cv2.GaussianBlur(gray, (5, 5), 1.4) # Canny 雙閾值低閾值 高閾值 / 3 edges cv2.Canny(blurred, 60, 180) # 閉運(yùn)算補(bǔ)斷口 kernel cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)) closed cv2.morphologyEx(edges, cv2.MORPH_CLOSE, kernel) # 提取輪廓 contours, hierarchy cv2.findContours( closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE ) result img.copy() for cnt in contours: area cv2.contourArea(cnt) if area min_area: continue cv2.drawContours(result, [cnt], -1, (0, 200, 0), 2) return edges, closed, contours, result我自己的經(jīng)驗(yàn)是把edges、closed、result三個(gè)中間結(jié)果并排顯示出來看一遍比任何參數(shù)分析都直觀。你會(huì)立刻看到哪些斷口是閉運(yùn)算補(bǔ)上的哪些是閉運(yùn)算把兩個(gè)不該連的邊緣焊在一起的。大部分情況下見好就收比追求完美邊緣圖更實(shí)際輪廓提取要的只是足夠閉合的邊界不是絕對(duì)精確的梯度結(jié)果。5. 實(shí)戰(zhàn)復(fù)盤同源圖像上不同算子的表現(xiàn)以及參數(shù)選擇的通用思路5.1 同一張測(cè)試圖的三種輸出差異我在本地拿一張工業(yè)零件拍攝圖做了個(gè)對(duì)比測(cè)試圖中既有平直表面、圓弧倒角也有較細(xì)的螺紋紋理。分別使用 Sobel 直接閾值、Scharr 自適應(yīng)閾值、Canny 三種方式提取邊緣結(jié)果差異非常明顯。Sobel 的梯度幅值圖整體偏散閾值設(shè)低了螺紋紋理全部變成細(xì)碎邊緣設(shè)高了圓弧部分的邊緣缺失嚴(yán)重邊緣寬度也偏粗。Scharr 配自適應(yīng)閾值的邊緣連續(xù)性比 Sobel 好一些尤其在斜向倒角處但同樣存在閾值敏感的問題。Canny 綜合表現(xiàn)最穩(wěn)邊緣細(xì)、噪點(diǎn)少、斷口相比前兩者少很多但代價(jià)是參數(shù)有三四個(gè)調(diào)起來更麻煩。這個(gè)對(duì)比說明一個(gè)問題邊緣檢測(cè)沒有絕對(duì)最優(yōu)只有最匹配當(dāng)前場(chǎng)景的選擇。如果你的任務(wù)簡(jiǎn)單比如只是做一個(gè)二維碼定位或者文檔掃描的預(yù)處理Sobel 直接閾值足夠如果你想做物體輪廓提取、測(cè)量尺寸Canny 會(huì)是更穩(wěn)健的起點(diǎn)如果你處理的是紋理特別豐富的圖像比如布匹檢測(cè)那 Canny 配一個(gè)偏大的高斯模糊核往往比瘋狂調(diào)閾值更有效。下表是我根據(jù)自己的項(xiàng)目經(jīng)驗(yàn)整理的選型速查算子核心原理優(yōu)點(diǎn)缺點(diǎn)典型場(chǎng)景Sobel差分平滑核組合計(jì)算量小好理解能輸出梯度方向邊緣較粗噪聲敏感簡(jiǎn)易邊緣圖、梯度方向計(jì)算ScharrSobel 的權(quán)重增強(qiáng)版對(duì)斜向邊緣響應(yīng)更均勻速度略慢仍需閾值處理需要各向同性梯度時(shí)Laplacian二階導(dǎo)檢測(cè)灰度突變過零點(diǎn)無需分別算 x/y 方向?qū)υ肼晿O敏感邊緣雙響應(yīng)配合高斯做 LOG 邊緣Canny高斯 梯度 NMS 雙閾值邊緣細(xì)、連續(xù)性好、噪點(diǎn)少參數(shù)多調(diào)試負(fù)擔(dān)大輪廓提取前的主流選擇5.2 閾值階梯調(diào)試法與常見翻車點(diǎn)調(diào) Canny 參數(shù)時(shí)我常用階梯法固定高斯核為 (5, 5)把低閾值從 30 到 90 每次加 10高閾值固定為低閾值的 3 倍逐一觀察邊緣圖變化。你會(huì)發(fā)現(xiàn)幾個(gè)清晰的拐點(diǎn)低閾值 30 時(shí)大量紋理噪點(diǎn)混入低閾值 50 時(shí)主邊緣完整且噪點(diǎn)明顯減少低閾值 90 時(shí)主邊緣開始斷裂。拐點(diǎn)附近再細(xì)調(diào)一遍就能很快鎖定合適的區(qū)間。幾個(gè)容易翻車的點(diǎn)我必須單獨(dú)拎出來說第一不要把 Canny 的輸入直接設(shè)為彩色圖。OpenCV 的 Canny 默認(rèn)只接受灰度圖傳彩色圖很可能直接報(bào)錯(cuò)。正確做法是先轉(zhuǎn)灰度必要時(shí)做亮度均衡。第二高斯模糊不是隨便做的。很多人習(xí)慣調(diào)用一遍 Canny 內(nèi)部的 Sobel卻忽略輸入噪聲對(duì)梯度幅值的影響。如果原圖的傳感器噪點(diǎn)明顯建議先顯示一遍中值濾波或高斯濾波的中間結(jié)果確認(rèn)邊緣還在再進(jìn)行 Canny。否則邊緣可能會(huì)被平滑成不存在。第三輪廓提取要在二值圖上做但不要在 Canny 的產(chǎn)物上直接做。前面說過 Canny 輸出不閉合是常態(tài)必須配合閉運(yùn)算。閉運(yùn)算核太大又會(huì)產(chǎn)生粘連問題我的經(jīng)驗(yàn)是從 3x3 開始嘗試每次加 2直到輪廓基本閉合為止。第四別迷信某個(gè)固定組合(50, 150) 只是一個(gè)很普通的起點(diǎn)。光照變化、鏡頭對(duì)焦、被測(cè)物表面反光都會(huì)大幅改變梯度分布換一張圖參數(shù)就得重調(diào)。與其死記參數(shù)不如在預(yù)處理和參數(shù)之間多做橫向?qū)Ρ取?.3 如果目標(biāo)直指輪廓可以跳過低階算子的中間環(huán)節(jié)如果你的最終目標(biāo)是找到物體的輪廓、測(cè)面積、做形狀判斷那么路線通常是灰度圖 → 去噪 → Canny → 閉運(yùn)算 → findContours。這條路我已經(jīng)跑過很多次效率高、代碼短。但有時(shí)候 Canny 參數(shù)怎么調(diào)都調(diào)不穩(wěn)定這時(shí)候要回頭看看前面的環(huán)節(jié)光照不均會(huì)導(dǎo)致局部對(duì)比度差異太大可以試試自適應(yīng)閾值或者先做頂帽變換背景噪點(diǎn)太多可以先做連通域分析再篩輪廓。邊緣檢測(cè)是一個(gè)很典型的木桶問題。梯度算法只是板壁之一噪聲預(yù)處理和后續(xù)形態(tài)學(xué)操作同樣重要。你單看 Canny 的實(shí)現(xiàn)會(huì)覺得作者做了很多數(shù)學(xué)上的精巧設(shè)計(jì)掏出來用的體驗(yàn)卻是螺旋式調(diào)參。這不是理論不好用而是真實(shí)圖像里噪聲分布遠(yuǎn)比教科書復(fù)雜。理解了模塊之間的耦合關(guān)系你調(diào)起參來就有了方向感而不是碰運(yùn)氣。我在實(shí)際使用中的一個(gè)體會(huì)是邊緣檢測(cè)的調(diào)試順序不要太亂。先保證預(yù)處理干凈再看算子輸出最后才做形態(tài)學(xué)修復(fù)。如果一上來就猛調(diào) Canny 的高低閾值很容易陷入每張圖都要調(diào)一遍的死循環(huán)。這套預(yù)處理 → 梯度 → 閾值 → 形態(tài)學(xué) → 輪廓的流水線思維基本可以覆蓋絕大多數(shù)視覺定位和尺寸測(cè)量需求。把它吃透邊緣檢測(cè)的數(shù)學(xué)之美才能真正落到你的項(xiàng)目里。