2.Harris角點檢測

真真夜夜發表於2024-04-20
  1. 將輸入影像轉換為灰度影像。
  2. 計算影像的梯度,可以使用 Sobel 運算元計算水平和垂直方向的梯度。
  3. 對影像中的每個畫素應用一個高斯加權視窗,以考慮畫素周圍的區域性區域。
    img
  4. 計算每個畫素的 Harris 矩陣。Harris 矩陣是一個 2x2 的矩陣,包含了區域性區域的梯度資訊。
  5. 計算每個畫素的 Harris 響應函式值,即 Harris 矩陣的行列式除以跡的值。這個值衡量了影像中該點是否是角點的可能性。
    img
  6. 使用一個閾值來選擇 Harris 響應較高的畫素,這些畫素被認為是角點的候選點。
    img
  7. 對候選點進行非極大值抑制,去除區域性最大值以得到最終的角點。
import cv2
import numpy as np
import matplotlib.pyplot as plt
from argparse import Namespace
from typing import Any, Tuple


def BGR2GRAY(img: np.ndarray, weights: list[float]) -> np.ndarray:
    """
    將BGR影像轉換為灰度影像。

    Args:
        img: numpy.ndarray
            輸入的BGR影像,形狀為 (height, width, 3)。
        weights: list[float]
            包含三個浮點數的列表,表示灰度轉換中紅色、綠色和藍色通道的權重。

    Returns:
        gray: numpy.ndarray
            灰度影像,資料型別為 uint8。
    """
    # 透過加權求和計算灰度值
    gray = (
        weights[0] * img[:, :, 2]
        + weights[1] * img[:, :, 1]
        + weights[2] * img[:, :, 0]
    )

    # 將灰度影像轉換為8位無符號整數型別(uint8)
    return gray.astype(np.uint8)



def Sobel_filtering(gray: np.ndarray, args: Any) -> Tuple[np.ndarray, np.ndarray]:
    """
    使用Sobel運算元對灰度影像進行濾波。

    Args:
        gray: numpy.ndarray
            輸入的灰度影像,形狀為 (H, W)。
        args: Any
            包含Sobel運算元引數的物件。

    Returns:
        Ix: numpy.ndarray
            x方向的Sobel濾波結果,資料型別為 float32。
        Iy: numpy.ndarray
            y方向的Sobel濾波結果,資料型別為 float32。
    """
    # 獲取影像形狀
    H, W = gray.shape

    # Sobel運算元
    sobely = np.array(args.sobelx, dtype=np.float32)
    sobelx = np.array(args.sobely, dtype=np.float32)

    # 填充
    tmp = np.pad(gray, (1, 1), "edge")

    # 準備
    Ix = np.zeros_like(gray, dtype=np.float32)
    Iy = np.zeros_like(gray, dtype=np.float32)

    # 計算差分
    for y in range(H):
        for x in range(W):
            Ix[y, x] = np.mean(tmp[y : y + 3, x : x + 3] * sobelx)
            Iy[y, x] = np.mean(tmp[y : y + 3, x : x + 3] * sobely)

    return Ix, Iy



