- 將輸入影像轉換為灰度影像。
- 計算影像的梯度,可以使用 Sobel 運算元計算水平和垂直方向的梯度。
- 對影像中的每個畫素應用一個高斯加權視窗,以考慮畫素周圍的區域性區域。
- 計算每個畫素的 Harris 矩陣。Harris 矩陣是一個 2x2 的矩陣,包含了區域性區域的梯度資訊。
- 計算每個畫素的 Harris 響應函式值,即 Harris 矩陣的行列式除以跡的值。這個值衡量了影像中該點是否是角點的可能性。
- 使用一個閾值來選擇 Harris 響應較高的畫素,這些畫素被認為是角點的候選點。
- 對候選點進行非極大值抑制,去除區域性最大值以得到最終的角點。
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)