5. 濾波器
5.1 卷積
5.1.1 什么是圖片卷積
影像卷積就是卷積核在影像上按行滑動遍歷像素時不斷的相乘求和的程序

5.1.2 步長
**步長就是卷積核在影像上移動的步幅.**上面例子中卷積核每次移動一個像素步長的結果, 如果將這個步長修改為2, 結果會如何?
為了充分掃描圖片, 步長一般設為1.

5.1.3 padding
從上面例子中我們發現, 卷積之后圖片的長寬會變小. 如果要保持圖片大小不變, 我們需要在圖片周圍填充0. padding指的就是填充的0的圈數.
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-eltEuVO8-1643352703508)(.\img\零填充一層.png)]
我們可以通過公式計算出需要填充的0的圈數.

如果要保持卷積之后圖片大小不變, 可以得出等式: ( N + 2 P ? F + 1 ) = N (N + 2P - F + 1) = N (N+2P?F+1)=N從而可以推匯出 P = F ? 1 2 P = \frac{F -1}{2} P=2F?1?
5.1.4 卷積核的大小
圖片卷積中, 卷積核一般為奇數, 比如 3 * 3, 5 * 5, 7 * 7.為什么一般是奇數呢, 出于以下兩個方面的考慮:
- 根據上面padding的計算公式, 如果要保持圖片大小不變, 采用偶數卷積核的話, 比如4 * 4, 將會出現填充1.5圈零的情況.
- 奇數維度的過濾器有中心,便于指出過濾器的位置, 即OpenCV卷積中的錨點.
5.1.5 卷積案例
- filter2D(src, ddepth, kernel[, dst[, anchor[, delta[, borderType]]]])
- ddepth是卷積之后圖片的位深, 即卷積之后圖片的資料型別, 一般設為-1, 表示和原圖型別一致.
- kernel是卷積核大小, 用元組或者ndarray表示, 要求資料型別必須是float型.
- anchor 錨點, 即卷積核的中心點, 是可選引數, 默認是(-1,-1)
- delta 可選引數, 表示卷積之后額外加的一個值, 相當于線性方程中的偏差, 默認是0.
- borderType 邊界型別.一般不設.
# OpenCV影像卷積操作
import cv2
import numpy as np
#匯入圖片
img = cv2.imread('./dog.jpeg')
# 相當于原始圖片中的每個點都被平均了一下, 所以影像變模糊了.
kernel = np.ones((5, 5), np.float32) / 25
# ddepth = -1 表示圖片的資料型別不變
dst = cv2.filter2D(img, -1, kernel)
# 很明顯卷積之后的圖片模糊了.
cv2.imshow('img', np.hstack((img, dst)))
cv2.waitKey(0)
cv2.destroyAllWindows()

5.2 方盒濾波與均值濾波
-
boxFilter(src, ddepth, ksize[, dst[, anchor[, normalize[, borderType]]]]) 方盒濾波.
-
方盒濾波的卷積核的形式如下:

