戰(zhàn)指南:從卷積到頻域,48種去噪與邊緣保持技術(shù)全解析)
1. 從一張?jiān)肼晥D說(shuō)起濾波在圖像處理中的“地基”地位去年我在做一個(gè)工業(yè)質(zhì)檢項(xiàng)目需要在鋁型材表面找到3毫米內(nèi)的細(xì)微劃痕。原始圖像在500萬(wàn)像素工業(yè)相機(jī)下噪點(diǎn)密密麻麻像撒了一層鹽直接做邊緣檢測(cè)出來(lái)的全是假輪廓閾值分割更是拆得七零八落。后來(lái)把一套組合濾波流程跑通檢測(cè)精度直接提升了兩個(gè)量級(jí)。那個(gè)時(shí)刻我意識(shí)到濾波不是預(yù)處理里的“可有可無(wú)”它就是整個(gè)視覺系統(tǒng)的地基。濾波說(shuō)白了就是用鄰域信息重新計(jì)算像素值目標(biāo)只有兩類把不要的去掉去噪、去紋理把要的留下來(lái)邊緣、細(xì)節(jié)、輪廓。48種算法看著嚇人但它們90%都建立在同一個(gè)數(shù)學(xué)模型上——卷積。你又卷積又線性又非線性又形態(tài)學(xué)繞來(lái)繞去跳不出幾個(gè)家族。這篇文章不堆公式直接按“家族分類”帶你走一遍48種算法的核心邏輯然后每個(gè)大類配一段能跑的Python實(shí)戰(zhàn)代碼。我會(huì)把我在實(shí)際項(xiàng)目里總結(jié)的選型經(jīng)驗(yàn)也一起寫出來(lái)什么場(chǎng)景用均值、什么場(chǎng)景死都要用中值、頻域?yàn)V波到底值不值得學(xué)。你跟著讀一遍基本就能自己搭配濾波流程了。適合誰(shuí)看剛?cè)腴T的Python圖像處理學(xué)習(xí)者、做傳統(tǒng)CV但被OpenCV函數(shù)選擇困擾的工程師、以及準(zhǔn)備面試算法崗需要系統(tǒng)梳理濾波知識(shí)的人??赐赀@篇至少你在面對(duì)一張?jiān)肼晥D時(shí)心里不慌了。2. 濾波的本質(zhì)卷積、掩膜與頻率的三個(gè)視角2.1 卷積視角每個(gè)像素都是“加權(quán)鄰居投票”卷積在圖像里的意思是用一個(gè)核Kernel也叫掩膜滑過(guò)整張圖每滑到一個(gè)位置就把核所覆蓋的鄰域像素值分別乘上核里對(duì)應(yīng)的權(quán)重然后求和得到新像素值。這是所有線性濾波的共同爹。舉個(gè)例子最常見的3×3均值核長(zhǎng)這樣1/9 × [1 1 1; 1 1 1; 1 1 1]這個(gè)東西滑到哪兒就把哪兒周圍9個(gè)像素的顏色平均了。噪聲點(diǎn)的像素值遠(yuǎn)高于或低于鄰居一平均就被“拉”回正常水平。代價(jià)是邊緣也變得模糊了因?yàn)檫吘墐蓚?cè)的顏色差異同樣被平均掉。在Python中最底層的實(shí)現(xiàn)是逐像素循環(huán)但千萬(wàn)別那么干——一張1080p的圖純Python循環(huán)跑一次均值濾波要好幾分鐘。實(shí)際項(xiàng)目里我們用卷積庫(kù)如scipy.ndimage.convolve或者直接用OpenCV的cv2.filter2D底層是C優(yōu)化過(guò)的。2.2 掩膜視角濾波就是“有挑有揀地看鄰域”不是所有濾波都老老實(shí)實(shí)走卷積。有些濾波只對(duì)鄰域里的特定像素感興趣。比如中值濾波它把鄰域所有像素排個(gè)序然后取中間值排序過(guò)程不涉及任何權(quán)重計(jì)算。比如自適應(yīng)濾波它先分析鄰域的方差方差大說(shuō)明是邊緣區(qū)域就少模糊方差小說(shuō)明是平坦區(qū)就多模糊。掩膜這個(gè)詞在工程師語(yǔ)境里更偏向“模板匹配”或者“感興趣區(qū)域標(biāo)記”但濾波語(yǔ)境下的掩膜指的是決定鄰域里哪些像素參與計(jì)算的窗口形狀和邏輯。很多進(jìn)階濾波如雙邊濾波的聯(lián)合權(quán)重本質(zhì)上就是你自定義了一個(gè)“動(dòng)態(tài)掩膜”權(quán)重不再固定而是根據(jù)像素值差異、距離、梯度方向?qū)崟r(shí)生成。2.3 頻率視角濾波是“讓某些頻段通過(guò)或衰減”如果你學(xué)過(guò)一點(diǎn)信號(hào)處理你會(huì)知道任何信號(hào)都能拆成不同頻率的正弦波疊加。圖像也一樣——低頻對(duì)應(yīng)平坦區(qū)域和緩慢變化的背景高頻對(duì)應(yīng)邊緣、紋理和噪聲。噪聲通常是高頻的所以“去噪”在頻率視角里就是“低通濾波”只讓低頻通過(guò)而銳化恰恰相反需要“高通濾波”增強(qiáng)高頻分量。這里就會(huì)引出傅里葉變換這個(gè)工具把圖像從空間域變換到頻率域在頻率域里設(shè)計(jì)濾波器函數(shù)再變換回空間域。傅里葉變換不是48種濾波算法里的某幾種而是一整族算法的數(shù)學(xué)基礎(chǔ)。比如理想低通濾波、巴特沃斯低通濾波、高斯低通濾波這些算法在空間域能實(shí)現(xiàn)但在頻率域里操作更直觀——你直接“削掉”某個(gè)頻率范圍的數(shù)據(jù)就行。實(shí)操中我遇到的最大坑初學(xué)者總以為頻域?yàn)V波是萬(wàn)能的結(jié)果一遇到分辨率大的圖來(lái)回FFT的開銷比空間域卷積高好幾個(gè)數(shù)量級(jí)。高頻細(xì)節(jié)丟了還找不回來(lái)。實(shí)際上頻域?yàn)V波在工業(yè)場(chǎng)景里主要用于紋理分析和特定周期的準(zhǔn)確定制濾波比如去除周期性網(wǎng)格紋路日常去噪還是空間域的小核卷積更快更穩(wěn)。3. 四大經(jīng)典濾波家族均值、中值、高斯、雙邊3.1 均值濾波簡(jiǎn)單但邊緣拉胯只適合快速預(yù)覽均值濾波的核心就一句話把當(dāng)前像素替換為鄰域像素的平均值。優(yōu)點(diǎn)是可解釋性強(qiáng)、計(jì)算極快缺點(diǎn)是在去噪的同時(shí)把邊緣和細(xì)節(jié)一鍋端了圖像變得“水潤(rùn)潤(rùn)”的糊感。實(shí)際項(xiàng)目里均值濾波我基本只用在一個(gè)場(chǎng)景做縮放之前的預(yù)處理。圖片縮小時(shí)如果直接從原分辨率采樣會(huì)出現(xiàn)嚴(yán)重的鋸齒——奈奎斯特采樣定理告訴我們縮小相當(dāng)于降采樣高頻噪聲會(huì)被折疊進(jìn)低頻造成混疊。先過(guò)一遍均值濾波壓掉高頻后面縮出來(lái)的圖才會(huì)干凈。代碼實(shí)戰(zhàn)基于NumPy逐像素實(shí)現(xiàn) OpenCV快速實(shí)現(xiàn)import cv2 import numpy as np # 讀入灰度圖 img cv2.imread(industrial_part.jpg, cv2.IMREAD_GRAYSCALE) # 方式1OpenCV 直接均值模糊boxFilter歸一化版 blur_3x3 cv2.blur(img, (3, 3)) blur_5x5 cv2.blur(img, (5, 5)) # 方式2filter2D 自定義核理解卷積本質(zhì) kernel_3x3 np.ones((3, 3), np.float32) / 9.0 blur_custom cv2.filter2D(img, -1, kernel_3x3) # 方式3僅橫向均值適合壓橫向條紋噪聲 kernel_h np.ones((1, 5), np.float32) / 5.0 blur_h cv2.filter2D(img, -1, kernel_h)注意一點(diǎn)cv2.blur的核大小必須是正奇數(shù)。如果你給(4, 4)它默認(rèn)給你截?cái)嘈袨椴豢深A(yù)期。這是OpenCV歷史遺留的坑。3.2 中值濾波椒鹽噪聲的克星細(xì)節(jié)保持之王中值濾波不平均而是把窗口內(nèi)像素排序后取中位數(shù)。核心理由如果是椒鹽噪聲孤立的極亮或極暗像素點(diǎn)排序后它要么排在開頭要么排在末尾中位數(shù)肯定還是正常像素值。這就做到了一件均值做不到的事——去噪的同時(shí)不模糊邊緣因?yàn)檫吘壪袼刂导词贡贿x為窗口中心它的值大概率還是窗口內(nèi)排在中位附近。代價(jià)是計(jì)算開銷高尤其是窗口變大時(shí)每個(gè)像素都要做一次排序。但在小窗口3×3或5×5下OpenCV優(yōu)化得極好幾乎不影響實(shí)時(shí)性。我在一個(gè)芯片引腳檢測(cè)項(xiàng)目里遇到過(guò)這種事用高斯濾波去噪非常徹底但引腳邊緣的灰度過(guò)渡被拉寬了兩個(gè)像素導(dǎo)致后續(xù)輪廓擬合的亞像素精度不夠。換成3×3中值濾波后噪聲能壓住邊緣位置偏移控制在0.5像素內(nèi)問(wèn)題直接解決。代碼實(shí)戰(zhàn)# 中值濾波 median_3 cv2.medianBlur(img, 3) median_5 cv2.medianBlur(img, 5) # 注意核大小只接受奇數(shù)3和5是最常用的兩個(gè) # 7以上慎用邊緣會(huì)被“揉”得太碎3.3 高斯濾波幾乎一切平滑任務(wù)的默認(rèn)起點(diǎn)高斯濾波的核不是均勻的1而是按照高斯分布生成——離中心越遠(yuǎn)的像素權(quán)重越小。這個(gè)設(shè)計(jì)和圖像本身的統(tǒng)計(jì)特性是對(duì)齊的自然圖像中距離近的像素相關(guān)性更高所以加權(quán)更合理。高斯核的生成與標(biāo)準(zhǔn)差σ直接相關(guān)# 生成一個(gè)5x5的高斯核sigma 1.5 kernel_5 cv2.getGaussianKernel(5, 1.5) # 返回的是5x1的一維核真正二維核需要外積 kernel_2d kernel_5 * kernel_5.T實(shí)際用cv2.GaussianBlur更省事gauss_5 cv2.GaussianBlur(img, (5, 5), 0) # sigmaX0 表示由核大小自動(dòng)推算sigma高斯濾波的“模糊半徑”由σ決定而非核大小。核大小只是截?cái)喾秶也攀蔷唧w模糊尺度的物理量。所以調(diào)參時(shí)我會(huì)優(yōu)先調(diào)σ而不是板子和核的大小σ越大模糊越猛。一個(gè)容易被忽略的點(diǎn)高斯濾波對(duì)高斯噪聲效果最好但對(duì)椒鹽噪聲效果一般——高斯噪聲是每個(gè)像素都加入了隨機(jī)偏移均值類操作正好匹配椒鹽噪聲是極端離群點(diǎn)均值會(huì)被拉偏中值才治得住。所以聽到“噪聲”別急著上高斯先搞清楚噪聲類型。3.4 雙邊濾波去噪保邊的折中之選雙邊濾波的核心突破是高斯濾波只考慮空間距離離得近的權(quán)重大雙邊濾波額外引入一個(gè)灰度相似度權(quán)重像素值接近的權(quán)重大。結(jié)果就出現(xiàn)了“同色區(qū)域被平滑跨邊緣區(qū)域不被平滑”的效果邊緣輪廓會(huì)被完整保留。實(shí)際參數(shù)就三個(gè)d鄰域直徑、sigmaColor灰度相似度閾值、sigmaSpace空間相似度閾值。注意這里的坑sigmaColor設(shè)太大整個(gè)圖就會(huì)被“磨皮”磨掉細(xì)節(jié)設(shè)太小噪點(diǎn)根本沒被平滑。# 雙邊濾波實(shí)戰(zhàn) bilateral cv2.bilateralFilter(img, d9, sigmaColor75, sigmaSpace75)我在做醫(yī)療顯微圖片預(yù)處理時(shí)雙邊濾波的效果比高斯好很多——細(xì)胞膜的邊緣是判斷病理的關(guān)鍵特征高斯會(huì)把膜邊緣模糊成漸變帶雙邊能把它留住。但雙邊是計(jì)算大戶千萬(wàn)別在實(shí)時(shí)視頻流里直接每個(gè)幀都無(wú)腦雙邊1080p都能卡到15幀以下。工程上如果既要保邊又要實(shí)時(shí)常規(guī)替代方案是先高斯降采樣再雙邊小范圍精修或者直接換導(dǎo)向?yàn)V波。4. 現(xiàn)代濾波算法群從引導(dǎo)濾波到非局部均值4.1 引導(dǎo)濾波保邊平滑的“快男”雙邊濾波雖好但是計(jì)算重、參數(shù)多。引導(dǎo)濾波是目前工程界綜合性價(jià)比最高的選擇它假設(shè)濾波輸出和引導(dǎo)圖像通常是輸入圖本身之間存在局部線性關(guān)系通過(guò)求解局部線性模型的系數(shù)來(lái)實(shí)現(xiàn)邊緣保持。這屬于優(yōu)化類濾波算法不再依賴卷積核的固定形狀。實(shí)際用起來(lái)OpenCV沒有內(nèi)建引導(dǎo)濾波OpenCV 4.x之后在擴(kuò)展模塊里有所以我通常自己寫或調(diào)第三方實(shí)現(xiàn)。import cv2 def guided_filter(src, guide, radius8, eps1e-6): # 均值模糊實(shí)現(xiàn)局部均值和方差計(jì)算 mean_src cv2.boxFilter(src, cv2.CV_32F, (radius, radius)) mean_guide cv2.boxFilter(guide, cv2.CV_32F, (radius, radius)) mean_src2 cv2.boxFilter(src * src, cv2.CV_32F, (radius, radius)) mean_guide2 cv2.boxFilter(guide * guide, cv2.CV_32F, (radius, radius)) mean_guidesrc cv2.boxFilter(src * guide, cv2.CV_32F, (radius, radius)) var_guide mean_guide2 - mean_guide * mean_guide cov_guidesrc mean_guidesrc - mean_guide * mean_src # 線性系數(shù) a 和 b a cov_guidesrc / (var_guide eps) b mean_src - a * mean_guide mean_a cv2.boxFilter(a, cv2.CV_32F, (radius, radius)) mean_b cv2.boxFilter(b, cv2.CV_32F, (radius, radius)) return mean_a * guide mean_b # 使用引導(dǎo)圖用原圖本身就是保邊平滑 result guided_filter(img.astype(np.float32) / 255.0, img.astype(np.float32) / 255.0, radius4, eps0.01)這段代碼的思路翻譯成人話是先算小鄰域內(nèi)的均值和方差方差大的區(qū)域邊緣線性系數(shù)a接近1幾乎不變方差小的區(qū)域平坦a接近0直接取均值。所以它天然擁有“邊緣保留平坦區(qū)平滑”的能力。注意eps非常關(guān)鍵調(diào)太小會(huì)保留過(guò)多噪聲調(diào)太大就變純均值濾波了。4.2 非局部均值濾波NLM讓“相似塊”互相投票傳統(tǒng)濾波只考慮當(dāng)前像素周圍的小窗口但非局部均值濾波的思路非常像“找相似臉”在全圖范圍內(nèi)實(shí)際是大范圍內(nèi)搜索與當(dāng)前塊結(jié)構(gòu)相似的圖像塊然后用這些相似塊的均值替代當(dāng)前像素。這種去噪效果在理論上接近最優(yōu)平坦區(qū)域能去得異常干凈紋理區(qū)域也能保住。但代價(jià)是奇慢無(wú)比——大范圍搜索是O(N2)級(jí)別的。工程里如果非用不可我會(huì)把搜索窗口限制在21×21、相似塊大小設(shè)為7×7并且只在離線處理小尺寸圖像時(shí)用。OpenCV內(nèi)建實(shí)現(xiàn)# 非局部均值去噪h越大去噪越強(qiáng)但細(xì)節(jié)損失也越大 nlm cv2.fastNlMeansDenoising(img, h10, templateWindowSize7, searchWindowSize21)h這個(gè)參數(shù)我一般按經(jīng)驗(yàn)調(diào)灰度8位圖噪點(diǎn)肉眼可見但不算爆炸時(shí)h10左右噪聲很明顯時(shí)h15~20超過(guò)30就會(huì)把真實(shí)邊緣直接干掉。實(shí)測(cè)過(guò)很多次這個(gè)經(jīng)驗(yàn)值穩(wěn)定可靠。4.3 各向異性擴(kuò)散濾波像擴(kuò)散過(guò)程一樣平滑圖像這個(gè)算法思想非常優(yōu)雅把圖像當(dāng)作一個(gè)熱量分布場(chǎng)噪聲是高溫點(diǎn)圖像平滑是熱擴(kuò)散過(guò)程。但擴(kuò)散的“導(dǎo)熱系數(shù)”不是固定的——在梯度大的邊緣處導(dǎo)熱系數(shù)急劇降低于是邊緣“凍結(jié)”而平坦區(qū)域繼續(xù)平滑。我一般直接用scikit-image實(shí)現(xiàn)來(lái)驗(yàn)證效果from skimage.restoration import denoise_tv_chambolle # 這是TV去噪不是同一個(gè) # 各向異性擴(kuò)散需要自己實(shí)現(xiàn)或找第三方包skimage沒有直接接口 # 常用做法是perona-malik迭代 def perona_malik(img, niter10, kappa50, gamma0.25): u img.copy().astype(np.float32) for _ in range(niter): grad_x np.roll(u, -1, axis1) - u grad_y np.roll(u, -1, axis0) - u c_x 1.0 / (1.0 (grad_x / kappa) ** 2) c_y 1.0 / (1.0 (grad_y / kappa) ** 2) u u gamma * (c_x * grad_x np.roll(c_x * grad_x, 1, axis1) c_y * grad_y np.roll(c_y * grad_y, 1, axis0)) # 這里簡(jiǎn)化了公式真正實(shí)現(xiàn)需要更精確的散度計(jì)算 return u實(shí)際在項(xiàng)目中各向異性擴(kuò)散的用處比較尷尬它迭代慢調(diào)參麻煩迭代次數(shù)、衰減系數(shù)都是敏感參數(shù)而且效果和雙邊濾波高度重疊。我基本只在學(xué)術(shù)對(duì)比實(shí)驗(yàn)里用它工業(yè)落地極少。4.4 小波濾波與深度學(xué)習(xí)濾波現(xiàn)代的分水嶺小波濾波把圖像分解成不同尺度的頻帶然后對(duì)高頻子帶做閾值收縮去噪。它比純頻域?yàn)V波強(qiáng)的地方在于時(shí)頻局部化——既能找到“頻率高”的位置也能定位“發(fā)生在哪里”這對(duì)紋理豐富區(qū)域的去噪很關(guān)鍵。但小波變換本身選擇基函數(shù)不同小波會(huì)影響結(jié)果閾值選也不好調(diào)。至于深度學(xué)習(xí)濾波DnCNN、FFDNet、SCUNet這一類其實(shí)是把濾波器參數(shù)換成從數(shù)據(jù)里學(xué)到的權(quán)重推理時(shí)本質(zhì)上還是一堆卷積層的堆疊。它們?nèi)ピ胄Чh(yuǎn)超傳統(tǒng)算法尤其是復(fù)雜噪聲場(chǎng)景。但代價(jià)是需要訓(xùn)練數(shù)據(jù)、GPU推理、且對(duì)分布外的噪聲魯棒性存疑。我的態(tài)度是傳統(tǒng)濾波是“兜底”方案深度學(xué)習(xí)濾波是“提上限”方案兩者不是替代關(guān)系是組合關(guān)系。5. 頻域?yàn)V波實(shí)戰(zhàn)FFT去周期噪聲與理想低通5.1 為什么學(xué)頻域有些噪聲在空間域無(wú)解空間域?yàn)V波解決的是局部噪聲但有一類噪聲是“全局周期性的”——比如工業(yè)掃描圖里的正弦紋路織物表面的經(jīng)緯紋理干擾衛(wèi)星影像里的條帶噪聲。它們?cè)谡麄€(gè)圖像中周期性重復(fù)在空間域你無(wú)論用什么鄰域操作都很難根除因?yàn)槊總€(gè)鄰域都混了相同波段的周期信號(hào)。但放到頻率域這些周期噪聲會(huì)呈現(xiàn)在頻譜上的特定亮點(diǎn)你可以像切西瓜一樣把那一塊頻率直接挖掉。我做過(guò)一個(gè)布料疵點(diǎn)檢測(cè)項(xiàng)目面料表面帶著周期性的經(jīng)緯紋在空間域上即使用了中值和雙邊紋理依然嚴(yán)重干擾疵點(diǎn)識(shí)別。最后轉(zhuǎn)到頻域把經(jīng)緯紋對(duì)應(yīng)的頻峰置零再逆變換回來(lái)紋理瞬間變成干凈背景疵點(diǎn)檢測(cè)才終于可行。5.2 FFT頻域?yàn)V波代碼實(shí)操import numpy as np import cv2 img cv2.imread(fabric.jpg, cv2.IMREAD_GRAYSCALE).astype(np.float32) # 1. 轉(zhuǎn)頻域中心化使低頻在中心 f np.fft.fft2(img) f_shift np.fft.fftshift(f) magnitude_spectrum 20 * np.log(np.abs(f_shift) 1e-6) # 2. 構(gòu)造理想低通/高通/帶阻掩膜 rows, cols img.shape crow, ccol rows // 2, cols // 2 # 理想低通半徑30以內(nèi)保留 mask_lowpass np.zeros((rows, cols), np.float32) cv2.circle(mask_lowpass, (ccol, crow), 30, 1, -1) # 帶阻挖掉中心附近環(huán)形區(qū)域去周期噪聲 mask_bandstop np.ones((rows, cols), np.float32) cv2.circle(mask_bandstop, (ccol, crow), 25, 0, -1) cv2.circle(mask_bandstop, (ccol, crow), 5, 1, -1) # 實(shí)際周期噪聲可能在非中心位置需要結(jié)合頻譜圖手動(dòng)定位坐標(biāo) # 3. 頻率域?yàn)V波 f_shift_filtered f_shift * mask_lowpass f_filtered np.fft.ifftshift(f_shift_filtered) img_filtered np.fft.ifft2(f_filtered) img_filtered np.real(img_filtered).clip(0, 255).astype(np.uint8) cv2.imwrite(fabric_filtered.jpg, img_filtered)這里有幾個(gè)必踩的坑第一FFT輸入尺寸最好優(yōu)化成2的冪或至少能分解成小素因子的數(shù)。如果原始圖像是1920×1080這種非友好尺寸算法會(huì)慢很多。我通常先cv2.copyMakeBorder把圖填充到2048×2048算完后再裁剪回來(lái)。第二頻域?yàn)V波振鈴效應(yīng)。理想低通有突變的截止頻率逆變換后會(huì)在邊緣產(chǎn)生一圈圈振鈴波紋。實(shí)際用高斯低通或巴特沃斯低通有平滑過(guò)渡帶能有效緩解。原因說(shuō)白了就是突變?cè)陬l域?qū)?yīng)無(wú)限長(zhǎng)的空間域脈沖響應(yīng)過(guò)渡越平緩反卷積出來(lái)的偽影越少。第三處理彩色圖必須逐通道做FFT千萬(wàn)別先轉(zhuǎn)灰度。若想保留顏色對(duì)每個(gè)通道分別FFT濾波再合回來(lái)。6. 濾波選型速查表48種算法的工程歸類我現(xiàn)在把48種算法按“工程用途”歸成幾個(gè)大類你在項(xiàng)目里遇到問(wèn)題直接按圖索驥用途分類代表算法特點(diǎn)推薦指數(shù)通用去噪高斯濾波、均值濾波快、保邊緣差★★★★椒鹽去噪中值濾波、加權(quán)中值保邊較好、對(duì)離群點(diǎn)極強(qiáng)★★★★★保邊去噪雙邊濾波、引導(dǎo)濾波、NLM效果強(qiáng)但慢★★★★銳化增強(qiáng)拉普拉斯、Unsharp Mask、Sobel高通提升邊緣對(duì)比度★★★★周期噪聲去除頻域帶阻、陷波濾波空間域無(wú)解時(shí)唯一方案★★★★形態(tài)學(xué)處理膨脹、腐蝕、開運(yùn)算、閉運(yùn)算二值圖處理主力★★★★★邊緣保留平滑各向異性擴(kuò)散、全變分去噪學(xué)術(shù)價(jià)值高、工程少用★★★深度學(xué)習(xí)去噪DnCNN、FFDNet、Restormer效果上限最高★★★門檻高頻率分析小波變換、Gabor濾波紋理分析很強(qiáng)★★★★這個(gè)表是我根據(jù)不同項(xiàng)目的“投入產(chǎn)出比”總結(jié)的不是算法學(xué)術(shù)地位排名。比如非局部均值效果確實(shí)好但在產(chǎn)線上10幀一秒都會(huì)被吊打所以我給了四顆星而不是五顆。6.1 邊緣保留需求怎么選雙邊 vs 引導(dǎo)濾波項(xiàng)目里需要“去噪但不掉邊緣”我先看有沒有實(shí)時(shí)性要求。沒有上NLM效果最好有上引導(dǎo)濾波速度快還能控制邊緣強(qiáng)度。雙邊濾波永遠(yuǎn)是我的第二順位因?yàn)樗鼉蓚€(gè)sigma參數(shù)騷操作太多稍不小心就把圖像磨成塑料。我個(gè)人在工業(yè)檢測(cè)里大量使用引導(dǎo)濾波 高斯梯度這個(gè)組合既做到了保邊平滑又能在平滑后讓梯度檢測(cè)更干凈。這個(gè)組合我在至少四個(gè)項(xiàng)目里用得非常穩(wěn)。6.2 銳化需求怎么選Unsharp Mask比直拉對(duì)比度強(qiáng)得多銳化的本質(zhì)是增強(qiáng)高頻但直接拉對(duì)比度會(huì)讓噪點(diǎn)也被放大。Unsharp Mask的標(biāo)準(zhǔn)流程原圖減去模糊圖得到邊緣細(xì)節(jié)層然后把細(xì)節(jié)層乘一個(gè)增益系數(shù)加回原圖。這樣你可以單獨(dú)控制邊緣增強(qiáng)量而不改變整體亮度遠(yuǎn)優(yōu)于無(wú)腦直拉。# Unsharp Mask 實(shí)戰(zhàn) blurred cv2.GaussianBlur(img, (0, 0), 3) detail img - blurred # 這就是邊緣細(xì)節(jié)層 sharpened img 2.0 * detail # 增益系數(shù)2.0(0, 0)核大小表示只用高斯sigma確定模糊范圍這也是OpenCV的慣用法需要記住。增益系數(shù)我一般取1.5~2.5再大邊緣會(huì)出現(xiàn)白邊黑線。彩色圖像上這個(gè)白邊更明顯嚴(yán)重的會(huì)有“塑料味”。7. 高質(zhì)量濾波實(shí)戰(zhàn)組合流水線的完整流程與參數(shù)心得7.1 一個(gè)完整的工業(yè)圖像處理流水線示例光講單個(gè)算法沒有用工程實(shí)踐永遠(yuǎn)是組合拳。下面給你看一個(gè)我處理金屬零件表面圖像的完整流水線import cv2 import numpy as np def industrial_pipeline(img): # 1. 尺寸預(yù)檢查如果是大圖先降采樣加速 if img.shape[1] 2000: scale 2000 / img.shape[1] img cv2.resize(img, None, fxscale, fyscale) # 2. 對(duì)比度受限自適應(yīng)直方圖均衡CLAHE clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8)) img_eq clahe.apply(img) # 3. 保邊去噪引導(dǎo)濾波或雙邊小范圍 denoised cv2.bilateralFilter(img_eq, d5, sigmaColor30, sigmaSpace30) # 4. 梯度增強(qiáng)Unsharp Mask blurred cv2.GaussianBlur(denoised, (0, 0), 2) detail denoised - blurred sharpened np.clip(denoised 1.8 * detail, 0, 255).astype(np.uint8) # 5. 形態(tài)學(xué)處理開運(yùn)算去小噪點(diǎn)核大小按顆粒直徑定 kernel cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)) opened cv2.morphologyEx(sharpened, cv2.MORPH_OPEN, kernel) return opened img cv2.imread(metal_surface.jpg, cv2.IMREAD_GRAYSCALE) result industrial_pipeline(img)每一步都有存在的理由CLAHE不是濾潑算法但是預(yù)處理的關(guān)鍵——金屬表面光照不均直接濾波會(huì)保留明暗過(guò)渡造成的偽邊緣CLAHE把局部對(duì)比度拉勻之后濾波才能專注處理噪聲。雙邊濾波在這個(gè)流程里的作用不是追求最強(qiáng)去噪而是“溫和降噪同時(shí)不破壞細(xì)微裂紋”所以我只用了d5、sigmaColor30這樣偏保守的參數(shù)。Unsharp Mask則是后期銳化為的是讓之后的邊緣檢測(cè)算法有更陡峭的灰度梯度。注意我用了np.clip防溢出——灰度值超出0~255之后強(qiáng)制截?cái)鄷?huì)產(chǎn)生很多“假細(xì)節(jié)”一定要clip。形態(tài)學(xué)開運(yùn)算其實(shí)是“用小尺寸的腐蝕再膨脹”可以把小于kernel大小的亮點(diǎn)噪點(diǎn)直接吃掉。這一步很多人忘了加結(jié)果邊緣檢測(cè)后滿屏都是破碎小點(diǎn)。7.2 自適應(yīng)閾值如何與濾波配合打印質(zhì)量檢測(cè)實(shí)例我做過(guò)一個(gè)打印質(zhì)量檢測(cè)項(xiàng)目需要在紙面上檢測(cè)墨水?dāng)嗑€和飛濺。原始圖像有紙張紋理干擾和燈光漸變。當(dāng)時(shí)用的處理方法先高斯濾波壓掉傳感器熱噪聲然后自適應(yīng)閾值二值化找回打印區(qū)域再用形態(tài)學(xué)閉運(yùn)算連接斷線。def print_qc(img): # 濾波器選擇小尺寸高斯保留字跡細(xì)節(jié) smooth cv2.GaussianBlur(img, (3, 3), 0.8) # 自適應(yīng)閾值blockSize設(shè)大一點(diǎn)可以避免局部陰影誤判 binary cv2.adaptiveThreshold( smooth, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, blockSize35, C9 ) # 形態(tài)學(xué)閉運(yùn)算連接斷裂筆畫 kernel cv2.getStructuringElement(cv2.MORPH_RECT, (5, 1)) closed cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel, iterations2) return binary, closed注意自適應(yīng)閾值的blockSize必須為奇數(shù)C是偏移量越大閾值越保守。BlockSize太小會(huì)把單個(gè)文字識(shí)別成背景太大會(huì)把陰影當(dāng)成前景。我調(diào)參數(shù)的經(jīng)驗(yàn)先用灰度圖的噪聲水平確定濾波強(qiáng)度再用紙張紋理周期確定blockSize比紋理周期大2~3倍。7.3 超參數(shù)調(diào)優(yōu)的遞進(jìn)策略濾波參數(shù)在我看來(lái)是三個(gè)層次的遞進(jìn)而不是一個(gè)個(gè)孤立值層次一噪聲水平。先看噪聲的強(qiáng)度和多寡決定濾波器的強(qiáng)度。顯示椒鹽噪聲的中值核從3加到5就夠不夠再看是不是需要兩種濾波器組合。層次二目標(biāo)物體尺寸。你要保留的最小特征有多大特征越大濾波核可以越大特征小核一旦大過(guò)特征就會(huì)被抹掉。所以核大小的上限就是“我不想抹掉的最小小目標(biāo)的尺寸”。層次三邊緣保真度要求。下游算法要亞像素精度嗎要的話最好銳化配合或者用保邊濾波器。不需要太精細(xì)的話直接用高斯也沒問(wèn)題。這三個(gè)層次想通之后參數(shù)就“對(duì)”了百分之八十。剩下百分之二十靠一張接一張的測(cè)試圖微調(diào)。8. 常見問(wèn)題與排查技巧實(shí)錄8.1 濾波后圖像變灰白或偏色大概率是把uint8轉(zhuǎn)float時(shí)沒歸一化到0~1或者濾波后忘了clip(0,255)再轉(zhuǎn)回uint8。很多算法內(nèi)部用float64計(jì)算中間值有負(fù)數(shù)或者超過(guò)255直接賦值或保存就會(huì)出現(xiàn)色偏。我每次轉(zhuǎn)換類型之后都要檢查dtype和值域這是最基本的防呆習(xí)慣。8.2 代碼用了很長(zhǎng)時(shí)間跑還是卡頓先查核大小中值核5×5比3×3慢好幾倍再查是否用了NLM或雙邊濾波最后查是不是沒有縮放圖。很多圖像處理代碼慢不是因?yàn)樗惴ǘ且驗(yàn)槟銢]降采樣。工業(yè)視覺里永遠(yuǎn)先考慮“能不能縮小再做”別一上來(lái)就原分辨率硬剛。8.3 濾波后邊緣變寬檢測(cè)精度下降這是“保邊需求”沒被滿足。檢查你是不是用了均值或大核高斯。如果是換成雙邊、引導(dǎo)濾波或中值另一種可能是你去噪后沒做銳化梯度不夠陡峭邊緣被檢測(cè)算法取寬了。給濾波鏈路加一步Unsharp Mask通常能解決。8.4 周期噪聲去不掉改頻域?yàn)V波??臻g域的局部操作天然對(duì)付不了全局周期信號(hào)這屬于算法族錯(cuò)配不是你參數(shù)沒調(diào)好。8.5 濾波把細(xì)小缺陷也去掉了常見于檢測(cè)微裂紋或針孔濾波器強(qiáng)度太猛。你要不換保邊濾波雙邊/引導(dǎo)要不把核縮小要不換成“只做小半徑中值”。8.6 形態(tài)學(xué)操作越做越偏目標(biāo)形狀變形檢查你是不是連續(xù)用了多次開閉運(yùn)算迭代次數(shù)多了會(huì)系統(tǒng)性收縮或膨脹。每次迭代都在改變目標(biāo)形狀三次以上基本就走形了。開運(yùn)算迭代次數(shù)能1次就絕不用2次用一次不行就換更大的核而不是多次迭代。8.7 調(diào)參調(diào)了一下午效果還不如原始圖如果濾波效果持續(xù)變差通常不是參數(shù)問(wèn)題是流程問(wèn)題。停下來(lái)想一下你的最終目標(biāo)是什么是邊緣檢測(cè)、模板匹配還是閾值分割針對(duì)下游任務(wù)逆行定義濾波目標(biāo)然后從下往上選效率高得多。9. 濾波算法學(xué)習(xí)的延伸方向別再只會(huì)調(diào)cv2.GaussianBlur我前面講過(guò)傳統(tǒng)濾波是地基但如果你止步于會(huì)用OpenCV那幾個(gè)函數(shù)上限很快見頂。這里給三條值得深挖的路線第一可分離濾波器的加速原理。高斯核是二維可分離的你可以把它拆成一行和一列分別做一維卷積計(jì)算量從O(K2·N)降到O(2K·N)。這個(gè)原理面試必考工程上對(duì)性能也有直接影響。第二導(dǎo)向?yàn)V波在摳圖、深度圖補(bǔ)全、HDR色調(diào)映射里的延伸用法。它不只是“保邊平滑”它的引導(dǎo)圖可以是另一張圖——比如用全聚焦圖引導(dǎo)散焦圖恢復(fù)清晰度用彩色圖引導(dǎo)深度圖補(bǔ)洞。我強(qiáng)烈建議你把引導(dǎo)濾波的代碼玩熟它是現(xiàn)代濾波算法里性價(jià)比最高的一招。第三深度學(xué)習(xí)與傳統(tǒng)濾波的混合。現(xiàn)在主流做法是先跑一個(gè)輕量的深度學(xué)習(xí)去噪網(wǎng)絡(luò)再后面接傳統(tǒng)的形態(tài)學(xué)或引導(dǎo)濾波做后處理。質(zhì)量好而且好笑的是深度學(xué)習(xí)網(wǎng)絡(luò)經(jīng)常也會(huì)學(xué)到類似卷積核的算子。最后分享一個(gè)重要的小技巧任何濾波器進(jìn)入正式流程前一定要做“假圖測(cè)試”——用已知的干凈圖加上已知強(qiáng)度的噪聲跑一遍你的流水線用PSNR或SSIM量化濾波器的好壞。我在實(shí)際項(xiàng)目中很多“感覺效果好”的濾波器假圖測(cè)試跑完P(guān)SNR反而下降頓時(shí)覺得調(diào)參調(diào)了個(gè)寂寞。數(shù)據(jù)不會(huì)騙人這個(gè)習(xí)慣讓我少走了非常多彎路。