摘要

之前在SIFT算法中,有一个加速操作是使用图像金字塔,即不断对图像进行降采样。按照算法的思想表明:降采样后,标准差为$\sigma$的高斯模糊图像标准差会减半,得到标准差为$1/2\sigma$的高斯模糊图像

这里我不知道该如何证明....网上也没有相关资料,所以暂时采用数值解去验证这个说法。

实验过程

代码贴在最后,主要思路是比较两张图像:一张是先降采样一倍再用$\sigma$高斯模糊的图像;另一张是先使用$2\sigma$进行高斯模糊,再在模糊的图像上进行一倍降采样。

首先可视化这两张图,肉眼查看之间的差距,确实差距还是挺小的。此处$\sigma=30$(忽略窗口的值,那里标错了~)

image-20230709221257222

为了对比,这里把原图分别使用$\sigma$和$2\sigma$进行高斯模糊的结果也可视化了出来。这两张图就明显存在差异,这说明对高斯模糊过的图像降采样,确实会对其$sigma$产生影响

image-20230709221347242

image-20230709221357634

接着最早的两张图做差并画出来,可以看到形成了一个类似边缘检测的图像。这说明“先降采样再$\sigma$高斯模糊”跟“先$2\sigma$高斯模糊再降采样”这两个操作不完全等价。

为什么看上去是边缘检测图像?其实也很好理解,对于不同$\sigma$的高斯模糊图像相减,就是对高斯模糊图像微分,即DOG,DOG和LOG又只差一个常数倍,所以等效边缘检测了~

image-20230709221740842

接下来再探究“先$2\sigma$高斯模糊再降采样”得到的模糊图像的标准差到底是多少。最暴力的方式就是搜索,我们在降采样的图像上使用不同的$\sigma$进行遍历,画出delta的范数变化情况。最后我们发现:最接近的$\sigma_0$就是$\sigma$!

5d45f0cae88e5a5ae970dc07cc4d735

至此,我们知道上述的两个操作并不等价,但是它们足够接近。所以SIFT算法通过这种近似去做图像金字塔,大幅提高运算效率

代码

import cv2

Path = "C:\\Users\\Axuanz\\Desktop\\download.png"

if __name__ == "__main__":
    img = cv2.imread(Path,cv2.IMREAD_GRAYSCALE)

    sigma = 30

    ksize1 = sigma*6+1
    ksize2 = int(sigma/2*6+1)

    blur1 = cv2.GaussianBlur(img,ksize=(ksize1,ksize1),sigmaX=sigma)
    blur1 = cv2.pyrDown(blur1)

    downsample_img = cv2.pyrDown(img)
    blur2 = cv2.GaussianBlur(downsample_img,ksize=(ksize2,ksize2),sigmaX=sigma/2)

    blur3 = cv2.GaussianBlur(img,ksize=(ksize1,ksize1),sigmaX=sigma)
    blur4 = cv2.GaussianBlur(img,ksize=(ksize2,ksize2),sigmaX=sigma/2)

    delta = blur2 - blur1
    print(cv2.norm(delta))

    cv2.imshow("sigma=10",blur1)
    cv2.imshow("sigma=5",blur2)
    cv2.imshow("blur3",blur3)
    cv2.imshow("blur4",blur4)
    cv2.imshow("delta",delta)
    # cv2.waitKey()

###########################################
    sigma2 = 1
    norm_list = []
    while sigma2 <= sigma:
        _ksize = sigma2*6 + 1
        blur = cv2.GaussianBlur(downsample_img,ksize=(_ksize,_ksize),sigmaX = sigma2)
        delta = blur - blur1
        
        norm = cv2.norm(delta)
        print(f"sigma2 = {sigma2},delta norm ={norm}")

        norm_list.append(norm)
        sigma2 += 1
    
    import matplotlib.pyplot as plt
    x = list(range(1,sigma+1))
    print(x)
    print(norm_list)
    plt.plot(x,norm_list)
    plt.show()
###########################################

摘要

SVM是神经网络兴起之前最常用的机器学习分类器,本篇主要介绍SVM的具体实现,包括硬间隔/软间隔、合页损失函数。PPT参考https://www.bilibili.com/video/BV1zq4y1g74J/?spm_id_from=333.788&vd_source=6e11e901eb83e70a9bb55225ac28b9d9

SVM推导

SVM一般用于解决数据的二分类问题,对于高维数据,就是找到一个超平面将两类数据分开。以二维平面为例,就是找到一条直线作为分割线。

当然有时候我们无法找到理想直线将两类数据分离,这个时候就需要用到非线性SVM,通过核函数将数据点映射到高维空间,以期望在高维空间找到一个超平面分离数据。

image-20230709104533915

SVM的思想不仅是找到一个分割直线,它还希望这条直线离两类数据都尽可能远,也就是最大小下图中的$margin$。

image-20230709104825169

$margin(W,b)$与直线参数$W,b$有关,形式化表示可以写成:

$$ max \space margin(W,b)=max\mathop{min}\limits_{i=1,2,...N} \frac{1}{||W||_2}|W^TX^{(i)}+b| $$

$margin$的表达式为什么是直接将数据点$X^{(i)}$带入直线(高维数据时其实是超平面,但是为了描述方便之后都用直线方程替代)方程然后除以W的L2范数?推导如下,首先写出某个数据点到直线的距离方程,距离可以表达成W和数据点向量之间的点积除以W的模。假设$x^{(0)}$是平面上的一点(所以$W^TX^{(0)}+b=0$),那么距离H就可以表示为:

$$ \begin{aligned} H &= |\frac{W}{||W||_2}(X^{(i)}-X^{(0)})|\\ &= |\frac{1}{||W||_2}(W^TX^{(i)}-W^TX^{(0)})|\\ &= |\frac{1}{||W||_2}(W^TX^{(i)}+b)| \end{aligned} $$

image-20230709105549162

除了满足最大$margin$外,我们还希望这个超平面可以正确分割数据点,我们将数据点的标签标为1或-1,那么如果直线可以正确分类,那么满足以下两个条件:

image-20230709110437048

进一步将优化问题转变成以下条件:

image-20230709110531551

为了简化问题,我们将离直线最近的数据点$X^{(i)}$离直线的距离$|(W^TX^{(i)}+b)|$约束到1,那么优化问题就变成了下图,并且添加了一个约束条件。之所以可以这么优化,是因为对于原来的$margin$,$W,b$同时扩大N倍,都不会影响margin的结果,所以这里可以扩大(缩小)两者的值,使$|(W^TX^{(i)}+b)|$约束到1,从而简化问题

注意,这里的$X^{(i)}$是离直线最近的那个数据点,所以可以把$margin$的min脱掉

image-20230709110840827

合并约束条件得到以下优化条件:

image-20230709111411569

将max转换成min,得到:

image-20230709111525424

进一步写成矩阵相乘的形式,并通过拉格朗日乘子法优化,这里约束条件是不等式,满足KKT条件。数据点在边界上时$\lambda$不等于0,存在约束;如果不在边界上则不存在约束。具体可查阅KKT条件相关知识。

这里引入1/2不会影响结果,但是可以方便之后的求导运算

image-20230709111644213

对上图中的L求导,得到最优解。此时W可以表示为边界上各个数据点的线性组合,这些数据点$X^{(j)}$就被称为支持向量

image-20230709111852303

SVM的损失函数

我们回过头看SVM需要优化的那个拉格朗日函数,其实它和神经网络中的损失函数很像,第一项相当于W的L2正则化项,第二项则是”损失函数“。

image-20230709112110624

上述的SVM是硬间隔SVM,因为它的损失函数要求SVM直线对于每个数据点都分类正确,然而实际上会出现不可分的情况。那么这个时候就需要对优化函数进行修正,从而得到软间隔SVM。

image-20230709112358327

通过添加$\xi$,使约束条件放开。同时也将$\xi$的常数倍加入损失函数优化,从而尽可能找到一个小的间隔$\xi$。

image-20230709112419232

合并约束条件,我们可以得到以下结果。这个时候我们已经可以看到Hinge loss的身影了。

image-20230709113135754

把约束项合并到损失函数中,就可以得到软间隔SVM最终的损失函数形态。第一项是损失函数,第二项是W的正则化项。

image-20230709114151469

而第一项就是我们熟知的Hinge Loss,合页损失函数。

image-20230709114258657

Program Links : https://github.com/JJJYmmm/Image-Classification-with-SIFT-and-BOW

ReadMe

使用SIFT+BOW+SVM实现的一个图像分类器。SIFT负责提取图片中的尺度不变特征,词袋模型BOW则负责描述一张图片的SIFT特征分布,将结果送入SVM进行学习。项目参考https://github.com/CV-xueba/A01_cvclass_basic_exercise,不过修复了其中的SPM特征提取代码bug,具体修复见Vocabulary.py中的calSPMFeature函数。

项目结构如下:

  • main.py : 程序入口点
  • DataProcess.py : 读取图像,提取图像的SIFT特征
  • ImageInfo.py : 图像相关信息的类,包括类别、大小、SIFT特征的位置和描述符等
  • Vocabulary.py : 词袋模型,将所有SIFT特征进行聚类得到单词,并提取每个图像的SPM特征
  • ClassifierKernel.py : SVM学习BOW特征的实现

结果见项目output.txt

以下是测试集的混淆矩阵

confusion matrix

摘要

本篇主要介绍目标检测的一些基本概念,以及一个人脸检测的实例来加深印象,最后还谈了以下HOG特征的提取。

简单介绍(非常简单)

目标检测就是在负责在一幅图中检测出感兴趣的物体,一般采用滑动窗口来实现。但是实际应用中,检测效果依赖于光照、物体姿态、视角等影响。具体来说,目标检测需要考虑以下几个问题:

  • 如何选择滑动窗口的大小,从而克服检测物体的尺度变化
  • 如何建模图片的特征
  • 如何找到物体对应的特征
  • 如何克服不同摄影角度的问题(最原始的方法是训练多个视角的模型)

