Project 1: Image
Filtering and Hybrid Images
Generating Gaussian Kernel
kernel 是一个 矩阵,
保证 为奇数,
这样的矩阵一定会有一个中心.
Gaussian Kernel 是从二维正态分布
中用一个均匀的网格采样, 设密度函数为 ,
, 则 其中
为归一化系数满足 , 此时 Kernel 的中心为
.
注意到正态分布具有 原则,
为了保证取到的点对卷积后的结果有明显的贡献(要使
中的每个元素尽可能不那么小)以及网格所覆盖的矩形区域 满足
尽可能接近 以保持高斯核的完整性,
一般令 .
由于协方差矩阵 为对角阵,
两个维度的正态分布的相关系数为 ,
有 ,
显然若矩阵 ,
则可对 做秩 1 分解, , 除此之外 还是对称阵, 因此 ,
其中 . 由于最终都要做归一化, 因此计算 的时候可只用指数部分:
1 2 3 4 5 6 sigma = cutoff_frequency pdf = lambda x, mu, sigma: np.exp(-0.5 * (x - mu / sigma)**2 ) / ( sigma * np.sqrt(2 * np.pi)) x = np.array([pdf(i - k // 2 , 0 , sigma) for i in range (k)]) x = x / np.sum (x) return np.outer(x, x)
My Image Filter
设 形状为 , , 那么
实现上需要先对原图像进行 padding, 然后遍历通道和每个像素 , 取以像素 为中心的一个与 形状相同的矩阵即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 time_1 = time.time() filter_shape = filter .shape H, W, C = image.shape a = filter_shape[0 ] // 2 b = filter_shape[1 ] // 2 padding_image = np.zeros((H + 2 * a, W + 2 * b, C)) padding_image[a:H + a, b:W + b, :] = image filtered_image = np.zeros((H, W, C)) for i in range (C): for j in range (H): for k in range (W): filtered_image[j, k, i] = np.sum ( padding_image[j:j + 2 * a + 1 , k:k + 2 * b + 1 , i] * filter ) time_2 = time.time() print ("Running time of the image filter is: " , time_2 - time_1, "s" )return filtered_image
原来 numpy
提供了 padding
的方法.
Create Hybrid Image
图像的高频成分指像素值变化较大的部分(比如边缘, 纹理), 低频部分反之,
指图像的像素值变化较小的部分.
图像经过高斯核滤波平滑后得到的图像可以认为是原图像的低频成分,
而用原图像减去低频成分后就可以得到原图像的高频成分.
1 2 3 4 5 low_f_of_image1 = my_imfilter(image1, filter ) low_f_of_image2 = my_imfilter(image2, filter ) high_f_of_image2 = image2 - low_f_of_image2 hybrid_image = np.clip(low_f_of_image1 + high_f_of_image2, 0 , 1 ) return low_f_of_image1, high_f_of_image2, hybrid_image
除此之外还可以用 Fourier
变换将图像转为频谱表示,其中低频成分通常位于频谱的中心,而高频成分则位于频谱的边缘。通过分析图像的频谱,可以定量地识别图像中的低频和高频成分,并据此进行图像处理和特征提取。
注意事项
np.zeros()
的参数是一个 tuple
,
记得把形状加括号.
逐 pixel 的 modification.
频谱图是什么.
缩小丢失了什么信息?
肉眼对高频信息极度不敏感.
神经网络中, 随着层数的增加不断地 subsampling,
丢失的高频信息越来越多.
Project 2: Local Feature
Matching
Harris Corner Detection
译作 Harris 角点检测. 角点 没有很严格的定义,
但是一般具有如下特征:
包含角点的一个局部窗口内部,
从角点向各个方向移动都能检测到较大的像素值变化
与之不同的还有两种情况:
若某个局部窗口内部的某个点沿多个方向移动像素值变化都很小,
则该点所在区域为平坦区域
若某个局部窗口内部某个点沿着一个方向移动像素值变化很大,
而沿着与该方向垂直的方向移动像素值变化很小,
那么该点所在区域为边缘
一般而言, 可以认为角点是边缘的交点.
按照这种定义方式, 给定图像
和像素点 处的局部窗口 ,
那么只需要计算窗口内部的点与移动后的点之间像素差值绝对值(或者平方)的和,
这个和就能体现出上述定义中的"像素值变化"的大小, 将这个函数定义为 这称为 sum of square differences, 其中 为权重函数,
一般为均值或者高斯.
减小计算量
对于大小为
的图像与大小为 的窗口,
上述函数处理整张图像的计算量是 , 考虑对 进行泰勒展开.
首先对于 元实值函数 , 它在点 处的二阶
Taylor 展开式为 那么 于是记 , 那么 因此 这是一个二次型, 取值由 决定,
现在要考虑该二次型的取值. 注意到 是实对称矩阵,
那么一定可以进行相似对角化, 设 , 其中 ,
二次型 的标准型为
, 显然
随 的变化情况与 的大小强相关,
由下述几种情况:
, 这意味着当 变化时
不会有显著的变化, 局部窗口沿着
方向的梯度几乎为 0, 因此是边缘,
也是一样的
, 变化时 都不会有显著的变化, 因此是平坦区域
, 变化或者 变化时 都会有显著的变化, 因此是角点.
通过一些数学上的操作, 将求解
转换成了求解 的特征值,
复杂度由 降为了 ,
求解二阶矩阵的特征值的时间可看作常数.
又注意到 ,
因此
的值的变化情况和上述二次型的值的变化情况是一致的, 其中 是一个经验上的常数, 取值为 , 将
或者是大于某个阈值的点作为角点, 这就避免了特征值的计算,
同时二阶矩阵的行列式和迹都非常容易得到, 这就进一步简化了运算.
特点
具有灰度不变性: Harris 角点检测主要依赖的是图像在各个像素点上的梯度,
而梯度取决于像素值之间的大小关系, 和像素值本身的大小关系不大,
因此仅改变图像的灰度对该方法几乎没有影响.
具有旋转不变性: 这是由于旋转相当于对梯度实施了线性变换, 这不影响
的特征值大小.
不具有尺度不变性: 尺度放大会使得某些角点变成边缘,
而缩小会使某些边缘变成角点.
噪声会影响梯度的大小, 由于噪声是高频信息, 因此会使梯度偏大,
于是会导致角点数增多.
SIFT 算法
SIFT 全称 Scale-Invariant Feature Transform, 顾名思义,
具有尺度不变性, 它用来检测, 表述以及匹配图像的局部特征. 下面给出用 SIFT
算子来匹配图像局部特征的算法, 这里的匹配图像局部特征指的是,
给定两张对同一对象从不同角度拍摄的图像,
检测出两张照片的关键特征后(像素点),
将具有相同(实际)位置的关键特征点进行匹配.
这一过程与人眼判断两张照片是否描述了同一物体的原理基本一致.
算法主要有四个步骤:
Scale-space extrema detection .
Keypoint localization .
Orientation assignment
Keypoint descriptor .
Scale-space extrema
detection
为了使算法具有尺度不变性, 需要对图像进行多次下采样;
为了减少噪声对关键点检测的影响, 需要对图像进行多次高斯模糊.
这样构造出一组图像: 满足 ,
其中
是高斯核, 是卷积操作, 是 进行下采样得到的图像,
每一行图像被称为一个 octave, 每一个 octave 中有若干 interval.
然后构造高斯差分金字塔, 定义 DOG image 是每一个 octave
中的相邻图像做差分得到的新图像 ,
高斯差分金字塔就是由所有 octave 的 DOG image 组成. DOG
更能凸显图像的特征.
关键点的确定就是通过不同尺度的 DOG 上的像素的极大点或极小点.
这里的极值点不只指在一幅图像中像素点为极值的点, 某个点为极值点,
需要它的像素值大于或小于自身所在图像的八邻域,
上一张图像相同位置的像素的八(闭)邻域以及下一张图像相同位置的像素的八(闭)邻域这一共
个点的每一个点的像素值.
注意每一个 octave 中边缘的图像无法求极值点,
因为它没有上一张图像或下一张图像.
Keypoint localization
上述选取的高斯差分金字塔中图像的极值点只是关键点的候选点, 如果要提升
robustness, 还需要用对比测试和边缘测试剔除一些点.
设 在候选点 处的函数值,
梯度和黑塞矩阵分别为 , 是相对候选点
的偏移量, 那么
(也就是说这个二次型是 DOG 在点
处的二阶泰勒展开), 求出这个二次型的极值点 ,
可以计算出拟合处的函数的极值点(而这个极值点又是对 DOG 的极值点的估计)与候选点的偏移量 , 有两种情况:
如果
的任意一个分量大于 ,
就认为候选点距离 DOG
真正的极值点距离偏差过大, 也就是说有另一个距离极值点更近的候选点,
这种情况下对 和 做插值作为新的候选点
否则直接将
直接作为新的候选点
舍弃低对比度的关键点
如果
在上述求出的极值点
处的函数值的绝对值小于 ,
就舍弃对应的候选点 ;
否则的话保留候选点 , 并把
作为最终的尺度空间位置.
消除边缘响应
设 关于 的 Hessian 矩阵为 , 其特征值为 , 令 , 则每个候选关键点可以求出
.
给定阈值 ,
若某个候选关键点对应的 ,
则剔除该特征点.
Orientation assignment
为了实现旋转不变性,
需要基于每个关键点对每个关键点分配一个或多个方向 .
取关键点所在的高斯平滑后的图像 , 计算两个指标:
需要计算以关键点为中心某个邻域
内的所有点的梯度幅度和方向, 设这些点的梯度和幅值为 ,
然后给定 个候选方向 ,
这些方向均匀地 分布在 的范围内(以 作为第一个候选方向),
然后统计
上每个点的梯度在方向
上的贡献, 这个贡献定义为 即将每个点的梯度向方向 上进行投影, 投影做加权平均即可,
这个加权平均是二维高斯核加权的 ,
因此实际计算的时候要根据第
个点与候选点在图像上的相对位置来得到权重 . 得到贡献 后,
对应的方向即为分配给该候选点的主导方向 .
除此之外, 设主方向为 ,
若 满足 , 则可以将 作为辅方向,
这里的辅方向的含义相当于产生了两个候选点, 每个候选点都具有四个属性 , 产生的这两个候选点的
分别为 . 辅方向可以有多个.
Keypoint descriptor
到这一步, 对每个高斯差分金字塔上的每个尺度都找到了关键点的位置,
每个关键点都分配了方向,
现在需要为每个关键点再计算一个描述子(descriptor),
使描述符具有高度的区别性.
首先对于候选点, 确定半径
以确定计算描述子所需要的图像区域, 对该区域进行划分, 划分为 个区域 ,
然后对每个区域都可以产生对应的 , 这样产生了一个描述向量
, 对
进行归一化,
增加对光照的仿射变换的不变性, 得到所谓的描述子.
Match
对两张图片分别计算出关键点的描述子之后, 可以进行匹配.
匹配的基本思想就是计算描述子之间的距离, 距离近的自然可以匹配到一起,
这里采用欧氏距离, 并且需要设置一个阈值. 首先是计算描述子之间的距离:
1 2 3 4 5 6 7 8 def compute_feature_distances (features1, features2 ): n, _ = features1.shape m, _ = features2.shape dist = np.zeros((n, m)) for i in range (n): for j in range (m): dist[i][j] = np.linalg.norm(features1[i] - features2[j]) return dist
然后是匹配:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 dist = compute_feature_distances(features1, features2) matches = [] confidences = [] for i in range (len (features1)): sorted_indices = np.argsort(dist[i]) closest_idx = sorted_indices[0 ] second_closest_idx = sorted_indices[1 ] ratio = dist[i, closest_idx] / dist[i, second_closest_idx] if ratio < 0.9 : matches.append([i, closest_idx]) confidences.append(1.0 - ratio) matches = np.array(matches) confidences = np.array(confidences) if len (matches) == 0 : return np.empty((0 , 2 )), np.empty((0 , )) else : return matches, confidences
实验结果
不放了.
Project 3: Scene
Recognition with Bag of Words
Project 4:Scene
Recognition with Deep Learning
数据处理
返回一个图像处理方法序列(transforms.Compose
对象),
包括:
resize
: 将图像的形状调整为 inp_size
totensor
: 将图像转化为张量格式
normalize
: 对图像的像素值进行标准化, 需要用到参数
pixel_mean
和 pixel_std
用 transforms.Compose
组织在一起.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def get_fundamental_transforms (inp_size: Tuple [int , int ], pixel_mean: np.array, pixel_std: np.array ) -> transforms.Compose: ''' Returns the core transforms needed to feed the images to our model Args: - inp_size: tuple denoting the dimensions for input to the model - pixel_mean: the mean of the raw dataset - pixel_std: the standard deviation of the raw dataset Returns: - fundamental_transforms: transforms.Compose with the fundamental transforms ''' resize = transforms.Resize(inp_size) totensor = transforms.ToTensor() normalize = transforms.Normalize(pixel_mean, pixel_std) fundamental_transforms = transforms.Compose([resize, totensor, normalize]) return fundamental_transforms
和上述函数一样, 返回一个方法的组合, 是
transforms.Compose
对象,
进行一系列图像增强操作以及一系列必备操作, 包括:
resize
: 将图像调整至要求的大小
inp_size
randomcrop
: 对图像进行随机裁剪
hflip
: 对图像进行随机水平翻转
totensor
: 和上个函数作用一致
normalize
: 和上个函数作用一致
然后用 transforms.Compose
组合为
aug_transforms
并返回:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 def get_data_augmentation_transforms (inp_size: Tuple [int , int ], pixel_mean: np.array, pixel_std: np.array ) -> transforms.Compose: ''' Returns the data augmentation + core transforms needed to be applied on the train set Args: - inp_size: tuple denoting the dimensions for input to the model - pixel_mean: the mean of the raw dataset - pixel_std: the standard deviation of the raw dataset Returns: - aug_transforms: transforms.Compose with all the transforms ''' resize = transforms.Resize(inp_size) randomcrop = transforms.RandomCrop(inp_size) hflip = transforms.RandomHorizontalFlip() totensor = transforms.ToTensor() normalize = transforms.Normalize(pixel_mean, pixel_std) aug_transforms = transforms.Compose([resize, randomcrop, hflip, totensor, normalize]) return aug_transforms