邊緣檢測是圖像處理和計算機視覺中的關鍵技術之一,旨在識別圖像中像素強度發生顯著變化的區域,這些區域通常對應于物體的邊界或輪廓。邊緣檢測在機器視覺中具有重要的需求背景,主要體現在以下幾個方面:
- 圖像分割:邊緣檢測可以幫助將圖像分割成不同的區域,便于后續的圖像分析和處理。
- 物體識別:通過檢測物體的邊緣,可以提取物體的特征,為物體識別提供基礎。
- 圖像增強:邊緣檢測可以增強圖像中的重要特征,提高圖像的清晰度和質量。
- 三維重建:在三維重建中,邊緣檢測可以幫助確定物體的形狀和結構。
1.1.2. 應用場景
邊緣檢測在機器視覺中有著廣泛的應用場景,包括但不限于以下幾個方面:
- 工業檢測:用于檢測產品表面的缺陷,如裂紋、劃痕、凹陷等。
- 自動駕駛:用于檢測道路、交通標志、車輛和行人等物體的輪廓,輔助自動駕駛系統進行決策。
- 醫學圖像分析:用于識別醫學圖像中的器官、病變和組織邊界,輔助醫生進行診斷。
- 安防監控:用于檢測視頻中的異常行為或物體,提高監控系統的智能化水平。
- 機器人視覺:用于識別環境中的物體和障礙物,幫助機器人進行導航和避障。
1.2. OpenCV中的實現方法
OpenCV提供了多種邊緣檢測方法,常用的包括:
- Canny 邊緣檢測
- Sobel 算子
- Laplacian 算子
1.2.1. Canny 邊緣檢測
原理及內部流程: Canny 邊緣檢測是一種多階段的邊緣檢測算法,主要包括以下步驟:
- 噪聲抑制:使用高斯濾波器對圖像進行平滑處理,以減少噪聲的影響。
- 計算梯度:使用Sobel算子計算圖像的梯度幅值和方向。
- 非極大值抑制:沿著梯度方向,保留局部梯度最大的像素點,抑制其他像素點,有效的去除多余的邊緣效應,精確圖像的邊緣。
- 雙閾值檢測:設置高閾值和低閾值,高于高閾值的像素被認為是強邊緣,介于高閾值和低閾值之間的像素被認為是弱邊緣。
- 邊緣連接:通過強邊緣像素和與之相鄰的弱邊緣像素進行連接,形成完整的邊緣線。
參數確定及影響:
- 高斯濾波器的參數:通常根據圖像的噪聲水平選擇合適的高斯濾波器參數,如標準差和核大小。
- 閾值:高閾值和低閾值的選擇對邊緣檢測結果有重要影響。高閾值用于檢測強邊緣,低閾值用于檢測弱邊緣。閾值的選擇需要根據圖像的特點和應用場景進行調整。
1.2.2. Sobel 算子
原理及內部流程: Sobel 算子是一種基于一階導數的邊緣檢測算子,通過計算圖像在水平和垂直方向的梯度來檢測邊緣。Sobel 算子使用兩個3×3的卷積核,分別計算水平和垂直方向的梯度,然后通過這兩個梯度來估計邊緣。
參數確定及影響:
- 卷積核大小:通常為3×3,但可以根據需要選擇其他大小。
- 閾值:通過設置閾值來確定邊緣像素,閾值的選擇需要根據圖像的特點和應用場景進行調整。
1.2.3. Laplacian 算子
原理及內部流程: Laplacian 算子是一種基于二階導數的邊緣檢測算子,通過對圖像進行二階微分操作來檢測邊緣。Laplacian 算子可以提供邊緣的強度和方向信息。
參數確定及影響:
- 卷積核大小:通常為3×3,但可以根據需要選擇其他大小。
- 閾值:通過設置閾值來確定邊緣像素,閾值的選擇需要根據圖像的特點和應用場景進行調整。、
1.2.4. 在openCV中如何使用
Sobel 算子與Laplacian 算子:這個在之前的高通濾波中已經說明過。
Canny 算子:
edges = cv2.Canny(image, threshold1, threshold2, apertureSize=3, L2gradient=False)
image
-
- 作用:輸入圖像,必須是單通道灰度圖像。
- 確認方法:如果輸入圖像是彩色圖像,需要先將其轉換為灰度圖像:Python復制
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
threshold1
-
- 作用:低閾值,用于滯后閾值處理。
- 作用機制:用于檢測弱邊緣。如果梯度幅值低于此閾值,則認為該像素不是邊緣。
- 確認方法:
-
-
- 根據經驗,可以設置為 50 左右。
- 通過實驗調整,觀察邊緣檢測的效果。通常需要結合高閾值一起調整。
- 調的較低會保留更多的弱邊緣,對于邊緣較弱的,可以適當降低低閾值
-
threshold2
-
- 作用:高閾值,用于滯后閾值處理。
- 作用機制:用于檢測強邊緣。如果梯度幅值高于此閾值,則認為該像素是強邊緣。
- 確認方法:
-
-
- 根據經驗,可以設置為 150 左右。
- 高閾值與低閾值的比值通常在 2:1 到 3:1 之間。例如,低閾值為 50,高閾值為 150 或 100。
- 對于邊緣較復雜的圖像,可以適當增加高閾值。
-
apertureSize
-
- 作用:Sobel 算子的卷積核大小,影響梯度計算的精度。
- 默認值:3
- 確認方法:
-
-
- 如果需要更平滑的邊緣,可以嘗試更大的值(如 5 或 7)。
- 值越大,計算復雜度越高,但邊緣檢測結果可能更平滑。
-
L2gradient
-
- 作用:決定梯度幅值的計算方式。
-
-
- 如果為
True
,則使用 L2 范數計算梯度幅值(M=Gx2+Gy2)。 - 如果為
False
,則使用 L1 范數計算梯度幅值(M=∣Gx∣+∣Gy∣)。
- 如果為
-
-
- 默認值:
False
- 確認方法:
- 默認值:
-
-
- 如果對精度要求較高,可以設置為
True
。 - 如果對性能要求較高,可以使用默認值
False
。
- 如果對精度要求較高,可以設置為
-
# Canny 邊緣檢測
edges = cv2.Canny(gray_image, 100, 200)
我們可以找一張圖片,先高斯濾波,然后Canny檢測,再邊緣檢測,感受一下各參數的改變的Canny邊緣檢測的影響:
新建文件:UiTest.py 在其中構建下列類
import cv2
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Sliderclass EdgeDetectionDemo:def __init__(self, image_path):self.image = cv2.imread(image_path)if self.image is None:raise ValueError(f"Failed to load image at path: {image_path}")self.image_gray = cv2.cvtColor(self.image, cv2.COLOR_BGR2GRAY)# 初始化默認參數self.blur_size = 5self.canny_low = 100self.canny_high = 200self.min_w = 100self.min_h = 100# 創建圖像窗口self.fig, (self.ax1, self.ax2, self.ax3) = plt.subplots(1, 3, figsize=(18, 6))self.fig.subplots_adjust(bottom=0.25) # 調整底部空間以放置滑塊# 顯示原始圖像self.ax1.imshow(cv2.cvtColor(self.image, cv2.COLOR_BGR2RGB))self.ax1.set_title("Original Image")self.ax1.axis('off')# 顯示邊緣檢測和輪廓檢測結果self.update_processing()# 創建滑塊self.create_sliders()plt.show()def update_processing(self):# 高斯模糊(確保核大小為奇數)blur_size = self.blur_size if self.blur_size % 2 == 1 else self.blur_size + 1blurred = cv2.GaussianBlur(self.image_gray, (blur_size, blur_size), 0)# Canny邊緣檢測edges = cv2.Canny(blurred, self.canny_low, self.canny_high)# 查找輪廓contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)# 繪制邊緣檢測結果self.ax2.clear()self.ax2.imshow(edges, cmap='gray')self.ax2.set_title(f"Canny Edges (Low={self.canny_low}, High={self.canny_high})")self.ax2.axis('off')# 繪制輪廓檢測結果contour_img = self.image.copy()valid_contours = []for cnt in contours:x, y, w, h = cv2.boundingRect(cnt)if w > self.min_w and h > self.min_h:cv2.rectangle(contour_img, (x, y), (x+w, y+h), (0, 255, 0), 2)valid_contours.append(cnt)self.ax3.clear()self.ax3.imshow(cv2.cvtColor(contour_img, cv2.COLOR_BGR2RGB))self.ax3.set_title(f"Contours (Min Size: {self.min_w}x{self.min_h})")self.ax3.axis('off')self.fig.canvas.draw_idle() # 重新繪制圖像def create_sliders(self):# 創建滑塊ax_blur_size = self.fig.add_axes([0.25, 0.15, 0.65, 0.03])ax_canny_low = self.fig.add_axes([0.25, 0.12, 0.65, 0.03])ax_canny_high = self.fig.add_axes([0.25, 0.09, 0.65, 0.03])ax_min_w = self.fig.add_axes([0.25, 0.06, 0.65, 0.03])ax_min_h = self.fig.add_axes([0.25, 0.03, 0.65, 0.03])self.slider_blur_size = Slider(ax_blur_size, 'Blur Size', 1, 15, valinit=self.blur_size, valstep=2)self.slider_canny_low = Slider(ax_canny_low, 'Canny Low', 0, 300, valinit=self.canny_low, valstep=10)self.slider_canny_high = Slider(ax_canny_high, 'Canny High', 50, 500, valinit=self.canny_high, valstep=10)self.slider_min_w = Slider(ax_min_w, 'Min Width', 50, 500, valinit=self.min_w, valstep=10)self.slider_min_h = Slider(ax_min_h, 'Min Height', 50, 500, valinit=self.min_h, valstep=10)# 綁定滑塊事件self.slider_blur_size.on_changed(self.update_slider)self.slider_canny_low.on_changed(self.update_slider)self.slider_canny_high.on_changed(self.update_slider)self.slider_min_w.on_changed(self.update_slider)self.slider_min_h.on_changed(self.update_slider)def update_slider(self, val):self.blur_size = int(self.slider_blur_size.val)self.canny_low = int(self.slider_canny_low.val)self.canny_high = int(self.slider_canny_high.val)self.min_w = int(self.slider_min_w.val)self.min_h = int(self.slider_min_h.val)self.update_processing()
在另外一個文件(learnCV.py)中引用該類
from ImageManipulation import cvTest
from UiTest import EdgeDetectionDemo
def main():# cvTest.TestGrayImage()# cvTest.TestChannelSplite()demo = EdgeDetectionDemo("Cars.jpg")if(__name__=="__main__"):main()
- 濾波核大小對弱邊緣有影響,濾波核越大,弱邊緣越少
?
?
高閾值越高,弱邊緣越少
?
極限檢測:
上下限和濾波都是0:
?
先逐漸把低閾值拉滿:
?
再把高閾值拉滿:
?
弱邊緣是在持續減少的。
大家可以個人自己多嘗試嘗試
?