SIFT算法

首先感谢 会飞的吴克 在B站中的教学视频,本篇博客只用于交流学习
在这里贴出参考教学视频的网址SIFT(尺度不变特征变换)
下面所有标红的部分都是目前暂时不是很理解的部分,等待后面有时间必须将这一部分了解透彻

SIFT过程

1建立高斯差分金字塔

image
图1
首先如图1所示,这是高斯金字塔。我们可以看到其是由很多组组成,每一组包含很多层,其中每一组的层代表的是一张照片,每一组的照片的大小尺寸是相同的。

可以使用不同的方差计算高斯核

用不同的方差的高斯核对原图片进行卷积就能得到第一组的所有层,也就是图1中最下面的Octave1组,也就是第一组的每一层都是由不同尺度方差的高斯核计算得到的。
第二层的初始第一张图片是由第一组的图片进行降采样得到的。降采样就是隔点取点,也就是第一个像素点留下,第二个去掉,第三个留下,第四个去掉,依次这么做,最后第二层的图片的长宽都是第一层的1/2,之后再用不同尺度的高斯核进行卷积。之后的每一组都是这么顺序操作。这样得到的图1就是高斯金字塔,这个金字塔也可以叫做尺度空间。
image

图2
在得到的图1的高斯金字塔上的同一组的每一层相减,就是单纯对应位置的四则减法运算,得到的就是高斯差分金字塔,如图2所示。
对于高斯金字塔中应该由多少组,每组应该有多少层,算法的发明者在论文中的建议值为
image

图3
如图3所示,o表示多少组,M N表示原图片的长和宽,S是层数

n表示希望提取几个尺度的极值点(具体意义和作用暂不理解知道)

S=n+3的由来是:
在图2中S=5,所以只能提取到两张图片中的特征,找特征点实际上是在高斯差分金字塔中进行寻找,此时高斯差分金字塔中是4张图片,在三个方向中找极值点。三个方向是图片的两个方向x y,以及尺度方向,也就是图2中的竖着的方向,因为差分金字塔的最上面一层没有向上的竖着的方向,所以无法求导,同理最下面一层也没有向下的方向,所以也无法求导,因为找极值点就是求导,无法求导就是不能寻找极值点,所以需要再减2.这样得到的n+3
为什么建立高斯金字塔,高斯差分金字塔?
SIFT应该有远近不变性,也就是不管是离着远拍还是离着仅拍,同一个物品都应该是能识别出来的。金字塔其实就是模拟的近大远小。高斯核卷积模拟的是近处清晰,远处模糊。在数学上有个证明,高斯核是唯一可以模拟近处清晰,远处模糊的线性核。

每次卷积的时候的尺度怎么定?高斯核的标准差怎么定?
image

图4

image

图5
由图4和图5可知,第一组的子一层是标准差0,第二层就是k标准差,依次类推。第二组的第一层是k的指数与第一组的倒数第三层一样,这个时候系数为2,也就是为了凑一个2标准差.也就是2标注差的图片隔点取点得到第二组第一层的图片。
标准差0,照相机照相的时候也不是完全清晰的,自带一个模糊尺度,认为这个尺度是0.5,希望第一次高斯核卷积之后能达到1.6,这样就出来了标准差的公式。也就是用0.5尺度卷积,再用1.52尺度卷积得到的结果与直接用1.6尺度卷积得到的效果一样。

2关键点位置的确定

关键点是稳定的点,变化少,有稳定性质的点,包含很多信息的点,这些点通常是在极值位置,可以是极大值,也可以是极小值。

2.1 阈值化

image

图6
首先进行阈值化,每个像素点的绝对值满足上面的要求,如果不满足,我们认为是极值点的可能性非常小。

2.2在高斯差分金字塔中找极值

image

图7
图7找的极值点是离散的极值点,因为我们的空间是一个像素一个像素的,是离散的。
在图7中如果是在27个像素点中找到一个最大的,那么这就是一个离散的极值点,并不是一个真正的极值点。
image

