特徵檢測中一般都會涉及角點、邊、斑點,而Harris是一種常用的角點檢測的方法。
角點是什麼呢?下面看一幅圖來理解:
上圖中標紅的點即角點,可以很清楚的看出,物體拐角處的點就是角點(字面上的理解)。
Harris角點檢測的思想:使用一個固定滑動窗口在圖像上的任意方向進行滑動,比較窗口中的像素灰度變化程度,如果在任意方向上都有較大灰度值變化,那麼可以認爲該窗口中存在角點。
上面的基本思想可能有點抽象,下面看一組圖輔助理解:
根據上圖來看:
(1)對於左圖,滑動窗口往任意方向滑動,都不會有較大的灰度值變化,因此滑動窗口對應的區域爲平坦區域;
(2)對於中圖,滑動窗口沿着邊線移動,無灰度變化;而其他方向可以有灰度變化,因此屬於邊緣部分;
(3)對於右圖,滑動窗口無論往哪個方向移動,對會帶來較大的灰度變化,因此滑窗對應的區域有角點。
下面是Harris角點檢測的數學原理:
1.灰度變化描述
當窗口發生[u,v]移動時,那麼滑動前與滑動後對應的窗口中的像素點灰度變化描述如下:
其中是窗口的偏移量,是窗口所對應的像素點位置,是對應位置的像素值,這裏一般選擇的是高斯
加權函數,即離中心點近的梯度貢獻更大,距離中心遠的點貢獻小。
通過上式可以看出,在平坦區域的灰度變化不大,很小;而在紋理豐富的區域,會比較大。
2. E(u,v)化簡
首先對原始的進行泰勒展開(省略掉無窮小項),得到:
因此,可以更新爲:
其中
和是滑動窗口內的像素點在x和y方向上的梯度值。
3. 關鍵的矩陣M
Harris角點檢測並沒有直接使用來確定角點。而是以x和y方向上的梯度爲座標着,對窗口內每個點的梯度進行統計分析。最終將M的特徵值轉化爲橢圓的兩個軸,更詳細的原理可以參考這篇博客: https://www.cnblogs.com/zyly/p/9508131.html
得到的結論如下圖:
對於矩陣M的特徵值:
(1)當都較小時,對應的區域爲平坦區域;
(2)當有一個較大,而另一個較小時,對應的區域存在邊緣;
(3)當都較大時,對應的區域存在角點。
4. 角點響應的度量
對於每一個滑動窗口計算一個得分R,如果R大於某一個閾值,那麼可以認爲該滑動窗口對應的區域內存在角點。
其中:
其中是矩陣M的兩個特徵值,k是一個超參數,一般設置爲0.04-0.06比較好,其存在意義是調節函數的形狀。
對於R來說:
(1)當滑窗內存在角點是,R爲正數且不會太小;
(2)當滑窗內爲平坦區域,R是一個很小的值;
(3)當滑窗內存在邊緣,R值爲負。
因此可以設定一個合理的閾值,當R大於該閾值時,認爲滑窗內存在角點。
Harris角點檢測實例
下面的一個例子是檢測一副RGB圖像的角點。
(1)將RGB三通道圖像轉化爲灰度圖;
(2)利用Sobel算子計算灰度圖在x和y方向上的梯度矩陣;
(3)對得到的兩個梯度矩陣進行高斯濾波,得到新的梯度矩陣;
(4)計算梯度矩陣上每個點的R值(每個點對應之前的一個滑窗),如果R值大於一個設定的閾值,則該點是角點。
該例子的實現源代碼如下:
import cv2
import numpy as np
import matplotlib.pyplot as plt
# Harris corner detection
def Harris_corner(img):
## Grayscale
def BGR2GRAY(img):
gray = 0.2126 * img[..., 2] + 0.7152 * img[..., 1] + 0.0722 * img[..., 0]
gray = gray.astype(np.uint8)
return gray
## Sobel
def Sobel_filtering(gray):
# get shape
H, W = gray.shape
# sobel kernel
sobely = np.array(((1, 2, 1),
(0, 0, 0),
(-1, -2, -1)), dtype=np.float32)
sobelx = np.array(((1, 0, -1),
(2, 0, -2),
(1, 0, -1)), dtype=np.float32)
# padding
tmp = np.pad(gray, (1, 1), 'edge')
# prepare
Ix = np.zeros_like(gray, dtype=np.float32)
Iy = np.zeros_like(gray, dtype=np.float32)
# get differential
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)
Ix2 = Ix ** 2
Iy2 = Iy ** 2
Ixy = Ix * Iy
return Ix2, Iy2, Ixy
# gaussian filtering
def gaussian_filtering(I, K_size=3, sigma=3):
# get shape
H, W = I.shape
## gaussian
I_t = np.pad(I, (K_size // 2, K_size // 2), 'edge')
# gaussian kernel
K = np.zeros((K_size, K_size), dtype=np.float)
for x in range(K_size):
for y in range(K_size):
_x = x - K_size // 2
_y = y - K_size // 2
K[y, x] = np.exp( -(_x ** 2 + _y ** 2) / (2 * (sigma ** 2)))
K /= (sigma * np.sqrt(2 * np.pi))
K /= K.sum()
# filtering
for y in range(H):
for x in range(W):
I[y,x] = np.sum(I_t[y : y + K_size, x : x + K_size] * K)
return I
# corner detect
def corner_detect(gray, Ix2, Iy2, Ixy, k=0.04, th=0.1):
# prepare output image
out = np.array((gray, gray, gray))
out = np.transpose(out, (1,2,0))
# get R
R = (Ix2 * Iy2 - Ixy ** 2) - k * ((Ix2 + Iy2) ** 2)
# detect corner
out[R >= np.max(R) * th] = [0, 0, 255]
out = out.astype(np.uint8)
return out
# 1. grayscale
gray = BGR2GRAY(img)
# 2. get difference image
Ix2, Iy2, Ixy = Sobel_filtering(gray)
# 3. gaussian filtering
Ix2 = gaussian_filtering(Ix2, K_size=3, sigma=3)
Iy2 = gaussian_filtering(Iy2, K_size=3, sigma=3)
Ixy = gaussian_filtering(Ixy, K_size=3, sigma=3)
# 4. corner detect
out = corner_detect(gray, Ix2, Iy2, Ixy)
return out
# Read image
img = cv2.imread("thorino.jpg").astype(np.float32)
# Harris corner detection
out = Harris_corner(img)
cv2.imwrite("out.jpg", out)
cv2.imshow("result", out)
cv2.waitKey(0)
代碼來自: https://github.com/gzr2017/ImageProcessing100Wen/blob/master/Question_81_90/README.md
裏面的圖片資源可在上面的鏈接中下載。