第二章 局部图像描述子

文章目录

  • 第二章 局部图像描述子
    • 2.1Harris角点检测器
    • 2.2SIFT(尺度不变特征变换)
      • 2.2.1兴趣点
      • 2.2.2描述子
      • 2.2.3检测兴趣点

第二章 局部图像描述子

本章旨在寻找图像间的对应点和对应区域。本章将介绍用于图像匹配的两种局部描述子算法。本书的很多内容中都会用到这些局部特征,它们在很多应用中都有重要作用,比如创建全景图、增强现实技术以及计算图像的三维重建。

2.1Harris角点检测器

Harris 角点检测算法(也称 Harris & Stephens 角点检测器)是一个极为简单的角点检测算法。该算法的主要思想是,如果像素周围显示存在多于一个方向的边,我们认为该点为兴趣点。该点就称为角点。我们把图像域中点 x 上的对称半正定矩阵 M I = M I ( x ) M_I=M_I(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}[I_xI_y]=\begin{bmatrix}I_x^2&I_xI_y\\I_xI_y&I_y^2\end{bmatrix} MI=IIT=[IxIy][IxIy]=[Ix2IxIyIxIyIy2]

选择权重矩阵 W(通常为高斯滤波器gaussian_filter),我们可以得到卷积: M ‾ i = W ∗ M i \overline{\boldsymbol{M}}_i=\boldsymbol{W}*\boldsymbol{M}_i Mi=WMi

使用商数作为指示函数 det ⁡ ( M ‾ i ) = M x x M y y − M x y 2 \det(\overline{M}_i)=M_{xx}M_{yy}-M_{xy}^2 det(Mi)=MxxMyyMxy2 t r a c e ( M ‾ i ) = M x x + M y y \mathrm{trace}(\overline{M}_i)=M_{xx}+M_{yy} trace(Mi)=Mxx+Myy r e s p o n c e = det ⁡ ( M ‾ i ) trace ⁡ ( M ‾ i ) 2 \mathrm{responce=}\frac{\det(\overline{M}_i)}{\operatorname{trace}\left(\overline{M}_i\right)^2} responce=trace(Mi)2det(Mi)

from scipy.ndimage import gaussian_filter
import numpy as np
from numpy import *
from PIL import Image
from matplotlib import pyplot as plt

img = Image.open('am.png')
plt.figure()
plt.axis('off')
plt.imshow(img)
plt.show()

第二章 局部图像描述子_第1张图片

原图为上图所示,是一个卡通人物

def compute_harris_response(im, sigma=3):
    """ 在一幅灰度图像中,对每个像素计算 Harris 角点检测器响应函数 """
    # 计算导数
    im=np.array(im.convert('L'))
    Ix = gaussian_filter(im, (sigma, sigma), (0, 1))
    Iy = gaussian_filter(im, (sigma, sigma), (1, 0))
    # 计算 Harris 矩阵的分量
    Mxx = gaussian_filter(Ix*Ix, sigma)
    Mxy = gaussian_filter(Ix*Iy, sigma)
    Myy = gaussian_filter(Iy*Iy, sigma)
    # 计算特征值和迹
    det_M = Mxx*Myy - Mxy**2
    trace_M = Mxx + Myy
    return np.divide(det_M, trace_M, out=np.zeros_like(det_M, dtype=np.float64), where=(trace_M != 0))
def plot_harris_points(image, filtered_coords):
    """ 绘制图像中检测到的角点 """
    plt.figure()
    plt.imshow(image)
    # 将坐标以*标出
    plt.plot([p[1] for p in filtered_coords],[p[0] for p in filtered_coords], '*')
    plt.axis('off')
    plt.show()
# print(img)
harrisim = compute_harris_response(img)
# harrisim_t=255*(harrisim>0.1)
# plt.figure(figsize=(8, 8))
# plt.imshow(harrisim_t)
n,m=len(harrisim),len(harrisim[0])
coords=[]
for i in range(n):
    for j in range(m):
        if harrisim[i][j]:
            coords.append([i,j])
plot_harris_points(img, coords)

第二章 局部图像描述子_第2张图片