图8
按照图7极值点的求取办法是找到一个真正极值点的附近的一个点,我们需要找到一个更准确的亚像素位置的极值点。

2.3 调整极值点的位置

image

图9
上面的X0就是在图7中检测到的极值点。表示成矢量的形式是为了找到极值点的精确位置,泰勒展开的f(x)是在X0处原函数的近似,在这里就认为是原函数。
image

图10
image

图11
image

图12
image

图13
如上图所示,求导使用差商来代替,例如0处对x的求导,其中f1=3,f3=1,至于h取什么值暂时无所谓。
因为X(上面图片中的X上有一个小弧线,在博客园中暂时打不出,所以之后都使用加粗倾斜的X表示)是X-X0,所以得到的解X是真正的极值点相对于检测到的极值点的位移量。最后求出的f(X)是真正极值点对应的值。

算法实现的时候是迭代的过程,会涉及一些细节问题,这里暂时不讨论,我也不太理解。

X的三个分量都小于0.5的时候,就认为已经比较接近真正的极值点,就不需要再进行迭代了。如果超出迭代次数限制,三个分量都没有小于0.5,认为它可能收敛不了了,就直接舍去这个点。如果最后收敛了,但是解超出了一定范围(因为这个泰克展开只能在找到的极值点的附近拟合原函数),也进行舍去操作。

2.4 舍去低对比度的点

image

图14

如果最后的f(x)的绝对值满足图14的条件,则直接舍去。

2.5 边缘效应的去除

需要用到海森矩阵

image

图15

海森矩阵可以描述曲线的曲率,在两个方向上的曲率差不多的时候才可以,否则很可能是一个边缘。海森矩阵的特征值和曲率成正比,这里使用Tr(H)和Set(H)来代替进行操作

其中α β就是两个特征值。

若Det(H)《0,这时特征值异号了,此时有边缘效应了。

图15最下面的Tr(H)少写一个平方

需要注意的是例如在第二层求出的极值点,映射到第一层其x 和 y分别乘2

3 为关键点赋予方向

image

图16
统计以特征点为圆心,以该特征点所在的高斯图像的尺度1.5倍为半径的圆内的所有的像素的梯度的方向及其梯度赋值,并作1.5标准差的高斯滤波。
关键点的参数是 (x, y, 标准差),可以都不是整数,标准差也可以不落在某一层上
因为关键点的标准差可能在亚像素上,也就是并不在当前组的某一层上,所以统计的高斯图像是最接近这个标准差的某一层(在高斯金字塔中找,而不是在高斯差分金字塔中)。
统计其梯度方向和大小,方向有360°,但是最后统计成8个方向,落在那个区间就是在哪个方向。
做高斯滤波是为了越接近特征点的权重就越大
在实际的代码中分成了36个方向,每十度是一个方向,从其中选择主方向,主方向就是最后的权重最大的方向,有时也会存在一个辅方向(权重大于等于主方向的80%)

image

图17
此时就可以得到如图17所示的图像
提取出了一张图片中的特征点,位置代表了特征点的位置,圆圈的大小代表了尺度的大小,线代表了主方向。有两个方向就是存在一个辅方向,辅方向的处理方法是当作两个特征点进行处理。

4构建关键点描述符

关键点的参数(x,y,标准差,主方向)
在一张图片中找到关键点不算完,要在两张图片中分别找到它们的关键点,把对应的关键点进行匹配,这个时候就需要通过描述符进行匹配。
在SIT算法中描述符是一个128维的向量,也就是128个数。

使用k近邻(KNN)算法进行匹配

其实就是找最近的两个向量。
image

图18
在关键点周围的一个区域,如图18所示。一共分成了16个子区域,每一个最小格是一个像素。在每个子区域内统计八个方向(东、南、西、北、东北、东南、西北、西南)上的梯度(也是经过高斯加权)的长度,所以共有8*16=128个数,这128个数按照顺序写出来就是描述符
image

