OpenCV計算機視覺實戰(11)——邊緣檢測詳解
- 0. 前言
- 1. Sobel 算子與方向梯度
- 1.1 Sobel 算子簡介
- 1.2 實現過程
- 2. Laplacian 邊緣檢測
- 2.1 Laplacian 算子簡介
- 2.2 實現過程
- 3. Canny 算法
- 3.1 Canny 算法簡介
- 3.2 實現過程
- 小結
- 系列鏈接
0. 前言
邊緣檢測能夠將圖像中最關鍵的輪廓與結構清晰呈現。從一階梯度的 Sobel
算子,到二階導數的 Laplacian
,再到集平滑、非極大值抑制與雙閾值連接于一體的 Canny
算法,三者各有千秋,共同構筑了邊緣檢測的基石。本節將深入探索 OpenCV
中三大經典邊緣檢測算子:Sobel
算子、Laplacian
與 Canny
算法,并詳細介紹每種方法的實現細節與優化技巧。
1. Sobel 算子與方向梯度
1.1 Sobel 算子簡介
Sobel
算子通過對圖像執行一階導數運算,計算水平方向和垂直方向的梯度,從而提取邊緣,能同時濾除高頻噪聲與提取梯度信息。它不僅告訴我們“哪里有邊緣”,更指明了“邊緣朝哪個方向”——非常適合做紋理分析、特征描述、甚至流場估計的第一步。可以分別獲取 Gx
( x
方向梯度)、Gy
(y
方向梯度),再合成強度圖或直接根據方向信息進行后續處理。
1.2 實現過程
(1) 噪聲與紋理權衡:
- 高斯平滑 (
ksize ≈ 5, alpha ≈ 1.0
):抑制圖像中細小噪聲,但會稍微模糊紋理 - 中值濾波 (
cv2.medianBlur
):對椒鹽噪聲更有效,但對細節損傷較小 - 根據場景選擇:工業檢測用高斯,中值或雙邊濾波;醫學影像則可考慮更強的噪聲抑制
(2) 梯度計算細節:
dx=1, dy=0
:提取水平方向的亮度變化,突出垂直邊dx=0, dy=1
:提取垂直方向的亮度變化,突出水平邊- 卷積核大小 (
ksize=3 vs 5
):3×3
速度更快,5×5
更平滑
(3) 梯度合成與可視化:
- 絕對值轉換后,不同通道的梯度可做彩色疊加:
sobel_color = cv2.merge([abs_x, abs_y, np.zeros_like(abs_x)])
- 方向圖:
theta = arctan2(Gy, Gx)
可繪制Pseudo-color
方向圖,輔助分割
import cv2
import numpy as np# 1. 讀取并灰度化
img = cv2.imread('10.jpeg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 可選降噪
blur = cv2.GaussianBlur(gray, (5, 5), 1.4)# 2. 計算 Sobel 梯度
# 參數 ksize=3 指定 Sobel 卷積核大小
grad_x = cv2.Sobel(blur, cv2.CV_16S, dx=1, dy=0, ksize=3)
grad_y = cv2.Sobel(blur, cv2.CV_16S, dx=0, dy=1, ksize=3)# 3. 轉換為絕對值并歸一化
abs_x = cv2.convertScaleAbs(grad_x)
abs_y = cv2.convertScaleAbs(grad_y)
sobel_combined = cv2.addWeighted(abs_x, 0.5, abs_y, 0.5, 0)# 4. 顯示結果
cv2.imshow('Original', img)
cv2.imshow('Sobel X', abs_x)
cv2.imshow('Sobel Y', abs_y)
cv2.imshow('Sobel Combined', sobel_combined)
cv2.waitKey(0)
cv2.destroyAllWindows()
關鍵函數解析:
-
cv2.Sobel(src, ddepth, dx, dy, ksize)
src
:輸入圖像(建議先降噪)ddepth
:輸出圖像深度,CV_16S
可避免溢出后再轉換dx
,dy
:分別指定求導的方向ksize
:Sobel
卷積核大小(常用3
或5
)
-
cv2.convertScaleAbs(src)
:將帶符號圖像轉換為無符號8
位圖像,并計算絕對值,便于后續可視化 -
cv2.addWeighted(src1, alpha, src2, beta, gamma)
:對兩幅圖像按權重線性疊加,dst = src1*alpha + src2*beta + gamma
,常用于合成x
、y
梯度
2. Laplacian 邊緣檢測
2.1 Laplacian 算子簡介
Laplacian
算子基于二階導數,對亮度突變更為敏感,通過對圖像應用 Laplacian
卷積核,能夠在一次操作中同時獲得所有方向的邊緣,并且通過零交叉 (zero-crossing
) 定位精確的邊界。但由于二階導數對噪聲非常敏感,通常需要先進行平滑再做檢測。
2.2 實現過程
(1) 高階平滑:
LoG
(Laplacian of Gaussian
):先高斯再Laplacian
,可直接調用cv2.GaussianBlur + cv2.Laplacian
- 也可用
cv2.filter2D
自定義LoG
卷積核,一步到位
(2) 零交叉檢測:
- 在
lap
圖中,像素值符號變化(正→負)即為邊緣 - 可通過掃描
3×3
鄰域,判斷中心像素與鄰居異號來定位“零交叉點”
(3) 閾值篩選與細化:
- 首次檢測后,用
cv2.threshold
去除微弱響應 - 再做形態學細化 (
cv2.ximgproc.thinning
) 得到單像素寬度邊緣
import cv2
import numpy as np# 1. 讀取與 LoG 平滑
img = cv2.imread('10.jpeg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur_log = cv2.GaussianBlur(gray, (7, 7), sigmaX=2)
lap = cv2.Laplacian(blur_log, cv2.CV_16S, ksize=3)
lap_abs = cv2.convertScaleAbs(lap)# 2. 零交叉檢測(簡易實現)
zero_cross = np.zeros_like(lap, dtype=np.uint8)
for y in range(1, lap.shape[0]-1):for x in range(1, lap.shape[1]-1):patch = lap[y-1:y+2, x-1:x+2]if np.min(patch) < 0 < np.max(patch):zero_cross[y, x] = 255# 3. 細化邊緣
thinned = cv2.ximgproc.thinning(zero_cross)# 4. 顯示
cv2.imshow('Laplacian', lap_abs)
cv2.imshow('Zero Crossing', zero_cross)
cv2.imshow('Thinned Edges', thinned)
cv2.waitKey(0)
cv2.destroyAllWindows()
關鍵函數解析:
cv2.Laplacian(src, ddepth, ksize)
:計算二階導數,ksize
越大,對噪聲抑制越好但細節越模糊- 零交叉:檢測符號變化來定位精確邊緣點,是
LoG
方法的經典后處理 cv2.ximgproc.thinning(src)
:細化算法,將二值邊緣收斂為單像素寬度
3. Canny 算法
3.1 Canny 算法簡介
Canny
算法集平滑、梯度、非極大值抑制與雙閾值連接于一體,是產業、科研常用的邊緣檢測算法。合理調參與后處理,能在保持細節的同時,極大抑制噪聲與偽邊緣。
3.2 實現過程
(1) 高斯平滑:
- 使用
cv2.GaussianBlur
,核大小與sigma
需配合調整: - 較大核與
sigma
:去噪更多,但細節丟失 - 較小核與
sigma
:保留細節,但噪聲多
(2) 非極大值抑制 (Non-Maximum Suppression
, NMS
) 剖析:
Canny
內部已實現,但了解其原理有助于后續定制:- 根據梯度方向,將像素與梯度方向上的兩個鄰居比較
- 保留局部最大值,抑制非極大響應
- 可自定義
NMS
,加入方向高精度分桶 (8
個方向),進一步細化
(3) 雙閾值與邊緣連接:
threshold1
(低閾值) 與threshold2
(高閾值)- 一般建議
threshold2 ≈ 2~3 × threshold1
(4) (可選)自適應閾值:
- 可先計算圖像梯度的全局統計量,再動態設置低高閾值
- 或根據圖像直方圖
Otsu
自動選閾,再衍生雙閾值
import cv2# 1. 讀取并灰度化
img = cv2.imread('input.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# 2. Gaussian 平滑
blur = cv2.GaussianBlur(gray, (5, 5), 1.4)# 3. Canny 邊緣檢測
low_thresh = 50
high_thresh = 150
edges = cv2.Canny(blur, low_thresh, high_thresh, apertureSize=3, L2gradient=True)# 4. 顯示結果
cv2.imshow('Original', img)
cv2.imshow('Canny Edges', edges)
cv2.waitKey(0)
cv2.destroyAllWindows()
關鍵函數解析:
cv2.Canny(image, threshold1, threshold2, apertureSize, L2gradient)
:threshold1
:較低閾值,用于連接邊緣threshold2
:較高閾值,定義強邊緣apertureSize
:Sobel
卷積核大小,常用3
L2gradient=True
:使用更精確的歐氏梯度 ( ( d x 2 + d y 2 ) \sqrt {(dx2+dy2)} (dx2+dy2)?) 替代L1
范數
小結
在本節中,我們系統介紹了三大經典邊緣檢測算子,包括 Sobel
算子:一階導數平衡了噪聲抑制與邊緣提取,結合方向信息可用于紋理分析與車道檢測;Laplacian
算子:二階導數對微小亮度突變尤為敏感,配合零交叉和細化技術,可精確捕捉任意方向的細節邊緣;Canny
算法:集成多階段處理與雙閾值策略,通過多尺度融合與自適應閾值優化,達到抗噪與細節兼顧的卓越效果。
系列鏈接
OpenCV計算機視覺實戰(1)——計算機視覺簡介
OpenCV計算機視覺實戰(2)——環境搭建與OpenCV簡介
OpenCV計算機視覺實戰(3)——計算機圖像處理基礎
OpenCV計算機視覺實戰(4)——計算機視覺核心技術全解析
OpenCV計算機視覺實戰(5)——圖像基礎操作全解析
OpenCV計算機視覺實戰(6)——經典計算機視覺算法
OpenCV計算機視覺實戰(7)——色彩空間詳解
OpenCV計算機視覺實戰(8)——圖像濾波詳解
OpenCV計算機視覺實戰(9)——閾值化技術詳解
OpenCV計算機視覺實戰(10)——形態學操作詳解