-
normalize = True時, a = 1 / (W * H) 濾波器的寬高
-
normalize = False是. a = 1
-
一般情況我們都使用normalize = True的情況. 這時 方盒濾波 等價于 均值濾波
-
-
blur(src, ksize[, dst[, anchor[, borderType]]]) 均值濾波.
import cv2
import numpy as np
#匯入圖片
img = cv2.imread('./dog.jpeg')
# kernel = np.ones((5, 5), np.float32) / 25
# ddepth = -1 表示圖片的資料型別不變
dst = cv2.blur(img, (5, 5))
# 很明顯卷積之后的圖片模糊了.
cv2.imshow('img', img)
cv2.imshow('dst', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
5.3 高斯濾波
高斯濾波的核心思想是讓臨近的像素具有更高的重要度. 對周圍像素計算加權平均值, 較近的像素具有較大的權重值.
要理解高斯濾波首先要知道什么是高斯函式.高斯函式在是符合高斯分布(也叫正態分布)的資料的概率密度函式.畫出來長這樣子:

高斯函式的特點是以x軸某一點(這一點稱為均值)為對稱軸, 越靠近中心資料發生的概率越高, 最終形成一個兩邊平緩, 中間陡峭的鐘型(有的地方也叫帽子)圖形.
高斯函式的一般形式為:

高斯濾波就是使用符合高斯分布的卷積核對圖片進行卷積操作. 所以高斯濾波的重點就是如何計算符合高斯分布的卷積核, 即高斯模板.
假定中心點的坐標是(0,0),那么取距離它最近的8個點坐標,為了計算,需要設定σ的值,假定σ=1.5,則模糊半徑為1的高斯模板就算如下:

我們可以觀察到越靠近中心, 數值越大, 越邊緣的數值越小.符合高斯分布的特點.
通過高斯函式計算出來的是概率密度函式, 所以我們還要確保這九個點加起來為1,這9個點的權重總和等于0.4787147,因此上面9個值還要分別除以0.4787147,得到最終的高斯模板,
注: 有些整數高斯模板是在歸一化后的高斯模板的基礎上每個數除上左上角的值, 然后取整.

有了卷積核, 計算高斯濾波就簡單了.假設現有9個像素點,灰度值(0-255)的高斯濾波計算如下:

將這9個值加起來,就是中心點的高斯濾波的值,對所有點重復這個程序,就得到了高斯模糊后的影像,
-
GaussianBlur(src, ksize, sigmaX[, dst[, sigmaY[, borderType]]])
- kernel 高斯核的大小.
- sigmaX, X軸的標準差
- sigmaY, Y軸的標準差, 默認為0, 這時sigmaY = sigmaX
- 如果沒有指定sigma值, 會分別從ksize的寬度和高度中計算sigma.
-
選擇不同的sigma值會得到不同的平滑效果, sigma越大, 平滑效果越明顯.

-
沒有指定sigma時, ksize越大, 平滑效果越明顯

高斯濾波實戰
# 高斯濾波 import cv2 import numpy as np #匯入圖片 img = cv2.imread('./gaussian.png') dst = cv2.GaussianBlur(img, (5, 5), sigmaX=1) cv2.imshow('img', np.hstack((img, dst))) cv2.waitKey(0) cv2.destroyAllWindows()
5.4 中值濾波
中值濾波原理非常簡單, 假設有一個陣列[1556789], 取其中的中間值(即中位數)作為卷積后的結果值即可.中值濾波對胡椒噪音(也叫椒鹽噪音)效果明顯.
# 中值濾波
import cv2
import numpy as np
#匯入圖片
img = cv2.imread('./papper.png')
# 注意這里的ksize就是一個數字
dst = cv2.medianBlur(img, 5)
cv2.imshow('img', np.hstack((img, dst)))
cv2.waitKey(0)
cv2.destroyAllWindows()

5.5 雙邊濾波
雙邊濾波對于影像的邊緣資訊能過更好的保存,其原理為一個與空間距離相關的高斯函式與一個灰度距離相關的高斯函式相乘,

雙邊濾波本質上是高斯濾波, 雙邊濾波和高斯濾波不同的就是:雙邊濾波既利用了位置資訊又利用了像素資訊來定義濾波視窗的權重,而高斯濾波只用了位置資訊.
對于高斯濾波,僅用空間距離的權值系數核與影像卷積后,確定中心點的灰度值,即認為離中心點越近的點,其權重系數越大,
雙邊濾波中加入了對灰度資訊的權重,即在鄰域內,灰度值越接近中心點灰度值的點的權重更大,灰度值相差大的點權重越小,此權重大小,則由值域高斯函式確定,
兩者權重系數相乘,得到最終的卷積模板,由于雙邊濾波需要每個中心點鄰域的灰度資訊來確定其系數,所以其速度與比一般的濾波慢很多,而且計算量增長速度為核大小的平方,

雙邊濾波可以保留邊緣, 同時可以對邊緣內的區域進行平滑處理.
雙邊濾波的作用就相當于做了美顏.
-
bilateralFilter(src, d, sigmaColor, sigmaSpace[, dst[, borderType]])
- sigmaColor是計算像素資訊使用的sigma
- sigmaSpace是計算空間資訊使用的sigma
# 雙邊濾波 # 中值濾波 import cv2 import numpy as np #匯入圖片 img = cv2.imread('./lena.png') dst = cv2.bilateralFilter(img, 7, 20, 50) cv2.imshow('img', np.hstack((img, dst))) cv2.waitKey(0) cv2.destroyAllWindows()

5.6 索貝爾(sobel)算子
邊緣是像素值發生躍遷的位置,是影像的顯著特征之一,在影像特征提取,物件檢測,模式識別等方面都有重要的作用,
人眼如何識別影像邊緣?
比如有一幅圖,圖里面有一條線,左邊很亮,右邊很暗,那人眼就很容易識別這條線作為邊緣.也就是像素的灰度值快速變化的地方.
sobel算子對影像求一階導數,一階導數越大,說明像素在該方向的變化越大,邊緣信號越強,
因為影像的灰度值都是離散的數字, sobel算子采用離散差分算子計算影像像素點亮度值的近似梯度.
影像是二維的,即沿著寬度/高度兩個方向.
我們使用兩個卷積核對原影像進行處理:
-
水平方向

-
垂直方向

這樣的話,我們就得到了兩個新的矩陣,分別反映了每一點像素在水平方向上的亮度變化情況和在垂直方向上的亮度變換情況.
綜合考慮這兩個方向的變化,我們使用以下公式反映某個像素的梯度變化情況.
G = G x 2 + G y 2 G= \sqrt{G^2_x+G^2_y} G=Gx2?+Gy2? ?
有時候為了簡單起見,也直接用絕對值相加替代. G = ∣ G X ∣ + ∣ G Y ∣ G = |G_X| + |G_Y| G=∣GX?∣+∣GY?∣
# 索貝爾算子.
import cv2
import numpy as np
#匯入圖片
img = cv2.imread('./chess.png')#
# x軸方向, 獲取的是垂直邊緣
dx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=5)
dy = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=5)
# 可利用numpy的加法, 直接整合兩張圖片
# dst = dx + dy
# 也可利用opencv的加法
dst = cv2.add(dx, dy)
cv2.imshow('dx', np.hstack((dx, dy, dst)))
cv2.waitKey(0)
cv2.destroyAllWindows()

5.7 沙爾(Scharr)算子
-
Scharr(src, ddepth, dx, dy[, dst[, scale[, delta[, borderType]]]])
-
當內核大小為 3 時, 以上Sobel內核可能產生比較明顯的誤差(畢竟,Sobel算子只是求取了導數的近似值), 為解決這一問題,OpenCV提供了 Scharr函式,但該函式僅作用于大小為3的內核,該函式的運算與Sobel函式一樣快,但結果卻更加精確.
-
Scharr算子和Sobel很類似, 只不過使用不同的kernel值, 放大了像素變換的情況:

-
Scharr算子只支持3 * 3 的kernel所以沒有kernel引數了.
-
Scharr算子只能求x方向或y方向的邊緣.
-
Sobel算子的ksize設為-1就是Scharr算子.
-
Scharr擅長尋找細小的邊緣, 一般用的較少.
# 索貝爾算子.
import cv2
import numpy as np
#匯入圖片
img = cv2.imread('./lena.png')#
# x軸方向, 獲取的是垂直邊緣
dx = cv2.Scharr(img, cv2.CV_64F, 1, 0)
# y軸方向, 獲取的是水平邊緣
dy = cv2.Scharr(img, cv2.CV_64F, 0, 1)
# 可利用numpy的加法, 直接整合兩張圖片
# dst = dx + dy
# 也可利用opencv的加法
dst = cv2.add(dx, dy)
cv2.imshow('dx', np.hstack((dx, dy, dst)))
cv2.waitKey(0)
cv2.destroyAllWindows()

5.8 拉普拉斯算子
索貝爾算子是模擬一階求導,導數越大的地方說明變換越劇烈,越有可能是邊緣.

那如果繼續對f’(t)求導呢?

可以發現"邊緣處"的二階導數=0, 我們可以利用這一特性去尋找影像的邊緣. 注意有一個問題,二階求導為0的位置也可能是無意義的位置.
-
拉普拉斯算子推導程序
-
以x方向求解為例:
一階差分: f ′ ( x ) = f ( x ) ? f ( x ? 1 ) f'(x) = f(x) - f(x - 1) f′(x)=f(x)?f(x?1)
二階差分: f ′ ′ ( x ) = f ′ ( x + 1 ) ? f ′ ( x ) = ( f ( x + 1 ) ? f ( x ) ) ? ( f ( x ) ? f ( x ? 1 ) ) f''(x) = f'(x+1) - f'(x) = (f(x + 1) - f(x)) - (f(x) - f(x - 1)) f′′(x)=f′(x+1)?f′(x)=(f(x+1)?f(x))?(f(x)?f(x?1))
化簡后: f ′ ′ ( x ) = f ( x ? 1 ) ? 2 f ( x ) ) + f ( x + 1 ) f''(x) = f(x - 1) - 2 f(x)) + f(x + 1) f′′(x)=f(x?1)?2f(x))+f(x+1)同理可得: f ′ ′ ( y ) = f ( y ? 1 ) ? 2 f ( y ) ) + f ( y + 1 ) f''(y) = f(y - 1) - 2 f(y)) + f(y + 1) f′′(y)=f(y?1)?2f(y))+f(y+1)
把x,y方向的梯度疊加在一起.
f ′ ′ ( x , y ) = f x ′ ( x , y ) + f y ′ ( x , y ) f''(x,y) = f'_x(x,y) + f'_y(x,y) f′′(x,y)=fx′?(x,y)+fy′?(x,y)
f ′ ′ ( x , y ) = f ( x ? 1 , y ) ? 2 f ( x , y ) ) + f ( x + 1 , y ) + f ( x , y ? 1 ) ? 2 f ( x , y ) ) + f ( x , y + 1 ) f''(x,y) = f(x - 1, y) - 2 f(x,y)) + f(x + 1, y) + f(x, y - 1) - 2 f(x,y)) + f(x,y + 1) f′′(x,y)=f(x?1,y)?2f(x,y))+f(x+1,y)+f(x,y?1)?2f(x,y))+f(x,y+1)
$f’’(x,y) = f(x - 1, y) + f(x + 1, y) + f(x, y - 1) + f(x,y + 1) - 4 f(x,y)) $
這個等式可以用矩陣寫成:
f ′ ′ ( x , y ) = [ 0 1 0 1 ? 4 1 0 1 0 ] ? [ f ( x ? 1 , y ? 1 ) f ( x , y ? 1 ) f ( x + 1 , y ? 1 ) f ( x ? 1 , y ) f ( x , y ) f ( x + 1 , y ) f ( x ? 1 , y + 1 ) f ( x , y + 1 ) f ( x + 1 , y + 1 ) ] f''(x,y) = \left[\begin{matrix}0 & 1 & 0\\1 & -4 & 1\\0 & 1 & 0\end{matrix}\right] \bigodot \left[\begin{matrix}f(x-1, y-1) & f(x, y-1) & f(x+1,y-1)\\f(x-1,y) & f(x,y) & f(x+1,y)\\f(x-1,y+1) & f(x,y+1) & f(x+1,y+1)\end{matrix}\right] f′′(x,y)=???010?1?41?010????????f(x?1,y?1)f(x?1,y)f(x?1,y+1)?f(x,y?1)f(x,y)f(x,y+1)?f(x+1,y?1)f(x+1,y)f(x+1,y+1)????
這樣就得到了拉普拉斯算子的卷積核即卷積模板.++
-
-
Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]])
-
可以同時求兩個方向的邊緣
-
對噪音敏感, 一般需要先進行去噪再呼叫拉普拉斯
# 拉普拉斯
import cv2
import numpy as np
#匯入圖片
img = cv2.imread('./chess.png')#
dst = cv2.Laplacian(img, -1, ksize=3)
cv2.imshow('dx', np.hstack((img, dst)))
cv2.waitKey(0)
cv2.destroyAllWindows()

5.9 邊緣檢測Canny
Canny 邊緣檢測演算法 是 John F. Canny 于 1986年開發出來的一個多級邊緣檢測演算法,也被很多人認為是邊緣檢測的 最優演算法, 最優邊緣檢測的三個主要評價標準是:
- 低錯誤率: 標識出盡可能多的實際邊緣,同時盡可能的減少噪聲產生的誤報,
- 高定位性: 標識出的邊緣要與影像中的實際邊緣盡可能接近,
- 最小回應: 影像中的邊緣只能標識一次,
-
Canny邊緣檢測的一般步驟
-
去噪. 邊緣檢測容易受到噪聲影響, 在進行邊緣檢測前通常需要先進行去噪, 一般用高斯濾波去除噪聲.
-
計算梯度: 對平滑后的影像采用sobel算子計算梯度和方向.
-
G = G x 2 + G y 2 G = \sqrt{G_x^2+G_y^2} G=Gx2?+Gy2? ? 為了方便一般可以改用絕對值
-
θ = a r c t a n ( G y G x ) \theta = arctan(\frac{G_y}{G_x}) θ=arctan(Gx?Gy??)
-
梯度的方向被歸為四類: 垂直, 水平和兩個對角線.
-
計算出來的梯度和方向大概如下圖:

-
-
非極大值抑制
-
在獲取了梯度和方向后, 遍歷影像, 去除所有不是邊界的點.
-
實作方法: 逐個遍歷像素點, 判斷當前像素點是否是周圍像素點中具有相同方向梯度的最大值.
-
下圖中, 點A,B,C具有相同的方向, 梯度方向垂直于邊緣.
-
判斷點A是否為A,B,C中的區域最大值, 如果是, 保留該點;否則,它被抑制(歸零)

-
更形象的例子:

-
-
滯后閾值

-
-
Canny(img, minVal, maxVal, …)
# Canny
import cv2
import numpy as np
#匯入圖片
img = cv2.imread('./lena.png')#
# 閾值越小, 細節越豐富
lena1 = cv2.Canny(img, 100, 200)
lena2 = cv2.Canny(img, 64, 128)
cv2.imshow('lena', np.hstack((lena1, lena2)))
cv2.waitKey(0)
cv2.destroyAllWindows()

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/423202.html
標籤:AI
下一篇:時間序列預測-ARMA實戰