def gaussian_filtering(I: np.ndarray, args: Any) -> np.ndarray:
    """
    使用高斯濾波器對影像進行濾波。

    Args:
        I: numpy.ndarray
            輸入的影像,形狀為 (H, W)。
        args: Any
            包含高斯濾波器引數的物件。

    Returns:
        I: numpy.ndarray
            濾波後的影像,形狀與輸入相同。
    """
    # 獲取影像形狀
    H, W = I.shape

    ## 高斯
    I_t = np.pad(I, (args.K_size // 2, args.K_size // 2), "edge")

    # 高斯核
    K = np.zeros((args.K_size, args.K_size), dtype=np.float32)
    for x in range(args.K_size):
        for y in range(args.K_size):
            _x = x - args.K_size // 2
            _y = y - args.K_size // 2
            K[y, x] = np.exp(-(_x**2 + _y**2) / (2 * (args.sigma**2)))
    K /= args.sigma * np.sqrt(2 * np.pi)
    K /= K.sum()

    # 濾波
    for y in range(H):
        for x in range(W):
            I[y, x] = np.sum(I_t[y : y + args.K_size, x : x + args.K_size] * K)

    return I



def corner_detect(
    img: np.ndarray, Ix2: np.ndarray, Iy2: np.ndarray, Ixy: np.ndarray, args: Any
) -> np.ndarray:
    """
    使用Harris角點檢測演算法檢測影像中的角點。

    Args:
        img: numpy.ndarray
            輸入的影像,形狀為 (H, W)。
        Ix2: numpy.ndarray
            x方向梯度的平方,形狀與輸入影像相同。
        Iy2: numpy.ndarray
            y方向梯度的平方,形狀與輸入影像相同。
        Ixy: numpy.ndarray
            x和y方向梯度的乘積,形狀與輸入影像相同。
        args: Any
            包含Harris角點檢測演算法引數的物件。

    Returns:
        R_th: numpy.ndarray
            Harris響應的二值影像,形狀與輸入影像相同。
    """
    # 準備輸出影像
    H, W = img.shape

    # 計算Harris角點響應矩陣
    R = np.zeros_like(img, dtype=np.float32)
    for y in range(H):
        for x in range(W):
            # 計算Harris矩陣的各個分量
            M = np.array(
                [[Ix2[y, x], Ixy[y, x]], [Ixy[y, x], Iy2[y, x]]], dtype=np.float32
            )
            # 計算行列式和跡
            det_M = np.linalg.det(M)
            trace_M = np.trace(M)
            if trace_M != 0:
                # 計算Harris響應
                R[y, x] = det_M / trace_M

    # 使用閾值比率閾值化Harris響應
    R_th = (R > R.max() * args.th_ratio) + 0
    return R_th



def threshold_and_nms(R: np.ndarray, args: Any) -> np.ndarray:
    """
    對角點響應圖進行閾值化和非極大值抑制。

    Args:
        R: numpy.ndarray
            輸入的角點響應影像,形狀為 (H, W)。
        args: Any
            包含閾值化和非極大值抑制引數的物件。

    Returns:
        R_final: numpy.ndarray
            經過閾值化和非極大值抑制處理後的角點響應圖,形狀與輸入影像相同。
    """
    # 閾值化處理
    R_th = (R > R.max() * args.th_nms) + 0
    # 使用膨脹操作對角點響應圖進行處理
    R_dilate = dilate(R, args.ks)
    # 根據膨脹操作得到的結果進行非極大值抑制
    R_nms = R >= R_dilate
    # 最終的角點響應圖
    R_final = R_th * R_nms

    return R_final



def dilate(R: np.ndarray, kernel_size: Tuple[int, int]) -> np.ndarray:
    """
    使用矩形結構元素對影像進行膨脹操作。

    Args:
        R: numpy.ndarray
            輸入的影像,形狀為 (H, W)。
        kernel_size: Tuple[int, int]
            結構元素的大小,形狀為 (kh, kw)。

    Returns:
        R_dilate: numpy.ndarray
            膨脹後的影像,形狀與輸入影像相同。
    """
    # 使用矩形結構元素進行膨脹操作
    R_dilate = np.zeros_like(R)

    H, W = R.shape
    kh, kw = kernel_size

    # 對影像進行遍歷
    for y in range(H):
        for x in range(W):
            # 在當前位置應用結構元素
            for ky in range(kh):
                for kx in range(kw):
                    ny = y + ky - kh // 2
                    nx = x + kx - kw // 2
                    # 確保結構元素不超出影像邊界
                    if 0 <= ny < H and 0 <= nx < W:
                        R_dilate[y, x] = max(R_dilate[y, x], R[ny, nx])

    return R_dilate



def DrawKeypoints(img: np.ndarray, R_final: np.ndarray, args: Any) -> None:
    """
    在影像上標記檢測到的角點。

    Args:
        img: numpy.ndarray
            輸入的影像,可以是灰度圖或RGB圖,形狀為 (H, W) 或 (H, W, 3)。
        R_final: numpy.ndarray
            角點響應圖,形狀與輸入影像相同。
        args: Any
            包含標記角點引數的物件,例如標記點的大小等。

    Returns:
        None
    """
    # 將影像轉換為RGB格式(如果是灰度圖的話)
    if len(img.shape) == 2:
        img_rgb = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)
    else:
        img_rgb = img.copy()

    # 找到角點的座標
    corner_y, corner_x = np.where(R_final > 0)

    # 在影像上繪製角點
    plt.imshow(img_rgb)
    plt.scatter(corner_x, corner_y, c="r", s=args.s)  # 繪製紅色的點作為角點
    plt.axis("off")
    plt.show()



def cornerHarris(img, args: Any) -> None:
    """
    使用 Harris 角點檢測演算法檢測影像中的角點,並標記在影像上。

    Args:
        args: Any
            包含 Harris 角點檢測演算法引數的物件。

    Returns:
        R_final: numpy.ndarray
    """

    gray = BGR2GRAY(img, args.gray)
    Ix, Iy = Sobel_filtering(gray, args)
    Ix2 = Ix ** 2
    Iy2 = Iy ** 2
    Ixy = Ix * Iy

    Ix2 = gaussian_filtering(Ix2, args)
    Iy2 = gaussian_filtering(Iy2, args)
    Ixy = gaussian_filtering(Ixy, args)

    R = corner_detect(gray, Ix2, Iy2, Ixy, args)
    R_final = threshold_and_nms(R, args)

    return R_final
    


if __name__ == "__main__":
    args = Namespace(
        img="image2.png",   # 輸入影像的路徑
        gray=[0.299, 0.114, 0.587], # 灰度轉換的權重
        sobelx=[[1, 2, 1], [0, 0, 0], [-1, -2, -1]],    # Sobel運算元的x方向
        sobely=[[1, 0, -1], [2, 0, -2], [1, 0, -1]],    # Sobel運算元的y方向
        K_size=3,   # 高斯濾波器的大小
        sigma=3,    # 高斯濾波器的標準差
        th_ratio=0.1,   # Harris角點檢測演算法的閾值比率
        th_nms=0.05,    # 非極大值抑制的閾值
        ks=(3, 3),  # 膨脹操作的結構元素大小
        s=0.2,   # 標記角點的大小
    )

    img = cv2.imread(args.img)
    R_final = cornerHarris(img, args)

    DrawKeypoints(img.copy(), R_final, args)

相關文章