从上图可以看到原图的卡通人物的基本轮廓。但是这里的点太多了,我们需要的是重要的交点,所以需要做以下筛选。

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 = np.array(harrisim_t.nonzero()).T
    # 以及它们的 Harris 响应值
    candidate_values = [harrisim[c[0], c[1]] for c in coords]
    # 对候选点按照 Harris 响应值进行排序
    index = argsort(candidate_values)
    # 将可行点的位置保存到数组中
    allowed_locations = np.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



filtered_coords = get_harris_points(harrisim, 3, 0.05)
# plt.figure(figsize=(8, 8))
# plt.imshow(im)
# plt.plot([p[1] for p in filtered_coords],[p[0] for p in filtered_coords], '*')
# plt.show()
plot_harris_points(img, filtered_coords)

第二章 局部图像描述子_第3张图片

上图和上上图对比,角点数量大大减少,但是效果不减。
方法是:选取像素值高于阈值的所有图像点;再加上额外的限制,即角点之间的间隔必须大于设定的最小距离。这种方法会产生很好的角点检测结果。为了实现该算法,我们获取所有的候选像素点,以角点响应值递减的顺序排序,然后将距离已标记为角点位置过近的区域从候选像素点中删除。
其中,我们可以通过设置阈值threshold角点之间的间隔min_dist来控制角点的数量

接下来,我们将归一化的互相关矩阵应用于 Harris 角点周围图像块,来寻找匹配对应点,

def yasuo(name1,n,name2):
    img=cv2.imread(name1)
    times=img.shape[0]/n
    m=int(img.shape[1]/times)
    print([n,m])
    img_t = np.zeros([n,m,3])
    for i in range(n):
        for j in range(m):
            for k in range(3):
                img_t[i][j][k]=img[int(i*times)][int(j*times)][k]
    img_t=img_t.astype(np.uint8)
    cv2.imwrite(name2,img_t)
    # plt_show(img,img_t)
    print(img.shape)
    print(img_t.shape)
    return img_t

import cv2
for i in range(1,3):
    name='r'+str(i)+'.jpg'
    name2='dormitory'+str(i)+'.jpg'
    img=cv2.imread(name)
    img=img[-2600:,-2300:,:]
    cv2.imwrite(name2,img)
    yasuo(name2,500,name2)
[500, 442]
(2600, 2300, 3)
(500, 442, 3)
[500, 442]
(2600, 2300, 3)
(500, 442, 3)
plt.figure(figsize=(9,5))
for i in range(1,3):
    name2='dormitory'+str(i)+'.jpg'
    img=cv2.imread(name2)
    plt.subplot(120+i)
    plt.imshow(img)
    plt.axis('off')
plt.show()

第二章 局部图像描述子_第4张图片

例如上图是两张宿舍部分图,我们接下来对其进行匹配。

# -*- coding: utf-8 -*-
from pylab import *
from PIL import Image

import harris


im1 = array(Image.open("dormitory1.jpg").convert("L"))
im2 = array(Image.open("dormitory2.jpg").convert("L"))


wid = 13
harrisim = harris.compute_harris_response(im1, wid)
filtered_coords1 = harris.get_harris_points(harrisim, wid+1)
d1 = harris.get_descriptors(im1, filtered_coords1, wid)

harrisim = harris.compute_harris_response(im2, wid)
filtered_coords2 = harris.get_harris_points(harrisim, wid+1)
d2 = harris.get_descriptors(im2, filtered_coords2, wid)


matches = harris.match_twosided(d1, d2)

plt.figure()
harris.plot_matches(im1, im2, filtered_coords1, filtered_coords2, matches)
plt.show()
plt.savefig('test.jpg')

第二章 局部图像描述子_第5张图片

该算法的结果存在一些不正确匹配。这是因为图像像素块的互相关矩阵具有较弱的描述性。实际运用中,我们通常使用更稳健的方法来处理这些对应匹配。这些描述符还有一个问题,它们不具有尺度不变性和旋转不变性,而算法中像素块的大小也会影响对应匹配的结果。

2.2SIFT(尺度不变特征变换)

SIFT(Scale-Invariant Feature Transform,尺度不变特征变换)是过去最成功的图像局部描述子之一。SIFT 特征包括兴趣点检测器和描述子。SIFT 描述子具有非常强的稳健性,这在很大程度上也是 SIFT 特征能够成功和流行的主要原因。SIFT 特征对于尺度、旋转和亮度都具有不变性,因此,它可以用于三维视角和噪声的可靠匹配。

