SIFT地理特征匹配-计算机视觉
1.Harris角点检测
Harris角点检测算法(也称Harris&Stephens角点检测器)是一个极为简单的角点检测算法。该算法的主要思想是,如果像素周围显示存在多于一个方向的边,我们认为该点为兴趣点。该点就称为角点。
我们把图像域中点x上的对称半正定矩阵MI=MI(x)定义为: M I = ∇ I ∇ I T = [ I x I y ] [ I x I y ] = [ I x 2 I x I y I x I y I y 2 ] M_I=\nabla I\nabla I^T=\begin{bmatrix}I_x \\ I_y\end{bmatrix}\begin{bmatrix}I_x & I_y\end{bmatrix}=\begin{bmatrix}I_x^2 & I_xI_y \\ I_xI_y & I_y^2\end{bmatrix} MI=∇I∇IT=[IxIy][IxIy]=[Ix2IxIyIxIyIy2]其中∇I为包含导数Ix和Iy的图像梯度。由于该定义,MI的秩为1,特征值为λ1=|∇I|2和λ2=0。现在对于图像的每个像素,我们可以计算出该矩阵。
选择权重矩阵W(通常为高斯滤波器Gσ),我们可以得到卷积: M ‾ I = W ∗ M I \overline{M}_I=W*M_I MI=W∗MI该卷积的目的是得到MI在周围像素上的局部平均。计算出的矩阵MI-有称为Harris矩阵。W的宽度决定了在像素x周围的感兴趣区域。像这样在区域附近对矩阵MI-取平均的原因是,特征值会依赖于局部图像特性而变化。如果图像梯度在该区域变化,那么MI-的第二个特征值将不再为0.如果图像的梯度没有变换,MI-的特征值也不会变化。
取决于该区域∇I的值,Harris矩阵MI-的特征值有三种情况: 1)如果λ1和λ2都是很大的正数,则该x点为角点; 2)如果λ1很大,λ2≈0,则该区域内存在一个边,该区域内的平均MI-的特征值不会变化太大; 3)如果λ1≈λ2≈0,则该区域为空。
在不需要实际计算特征值的情况下,为了把重要的情况和其他情况分开,Harris和Stephens在文献中引入了指示函数: d e t ( M ‾ I ) − κ t r a c e ( M ‾ I ) 2 det(\overline{M}_I)-\kappa trace(\overline{M}_I)^2 det(MI)−κtrace(MI)2为了去除加权常数κ,我们通常使用商数: d e t ( M ‾ I ) t r a c e ( M ‾ I ) 2 \frac{det(\overline{M}_I)}{trace(\overline{M}_I)^2} trace(MI)2det(MI)作为指示器。
首先,我们先完成计算Harris角点检测器的响应函数:
from scipy.ndimage import filters
def compute_harris_response(im, sigma=3):
"""在一幅灰度图像中,对每个像素计算Harris角点检测器响应函数"""
# 计算导数
imx = zeros(im.shape)
filters.gaussian_filter(im, (sigma, sigma), (0,1), imx)
imy = zeros(im.shape)
filters.gaussian_filter(im, (sigma, sigma), (1,0), imy)
# 计算Harris矩阵的分量
Wxx = filters.gaussian_filter(imx*imx, sigma)
Wxy = filters.gaussian_filter(imx*imy, sigma)
Wyy = filters.gaussian_filter(imy*imy, sigma)
# 计算特征值和迹
Wdet = Wxx * Wyy - Wxy**2
Wtr = Wxx + Wyy
return Wdet / Wtr
上面的函数会返回像素值为Harris响应函数值的一幅图像。现在,我们需要从这幅图像中挑选需要的信息。然后,选取像素值高于阈值的所有图像点;在加上额外的限制,即角点之间的间隔必须大于设定的最小距离。这种方法会产生很好的角点检测结果。为了实现该算法,我们获取所有候选像素点,以角点响应值递减的顺序排序,然后将距离已标记为角点位置过近的区域从候选像素点中删除:
下面展示一些 内联代码片
。
def get_harris_points(harrisim, min_dist=10, threshold=0.1):
"""从一幅Harris响应图像中返回角点。min_dist为分割角点和图像边界的最小像素数目"""
# 寻找高于阈值的候选角点
corner_threshold = harrisim.max() * threshold
harrisim_t = (harrisim > corner_threshold) * 1
# 得到候选点的坐标
coords = array(harrisim_t.nonzero()).T
# 以及它们的Harris响应值
candidate_values = [harrisim[c[0], c[1]] for c in coords]
# 对候选点按照Harris响应值进行排序
index = argsort(candidate_values)
# 将可行点的位置保存到数组中
allowed_locations = zeros(harrisim.shape)
allowed_locations[min_dist:-min_dist,min_dist:-min_dist] = 1
# 按照min_distance原则,选择最佳Harris点
filtered_coords = []
for i in index:
if allowed_locations[coords[i,0], coords[i,1]] == 1:
filtered_coords.append(coords[i])
allowed_locations[(coords[i,0]-min_dist):(coords[i,0]+min_dist),
(coords[i,1]-min_dist):(coords[i,1]+min_dist)] = 0
return filtered_coords
def plot_harris_points(image,filtered_coords):
"""绘制图像中检测到的角点"""
figure()
gray()
imshow(image)
plot([p[1] for p in filtered_coords],[p[0] for p in filtered_coords],'*')
axis('off')
show()
im = array(Image.open('jimei.jpg').convert('L'))
harrisim = compute_harris_response(im)
filtered_coords = get_harris_points(harrisim,6)
plot_harris_points(im, filtered_coords)
在图像中寻找对应点
Harris角点检测器仅仅能够检测出图像中的兴趣点,但没有给出通过比较图像间的兴趣点来寻找匹配角点的方法。我们需要在每个点上加入描述子信息,并给出一个比较这些描述子的方法。
兴趣点描述子是分配给兴趣点的一个向量,描述该点附近的图像的表现信息。描述子越好,寻找到的对应点越好。我们用对应点或者点的对应来描述相同物体和场景点在不同图像上形成的像素点。
Harris角点的描述子通常是由周围图像像素块的灰度值,以及用于比较的归一化互相关矩阵构成的。图像的像素块由以该像素点为中心的周围矩形部分图像构成。
通常,两个(相同大小)像素块I1(x)和I2(x)的相关矩阵定义为: c ( I 1 , I 2 ) = ∑ X f ( I 1 ( x ) , I 2 ( x ) ) c(I_1,I_2)=\sum_Xf(I_1(x),I_2(x)) c(I1,I2)=X∑f(I1(x),I2(x))其中,函数f随着相关方法的变化而变化。上式取像素块中所有像素位置x的和。对于互相关矩阵,函数f(I1,I2)=I1I2,因此,c(I1,I2)=I1·I2,其中·表示向量乘法。c(I1,I2)的值越大,像素块I1和I2的相似度越高。
归一化的互相关矩阵是互相关矩阵的一种变形,可以定义为:
n
c
c
(
I
1
,
I
2
)
=
1
n
−
1
∑
x
(
I
1
(
x
)
−
μ
1
)
σ
1
⋅
(
I
2
(
x
)
−
μ
2
)
σ
2
ncc(I_1,I_2)=\frac{1}{n-1}\sum_x\frac{(I_1(x)-\mu_1)}{\sigma_1}\cdot \frac{(I_2(x)-\mu_2)}{\sigma_2}
ncc(I1,I2)=n−11x∑σ1(I1(x)−μ1)⋅σ2(I2(x)−μ2)其中,n为像素块中像素的数目,μ1和μ2表示每个像素块中平均像素值强度,σ1和σ2分别表示每个像素块中的标准差。通过减去均值和除以标准差,该方法对图像亮度变化具有稳健性。
下面展示一些 内联代码片
。
def get_descriptors(image, filtered_coords, wid=5):
"""对于每个返回的点,返回点周围2*wid+1个像素的值(假设选取点的min_distance > wid)"""
desc = []
for coords in filtered_coords:
patch = image[coords[0]-wid:coords[0]+wid+1,
coords[1]-wid:coords[1]+wid+1].flatten()
desc.append(patch)
return desc
def match(desc1, desc2, threshold=0.5):
"""对于第一幅图像中的每个角点描述子,使用归一化互相关,选取它在第二幅图像中的匹配角点"""
n = len(desc1[0])
# 点对的距离
d = -ones((len(desc1), len(desc2)))
for i in range(len(desc1)):
for j in range(len(desc2)):
d1 = (desc1[i] - mean(desc1[i])) / std(desc1[i])
d2 = (desc2[j] - mean(desc2[j])) / std(desc2[j])
ncc_value = sum(d1 *d2) / (n-1)
if ncc_value > threshold:
d[i,j] = ncc_value
ndx = argsort(-d)
matchscores = ndx[:,0]
return matchscores
def match_twosided(desc1, desc2, threshold=0.5):
"""两边对称版本的match()"""
matches_12 = match(desc1, desc2, threshold)
matches_21 = match(desc2, desc1, threshold)
ndx_12 = where(matches_12 >= 0)[0]
# 去除非对称的匹配
for n in ndx_12:
if matches_21[matches_12[n]] != n:
matches_12[n] = -1
return matches_12
def appendimages(im1, im2):
"""返回将两幅图像并排拼接成的一幅新图像"""
# 选取具有最少行数的图像,然后填充足够的空行
row1 = im1.shape[0]
row2 = im2.shape[0]
if row1 < row2:
im1 = concatenate((im1,zeros((row2-row1,im1.shape[1]))), axis=0)
elif row1 > row2:
im2 = concatenate((im2,zeros((row1-row2,im2.shape[1]))), axis=0)
# 如果这些情况都没有,那么他们的行数相同,不需要进行填充
return concatenate((im1,im2), axis=1)
def plot_matches(im1, im2, locs1, locs2, matchscores, show_below=True):
"""显示一幅带有连接匹配之间连线的图片
输入:im1,im2(数组图像),locs1,locs2(特征位置),matchscores(match的输出),
show_below(如果图像应该显示再匹配下方)"""
im3 = appendimages(im1,im2)
if show_below:
im3 = vstack((im3,im3))
imshow(im3)
cols1 = im1.shape[1]
for i,m in enumerate(matchscores):
if m > 0:
plot([locs1[i][1],locs2[m][1]+cols1], [locs1[i][0], locs2[m][0]], 'c')
axis('off')
im1 = array(Image.open('raccoon.jpg').convert('L'))
im2 = array(Image.open('raccoon.jpg').convert('L'))
wid = 5
harrisim = compute_harris_response(im1, 5)
filtered_coords1 = get_harris_points(harrisim, wid+1)
d1 = get_descriptors(im1, filtered_coords1, wid)
harrisim = compute_harris_response(im2, 5)
filtered_coords2 = get_harris_points(harrisim, wid + 1)
d2 = get_descriptors(im1, filtered_coords2, wid)
print('starting matching')
matches = match_twosided(d1,d2)
figure()
gray()
plot_matches(im1, im2, filtered_coords1, filtered_coords2, matches)
show()
2.SIFT(尺度不变特征变换)
匹配描述子
对于将一幅图像中的特征匹配到另一幅图像的特征,一种稳健的准则(同样是由Lowe提出的)是使用者两个特征距离和两个最匹配特征距离的比率。相比于图像中的其他特征,该准则保证能够找到足够相似的唯一特征。使用该方法可以使错误的匹配数降低。下面的代码实现了匹配函数。
def match(desc1, desc2):
“”“对于第一幅图像的每个描述子,选取其在第二幅图像中的匹配
输入:desc1(第一幅图像中的描述子),desc2(第二幅图像中的描述子)”""
desc1 = array([d/linalg.norm(d) for d in desc1])
desc2 = array([d/linalg.norm(d) for d in desc2])
dist_ratio = 0.6
desc1_size = desc1.shape
matchscores = zeros((desc1_size[0],1), 'int')
desc2t = desc2.T #预先计算矩阵转置
for i in range(desc1_size[0]):
dotprods = dot(desc1[i,:], desc2t) #向量点乘
dotprods = 0.9999*dotprods
# 反余弦和反排序,返回第二幅图像中特征的索引
index = argsort(arccos(dotprods))
# 检查最近邻的角度是否小于dist_ratio乘以第二近邻的角度
if arccos(dotprods)[index[0]] < dist_ratio * arccos(dotprods)[index[1]]:
matchscores[i] = int(index[0])
return matchscores
def match_twosided(desc1,decs2):
"""双向对称版本的match"""
matches_12 = match(desc1, decs2)
matches_21 = match(decs2, decs2)
ndx_12 = matches_12.nonzero()[0]
# 去除不对称匹配
for n in ndx_12:
if matches_21[int(matches_12[n])] != n:
matches_12[n] = 0
return matches_12
def appendimages(im1, im2):
"""返回将两幅图像并排拼接成的一幅新图像"""
# 选取具有最少行数的图像,然后填充足够的空行
row1 = im1.shape[0]
row2 = im2.shape[0]
if row1 < row2:
im1 = concatenate((im1,zeros((row2-row1,im1.shape[1]))), axis=0)
elif row1 > row2:
im2 = concatenate((im2,zeros((row1-row2,im2.shape[1]))), axis=0)
# 如果这些情况都没有,那么他们的行数相同,不需要进行填充
return concatenate((im1,im2), axis=1)
def plot_matches(im1, im2, locs1, locs2, matchscores, show_below=True):
"""显示一幅带有连接匹配之间连线的图片
输入:im1,im2(数组图像),locs1,locs2(特征位置),matchscores(match的输出),
show_below(如果图像应该显示再匹配下方)"""
im3 = appendimages(im1,im2)
if show_below:
im3 = vstack((im3,im3))
imshow(im3)
cols1 = im1.shape[1]
for i in range(len(matchscores)):
if matchscores[i] > 0:
plot([locs1[i, 0], locs2[matchscores[i, 0], 0] + cols1], [locs1[i, 1], locs2[matchscores[i, 0], 1]], 'c')
axis('off')
im1f = r'jimei-sift2.jpg'
im2f = r'jimei-sift2.jpg'
im1 = array(Image.open(im1f))
im2 = array(Image.open(im2f))
process_image(imname,'jimei-sift2.sift','jimei-sift2.pgm')
l1,d1 = read_features_from_file('jimei-sift2.sift')
figure()
gray()
subplot(121)
plot_features(im1, l1, circle=False)
process_image(imname,'jimei-sift2.sift','jimei-sift2.pgm')
l2,d2 = read_features_from_file('jimei-sift2.sift')
subplot(122)
plot_features(im2, l2, circle=False)
matches = match_twosided(d1, d2)
print('{} matches'.format(len(matches.nonzero()[0])))
figure()
gray()
plot_matches(im1,im2,l1,l2,matches, show_below=True)
show()
3.地理特征匹配
from pylab import *
from PIL import Image
from PCV.localdescriptors import sift
from PCV.tools import imtools
import pydot
download_path = "D:\\pythonProjects\\sift\\img" # set this to the path where you downloaded the panoramio images
path = "D:\\pythonProjects\\sift\\img\\" # path to save thumbnails (pydot needs the full system path)
# list of downloaded filenames
imlist = imtools.get_imlist(download_path)
nbr_images = len(imlist)
# extract features
featlist = [imname[:-3] + 'sift' for imname in imlist]
for i, imname in enumerate(imlist):
sift.process_image(imname, featlist[i])
matchscores = zeros((nbr_images, nbr_images))
for i in range(nbr_images):
for j in range(i, nbr_images): # only compute upper triangle
print('comparing ', imlist[i], imlist[j])
l1, d1 = sift.read_features_from_file(featlist[i])
l2, d2 = sift.read_features_from_file(featlist[j])
matches = sift.match_twosided(d1, d2)
nbr_matches = sum(matches > 0)
print('number of matches = ', nbr_matches)
matchscores[i, j] = nbr_matches
print("The match scores is: \n", matchscores)
# copy values
for i in range(nbr_images):
for j in range(i + 1, nbr_images): # no need to copy diagonal
matchscores[j, i] = matchscores[i, j]
#可视化
threshold = 2 # min number of matches needed to create link
g = pydot.Dot(graph_type='graph') # don't want the default directed graph
for i in range(nbr_images):
for j in range(i + 1, nbr_images):
if matchscores[i, j] > threshold:
# first image in pair
im = Image.open(imlist[i])
im.thumbnail((100, 100))
filename = path + str(i) + '.png'
im.save(filename) # need temporary files of the right size
g.add_node(pydot.Node(str(i), fontcolor='transparent', shape='rectangle', image=filename))
# second image in pair
im = Image.open(imlist[j])
im.thumbnail((100, 100))
filename = path + str(j) + '.png'
im.save(filename) # need temporary files of the right size
g.add_node(pydot.Node(str(j), fontcolor='transparent', shape='rectangle', image=filename))
g.add_edge(pydot.Edge(str(i), str(j)))
g.write_png('jmu.png')
测试图片:
运行结果: