3. 影像梯度與邊緣偵測

import cv2
import matplotlib.pyplot as plt

3.1. 重點在這

  • 一階微分 (gradient).

    • 對一張影像的每個像素點,都可以算 gradient,得到 Gx 和 Gy.

    • 如果是直線的 edge,那邊緣像素點的 Gx 應該會很大,非邊緣像素點的 Gx 會很小,所以根據每個像素點的 Gx 來畫圖,可以得到 x 方向的邊緣圖像.

    • 同理,如果是橫線的 edge,那邊緣像素點的 Gy 應該會很大,非邊緣像素點的 Gy 會很小,所以根據每個像素點的 Gy 來畫圖,可以得到 x 方向的邊緣圖像.

    • 如果,把 Gx 的影像,和 Gy 的影像疊合在一起 (weight 都是 0.5),那就可以得到邊緣影像.

    • 至於 Gx, Gy 的算法,這邊介紹了兩種:

      • 直接照離散的公式算:

        • Gx = df/dx = f(x+1, y) - f(x, y),所以就是拿該像素點的右邊像素,來減自己;

        • Gy = df/dy = f(x, y+1) - f(x, y)

      • Sobel:

        • \(Gx = \left[{\begin{array}{cc} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{array}} \right]\);

        • \(Gy = \left[{\begin{array}{cc} -1 & -2 & -1 \\ 0 & 0 & 0 \\ 1 & 2 & 1 \end{array}} \right]\) ;

        • 概念上,就是我要算該像素點的 Gx 時,我拿上一列和下一列的 Gx 一起進來做加權平均(我自己這列的權重是2,上一列和下一列的權重是1)

      • Scharr:

        • \(Gx = \left[{\begin{array}{cc} -3 & 0 & 3 \\ -10 & 0 & 10 \\ -3 & 0 & 3 \end{array}} \right]\);

        • \(Gy = \left[{\begin{array}{cc} -3 & -10 & -3 \\ 0 & 0 & 0 \\ 3 & 10 & 3 \end{array}} \right]\)

        • 可視為 Sobel 的訊號加強版,因為他更凸顯像素值間的差異。這種作法對訊號比較弱的影像的邊緣偵測能更好,但對正常影像可能過度強調細節使得邊緣更複雜不乾淨

  • 二階微分

    • Laplacian 算子,他實際在做的是該像素點的全微分: d2f/dx2 + d2f/dy2,用離散點的方式來計算,就等於用以下 kernel 來做 conv:

    • \(\left[{\begin{array}{cc} 0 & 1 & 0 \\ 1 & -4 & 1 \\ 0 & 1 & 0 \end{array}} \right]\)

  • Canny 邊緣檢測

    • 先做 gaussian blur,把 noise 抹掉

    • 求 gradient 的大小和方向:

      • gradient 大小 = sqrt(Gx^2 + Gy^2)

      • gradient 方向: \(Angle(\theta) = \arctan \left( \frac{Gy}{Gx}\right)\) -> 邊緣的方向會和 gradient 方向垂直,所以就求得邊緣方向

    • non-maximum suppression (非最大值抑制)

      • 檢查該像素點,在梯度方向的鄰域中,是局部極大值得保留,不是的刪除,等於只留下真的是邊緣的像素點

    • 利用高閾值和低閾值繼續清掉不是邊緣的點

3.2. Sobel

  • 先看張圖:

src = cv2.imread("map.jpg")
print(src.shape)
print(src.dtype)
print((src[:,:,0] == src[:,:,1]).all())
plt.imshow(src);
(248, 389, 3)
uint8
True
../../_images/ch13_gradient_and_edge_detection_6_1.png
  • 這張圖的大小是 248x389, 然後是灰階圖 (雖然三個 channel,但每個 channel 的值都一樣),type 是 uint8 (0~255)

  • 計算 gradient 時,每個像素點,都會算出 x 方向的 gradient 和 y 方向的 gradient.

print(src.min())
print(src.max())

dst = cv2.Sobel(
    src = src,
    ddepth = cv2.CV_16S, # 可選 cv2.CV16S, cv2.CV32
    dx = 1, # 計算 x 軸的一階導數
    dy = 0 # y 軸不計算一階導數
)
print(dst.min())
print(dst.max())

dst2 = cv2.convertScaleAbs(dst)
print(dst2.min())
print(dst2.max())
0
255
-1012
1017
0
255
plt.imshow(dst2);
../../_images/ch13_gradient_and_edge_detection_9_0.png
  • 此時如果要看由左到右的 profile,看第一個 row

plt.plot(dst[0,:,0]);
../../_images/ch13_gradient_and_edge_detection_11_0.png
  • 看每個 row 的平均:

