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

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 实验结果

阅读终点,创作起航,您可以撰写心得或摘录文章要点写篇博文。去创作
  • 13
    点赞
  • 59
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
压缩包中包含的具体内容: 对给定数据中的6个不同场景图像,进行全景拼接操作,具体要求如下: (1) 寻找关键点,获取关键点的位置和尺度信息(DoG检测子已由KeypointDetect文件夹中的detect_features_DoG.m文件实现;请参照该算子,自行编写程序实现Harris-Laplacian检测子)。 (2) 在每一幅图像中,对每个关键点提取待拼接图像的SIFT描述子(编辑SIFTDescriptor.m文件实现该操作,运行EvaluateSIFTDescriptor.m文件检查实现结果)。 (3) 比较来自两幅不同图像的SIFT描述子,寻找匹配关键点(编辑SIFTSimpleMatcher.m文件计算两幅图像SIFT描述子间的Euclidean距离,实现该操作,运行EvaluateSIFTMatcher.m文件检查实现结果)。 (4) 基于图像中的匹配关键点,对两幅图像进行配准。请分别采用最小二乘方法(编辑ComputeAffineMatrix.m文件实现该操作,运行EvaluateAffineMatrix.m文件检查实现结果)和RANSAC方法估计两幅图像间的变换矩阵(编辑RANSACFit.m 文件中的ComputeError()函数实现该操作,运行TransformationTester.m文件检查实现结果)。 (5) 基于变换矩阵,对其中一幅图像进行变换处理,将其与另一幅图像进行拼接。 (6) 对同一场景的多幅图像进行上述操作,实现场景的全景拼接(编辑MultipleStitch.m文件中的makeTransformToReferenceFrame函数实现该操作)。可以运行StitchTester.m查看拼接结果。 (7) 请比较DoG检测子和Harris-Laplacian检测子的实验结果。图像拼接的效果对实验数据中的几个场景效果不同,请分析原因。 已经实现这些功能,并且编译运行均不报错!
### 回答1: Log Sift (对数尺度下的尺度不变特征变换) 是一种在图像处理领域广泛使用的特征提取算法,它可以在不同尺度下检测出图像中的关键点并提取出它们的特征。全景拼接是指将多张图片拼接成一张大的全景图片,这通常是通过将多张图片在重叠区域进行对齐,然后将它们合并在一起形成一个完整的图像。 在 Python 中,我们可以使用 OpenCV 库来实现 log sift全景拼接。我们首先需要读取多张待拼接的图片,并使用 log sift 算法提取它们的特征。这可以通过调用 OpenCV 提供的 SIFT() 函数实现,使用该函数的 flag 参数设置为 cv2.SIFT_CREATE() 可以指定使用 log sift 算法。通过提取特征点并计算特征向量,我们可以得到每个图像的特征描述子。 接下来,我们需要匹配两幅图像之间的特征点并进行图像对齐,这可以使用 OpenCV 提供的 matchFeatures() 函数实现。该函数可以使用不同的算法进行匹配,包括暴力匹配和 K-D tree 匹配。匹配完成后,我们可以使用 findHomography() 函数计算出两幅图像之间的变换矩阵,用于将它们进行对齐。 最后,我们将对齐后的图像合并在一起形成一张完整的全景图片。这可以使用 OpenCV 提供的 warpPerspective() 函数实现,该函数可以使用之前计算的变换矩阵将一张图片映射到另一张图片的坐标系中。通过将多张图片按照它们之间的对齐关系一张张进行拼接,我们就可以得到一张全景图片。 综上,使用 log sift 完成全景拼接 Python 可以简单地实现,通过 OpenCV 提供的函数和算法,我们可以实现特征提取、图像对齐和图像合并等基本操作,从而完成全景拼接。 ### 回答2: log sift 是一种用于图像配准和拼接的算法,可以用在 python 程序中实现全景拼接。 log sift 算法的原理是先对图像进行高斯平滑和拉普拉斯滤波,然后提取出图像中的 SIFT 特征点,通过匹配 SIFT 特征点来计算出变换矩阵,以此进行图像配准和拼接。 在 Python 中可以使用 OpenCV 库来实现 log sift 算法,具体步骤为: 1. 加载要拼接的图像,可以用 OpenCV 中的 imread 函数。 2. 对图像进行高斯平滑和拉普拉斯滤波,可以用 OpenCV 中的 PyrUp 和 Laplacian 函数。 3. 提取图像中的 SIFT 特征点,可以用 OpenCV 中的 xfeatures2d 库中的 SIFT_create 函数。 4. 匹配特征点并计算变换矩阵,可以用 OpenCV 中的 FlannBasedMatcher 函数和 findHomography 函数。 5. 将图像进行配准和拼接,可以用 OpenCV 中的 warpPerspective 函数和 addWeighted 函数。 通过以上步骤,就可以完成 log sift 算法的全景拼接功能。需要注意的是,在实际应用中需要优化算法参数和调整图像的位置和角度,以获得最佳的拼接效果。 ### 回答3: Log SIFT 是一种用于图像处理的算法,它是基于 SIFT 算法的改进版本。 它通过将图像亮度的对数变换应用于 SIFT 算法的输入来增强 SIFT 算法的性能。Log SIFT 算法在全景拼接中的应用主要是用于提高特征点的稳定性和匹配的准确性。 而 Python 作为一种流行的开源编程语言,也有着强大的图像处理和计算机视觉库。通过使用 Python 中的 OpenCV 库和 Numpy 库,我们可以很容易地实现全景拼接操作。在这里,我们可以将 Log SIFT 加载到图片处理和拼接的流程中使用。 首先,我们需要使用 OpenCV 库中的 imread 函数将要拼接的所有图像加载到内存中。然后,我们需要将这些图像转换为灰度图像,并运行 Log SIFT 算法来提取图像中的关键特征点。在这一步中,我们可以通过调整阈值、细节等参数来获得更好的效果。 提取完成关键特征点后,我们需要将这些特征点通过某种算法进行匹配。在 Python 中,我们可以使用 FlannBasedMatcher、BFMatcher 等库函数进行特征点匹配。当我们完成了匹配后,我们可以选择合适的特征点进行拼接。 在这个过程中,我们需要计算每个图像相对于拼接中心的变换矩阵,并使用 OpenCV 库中的 warpPerspective 函数将拼接好的图像贴到一起。 最后,我们可以使用拼接后的图像作为输出。 总的来说,通过将 Log SIFT 算法和 Python 图像处理库结合起来,我们可以很方便地实现全景拼接操作。 这将提高特征点的稳定性和匹配的准确性,并获得高质量的全景拼接图像。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吃花椒的恩酱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值