Python实现SIFT完整教程

本文是翻译的medium的一篇关于Python实现SIFT算法的文章。全文不用机翻均采用个人理解翻译。原文教程分两个文章,本人均翻译放在本篇博客中。(未完成!!!)

项目地址:https://github.com/rmislam/PythonSIFT

原文链接:https://medium.com/@russmislam/implementing-sift-in-python-a-complete-guide-part-1-306a99b50aa5

Part1

它在计算视觉领域中是经典的算法。许多计算机视觉工程师每天都依赖于这个算法,但是很少有人能很好地理解其内部原理。更糟糕的是有的人只是在大学里简单看到过关于它的一些幻灯片。你应该不会想成为这样的工程师。

没错,我正在讨论就是尺度不变特征变换(SIFT)算法。我们正在尝试跳入这个难题,仔细分析里面的细节。我们在分析里面的细节过程中将会学到一些东西。我准备一步步向你们介绍。这是两篇教程的第一篇(第二篇的内容在Part2

记住,只在高层次处理问题是不可能成为好的工程师和科学家的!

深度学习固然很好,不可否认在一些任务上,它确实实现SOAT效果,但是记住它也不是万能的。或许你可以将深度学习应用在许多应用中并且获得很好的结果,但是仍有许多基于先验的数学和物理学的算法得到的效果甚至更好。如果你要为手头的难题找到一种正确的解决方法,首先你得保持一种不可知的状态。举个例子,如果你手里有一个普通的锤子,所有的问题就好比普通钉子。如果你手里是一把深度学习的锤子,那么所有的问题都看起来像深度学习的钉子。但是当你遇见不能用深度学习解决的问题的时候你该怎么办。

是时候拿出我们的杀手锏了——传统的算法,例如SIFT。

这不仅仅是为了解决问题,同时也增长了你的能力。尽管你可以使用深度学习得到更好的结果,但你也不应该略过学习的机会,例如学习SIFT。学习SIFT背后的细节可以让你更深刻地理解计算机视觉的基本原理。你可以这样设想:尽管你可以编写深度学习的算法程序可以比牛顿力学更精确地解决力学问题,但不意味着你不应该学习牛顿力学的知识。你是更愿意成为一个使用各种算法解决任何问题的物理学家,还是原意成为一个朝九晚五只知道调用一些神经网络API的码农?

现在是时候跳入这个坑了。

The Code

你可以找的我用Python实现的SIFT算法在这。在这个教程中,我们将会顺着代码(pysift.py 文件)一步一步地输出以及可视化相关变量,帮助我们更好地理解每时每刻都在发生什么。我的算法实现非常接近OpenCV的实现,同时不牺牲任何细节简化和python化其中的逻辑。

使用方法非常简单:

1
2
3
4
5
import cv2
import pysift

image = cv2.imread('your_image.png', 0)
keypoints, descriptors = pysift.computeKeypointsAndDescriptors(image)

只需要传一个二维的NumPy数组给 computeKeypointsAndDescriptors() 即可返回一个OpenCV的KeyPoint数组对象和一个相关的长为128的向量描述。通过这个方法,pysift可以直接作为OpenCV自带SIFT功能的替代品。注意这个实现的目的是为了简单明了地理解,而不是为了高性能计算。在一台普通的笔记本电脑上处理一张普通的图片可能需要花几分钟的时间。

下载代码尝试运行demo的样例图片的匹配程序,得到的keypoint几乎和使用OpenCV的结果一样(具体差异是由于浮点运算错误导致的)

SIFT Theory and Overview

让我们简单回顾一下SIFT的背景,然后设计一下算法的路线图。我不细讲其中的数学原理。你可以(应该)阅读原论文

SIFT可以在不同的宽高以及比例尺度上识别关键点。通过考虑缩放因素,我们可以仍然可以保持我们感兴趣的关键点在模版大小视角变化,图像的质量改变时依然可以获取稳定的关键点。此外,每一个关键点都与一个方向向量相关联,这使得SIFT不受模板旋转的影响。最后SIFT将会为每一个关键点生成一个描述符。一个允许让关键点比较的128维的向量。这些描述符只不过是在关键点的邻域内计算出的梯度直方图。

译者注:所谓的模板可用如下图解释,图一为模板,需要在图二中匹配图一

box_in_scene

box

box_in_scene

box_in_scene

译者注结束

SIFT中大多数棘手的细节都与空间比例有关,比如将正确的模糊量应用到输入图像,或者将关键点从一个比例转换到另一个比例。

pysift的主函数computeKeypointsAndDescriptors() ,可以让你对SIFT中涉及的不同成分有了一个清晰的了解。首先我们调用generateBaseImage()来适当地模糊和放大输入图像,以生成我们的“图像金字塔”的基图像,这是一组连续模糊和向下采样的图像,构成我们的尺度空间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from numpy import all, any, array, arctan2, cos, sin, exp, dot, log, logical_and, roll, sqrt, stack, trace, unravel_index, pi, deg2rad, rad2deg, where, zeros, floor, full, nan, isnan, round, float32
from numpy.linalg import det, lstsq, norm
from cv2 import resize, GaussianBlur, subtract, KeyPoint, INTER_LINEAR, INTER_NEAREST
from functools import cmp_to_key
import logging

####################
# Global variables #
####################

logger = logging.getLogger(__name__)
float_tolerance = 1e-7

#################
# Main function #
#################

def computeKeypointsAndDescriptors(image, sigma=1.6, num_intervals=3, assumed_blur=0.5, image_border_width=5):
"""Compute SIFT keypoints and descriptors for an input image
"""
image = image.astype('float32')
base_image = generateBaseImage(image, sigma, assumed_blur)
num_octaves = computeNumberOfOctaves(base_image.shape)
gaussian_kernels = generateGaussianKernels(sigma, num_intervals)
gaussian_images = generateGaussianImages(base_image, num_octaves, gaussian_kernels)
dog_images = generateDoGImages(gaussian_images)
keypoints = findScaleSpaceExtrema(gaussian_images, dog_images, num_intervals, sigma, image_border_width)
keypoints = removeDuplicateKeypoints(keypoints)
keypoints = convertKeypointsToInputImageSize(keypoints)
descriptors = generateDescriptors(keypoints, gaussian_images)
return keypoints, descriptors

Part2

如果对你有帮助,请赏包辣条