plt.plot(dst[:,:,0].mean(axis = 0));
../../_images/ch13_gradient_and_edge_detection_13_0.png
plt.plot(dst2[0,:,0]);
../../_images/ch13_gradient_and_edge_detection_14_0.png
plt.plot(dst2[:,:,0].mean(axis = 0));
../../_images/ch13_gradient_and_edge_detection_15_0.png
# y 方向的
dst = cv2.Sobel(src, cv2.CV_32F, 0, 1)  # 計算 y 軸影像梯度
dst = cv2.convertScaleAbs(dst)          # 將負值轉正值
plt.imshow(dst);
../../_images/ch13_gradient_and_edge_detection_16_0.png
# 完整範例:
dstx = cv2.Sobel(src, cv2.CV_32F, 1, 0)         # 計算 x 軸影像梯度
dsty = cv2.Sobel(src, cv2.CV_32F, 0, 1)         # 計算 y 軸影像梯度
dstx = cv2.convertScaleAbs(dstx)                # 將負值轉正值
dsty = cv2.convertScaleAbs(dsty)                # 將負值轉正值
dst =  cv2.addWeighted(dstx, 0.5,dsty, 0.5, 0)  # 影像融合
plt.imshow(dst);
../../_images/ch13_gradient_and_edge_detection_17_0.png
# 應用到實際影像
src = cv2.imread("lena.jpg")
dstx = cv2.Sobel(src, cv2.CV_32F, 1, 0)         # 計算 x 軸影像梯度
dsty = cv2.Sobel(src, cv2.CV_32F, 0, 1)         # 計算 y 軸影像梯度
dstx = cv2.convertScaleAbs(dstx)                # 將負值轉正值
dsty = cv2.convertScaleAbs(dsty)                # 將負值轉正值
dst =  cv2.addWeighted(dstx, 0.5,dsty, 0.5, 0)  # 影像融合

# 將影像從 BGR 轉回 RGB
src = cv2.cvtColor(src, cv2.COLOR_BGR2RGB)
dstx = cv2.cvtColor(dstx, cv2.COLOR_BGR2RGB)
dsty = cv2.cvtColor(dsty, cv2.COLOR_BGR2RGB)
dst = cv2.cvtColor(dst, cv2.COLOR_BGR2RGB)
plt.imshow(src);
../../_images/ch13_gradient_and_edge_detection_19_0.png
plt.imshow(dstx);
../../_images/ch13_gradient_and_edge_detection_20_0.png
plt.imshow(dsty);
../../_images/ch13_gradient_and_edge_detection_21_0.png
plt.imshow(dst);
../../_images/ch13_gradient_and_edge_detection_22_0.png

3.3. Scharr

def myplot(img):
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    plt.imshow(img);
# Sobel()函數
src = cv2.imread("lena.jpg",cv2.IMREAD_GRAYSCALE)   # 黑白讀取
dstx = cv2.Sobel(src, cv2.CV_32F, 1, 0)         # 計算 x 軸影像梯度
dsty = cv2.Sobel(src, cv2.CV_32F, 0, 1)         # 計算 y 軸影像梯度
dstx = cv2.convertScaleAbs(dstx)                # 將負值轉正值
dsty = cv2.convertScaleAbs(dsty)                # 將負值轉正值
dst_sobel =  cv2.addWeighted(dstx, 0.5,dsty, 0.5, 0)    # 影像融合
# Scharr()函數
dstx = cv2.Scharr(src, cv2.CV_32F, 1, 0)        # 計算 x 軸影像梯度
dsty = cv2.Scharr(src, cv2.CV_32F, 0, 1)        # 計算 y 軸影像梯度
dstx = cv2.convertScaleAbs(dstx)                # 將負值轉正值
dsty = cv2.convertScaleAbs(dsty)                # 將負值轉正值
dst_scharr =  cv2.addWeighted(dstx, 0.5,dsty, 0.5, 0)   # 影像融合
myplot(src)
../../_images/ch13_gradient_and_edge_detection_26_0.png
myplot(dst_sobel)
../../_images/ch13_gradient_and_edge_detection_27_0.png
myplot(dst_scharr)
../../_images/ch13_gradient_and_edge_detection_28_0.png

3.4. Laplacian

src = cv2.imread("laplacian.jpg")
dst_tmp = cv2.Laplacian(src, cv2.CV_32F)    # Laplacian邊緣影像
dst = cv2.convertScaleAbs(dst_tmp)          # 轉換為正值
print(dst_tmp.max())
print(dst_tmp.min())

print(dst.max())
print(dst.min())
416.0
-482.0
255
0
myplot(src)
../../_images/ch13_gradient_and_edge_detection_32_0.png
myplot(dst)
../../_images/ch13_gradient_and_edge_detection_33_0.png
src = cv2.imread("geneva.jpg",cv2.IMREAD_GRAYSCALE)   # 黑白讀取
src = cv2.GaussianBlur(src,(3,3),0)             # 降低噪音
# Sobel()函數
dstx = cv2.Sobel(src, cv2.CV_32F, 1, 0)         # 計算 x 軸影像梯度
dsty = cv2.Sobel(src, cv2.CV_32F, 0, 1)         # 計算 y 軸影像梯度
dstx = cv2.convertScaleAbs(dstx)                # 將負值轉正值
dsty = cv2.convertScaleAbs(dsty)                # 將負值轉正值
dst_sobel =  cv2.addWeighted(dstx, 0.5,dsty, 0.5, 0)    # 影像融合
# Scharr()函數
dstx = cv2.Scharr(src, cv2.CV_32F, 1, 0)        # 計算 x 軸影像梯度
dsty = cv2.Scharr(src, cv2.CV_32F, 0, 1)        # 計算 y 軸影像梯度
dstx = cv2.convertScaleAbs(dstx)                # 將負值轉正值
dsty = cv2.convertScaleAbs(dsty)                # 將負值轉正值
dst_scharr =  cv2.addWeighted(dstx, 0.5,dsty, 0.5, 0)   # 影像融合
# Laplacian()函數
dst_tmp = cv2.Laplacian(src, cv2.CV_32F,ksize=3)    # Laplacian邊緣影像
dst_lap = cv2.convertScaleAbs(dst_tmp)          # 將負值轉正值
myplot(src)
../../_images/ch13_gradient_and_edge_detection_35_0.png
myplot(dst_sobel)
../../_images/ch13_gradient_and_edge_detection_36_0.png
myplot(dst_scharr)
../../_images/ch13_gradient_and_edge_detection_37_0.png
myplot(dst_lap)
../../_images/ch13_gradient_and_edge_detection_38_0.png

3.5. Canny

src = cv2.imread("lena.jpg",cv2.IMREAD_GRAYSCALE)
dst1 = cv2.Canny(src, 50, 100)      # minVal=50, maxVal=100
dst2 = cv2.Canny(src, 50, 200)      # minVal=50, maxVal=200
plt.imshow(dst1);
../../_images/ch13_gradient_and_edge_detection_41_0.png
plt.imshow(dst2);
../../_images/ch13_gradient_and_edge_detection_42_0.png
src = cv2.imread("geneva.jpg",cv2.IMREAD_GRAYSCALE)   # 黑白讀取
src = cv2.GaussianBlur(src,(3,3),0)             # 降低噪音
# Sobel()函數
dstx = cv2.Sobel(src, cv2.CV_32F, 1, 0)         # 計算 x 軸影像梯度
dsty = cv2.Sobel(src, cv2.CV_32F, 0, 1)         # 計算 y 軸影像梯度
dstx = cv2.convertScaleAbs(dstx)                # 將負值轉正值
dsty = cv2.convertScaleAbs(dsty)                # 將負值轉正值
dst_sobel =  cv2.addWeighted(dstx, 0.5,dsty, 0.5, 0)    # 影像融合
# Scharr()函數
dstx = cv2.Scharr(src, cv2.CV_32F, 1, 0)        # 計算 x 軸影像梯度
dsty = cv2.Scharr(src, cv2.CV_32F, 0, 1)        # 計算 y 軸影像梯度
dstx = cv2.convertScaleAbs(dstx)                # 將負值轉正值
dsty = cv2.convertScaleAbs(dsty)                # 將負值轉正值
dst_scharr =  cv2.addWeighted(dstx, 0.5,dsty, 0.5, 0)   # 影像融合
# Laplacian()函數
dst_tmp = cv2.Laplacian(src, cv2.CV_32F,ksize=3)    # Laplacian邊緣影像
dst_lap = cv2.convertScaleAbs(dst_tmp)          # 將負值轉正值
# Canny()函數
dst_canny = cv2.Canny(src, 50, 100)             # minVal=50, maxVal=100
plt.imshow(src, cmap = "gray");
../../_images/ch13_gradient_and_edge_detection_44_0.png
plt.imshow(dst_sobel, cmap = "gray");
../../_images/ch13_gradient_and_edge_detection_45_0.png
plt.imshow(dst_scharr, cmap = "gray")
<matplotlib.image.AxesImage at 0x121c81ee0>
../../_images/ch13_gradient_and_edge_detection_46_1.png
plt.imshow(dst_lap, cmap = "gray");
../../_images/ch13_gradient_and_edge_detection_47_0.png
plt.imshow(dst_canny, cmap = "gray");
../../_images/ch13_gradient_and_edge_detection_48_0.png