2.2.1兴趣点

SIFT 特征使用高斯差分函数来定位兴趣点: D ( x , σ ) = [ G κ σ ( x ) − G σ ( x ) ] ∗ I ( x ) = [ G κ σ − G σ ] ∗ I = I κ σ − I σ D(\mathbf{x},\sigma)=[G_{\kappa\sigma}(\mathbf{x})-G_{\sigma}(\mathbf{x})]*I(\mathbf{x})=[G_{\kappa\sigma}-G_{\sigma}]*I=I_{\kappa\sigma}-I_{\sigma} D(x,σ)=[Gκσ(x)Gσ(x)]I(x)=[GκσGσ]I=IκσIσ

Gσ 是上一章中介绍的二维高斯核,Iσ 是使用 Gσ 模糊的灰度图像,κ 是决定相差尺度的常数。兴趣点是在图像位置和尺度变化下 D(x,σ) 的最大值和最小值点

2.2.2描述子

上面讨论的兴趣点(关键点)位置描述子给出了兴趣点的位置和尺度信息。为了实现旋转不变性,基于每个点周围图像梯度的方向和大小,SIFT 描述子又引入了参考方向。SIFT 描述子使用主方向描述参考方向。主方向使用方向直方图(以大小为权重)来度量

SIFT 描述子的标准设置使用 4×4 的子区域,每个子区域使用 8 个小区间的方向直方图,会产生共128 个小区间的直方图(4×4×8=128)

第二章 局部图像描述子_第6张图片

2.2.3检测兴趣点

我们使用开源工具包 VLFeat 提供的二进制文件来计算图像的 SIFT 特征。VLFeat 工具包可以从 http://www.vlfeat.org/ 下载,二进制文件可以在所有主要的平台上运行。VLFeat 库是用 C 语言来写的,但是我们可以使用该库提供的命令行接口。

由于该二进制文件需要的图像格式为灰度 .pgm,所以如果图像为其他格式,我们需要首先将其转换成 .pgm 格式文件

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


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')
        im.save('tmp.pgm')
        imagename = 'tmp.pgm'

    cmmd = str(r"D:\software\vlfeat\vlfeat-0.9.20\bin\win32\sift.exe " +
               imagename+" --output="+resultname + " "+params)
    os.system(cmmd)
    print(cmmd)
    print('processed', imagename, 'to', resultname)

读取特征属性值,然后将其以矩阵的形式返回

import chardet

def read_features_from_file(filename):
    with open(filename, 'rb') as f:
        rawdata = f.read()
        result = chardet.detect(rawdata)
        encoding = result['encoding']

    f = loadtxt(filename, encoding=encoding)
    return f[:, :4], f[:, 4:]  # 特征位置,描述子

将特征位置和描述子保存到文件中:

def write_features_to_file(filename, locs, desc):
    savetxt(filename, hstack((locs, desc)))

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

def plot_features(im,locs,circle=False):

    def draw_circle(c,r): 
        t = arange(0,1.01,0.01)*2*pi
        x = r*cos(t) + c[0]
        y = r*sin(t) + c[1]
        plt.plot(x,y,'b',linewidth=2)
    
    plt.imshow(im)
    if circle:
        for p in locs:
            draw_circle(p[:2],p[2])
    else:
        plt.plot(locs[:,0],locs[:,1],'ob')
imgname = 'am.png'
im1 = array(Image.open(imgname).convert('L'))
process_image(imgname, 'am.sift')
l1, d1 = read_features_from_file('am.sift')

plt.figure()
plot_features(im1, l1, circle=True)
plt.show()-
D:\software\vlfeat\vlfeat-0.9.20\bin\win32\sift.exe tmp.pgm --output=am.sift --edge-thresh 10 --peak-thresh 5
processed tmp.pgm to am.sift

第二章 局部图像描述子_第7张图片

分析:SIFT算法的实质是在不同的尺度空间上查找关键点(特征点),并计算出关键点的方向。SIFT所查找到的关键点是一些十分突出,不会因光照、仿射变换和噪音等因素而变化的点(如角点、边缘点、暗区的亮点及亮区的暗点等)

在运行过程中,可能会出现一些问题,参考SIFT特征提取(PCV、VLFeat)的环境配置、常见Bug及修复方案。

你可能感兴趣的:(计算机视觉,计算机视觉)