1977 - H.P. Moravec 特征(兴趣点)——在各个方向上灰度变化很大的像素点,利用灰度图像的自相关函数提取角点。
1)计算待检测点在四个方向的灰度变化方差
计算某个像素点沿水平、垂直、对角线、反对角线八个方向的灰度方差
- 目标像素(c,r)
- 窗口大小 w*w
- 活动半径 k=int(w/2)
2)选取灰度方差最小值为此监测点的响应函数=兴趣值CRF,
兴趣值CRF和阈值比较,判断是否为角点
(阈值选择以候选点包含所需要的角点,而又不包含过多的假角点为原则。 )
#选择方差最小的为当前像素的CRF,所有点的CRF构成keymap
c = min(v1, v2, v3, v4, v5, v6, v7, v8)
keymap[i, j] = c
#thCRF:在对CRF进行筛选时使用的阈值,-1表示自动计算平均值作为阈值
if thCRF == -1:
# CRF的平均值作为筛选阈值
mean_c = np.mean(keymap)
print ('=>auto threshold for score value:', mean_c)
#thCRF:阈值需要手动设置
else:
mean_c = thCRF
print ('=>threshold for score value:', mean_c)
keymap = np.where(keymap < mean_c, 0, keymap)
3)兴趣值CRF使用非极大值抑制NMS减少角点
在一定大小的窗口内(可与兴趣值计算之窗口大小不同,如 3×3,5×5,7×7),选择角点响应函数值最大的候选角点为最终角点。
#非极大值抑制
for i in range(safe_range, img_h - safe_range):
for j in range(safe_range, img_w - safe_range):
win, stx, enx, sty, eny = getWindowWithRange(keymap, i, j, nonMax_size)
nonMax_win, row, col = nonMaximumSupression(win)
keymap[stx:enx, sty:eny] = nonMax_win
cv2.imwrite("keymap_nonMax.jpg", keymap)
#把使用阈值筛选过的窗口放入NMS函数中
def nonMaximumSupression(mat, nonMaxValue=0):
mask = np.zeros(mat.shape, mat.dtype) + nonMaxValue
max_value = np.max(mat)
loc = np.where(mat == max_value)
row = loc[0]
col = loc[1]
mask[row, col] = max_value
return mask, row, col
缺点:没有旋转不变性
Python代码
# coding=utf-8
import cv2
import numpy as np
from matplotlib import pyplot as plt
def calcV(window1, window2):
# 用于计算窗口间的差异
win1 = np.int32(window1)
win2 = np.int32(window2)
diff = win1 - win2
diff = diff * diff
return np.sum(diff)
def getWindow(img, i, j, win_size):
# 获得指定范围、大小的窗口内容
if win_size % 2 == 0:
win = None
return win
half_size = int(win_size / 2)
start_x = i - half_size
start_y = j - half_size
end_x = i + half_size + 1
end_y = j + half_size + 1
win = img[start_x:end_x, start_y:end_y]
return win
def getWindowWithRange(img, i, j, win_size):
# 获取指定范围、大小的窗口内容以及坐标
if win_size % 2 == 0:
win = None
return win
half_size = int(win_size / 2)
start_x = i - half_size
start_y = j - half_size
end_x = i + half_size + 1
end_y = j + half_size + 1
win = img[start_x:end_x, start_y:end_y]
return win, start_x, end_x, start_y, end_y
def get8directionWindow(img, i, j, win_size, win_offset):
# 获取8个方向的不同窗口内容
half_size = int(win_size / 2)
win_tl = img[i - win_offset - half_size:i - win_offset + half_size + 1,j - win_offset - half_size:j - win_offset + half_size + 1]
win_t = img[i - win_offset - half_size:i - win_offset + half_size + 1,j - half_size:j + half_size + 1]
win_tr = img[i - win_offset - half_size:i - win_offset + half_size + 1,j + win_offset - half_size:j + win_offset + half_size + 1]
win_l = img[i - half_size:i + half_size + 1,j - win_offset - half_size:j - win_offset + half_size + 1]
win_r = img[i - half_size:i + half_size + 1,j + win_offset - half_size:j + win_offset + half_size + 1]
win_bl = img[i + win_offset - half_size:i + win_offset + half_size + 1,j - win_offset - half_size:j - win_offset + half_size + 1]
win_b = img[i + win_offset - half_size:i + win_offset + half_size + 1,j - half_size:j + half_size + 1]
win_br = img[i + win_offset - half_size:i + win_offset + half_size + 1,j + win_offset - half_size:j + win_offset + half_size + 1]
return win_tl, win_t, win_tr, win_l, win_r, win_bl, win_b, win_br
def nonMaximumSupression(mat, nonMaxValue=0):
mask = np.zeros(mat.shape, mat.dtype) + nonMaxValue
max_value = np.max(mat)
loc = np.where(mat == max_value)
row = loc[0]
col = loc[1]
mask[row, col] = max_value
return mask, row, col
def getScore(item):
return item[2]
def getKeypoints(keymap, nonMaxValue, nFeature=-1):
# 用于获取角点的坐标以及对角点进行排序筛选
loc = np.where(keymap != nonMaxValue)
xs = loc[1]
ys = loc[0]
print (xs.__len__(), 'keypoints were found.')
kps = []
for x, y in zip(xs, ys):
kps.append([x, y, keymap[y, x]])
if nFeature != -1:
kps.sort(key=getScore)
kps = kps[:nFeature]
print (kps.__len__(), 'keypoints were selected.')
return kps
def drawKeypoints(img, kps):
for kp in kps:
pt = (kp[0], kp[1])
cv2.circle(img, pt, 3, [0, 0, 255], 1, cv2.LINE_AA)
return img
def getMoravecKps(img_path, win_size=3, win_offset=1, nonMax_size=5, nonMaxValue=0, nFeature=-1, thCRF=-1):
"""
将上面的步骤整合为一个函数,方便调用
:param img_path: 影像的路径
:param win_size: 滑动窗口的大小
:param win_offset: 窗口偏移的距离
:param nonMax_size: 非极大值抑制的滑动窗口大小
:param nonMaxValue: 非极大值抑制时,非极大值被赋的值
:param nFeature: 打算提取的角点个数,-1表示自动
:param thCRF: 在对CRF进行筛选时使用的阈值,-1表示自动计算平均值作为阈值
:return:
"""
print ("step 1:read image")
img_rgb = cv2.imread(img_path)
img = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
img_h = img.shape[0]
img_w = img.shape[1]
print ("=>image size:", img_h, '*', img_w)
keymap = np.zeros([img_h, img_w], np.int32)
print ("step 2:calculate score value using sliding window")
safe_range = win_offset + win_size
for i in range(safe_range, img_h - safe_range):
for j in range(safe_range, img_w - safe_range):
win = getWindow(img, i, j, win_size)
win_tl, win_t, win_tr, win_l, win_r, win_bl, win_b, win_br = get8directionWindow(img, i, j, win_size,win_offset)
v1 = calcV(win, win_tl)
v2 = calcV(win, win_t)
v3 = calcV(win, win_tr)
v4 = calcV(win, win_l)
v5 = calcV(win, win_r)
v6 = calcV(win, win_bl)
v7 = calcV(win, win_b)
v8 = calcV(win, win_br)
c = min(v1, v2, v3, v4, v5, v6, v7, v8)
keymap[i, j] = c
if thCRF == -1:
# CRF的平均值作为筛选阈值
mean_c = np.mean(keymap)
print ('=>auto threshold for score value:', mean_c)
else:
mean_c = thCRF
print ('=>threshold for score value:', mean_c)
print ("step 3:filter keypoints using threshold...")
cv2.imwrite("keymap.jpg", keymap)
keymap = np.where(keymap < mean_c, 0, keymap)
cv2.imwrite("keymap_th.jpg", keymap)
print ("step 4:non maximum supression...")
for i in range(safe_range, img_h - safe_range):
for j in range(safe_range, img_w - safe_range):
win, stx, enx, sty, eny = getWindowWithRange(keymap, i, j, nonMax_size)
nonMax_win, row, col = nonMaximumSupression(win)
keymap[stx:enx, sty:eny] = nonMax_win
cv2.imwrite("keymap_nonMax.jpg", keymap)
print ("step 5:get keypoint location and draw points.")
kps = getKeypoints(keymap, nonMaxValue=nonMaxValue, nFeature=nFeature)
img_kps = drawKeypoints(img_rgb, kps)
return kps, img_kps
if __name__ == '__main__':
kps, img = getMoravecKps("img.jpg", nFeature=300)
cv2.imwrite("moravec.jpg", img)
cv2.imshow("img", img)
cv2.waitKey(0)
结果: