计算机视觉3 SIFT特征提取与全景图像拼接

2023-05-16

1.原理

  1. 检测并提取图像的特征和关键点
  2. 匹配两个图像之间的描述符
  3. 使用RANSAC算法使用我们匹配的特征向量估计单应矩阵
  4. 拼接图像

        步骤一和步骤二过程是运用SIFT局部描述算子检测图像中的关键点和特征,SIFT特征是基于物体上的一些局部外观的兴趣点而与影像的大小和旋转无关。对于光线、噪声、些微视角改变的容忍度也很高。
而接下来即步骤三是找到重叠的图片部分,连接所有图片之后就可以形成一个基本的全景图了。匹配图片最常用的方式是采用RANSAC(RANdom SAmple Consensus, 随机抽样一致),用此排除掉不符合大部分几何变换的匹配。之后利用这些匹配的点来**“估算单应矩阵” (Homography Estimation),也就是将其中一张图像通过关联性和另一张匹配。

2.SIFT算法

2.1算法特点

        与上一期讲到的Harris角点检测不同
        1.具有较好的稳定性和不变性,能够适应旋转、尺度缩放、亮度的变化,能在一定程度上不受视角变化、仿射变换、噪声的干扰
        2.区分性好,能够在海量特征数据库中进行快速准确的区分信息进行匹配
        3.多量性,就算只有单个物体,也能产生大量特征向量
        4.高速性,能够快速的进行特征向量匹配
        5.可扩展性,能够与其它形式的特征向量进行联合

2.2对比Harris角点检测

原图:

 Harris角点检测:

 sift特征:

 很明显准确性提高了特别多,并且由于图像过大,在进行harris角点检测的时候,用了将近2个小时才完成,而sift只需要几分钟,体现了其高速性,并且能避免干扰,因此图像拼接我们使用sift算法


2.3 SIFT算法能解决的问题


1.目标的旋转、缩放、平移(RST)
2.图像仿射/投影变换(视点viewpoint )
3.弱光照影响( ilumination)
4.部分目标遮挡(occlusion)
5.杂物场景( clutter)
6.噪声


2.4 SIFT算法实现特征匹配的流程

        1.建立高斯差分金字塔


       2. 提取关键点:关键点是一些十分突出的不会因光照、尺度、旋转等因素而消失的点,比如角点、边缘点、暗区域的亮点以及亮区域的暗点。此步骤是搜索所有尺度空间上的图像位置。通过高斯微分函数来识别潜在的具有尺度和旋转不变的兴趣点。


        3.定位关键点并确定特征方向:在每个候选的位置上,通过一个拟合精细的模型来确定位置和尺度。关键点的选择依据于它们的稳定程度。然后基于图像局部的梯度方向,分配给每个关键点位置一个或多个方向。所有后面的对图像数据的操作都相对于关键点的方向、尺度和位置进行变换,从而提供对于这些变换的不变性。


        4.通过各关键点的特征向量,进行两两比较找出相互匹配的若干对特征点,建立景物间的对应关系

3 sift特征检测实现

        3.1 安装 vlfeat python图像处理工具

        VLFeat是一个跨平台的开源机器视觉库,它囊括了当前流行的机器视觉算法,如SIFT, MSER, HOG, 同时还包含了诸如K-MEANS, Hierarchical K-means的聚类算法。这个库的安装有些特殊和其他python官网可以找到的其他库的安装不一样。

        1.进入vlfeat库的下载网站 https://www.vlfeat.org/
        2.点击下图红框位置下载。

 

 记得一定要安装正确的版本 否则之后会出现错误


  File "C:\Users\95490\Desktop\计算机视觉python\sifttest1.py", line 14, in <module>
    l1, d1 ,d2,d3= sift.read_features_from_file('test.sift')

  File "C:\Users\95490\AppData\Roaming\Python\Python37\site-packages\PCV\localdescriptors\sift.py", line 26, in read_features_from_file
    return f[:,:4],f[:,4:] # feature locations, descriptors

IndexError: too many indices for array: array is 1-dimensional, but 2 were indexed

        然后将cmmd中的目录修改为你自己放置的Vlfeat bin所在目录。这里稍微解释一下os.system(cmmd)这句话的意思,这里Python通过os.system()调用外部可执行文件,也就是Vlfeat bin目录下的sift.exe,这样即完成了vlfeat库的安装,它是附在pcv库里的使用开源工具包         

        3.1 使用VLFeat 提供的二进制文件来计算图像的 SIFT 特征。

def process_image(imagename,resultname,params="--edge-thresh 10 --peak-thresh 5"):
    """ Process an image and save the results in a file. """

    if imagename[-3:] != 'pgm':
        # create a pgm file
        im = Image.open(imagename).convert('L')  #.convert('L') 将RGB图像转为灰度模式,灰度值范围[0,255]
        im.save('tmp.pgm')                       #将灰度值图像信息保存在.pgm文件中
        imagename = 'tmp.pgm'
   
    cmmd = str(r"F:/python/vlfeat-0.9.20/bin/win64/sift.exe "+imagename+" --output="+resultname+
                " "+params)
    os.system(cmmd)                              #执行sift可执行程序,生成resultname(test.sift)文件
    print('processed', imagename, 'to', resultname)

        由于该二进制文件需要的图像格式为灰度.pgm,所以如果图像为其他各是,我们需要首先将其转换成.pgm格式文件。其中数据的每一行前4个数值依次表示兴趣点的坐标、尺度和方向角度,后面紧跟着的是对应描述符的128维向量。

下面函数用于从上面输出文件中,将特征读取到Numpy数组中的函数。

def read_features_from_file(filename):
    """ Read feature properties and return in matrix form. """
    #print(filename)
    f = np.loadtxt(filename)
    return f[:,:4],f[:,4:] # feature locations, descriptors


def plot_features(im,locs,circle=True):
    """ Show image with features. input: im (image as array), 
        locs (row, col, scale, orientation of each feature). """

    def draw_circle(c,r):
        t = arange(0,1.01,.01)*2*pi
        x = r*cos(t) + c[0]
        y = r*sin(t) + c[1]
        plot(x,y,'b',linewidth=2)

    imshow(im)
    if circle:
        for p in locs:
            draw_circle(p[:2],p[2]) 
    else:
        plot(locs[:,0],locs[:,1],'ob')
    axis('off')

 读取特征后,通过在图像上绘制出它们的位置,可以将其可视化。

sift.py

from PIL import Image
#from numpy import *
from pylab import *
import os
import numpy as np
def process_image(imagename,resultname,params="--edge-thresh 10 --peak-thresh 5"):
    """ Process an image and save the results in a file. """

    if imagename[-3:] != 'pgm':
        # create a pgm file
        im = Image.open(imagename).convert('L')  #.convert('L') 将RGB图像转为灰度模式,灰度值范围[0,255]
        im.save('tmp.pgm')                       #将灰度值图像信息保存在.pgm文件中
        imagename = 'tmp.pgm'
   
    cmmd = str(r"F:/python/vlfeat-0.9.20/bin/win64/sift.exe "+imagename+" --output="+resultname+
                " "+params)
    os.system(cmmd)                              #执行sift可执行程序,生成resultname(test.sift)文件
    print('processed', imagename, 'to', resultname)


def read_features_from_file(filename):
    """ Read feature properties and return in matrix form. """
    #print(filename)
    f = np.loadtxt(filename)
    return f[:,:4],f[:,4:] # feature locations, descriptors


def plot_features(im,locs,circle=True):
    """ Show image with features. input: im (image as array), 
        locs (row, col, scale, orientation of each feature). """

    def draw_circle(c,r):
        t = arange(0,1.01,.01)*2*pi
        x = r*cos(t) + c[0]
        y = r*sin(t) + c[1]
        plot(x,y,'b',linewidth=2)

    imshow(im)
    if circle:
        for p in locs:
            draw_circle(p[:2],p[2]) 
    else:
        plot(locs[:,0],locs[:,1],'ob')
    axis('off')


if __name__ == '__main__':
    imname = ('./screen/2/4.jpg')               #待处理图像路径
    im=Image.open(imname)
    process_image(imname,'out_sift_4.txt')
    l1,d1 = read_features_from_file('out_sift_4.txt')           #l1为兴趣点坐标、尺度和方位角度 l2是对应描述符的128 维向
    figure()
    gray()
    plot_features(im,l1,circle = True)
    title('sift-features')
    show()

通过以上操作我们可以获得一个'out_sift_1.txt'的文件,里面存储的就是图像特征位置,描述子

原图:

结果 

 

 

         3.2 配对描述子

        对于将一幅图像中的特征匹配到另一幅图像的特征,一种稳健的准则是使用者两个特征距离和两个最匹配特征距离的比率。相比于图像中的其他特征,该准则保证能够找到足够相似的唯一特征。使用该方法可以使错误的匹配数降低。下面的代码实现了匹配函数。

如果PCV.localdescriptors安装的版本不对,即出现以下错误


IndexError: too many indices for array: array is 1-dimensional, but 2 were indexed

可以使用以下代码。

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
        # 反余弦和反排序,返回第二幅图像中特征的索引
        indx = argsort(arccos(dotprods))
 
        #检查最近邻的角度是否小于dist_ratio乘以第二近邻的角度
        if arccos(dotprods)[indx[0]] < dist_ratio * arccos(dotprods)[indx[1]]:
            matchscores[i] = int(indx[0])
 
    return matchscores
 
def match_twosided(desc1, desc2):
    """双向对称版本的match()"""
    matches_12 = match(desc1, desc2)
    matches_21 = match(desc2, desc1)
 
    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):
    """返回将两幅图像并排拼接成的一幅新图像"""
    #选取具有最少行数的图像,然后填充足够的空行
    rows1 = im1.shape[0]
    rows2 = im2.shape[0]
 
    if rows1 < rows2:
        im1 = concatenate((im1, zeros((rows2-rows1,im1.shape[1]))),axis=0)
    elif rows1 >rows2:
        im2 = concatenate((im2, zeros((rows1-rows2,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))
        
    plt.figure(figsize=(20, 10))
    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')

将获取的两张图以及'out_sift_1.txt'、'out_sift_2.txt'描述子 文件传入

# 示例
im1f = '3.jpg'
im2f = '4.jpg'
im1 = array(Image.open(im1f))
im2 = array(Image.open(im2f))
 
#process_image(im1f, 'out_sift_1.txt')
l1,d1 = read_features_from_file('out_sift_3.txt')
figure()
gray()
plt.figure(figsize=(20, 10))
subplot(121)
plot_features(im1, l1, circle=False)
 
#process_image(im2f, 'out_sift_2.txt')
l2,d2 = read_features_from_file('out_sift_4.txt')
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()

 结果

4.图像拼接部分

        4.1  RANSAC算法

        RANSAC通过反复选择数据中的一组随机子集来达成目标。被选取的子集被假设为局内点,并用下述方法进行验证:

  1. 有一个模型适用于假设的局内点,即所有的未知参数都能从假设的局内点计算得出。

  2. 用1中得到的模型去测试所有的其它数据,如果某个点适用于估计的模型,认为它也是局内点。

  3. 如果有足够多的点被归类为假设的局内点,那么估计的模型就足够合理。

  4. 然后,用所有假设的局内点去重新估计模型,因为它仅仅被初始的假设局内点估计过。

  5. 最后,通过估计局内点与模型的错误率来评估模型。

这个过程被重复执行固定的次数,每次产生的模型要么因为局内点太少而被舍弃,要么因为它比现有的模型更好而被选用。

步骤如下:

N:样本点个数,K:求解模型需要的最少的点的个数

  1. 随机采样K个点
  2. 针对该K个点拟合模型
  3. 计算其它点到该拟合模型的距离,小于一定阈值当做内点,统计内点个数
  4. 重复M次,选择内点数最多的模型
  5. 利用所有的内点重新估计模型(可选)

4.2 RANSAC算法与最小二乘法区别


        最小二乘方法,即通过最小化误差的平方和寻找数据的最佳函数匹配,广泛应用于数据辨识领域,但是其对于某些数据的拟合有一定的缺陷,最小二乘得到的是针对所有数据的全局最优,但是并不是所有数据都是适合去拟合的,也就是说数据可能存在较大的误差甚至差错,而这种差错的识别是需要一定的代价的,如下示意图(仅示意)就是一个典型的例子:

很显然,上述线性拟合的结果并不是我们想要的,我们真正需要的是一下拟合的效果:

而这是RANSAC算法的拟合效果,因为剔除了不想被参与拟合的红点而选择在一定范围内的蓝点,这样保证了样本数据的干净,保证拟合效果更接近真实。
 

4.3 单应性矩阵


单应性矩阵可以由两幅图像(或者平面)中对应点对计算出来。每个对应点可以写出两个方程,分别对应与x和y坐标。因此,计算单应性矩阵H至少需要4对匹配点,过程如下:

那么就可以每次从所有的匹配点中选出4对,计算单应性矩阵,然后选出内点个数最多的作为最终的结果。计算距离方法如下:

 

(1)随机选择4对匹配特征对应点对

(2)根据DLT计算单应性矩阵H(唯一解)

(3)对所有匹配点,计算映射误差(对每个对应点使用该单应性矩阵,然后返回平方距离之和)

(4)根据误差阈值,确定正常值inliers(小于阈值的点看做正确点,否则认为是错误的点)

重复步骤(1)-(4)

(5)针对最大inliers集合,重新计算单应性矩阵H

4.4 拼接图像

估计出单应性矩阵H后,我们需要将所有图像扭曲到一个公共的图像平面上。通常,这里的公共平面为中心平面(否则,需要进行大量变形)。一种方法是创建一个很大的图像,比如将平面中全部填充为0,使其和中心图像平行,然后将所有的图像扭曲到上面。其基本步骤如下:

(1)实现像素间的映射(计算像素和和单应性矩阵H相乘,然后对齐次坐标进行归一化)

(2)判断图像填补位置(查看H中的平移量,左边为正,右边为负)

(3)在单应性矩阵中加入平移量,进行alpha图填充
 

5.图像拼接实验步骤

        5.1 检测并提取图像的特征和关键点

        由sift特征提取提取图像的特征和关键角点,即'out_sift_1.txt'文件

        5.2 匹配两个图像之间的描述符

from PIL import Image

# If you have PCV installed, these imports should work
from PCV.geometry import homography, warp

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
        # 反余弦和反排序,返回第二幅图像中特征的索引
        indx = argsort(arccos(dotprods))
 
        #检查最近邻的角度是否小于dist_ratio乘以第二近邻的角度
        if arccos(dotprods)[indx[0]] < dist_ratio * arccos(dotprods)[indx[1]]:
            matchscores[i] = int(indx[0])
 
    return matchscores
 
def match_twosided(desc1, desc2):
    """双向对称版本的match()"""
    matches_12 = match(desc1, desc2)
    matches_21 = match(desc2, desc1)
 
    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

        5.3 使用RANSAC算法使用我们匹配的特征向量估计单应矩阵

5.3.1 构造单映性矩阵

from PCV.geometry import homography, warp

model = homography.RansacModel() 

5.3.2 将匹配转换成齐次坐标点

# 将匹配转换成齐次坐标点的函数

def convert_points(j):
    ndx = matches[j].nonzero()[0]
    fp = homography.make_homog(l[j+1][ndx,:2].T) 
    ndx2 = [int(matches[j][i]) for i in ndx]
    tp = homography.make_homog(l[j][ndx2,:2].T) 
    
    # switch x and y - TODO this should move elsewhere
    fp = vstack([fp[1],fp[0],fp[2]])
    tp = vstack([tp[1],tp[0],tp[2]])
    return fp,tp

fp,tp = convert_points(0)

5.3.3 获取单应性矩阵和对应该单应性矩阵的正确点对 

H_01 = homography.H_from_ransac(fp,tp,model)[0] 
class RansacModel(object):
    """ 用于测试单应性矩阵的类,其中单应性矩阵是由网站
        http://www.scipy.org/Cookbook/RANSAC上的ransac.py计算出来的"""
    
    def __init__(self,debug=False):
        self.debug = debug
        
    def fit(self, data):
        """计算选取的4个对应的单应性矩阵 """
        
        #将其转置,来调用H_from_points()计算单应性矩阵
        data = data.T
        
        # 映射的起始点
        fp = data[:3,:4]
        #映射的目标点
        tp = data[3:,:4]
        
        #计算单应性矩阵然后返回
        return H_from_points(fp,tp)
    
    def get_error( self, data, H):
        """对所有的对应计算单应性矩阵,然后对每个变换后的点,返回响应的误差"""
        
        data = data.T
        
        #映射的起始点
        fp = data[:3]
        #映射的目标点
        tp = data[3:]
        
        # 变换fp
        fp_transformed = dot(H,fp)
        
        # 归一化齐次坐标
        fp_transformed = normalize(fp_transformed)
                
        # 返回每个点的误差
        return sqrt( sum((tp-fp_transformed)**2,axis=0) )
        
 
def H_from_ransac(fp,tp,model,maxiter=1000,match_theshold=10):
    """ 使用RANSAC稳健性聚集点对应间的单应性矩阵H(ransac.py为从
        http://www.scipy.org/Cookbook/RANSAC下载的版本).
        
        输入: 齐次坐标表示的点fp,tp (3*n数组) """
    
    from PCV.tools import ransac
    
    # 对应点组
    data = vstack((fp,tp))
    
    # 计算H,并返回
    H,ransac_data = ransac.ransac(data.T,model,4,maxiter,match_theshold,10,return_all=True)
    return H,ransac_data['inliers']

        5.4 切割以及拼接图象

delta = 2000 # for padding and translation

im1 = array(Image.open(imname[0]), "uint8")

im2 = array(Image.open(imname[1]), "uint8")###
im_12 = warp.panorama(H_01,im1,im2,delta,delta)##

        其中warp.panorama()用于图像扭曲。

def panorama(H,fromim,toim,padding=2400,delta=2400):
    """ 使用单应性矩阵H(使用RANSAC稳健性估计得出),协调两幅图像,创建水平全景图。结果
        为一幅和toim具有相同高度的图像。padding指定填充像素的数目,delta指定额外的平移量""" 
    
    #检查图像是灰度图像,还是彩色图像
    is_color = len(fromim.shape) == 3
    
    # 用于geometric_transform()的单应性变换
    def transf(p):
        p2 = dot(H,[p[0],p[1],1])
        return (p2[0]/p2[2],p2[1]/p2[2])
    
    if H[1,2]<0: # fromim在右边
        print 'warp - right'
        # 变换fromim
        if is_color:
            # 在目标图像的右边填充0
            toim_t = hstack((toim,zeros((toim.shape[0],padding,3))))
            fromim_t = zeros((toim.shape[0],toim.shape[1]+padding,toim.shape[2]))
            for col in range(3):
                fromim_t[:,:,col] = ndimage.geometric_transform(fromim[:,:,col],
                                        transf,(toim.shape[0],toim.shape[1]+padding))
        else:
            # 在目标图像的右边填充0
            toim_t = hstack((toim,zeros((toim.shape[0],padding))))
            fromim_t = ndimage.geometric_transform(fromim,transf,
                                    (toim.shape[0],toim.shape[1]+padding)) 
    else:
        print 'warp - left'
        #为了补偿填充效果,在左边加入平移量
        H_delta = array([[1,0,0],[0,1,-delta],[0,0,1]])
        H = dot(H,H_delta)
        #fromim变换
        if is_color:
            # 在目标图像的左边填充0
            toim_t = hstack((zeros((toim.shape[0],padding,3)),toim))
            fromim_t = zeros((toim.shape[0],toim.shape[1]+padding,toim.shape[2]))
            for col in range(3):
                fromim_t[:,:,col] = ndimage.geometric_transform(fromim[:,:,col],
                                            transf,(toim.shape[0],toim.shape[1]+padding))
        else:
            # 在目标图像的左边填充0
            toim_t = hstack((zeros((toim.shape[0],padding)),toim))
            fromim_t = ndimage.geometric_transform(fromim,
                                    transf,(toim.shape[0],toim.shape[1]+padding))
    
    # 协调后返回(将fromim放在toim上)
    if is_color:
        # 所有非黑像素
        alpha = ((fromim_t[:,:,0] * fromim_t[:,:,1] * fromim_t[:,:,2] ) > 0)
        for col in range(3):
            toim_t[:,:,col] = fromim_t[:,:,col]*alpha + toim_t[:,:,col]*(1-alpha)
    else:
        alpha = (fromim_t > 0)
        toim_t = fromim_t*alpha + toim_t*(1-alpha)
    
    return toim_t

5.5 完整代码

修改sift.py以下代码两张图片的路径以及输出.txt文件名,运行得到out_sift_1.txt、out_sift_2.txt文件,然后运行后一段代码

#sift.py

from PIL import Image
#from numpy import *
from pylab import *
import os
import numpy as np
def process_image(imagename,resultname,params="--edge-thresh 10 --peak-thresh 5"):
    """ Process an image and save the results in a file. """

    if imagename[-3:] != 'pgm':
        # create a pgm file
        im = Image.open(imagename).convert('L')  #.convert('L') 将RGB图像转为灰度模式,灰度值范围[0,255]
        im.save('tmp.pgm')                       #将灰度值图像信息保存在.pgm文件中
        imagename = 'tmp.pgm'
   
    cmmd = str(r"F:/python/vlfeat-0.9.20/bin/win64/sift.exe "+imagename+" --output="+resultname+
                " "+params)
    os.system(cmmd)                              #执行sift可执行程序,生成resultname(test.sift)文件
    print('processed', imagename, 'to', resultname)


def read_features_from_file(filename):
    """ Read feature properties and return in matrix form. """
    #print(filename)
    f = np.loadtxt(filename)
    return f[:,:4],f[:,4:] # feature locations, descriptors


def plot_features(im,locs,circle=True):
    """ Show image with features. input: im (image as array), 
        locs (row, col, scale, orientation of each feature). """

    def draw_circle(c,r):
        t = arange(0,1.01,.01)*2*pi
        x = r*cos(t) + c[0]
        y = r*sin(t) + c[1]
        plot(x,y,'b',linewidth=2)

    imshow(im)
    if circle:
        for p in locs:
            draw_circle(p[:2],p[2]) 
    else:
        plot(locs[:,0],locs[:,1],'ob')
    axis('off')


if __name__ == '__main__':
    imname = ('./screen/2/1.png')               #待处理图像路径
    #imname = ('./screen/2/2.png')
    im=Image.open(imname)
    process_image(imname,'out_sift_1.txt')
    l1,d1 = read_features_from_file('out_sift_1.txt')           #l1为兴趣点坐标、尺度和方位角度 l2是对应描述符的128 维向
    #process_image(imname,'out_sift_2.txt')
    #l1,d1 = read_features_from_file('out_sift_2.txt')
    figure()
    gray()
    plot_features(im,l1,circle = True)
    title('sift-features')
    show()
# -*- coding: utf-8 -*-
"""
Created on Sat Apr 23 15:36:26 2022

@author: 95490
"""

from pylab import *
from numpy import *
from PIL import Image

# If you have PCV installed, these imports should work
from PCV.geometry import homography, warp
from PCV.localdescriptors import sift

"""
This is the panorama example from section 3.3.
"""

def read_features_from_file(filename):
    """读取特征属性值,然后将其以矩阵的形式返回"""
    f = loadtxt(filename)
    return f[:,:4], f[:,4:] #特征位置,描述子
 
def write_featrues_to_file(filename, locs, desc):
    """将特征位置和描述子保存到文件中"""
    savetxt(filename, hstack((locs,desc)))
 
def plot_features(im, locs, circle=False):
    """显示带有特征的图像
       输入:im(数组图像),locs(每个特征的行、列、尺度和朝向)"""
 
    def draw_circle(c,r):
        t = arange(0,1.01,.01)*2*pi
        x = r*cos(t) + c[0]
        y = r*sin(t) + c[1]
        plot(x, y, 'b', linewidth=2)
 
    imshow(im)
    if circle:
        for p in locs:
            draw_circle(p[:2], p[2])
    else:
        plot(locs[:,0], locs[:,1], 'ob')
    axis('off')
 
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
        # 反余弦和反排序,返回第二幅图像中特征的索引
        indx = argsort(arccos(dotprods))
 
        #检查最近邻的角度是否小于dist_ratio乘以第二近邻的角度
        if arccos(dotprods)[indx[0]] < dist_ratio * arccos(dotprods)[indx[1]]:
            matchscores[i] = int(indx[0])
 
    return matchscores
 
def match_twosided(desc1, desc2):
    """双向对称版本的match()"""
    matches_12 = match(desc1, desc2)
    matches_21 = match(desc2, desc1)
 
    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):
    """返回将两幅图像并排拼接成的一幅新图像"""
    #选取具有最少行数的图像,然后填充足够的空行
    rows1 = im1.shape[0]
    rows2 = im2.shape[0]
 
    if rows1 < rows2:
        im1 = concatenate((im1, zeros((rows2-rows1,im1.shape[1]))),axis=0)
    elif rows1 >rows2:
        im2 = concatenate((im2, zeros((rows1-rows2,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))
        
    plt.figure(figsize=(20, 10))
    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')

# set paths to data folder
featname = ['out_sift_'+str(i+1)+'.txt' for i in range(2)] 
imname = [str(i+1)+'.jpg' for i in range(2)]

# extract features and match
l = {}
d = {}

l[0],d[0] = read_features_from_file(featname[0])
l[1],d[1] = read_features_from_file(featname[1])
matches = {}

matches[0]  = match_twosided(d[1], d[0])##
    
# visualize the matches (Figure 3-11 in the book)

im1 = array(Image.open(imname[0]))
im2 = array(Image.open(imname[1]))
#figure()
#plot_matches(im2,im1,l[1],l[0],matches[0],show_below=True)


# function to convert the matches to hom. points
def convert_points(j):
    ndx = matches[j].nonzero()[0]
    fp = homography.make_homog(l[j+1][ndx,:2].T) 
    ndx2 = [int(matches[j][i]) for i in ndx]
    tp = homography.make_homog(l[j][ndx2,:2].T) 
    
    # switch x and y - TODO this should move elsewhere
    fp = vstack([fp[1],fp[0],fp[2]])
    tp = vstack([tp[1],tp[0],tp[2]])
    return fp,tp

# estimate the homographies
model = homography.RansacModel() 

fp,tp = convert_points(0)
H_01 = homography.H_from_ransac(fp,tp,model)[0] #im 0 to 1 


# warp the images
delta = 2000 # for padding and translation

im1 = array(Image.open(imname[0]), "uint8")

im2 = array(Image.open(imname[1]), "uint8")###
im_12 = warp.panorama(H_01,im1,im2,delta,delta)##

figure()
imshow(array(im_12, "uint8"))
axis('off')
show()



5.6 实验结果

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

计算机视觉3 SIFT特征提取与全景图像拼接 的相关文章

  • 普通门禁卡及各类复制卡相关知识

    转自 xff1a https nfctool cn 42 本文带你了解M1卡的数据结构 xff0c 为以后的破解提供理论基础 同时带你了解各种IC卡 xff0c 让你对破解和复制有更清晰的目标 请注意 xff0c ID卡没有密码 xff0c
  • 用XDS510-V4专业版仿真器连接CCS3.3与28335问题记录

    今天用仿真器连接28335一直没连上 xff0c 错误有 xff1a 1 xff0c 断开仿真器用ccs3 3连接的时候显示为 xff08 不接仿真器 xff0c 空连接 xff09 Error connecting to the targ
  • NVIDIA Jetson TX2 通过vnc 桌面控制

    1 安装Xrdp Windows远程桌面使用的是RDP协议 xff0c 所以ubuntu上就要先安装Xrdp xff0c 在ubuntu软件中心搜索xrdp安装 安装xrdp的同时会自动安装vnc4server xbase clients组
  • NVIDIA Jetson TX2 查看系统参数状态

    1 xff0c 查看Jetson TX2 L4T版本 xff1a head n 1 etc nv tegra release 在刷 JetPack 3 0之前 和刷之后 版本参数发生细微的变化 xff1a REVISION xff1a 由
  • 解决 ImportError: No module named 'serial' 问题

    在pycharm里编写Python串口程序的时候 xff0c 编译时提示 ImportError No module named 39 serial 39 解决办法 xff1a 安装 serial module 这里区分python2和 p
  • 查看ubuntu下Qt的版本

    1 xff0c 查看ubuntu下Qt的版本 打开命令行输入 xff1a span style font size 14px qmake v span
  • 运算放大器基本运算

    转自 xff1a http www 21ic com jichuzhishi analog amplifier 2014 11 11 606654 html 运算放大器组成的电路五花八门 xff0c 令人眼花瞭乱 xff0c 是模拟电路中学
  • KEIL 注解和去注解 快捷键添加

    KEIL 注解和去注解 快捷键添加方法 xff1a 菜单栏Edit gt Configuration gt Shortcut Keys 1 例如设置 注解快捷键 xff1a Ctrl 43 2 例如设置 去注解快捷键 xff1a Ctrl
  • git、vscode免密登录

    1 git配置 git config global list 查看当前配置 git config global user name 34 xiaoyaozi 34 git config global user name 34 xiaoyao
  • 555 单稳态电路

    555 定时器成本低 xff0c 性能可靠 xff0c 只需要外接几个电阻 电容 xff0c 就可以实现多谐振荡器 单稳态 触发器及施密特触发器等脉冲产生与变换电路 它内部包括两个电压比较器 xff0c 三个5K欧姆的等值串联分压电阻 xf
  • Allegro 铺铜设置

    软件版本 xff1a Allegro16 6 敷铜 xff1a 放置禁止敷铜区域 xff1a Setup Areas Route Keepout 1 标题栏选Shap gt Global Dynamic Params Shape Polyg
  • OVP 过压保护电路

    过压保护电路 OVP 为下游电路提供保护 xff0c 使其免受过高电压的损坏 OVP电路监测外部电源 如 xff1a 离线电源或电池 的直流电压 xff0c 通过下述两种方式中的一种保护后续电路 xff1a 撬棍钳位电路或串联开关 撬棍电路
  • 超全蓝牙芯片原厂总结(含芯片型号)

    转自 xff1a https blog csdn net weixin 42583147 article details 80923946 作者 xff1a XCODER 蓝牙芯片原厂 1 CSR 高通 xff08 被高通收购 xff09
  • ST-Link的internal command error问题的解决方法

    问题 xff1a 显示 xff1a internal command error 这是由于stlink无法识别到芯片的情况 xff0c 通过解决这个问题我找到几个原因和解决方法 xff1a 1 xff0c 芯片睡眠 xff0c 停机 xff
  • 蓝牙 UUID 解释

    一 xff0c 什么是 UUID UUID 可以简单理解为编号 xff0c 唯一的编号 xff0c 用于区分不同的个体 服务和特性都有各自的UUID 比如经典的9527 UUID 就跟身份证一样 xff0c 不管是你是局长还是科长 xff0
  • 【人工智能】传教士和野人问题(M-C问题)

    摘要 本题需要解决的是一般情况下的传教士和野人问题 xff08 M C问题 xff09 通过对问题的一般化 xff0c 我们用一个三元组定义了问题的状态空间 xff0c 并根据约束条件制定了一系列的操作规则 xff0c 最后通过两个启发式函
  • 【算法设计与数据结构】为何程序员喜欢将INF设置为0x3f3f3f3f?

    在算法竞赛中 xff0c 我们常常需要用到一个 无穷大 的值 xff0c 对于我来说 xff0c 大多数时间我会根据具体问题取一个99999999之类的数 xff08 显得很不专业啊 xff01 xff09 在网上看别人代码的时候 xff0
  • 【slighttpd】基于lighttpd架构的Server项目实战(7)—http-parser

    对于http服务器 xff0c http request的解析是比较麻烦的 xff0c 由于我们的重点并不在这上面 xff0c 所以这一部分不打算自己编写 xff0c 而是使用开源的http parser库 xff0c 下面我们将使用该库来
  • select和epoll 原理概述&优缺点比较

    这个问题在面试跟网络编程相关的岗位的时候基本都会被问到 xff0c 刚刚看到一个很好的比喻 xff1a 就像收本子的班长 xff0c 以前得一个个学生地去问有没有本子 xff0c 如果没有 xff0c 它还得等待一段时间而后又继续问 xff
  • 笔记-关于神经网络黑盒模型可解释性,可视化

    原博地址 xff1a 深度学习黑盒可视化指南 xff0c 从隐藏层开始 摘 xff1a 一旦神经网络接收到相当大的所需数据集后 xff0c 该网络就会使用其精确的知识 权重 来证明或识别未知数据样本上的模式 即在经过大量数据集训练以后 xf

随机推荐

  • C++11 多线程 future/promise简介

    1 lt future gt 头文件简介 Classes std future std future error std packaged task std promise std shared futureFunctions std as
  • C++异步调用利器future/promise实现原理

    前言 在异步编程中 xff0c 各种回调将让人眼花缭乱 xff0c 代码分散 xff0c 维护起来十分困难 boost和C 43 43 11 的 future promise 提供了一个很好的解决方案 xff0c 使得代码更加漂亮 易维护
  • 【Heydrones】飞手百科第一篇:一定要看的无人机原理总结

    飞手百科 知识是最好的保险 本文目录 1 xff0c 无人机的飞行原理 2 xff0c 无人机的几大系统 3 xff0c 无人机的外观介绍 4 xff0c 无人机的专业术语 xff08 一 xff09 无人机的飞行原理 旋翼和轮子一样 xf
  • 【Tars】腾讯微服务框架Tars介绍

    目录 1 介绍2 设计思路3 整体架构4 平台特性 1 介绍 Tars是 基于名字服务 使用Tars协议 的高性能 RPC 开发框架 xff0c 同时配套一体化的 服务治理平台 xff0c 帮助个人或者企业快速的以微服务的方式构建自己稳定可
  • C++11常用新特性快速一览

    最近工作中 xff0c 遇到一些问题 xff0c 使用C 43 43 11实现起来会更加方便 xff0c 而线上的生产环境还不支持C 43 43 11 xff0c 于是决定新年开工后 xff0c 在组内把C 43 43 11推广开来 xff
  • 语法糖:萃取lambda表达式

    背景 现在手头主负责的服务代码 xff0c 基本上都用C 43 43 11来开发了 xff0c 异步编程使用的是TAF的future promise future的then函数 xff0c 接受的是一个Callback对象 xff0c 该对
  • hashmap C++实现分析

    一 简介 Map 是 Key Value 对映射的抽象接口 xff0c 该映射不包括重复的键 xff0c 即一个键对应一个值 在HashMap中 xff0c 其会根据hash算法来计算key value的存储位置并进行快速存取 本文介绍的C
  • SpringMVC(04) -- SpringMVC获取请求参数

    SpringMVC学习笔记 源码地址 4 1 xff09 通过ServletAPI获取 将HttpServletRequest作为控制器方法的形参 xff0c 此时HttpServletRequest类型的参数表示封装了当前请求的请求报文的
  • docker删除所有容器/镜像

    1 想要删除容器 xff0c 则要先停止所有容器 xff08 当然 xff0c 也可以加 f强制删除 xff0c 但是不推荐 xff09 xff1a docker stop docker ps a q 2 删除所有容器 docker rm
  • php中常见的几种设计模式

    1 单例模式 单例模式可以说是面向对象语言里最常用 也是最简单的一种模式 单例就是单个实例 xff0c 单个对象的意思 xff0c 就是说我们去实例化一个类的时候 xff0c 不管调用多少次 xff0c 都永远只有一个实例 不会有多个 xf
  • Ubuntu查看文件大小或文件夹大小

    一 查看文件大小 查看文件大小的命令 xff1a ls l filename 会在终端输出 xff1a rw r r 1 root root 2147483648 Mar 5 09 39 filetemp0606 其中数字214748364
  • Git 遇到了 The remote end hung up unexpectedly -- early EOF -- index-pack failed 问题

    Git 遇到了 The remote end hung up unexpectedly early EOF index pack failed 问题 今天想 clone 在 gitlab 的 repo xff0c 结果在 clone 的过程
  • 【Docker】在Docker容器中运行VScode

    原文链接 xff1a 容器中的远程开发 Prerequisites VScodeDocker Desktop Steps 打开Docker xff1a 在Windows下出现鲸鱼图标且图标静止则打开成功 xff1b 检查Docker xff
  • github 常用命令汇总 更新代码和子模块的代码

    针对PX4代码 xff0c 在github库中建立了Firmware的分支 xff0c ADRC branch xff0c 用于存放修改的代码 xff0c 其中涉及了子模块ecl的修改 代码下载 xff1a git clone https
  • C、C++多线程编程

    本文的笔记来自于b站视频的爱编程的大丙 xff0c 博客链接 xff1a https subingwen cn xff0c 有做了相应的补充 xff01 一 线程概述 进程对应的虚拟地址空间的各个分区如图 xff1a 每个进程的虚拟地址空间
  • 【x86架构】中断基础介绍

    说明 本文讲的是Intel的x86架构下的中断 参考的文档主要如下所示 xff1a 64 ia 32 architectures software developer manual pdf PCI Express体系结构导读 x86 x64
  • Java中的this有哪四种用法

    JAVA中的this是一个非常重要的模块 在编程中有非常重要的地位 擅长用this的人常常可以使程序更加简洁和方便 今天来了解一下this的用法 java中this关键字必须放在非静态方法里面 xff0c this关键字代表自身 xff0c
  • 线程不执行delegate,防止线程结束

    如果我们将NSURLConnection放在线程中 xff0c 是不是delegate方法总是不会触发 xff1f 原因就是由于线程结束了 解决方法就是让线程在数据返回之前不结束 1 可以在线程中加一个timer防止结束 xff0c 这方法
  • vscode 配置 git (配置、暂存、推送、拉取、免密)

    前些天发现了一个巨牛的人工智能学习网站 xff0c 通俗易懂 xff0c 风趣幽默 xff0c 忍不住分享一下给大家 点击跳转到教程 vscode 中对 git 进行了集成 xff0c 很多操作只需点击就能操作 xff0c 无需写一些 gi
  • 计算机视觉3 SIFT特征提取与全景图像拼接

    1 原理 检测并提取图像的特征和关键点匹配两个图像之间的描述符使用RANSAC算法使用我们匹配的特征向量估计单应矩阵拼接图像 步骤一和步骤二过程是运用SIFT局部描述算子检测图像中的关键点和特征 xff0c SIFT特征是基于物体上的一些局