图19
图18的区域并不是直接拿关键点周围的高斯图像区域,而是首先把关键点周围的某个区域旋转到主方向上,为了SIFT的描述符具有旋转不变性(我的理解是可能比较的两张图片中其中一张存在旋转,这样我们将其区域都相对于主方向进行旋转,那么这个关键点周围区域的两张图片就是同一个方向,也就是旋转到了同一个方向),才可以得到稳定的描述符。
image

图20

图20是为了确定关键点周围区域的大小,插值的方式。

m一般取3,d有几个小区域(横着算竖着算)d=4

5程序运行结果示例

image

图21
如图21所示,可以将几个对应位置对应起来。

6 代码

点击查看代码
# coding: utf-8
import warnings
warnings.filterwarnings("ignore")  #忽略警告
import numpy as np
import matplotlib.pyplot as plt
from sklearn.neighbors import KNeighborsClassifier
from PIL import Image




def convolve(filter,mat,padding,strides):

    result = None
    filter_size = filter.shape
    mat_size = mat.shape
    if len(filter_size) == 2:
        if len(mat_size) == 3:
            channel = []
            for i in range(mat_size[-1]):
                pad_mat = np.pad(mat[:,:,i], ((padding[0], padding[1]), (padding[2], padding[3])), 'constant')
                temp = []
                for j in range(0,mat_size[0],strides[1]):
                    temp.append([])
                    for k in range(0,mat_size[1],strides[0]):
                        val = (filter*pad_mat[j:j+filter_size[0],k:k+filter_size[1]]).sum()
                        temp[-1].append(val)
                channel.append(np.array(temp))

            channel = tuple(channel)
            result = np.dstack(channel)
        elif len(mat_size) == 2:
            channel = []
            pad_mat = np.pad(mat, ((padding[0], padding[1]), (padding[2], padding[3])), 'constant')
            for j in range(0, mat_size[0], strides[1]):
                channel.append([])
                for k in range(0, mat_size[1], strides[0]):
                    val = (filter * pad_mat[j:j + filter_size[0],k:k + filter_size[1]]).sum()
                    channel[-1].append(val)


            result = np.array(channel)


    return result



def downsample(img,step = 2):
    return img[::step,::step]

def GuassianKernel(sigma , dim):
    '''
    :param sigma: Standard deviation
    :param dim: dimension(must be positive and also an odd number)
    :return: return the required Gaussian kernel.
    '''
    temp = [t - (dim//2) for t in range(dim)]
    assistant = []
    for i in range(dim):
        assistant.append(temp)
    # print(type(assistant))
    # print(assistant)
    assistant = np.array(assistant)
    # print(type(assistant))
    # print(assistant)
    temp = 2*sigma*sigma
    result = (1.0/(temp*np.pi))*np.exp(-(assistant**2+(assistant.T)**2)/temp)
    return result

# 返回高斯金字塔和高斯差分金字塔
def getDoG(img,n,sigma0,S = None,O = None):
    '''
    :param img: the original img.
    :param sigma0: sigma of the first stack of the first octave. default 1.52 for complicate reasons.
    :param n: how many stacks of feature that you wanna extract.
    :param S: how many stacks does every octave have. S must bigger than 3.
    :param k: the ratio of two adjacent stacks' scale.
    :param O: how many octaves do we have.
    :return: the DoG Pyramid
    '''
    # 确定高斯金字塔的组数O和每层的层数S
    if S == None:
        S = n + 3
    if O == None:
        O = int(np.log2(min(img.shape[0], img.shape[1]))) - 3

    k = 2 ** (1.0 / n)
    # 两维列表,第一维表示第几组,第二维表示第几层  直接将每一组的每一层的方差计算出来了
    sigma = [[(k**s)*sigma0*(1<<o) for s in range(S)] for o in range(O)]
    samplePyramid = [downsample(img, 1 << o) for o in range(O)]
    # 高斯金字塔的初始化,是列表的形式
    GuassianPyramid = []
    for i in range(O):  # 遍历每一组  每组的层数是相同的
        GuassianPyramid.append([])  # 高斯金字塔添加当前组的初始化列表
        for j in range(S):  # 遍历每一层
            dim = int(6*sigma[i][j] + 1)
            if dim % 2 == 0:
                dim += 1
            # 这里-1表示的是上面刚加入的当前组的初始化列表
            GuassianPyramid[-1].append(convolve(GuassianKernel(sigma[i][j], dim),samplePyramid[i],[dim//2,dim//2,dim//2,dim//2],[1,1]))
    DoG = [[GuassianPyramid[o][s+1] - GuassianPyramid[o][s] for s in range(S - 1)] for o in range(O)]


    return DoG,GuassianPyramid

def adjustLocalExtrema(DoG,o,s,x,y,contrastThreshold,edgeThreshold,sigma,n,SIFT_FIXPT_SCALE):
    SIFT_MAX_INTERP_STEPS = 5
    SIFT_IMG_BORDER = 5

    point = []

    img_scale = 1.0 / (255 * SIFT_FIXPT_SCALE)
    deriv_scale = img_scale * 0.5
    second_deriv_scale = img_scale
    cross_deriv_scale = img_scale * 0.25

    img = DoG[o][s]
    i = 0
    while i < SIFT_MAX_INTERP_STEPS:
        if s < 1 or s > n or y < SIFT_IMG_BORDER or y >= img.shape[1] - SIFT_IMG_BORDER or x < SIFT_IMG_BORDER or x >= img.shape[0] - SIFT_IMG_BORDER:
            return None,None,None,None

        img = DoG[o][s]
        prev = DoG[o][s - 1]
        next = DoG[o][s + 1]

        dD = [ (img[x,y + 1] - img[x, y - 1]) * deriv_scale,
               (img[x + 1, y] - img[x - 1, y]) * deriv_scale,
               (next[x, y] - prev[x, y]) * deriv_scale ]

        v2 = img[x, y] * 2
        dxx = (img[x, y + 1] + img[x, y - 1] - v2) * second_deriv_scale
        dyy = (img[x + 1, y] + img[x - 1, y] - v2) * second_deriv_scale
        dss = (next[x, y] + prev[x, y] - v2) * second_deriv_scale
        dxy = (img[x + 1, y + 1] - img[x + 1, y - 1] - img[x - 1, y + 1] + img[x - 1, y - 1]) * cross_deriv_scale
        dxs = (next[x, y + 1] - next[x, y - 1] - prev[x, y + 1] + prev[x, y - 1]) * cross_deriv_scale
        dys = (next[x + 1, y] - next[x - 1, y] - prev[x + 1, y] + prev[x - 1, y]) * cross_deriv_scale

        H=[ [dxx, dxy, dxs],
            [dxy, dyy, dys],
            [dxs, dys, dss]]

        X = np.matmul(np.linalg.pinv(np.array(H)),np.array(dD))

        xi = -X[2]
        xr = -X[1]
        xc = -X[0]

        if np.abs(xi) < 0.5 and np.abs(xr) < 0.5 and np.abs(xc) < 0.5:
            break

        y += int(np.round(xc))
        x += int(np.round(xr))
        s += int(np.round(xi))

        i+=1

    if i >= SIFT_MAX_INTERP_STEPS:
        return None,x,y,s
    if s < 1 or s > n or y < SIFT_IMG_BORDER or y >= img.shape[1] - SIFT_IMG_BORDER or x < SIFT_IMG_BORDER or x >= \
            img.shape[0] - SIFT_IMG_BORDER:
        return None, None, None, None


    t = (np.array(dD)).dot(np.array([xc, xr, xi]))

    contr = img[x,y] * img_scale + t * 0.5
    if np.abs( contr) * n < contrastThreshold:
        return None,x,y,s


    # 利用Hessian矩阵的迹和行列式计算主曲率的比值
    tr = dxx + dyy
    det = dxx * dyy - dxy * dxy
    if det <= 0 or tr * tr * edgeThreshold >= (edgeThreshold + 1) * (edgeThreshold + 1) * det:
        return None,x,y,s

    point.append((x + xr) * (1 << o))
    point.append((y + xc) * (1 << o))
    point.append(o + (s << 8) + (int(np.round((xi + 0.5)) * 255) << 16))
    point.append(sigma * np.power(2.0, (s + xi) / n)*(1 << o) * 2)

    return point,x,y,s

def GetMainDirection(img,r,c,radius,sigma,BinNum):
    expf_scale = -1.0 / (2.0 * sigma * sigma)

    X = []
    Y = []
    W = []
    temphist = []

    for i in range(BinNum):
        temphist.append(0.0)

    # 图像梯度直方图统计的像素范围
    k = 0
    for i in range(-radius,radius+1):
        y = r + i
        if y <= 0 or y >= img.shape[0] - 1:
            continue
        for j in range(-radius,radius+1):
            x = c + j
            if x <= 0 or x >= img.shape[1] - 1:
                continue

            dx = (img[y, x + 1] - img[y, x - 1])
            dy = (img[y - 1, x] - img[y + 1, x])

            X.append(dx)
            Y.append(dy)
            W.append((i * i + j * j) * expf_scale)
            k += 1


    length = k

    W = np.exp(np.array(W))
    Y = np.array(Y)
    X = np.array(X)
    Ori = np.arctan2(Y,X)*180/np.pi
    Mag = (X**2+Y**2)**0.5

    # 计算直方图的每个bin
    for k in range(length):
        bin = int(np.round((BinNum / 360.0) * Ori[k]))
        if bin >= BinNum:
            bin -= BinNum
        if bin < 0:
            bin += BinNum
        temphist[bin] += W[k] * Mag[k]

    # smooth the histogram
    # 高斯平滑
    temp = [temphist[BinNum - 1], temphist[BinNum - 2], temphist[0], temphist[1]]
    temphist.insert(0, temp[0])
    temphist.insert(0, temp[1])
    temphist.insert(len(temphist), temp[2])
    temphist.insert(len(temphist), temp[3])      # padding

    hist = []
    for i in range(BinNum):
        hist.append((temphist[i] + temphist[i+4]) * (1.0 / 16.0) + (temphist[i+1] + temphist[i+3]) * (4.0 / 16.0) + temphist[i+2] * (6.0 / 16.0))

    # 得到主方向
    maxval = max(hist)

    return maxval,hist

def LocateKeyPoint(DoG,sigma,GuassianPyramid,n,BinNum = 36,contrastThreshold = 0.04,edgeThreshold = 10.0):
    SIFT_ORI_SIG_FCTR = 1.52
    SIFT_ORI_RADIUS = 3 * SIFT_ORI_SIG_FCTR
    SIFT_ORI_PEAK_RATIO = 0.8

    SIFT_INT_DESCR_FCTR = 512.0
    # SIFT_FIXPT_SCALE = 48
    SIFT_FIXPT_SCALE = 1

    KeyPoints = []
    O = len(DoG)
    S = len(DoG[0])
    for o in range(O):
        for s in range(1,S-1):
            threshold = 0.5*contrastThreshold/(n*255*SIFT_FIXPT_SCALE)
            img_prev = DoG[o][s-1]
            img = DoG[o][s]
            img_next = DoG[o][s+1]
            for i in range(img.shape[0]):
                for j in range(img.shape[1]):
                    val = img[i,j]
                    eight_neiborhood_prev = img_prev[max(0, i - 1):min(i + 2, img_prev.shape[0]), max(0, j - 1):min(j + 2, img_prev.shape[1])]
                    eight_neiborhood = img[max(0, i - 1):min(i + 2, img.shape[0]), max(0, j - 1):min(j + 2, img.shape[1])]
                    eight_neiborhood_next = img_next[max(0, i - 1):min(i + 2, img_next.shape[0]), max(0, j - 1):min(j + 2, img_next.shape[1])]
                    if np.abs(val) > threshold and \
                        ((val > 0 and (val >= eight_neiborhood_prev).all() and (val >= eight_neiborhood).all() and (val >= eight_neiborhood_next).all())
                         or (val < 0 and (val <= eight_neiborhood_prev).all() and (val <= eight_neiborhood).all() and (val <= eight_neiborhood_next).all())):
                        #精调关键点位置
                        point,x,y,layer = adjustLocalExtrema(DoG,o,s,i,j,contrastThreshold,edgeThreshold,sigma,n,SIFT_FIXPT_SCALE)
                        if point == None:
                            continue

                        scl_octv = point[-1]*0.5/(1 << o)
                        #确定主方向
                        omax,hist = GetMainDirection(GuassianPyramid[o][layer],x,y,int(np.round(SIFT_ORI_RADIUS * scl_octv)),SIFT_ORI_SIG_FCTR * scl_octv,BinNum)
                        mag_thr = omax * SIFT_ORI_PEAK_RATIO
                        for k in range(BinNum):
                            if k > 0:
                                l = k - 1
                            else:
                                l = BinNum - 1
                            if k < BinNum - 1:
                                r2 = k + 1
                            else:
                                r2 = 0
                            if hist[k] > hist[l] and hist[k] > hist[r2] and hist[k] >= mag_thr:
                                bin = k + 0.5 * (hist[l]-hist[r2]) /(hist[l] - 2 * hist[k] + hist[r2])
                                if bin < 0:
                                    bin = BinNum + bin
                                else:
                                    if bin >= BinNum:
                                        bin = bin - BinNum
                                temp = point[:]
                                temp.append((360.0/BinNum) * bin)
                                KeyPoints.append(temp)


    return KeyPoints


def calcSIFTDescriptor(img,ptf,ori,scl,d,n,SIFT_DESCR_SCL_FCTR = 3.0,SIFT_DESCR_MAG_THR = 0.2,SIFT_INT_DESCR_FCTR = 512.0,FLT_EPSILON = 1.19209290E-07):
    dst = []
    pt = [int(np.round(ptf[0])), int(np.round(ptf[1]))] # 坐标点取整
    cos_t = np.cos(ori * (np.pi / 180)) # 余弦值
    sin_t = np.sin(ori * (np.pi / 180)) # 正弦值
    bins_per_rad = n / 360.0
    exp_scale = -1.0 / (d * d * 0.5)
    hist_width = SIFT_DESCR_SCL_FCTR * scl
    radius = int(np.round(hist_width * 1.4142135623730951 * (d + 1) * 0.5))
    cos_t /= hist_width
    sin_t /= hist_width

    rows = img.shape[0]
    cols = img.shape[1]


    hist = [0.0]*((d+2)*(d+2)*(n+2))
    X = []
    Y = []
    RBin = []
    CBin = []
    W = []

    k = 0
    for i in range(-radius,radius+1):
        for j in range(-radius,radius+1):

            c_rot = j * cos_t - i * sin_t
            r_rot = j * sin_t + i * cos_t
            rbin = r_rot + d // 2 - 0.5
            cbin = c_rot + d // 2 - 0.5
            r = pt[1] + i
            c = pt[0] + j

            if rbin > -1 and rbin < d and cbin > -1 and cbin < d and r > 0 and r < rows - 1 and c > 0 and c < cols - 1:
                dx = (img[r, c+1] - img[r, c-1])
                dy = (img[r-1, c] - img[r+1, c])
                X.append(dx)
                Y.append(dy)
                RBin.append(rbin)
                CBin.append(cbin)
                W.append((c_rot * c_rot + r_rot * r_rot) * exp_scale)
                k+=1

    length = k
    Y = np.array(Y)
    X = np.array(X)
    Ori = np.arctan2(Y,X)*180/np.pi
    Mag = (X ** 2 + Y ** 2) ** 0.5
    W = np.exp(np.array(W))

    for k in range(length):
        rbin = RBin[k]
        cbin = CBin[k]
        obin = (Ori[k] - ori) * bins_per_rad
        mag = Mag[k] * W[k]

        r0 = int(rbin)
        c0 = int(cbin)
        o0 = int(obin)
        rbin -= r0
        cbin -= c0
        obin -= o0

        if o0 < 0:
            o0 += n
        if o0 >= n:
            o0 -= n

        # histogram update using tri-linear interpolation
        v_r1 = mag * rbin
        v_r0 = mag - v_r1

        v_rc11 = v_r1 * cbin
        v_rc10 = v_r1 - v_rc11

        v_rc01 = v_r0 * cbin
        v_rc00 = v_r0 - v_rc01

        v_rco111 = v_rc11 * obin
        v_rco110 = v_rc11 - v_rco111

        v_rco101 = v_rc10 * obin
        v_rco100 = v_rc10 - v_rco101

        v_rco011 = v_rc01 * obin
        v_rco010 = v_rc01 - v_rco011

        v_rco001 = v_rc00 * obin
        v_rco000 = v_rc00 - v_rco001

        idx = ((r0 + 1) * (d + 2) + c0 + 1) * (n + 2) + o0
        hist[idx] +=  v_rco000
        hist[idx+1] += v_rco001
        hist[idx + (n+2)] += v_rco010
        hist[idx + (n+3)] += v_rco011
        hist[idx+(d+2) * (n+2)] += v_rco100
        hist[idx+(d+2) * (n+2)+1] += v_rco101
        hist[idx+(d+3) * (n+2)] += v_rco110
        hist[idx+(d+3) * (n+2)+1] += v_rco111

    # finalize histogram, since the orientation histograms are circular
    for i in range(d):
        for j in range(d):
            idx = ((i+1) * (d+2) + (j+1)) * (n+2)
            hist[idx] += hist[idx+n]
            hist[idx+1] += hist[idx+n+1]
            for k in range(n):
                dst.append(hist[idx+k])

    # copy histogram to the descriptor,
    # apply hysteresis thresholding
    # and scale the result, so that it can be easily converted
    # to byte array
    nrm2 = 0
    length = d * d * n
    for k in range(length):
        nrm2 += dst[k] * dst[k]
    thr = np.sqrt(nrm2) * SIFT_DESCR_MAG_THR

    nrm2 = 0
    for i in range(length):
        val = min(dst[i], thr)
        dst[i] = val
        nrm2 += val * val
    nrm2 = SIFT_INT_DESCR_FCTR / max(np.sqrt(nrm2), FLT_EPSILON)
    for k in range(length):
        dst[k] = min(max(dst[k] * nrm2,0),255)

    return dst


def calcDescriptors(gpyr,keypoints,SIFT_DESCR_WIDTH = 4,SIFT_DESCR_HIST_BINS = 8):
    # SIFT_DESCR_WIDTH = 4,描述直方图的宽度
    # SIFT_DESCR_HIST_BINS = 8
    d = SIFT_DESCR_WIDTH
    n = SIFT_DESCR_HIST_BINS
    descriptors = []

    for i in range(len(keypoints)):
        kpt = keypoints[i]
        o = kpt[2] & 255
        s = (kpt[2] >> 8) & 255 # 该特征点所在的组序号和层序号
        scale = 1.0 / (1 << o)  # 缩放倍数
        size = kpt[3] * scale # 该特征点所在组的图像尺寸
        ptf = [kpt[1] * scale, kpt[0] * scale] # 该特征点在金字塔组中的坐标
        img = gpyr[o][s] # 该点所在的金字塔图像

        descriptors.append(calcSIFTDescriptor(img, ptf, kpt[-1], size * 0.5, d, n))
    return descriptors

def SIFT(img,showDoGimgs = False):  # he
    SIFT_SIGMA = 1.6  # 想要得到的尺度
    SIFT_INIT_SIGMA = 0.5 # 假设的摄像头的尺度
    sigma0 = np.sqrt(SIFT_SIGMA**2-SIFT_INIT_SIGMA**2)

    n = 3
    # 得到高斯差分金字塔
    DoG,GuassianPyramid = getDoG(img, n, sigma0)  # he

    #展示高斯差分金字塔
    if showDoGimgs:
        for i in DoG:
            print(i)
            for j in i:
                plt.imshow(j.astype(np.uint8), cmap='gray')
                plt.axis('off')
                # plt.show()

    # 关键点定位
    KeyPoints = LocateKeyPoint(DoG, SIFT_SIGMA, GuassianPyramid, n)  # he
    # 计算描述符
    discriptors = calcDescriptors(GuassianPyramid,KeyPoints)  # he

    return KeyPoints,discriptors


def Lines(img,info,color = (255,0,0),err = 700):

    if len(img.shape) == 2:
        result = np.dstack((img,img,img))
    else:
        result = img
    k = 0
    for i in range(result.shape[0]):
        for j in range(result.shape[1]):
            temp = (info[:,1]-info[:,0])
            A = (j - info[:,0])*(info[:,3]-info[:,2])
            B = (i - info[:,2])*(info[:,1]-info[:,0])
            temp[temp == 0] = 1e-9
            t = (j-info[:,0])/temp
            e = np.abs(A-B)
            temp = e < err
            if (temp*(t >= 0)*(t <= 1)).any():
                result[i,j] = color
                k+=1
    print(k)

    return result

def drawLines(X1,X2,Y1,Y2,dis,img,num = 10):

    info = list(np.dstack((X1,X2,Y1,Y2,dis))[0])
    info = sorted(info,key=lambda x:x[-1])
    info = np.array(info)
    info = info[:min(num,info.shape[0]),:]
    img = Lines(img,info)
    #plt.imsave('./sift/3.jpg', img)

    if len(img.shape) == 2:
        plt.imshow(img.astype(np.uint8),cmap='gray')
    else:
        plt.imshow(img.astype(np.uint8))
    plt.axis('off')
    #plt.plot([info[:,0], info[:,1]], [info[:,2], info[:,3]], 'c')
    # fig = plt.gcf()
    # fig.set_size_inches(int(img.shape[0]/100.0),int(img.shape[1]/100.0))
    #plt.savefig('./sift/2.jpg')
    plt.show()


if __name__ == '__main__':
    print("********start**********")
    print("read the first image")
    origimg = plt.imread('./SIFTimg/1.jpeg')
    if len(origimg.shape) ==  3:
        img = origimg.mean(axis=-1)
    else:
        img = origimg
    # 计算图片的关键点和描述符
    keyPoints,discriptors = SIFT(img)

    print("read the second image")
    origimg2 = plt.imread('./SIFTimg/2.jpeg')
    if len(origimg.shape) == 3:
        img2 = origimg2.mean(axis=-1)
    else:
        img2 = origimg2
    ScaleRatio = img.shape[0]*1.0/img2.shape[0]  #第一张图片和第二张图片的比例大小
    print(img.shape,img2.shape,ScaleRatio)

    img2 = np.array(Image.fromarray(img2).resize((int(round(ScaleRatio * img2.shape[1])),img.shape[0]), Image.BICUBIC))
    print(img2.shape)


    keyPoints2, discriptors2 = SIFT(img2,True)

    # 使用knn算法进行匹配,然后将匹配结果放入画线函数
    print("start knn")
    knn = KNeighborsClassifier(n_neighbors=1)
    print("type"+str(type(knn)))
    print("content"+str(knn))
    print(knn)

    knn.fit(discriptors,[0]*len(discriptors))
    match = knn.kneighbors(discriptors2,n_neighbors=1,return_distance=True)
    # print("match")
    # print(match)
    keyPoints = np.array(keyPoints)[:,:2]
    keyPoints2 = np.array(keyPoints2)[:,:2]

    keyPoints2[:, 1] = img.shape[1] + keyPoints2[:, 1]

    origimg2 = np.array(Image.fromarray(origimg2).resize((img2.shape[1],img2.shape[0]), Image.BICUBIC))
    result = np.hstack((origimg,origimg2))


    keyPoints = keyPoints[match[1][:,0]]

    X1 = keyPoints[:, 1]
    X2 = keyPoints2[:, 1]
    Y1 = keyPoints[:, 0]
    Y2 = keyPoints2[:, 0]
    drawLines(X1,X2,Y1,Y2,match[0][:,0],result,6)



最后再次感谢 会飞的吴克,给出完整可运行代码
代码链接

posted @ 2022-01-08 22:27  白菜茄子  阅读(254)  评论(0编辑  收藏  举报