image-20230708152459133

人脸检测

本次介绍基于adaboost的人脸检测模型,它广泛用在相机、手机摄影的人脸检测器。

boosting模型

首先介绍boosting模型,它是一种投票式的判别模型,相比于直接训练一个强分类模型,它的思想是训练多个弱分类器,取长补短达到强分类的效果。

训练过程如下,最终目的是训练一个分类器可以分辨红蓝数据点。刚开始所有数据点的权重为1。

image-20230708153956057

接下来训练多个弱分类器,例如线性分类器组。找到一个正确率最高的分类器,将其保留。

这里有个先验假设,我们总能找到一个正确率大于50%的分类器。原因是如果所有分类器的准确率都小于50%,那我们只需要选正确率最低的那个分类器,然后跟他反着预测即可。

image-20230708154132799

我们选出来的分类器性能并不高,他会有一些分类错误的数据点,对于这些数据点,我们扩大它们的权重。(在代码中,我们选择缩小正确预测的数据点的权重,这里方便观察使用另外一种思路)

image-20230708154202268

在新的权重比例下,我们再次训练多个线性分类器,得到一个表现最好的分类器,将其保留。因为我们扩大了第一个分类器分类错误的那些数据点的权重,所以第二个分类器会更加关注这些点的分类。通俗的来说,第二个分类器可以解决第一个分类器没有解决的错误。

image-20230708154527206

同理,我们扩大第二个分类器的分类错误点的权重,并训练第三个分类器。

image-20230708154733591

最后,我们通过多个弱分类器得到一个强分类器。这就是boosting的思想。

image-20230708154822153

当对一个测试图像分类时,使用投票的方式进行预测,如果投票分数大于全局分数的半数,那么就将其分类为真。

image-20230708154849401

人脸检测

回到人脸检测领域,我们选择的弱分类器非常简单,其实就是一个卷积模板。如下图所示,选择一个固定位置的卷积核,卷积核分为白色区域和黑色区域,卷积结果就是白色区域的像素值和与黑色区域的像素值和的差。这个也叫harr-like算子

image-20230708154946084

例如人脸鼻翼区域的卷积核,因为光照的原因,鼻翼下方的像素值普遍比鼻翼位置的像素值低,因此对于人脸来说,整个模板的值往往小于0,这就组成了一个Weak Classifier。

image-20230708155730158

在实际运算中,为了解决运算成本,我们一般使用二维前缀和对图像的区域值计算进行加速

image-20230708155958610

接下来的思路就和boosting一致,我们寻找多个模板构成多个弱分类器。然后参考boosting的训练机制得到一个人脸的强分类器。

image-20230708160114467

image-20230708160119879

image-20230708160126913

不过对于一个准确率达到95%的强分类器,还是需要200个弱分类器参与运算。这效率还是不高,因为实际检测还需要通过滑动窗口确定人脸范围。

image-20230708160214266

因此为了加快速率,采用强分类器的级联结构。具体来说,就是训练多个强分类器(每个强分类器里面都有多个弱分类器)。第一个强分类器的准确率不需要太高,比50%高一点就可以;第二个强分类器则处理第一个强分类器无法解决的错误;第三个分类器则解决第二个分类器无法解决的问题.....(这个思想也很像boosting)。最后通过多个强分类器检测的滑动窗口区域才认为存在人脸,否则直接pass

这里为什么可以加快效率呢,因为强分类器不要求有很高的准确率,他只要保证可以让绝大部分正样本都通过,让部分负样本通过即可。通过多个强分类器的级联可以逐步过滤假阳样本。

假如第一个强分类器的准确率只需要50%,那么它大概只需要两个弱分类器就可以达到目的,这个远远低于95%准确率分类器的200个弱分类器。并且在检测过程中,大部分非人脸都会在这里被拒绝。所以可以提高检测效率

以上级联特点也启示我们,对于越明显的特征检测器(比如刚刚提到的鼻翼位置的harr-like算子),放在越前面,将进一步提高检测效率

说白了,强分类器的级联,其实可以理解成强分类器组的boosting

image-20230708160400464

行人检测

行人检测主要介绍一个HOG特征。行人检测一般就是给定窗口,通过计算窗口中的HOG特征,利用SVM判断窗口中是否包含行人。

image-20230708161410955

HOG的计算过程如下:

  • 首先计算图像的梯度大小和方向图。
  • 将图像分为若干个8x8大小的小单元,对于每个单元,统计其梯度直方图。统计直方图分成9份,即一个小单元对应一个9维向量。
  • 四个小单元为一个block,那么一个block对应一个36维向量。对于每个block中的36维向量进行归一化,这一步可以降低光照等环境影响
  • 滑动窗口大小为一个blcok,步长为一个cell,所以对于一个8x16小单元的图像,最后可以得到$(8-1)*(16-1)=105$个block,那么这张图片的HOG特征长度就为$105*36=3780$。