VLAD
从VLAD到NetVLAD,再到NeXtVlad
VLAD (Vector of Locally Aggregated Descriptors,局部特征聚合向量)。在处理图像检索,或是机器人的视觉定位与离线建图(比如地点识别和回环检测)时,不同图片提取出的局部特征数量往往是不一样的。VLAD 的核心使命就是:把一张图像中数量不定、杂乱无章的“局部特征”,压缩并聚合成一个长度固定的“全局特征向量”,从而方便计算两张图片的相似度。
假设你有一个巨大的图像库(比如机器人提前建好的地图数据库):
- 特征提取:你需要一个函数 \(f\),把库里的每一张图片 \(I_i\) 都变成一个特征向量 \(f(I_i)\)。
- Query 查询:当机器人当前相机拍到一张新图片 \(q\) 时,也用同样的函数提取特征 \(f(q)\)。
- 计算相似度:通过计算欧式距离 \(d(q, I) = ||f(q) - f(I)||\)。距离越小,说明当前拍到的画面跟地图库里的某个位置越相似。
VLAD 本身不直接从像素里找特征,它是对已有特征的再加工。你需要先用传统算法(如 SIFT、SURF、ORB)或者现代的 CNN(卷积神经网络)提取出图像的局部特征。
- 假设一张图片提取出了 \(N\) 个局部特征。
- 每个特征是一个 \(D\) 维的向量(例如 SIFT 是 128 维)。
- 痛点:不同图片的 N 通常不一样大,没法直接算欧式距离。
将所有特征进行 K-means 聚类,得到 \(K\) 个聚类中心,记作 \(c_k\)。这就像是把世界上所有的局部特征归纳为 \(K\) 种基本的“视觉单词”(比如有的代表边缘,有的代表角点)。\(c_k\) 就是这 \(K\) 个类的中心代表。
- \(x_i\):当前图片里的第 \(i\) 个局部特征。
- \(c_k\):第 \(k\) 个聚类中心(代表某种特定的视觉模式)。
- \(a_k(x_i)\):这是一个开关(符号函数)。它的意思是:“如果特征 \(x_i\) 属于聚类中心 \(c_k\),这个值就是 1,否则就是 0”。这保证了特征只会在它所属的那个类里参与计算。
- \((x_i(j) - c_k(j))\):这是残差 (Residual)。它计算的是局部特征 \(x_i\) 与它的类中心 \(c_k\) 在第 \(j\) 个维度上的差异。
对于第 \(k\) 个聚类中心,我们把当前图片里所有属于这个类的局部特征找出来,计算它们与类中心的“偏差(残差)”,然后把这些偏差全部加起来。
VLAD 的精髓:“抹去了图像本身的特征分布差异,只保留了局部特征与聚类中心的分布差异。”传统的词袋模型 (Bag of Visual Words) 只是简单地统计“这张图里有几个属于类 \(c_k\) 的特征”(算频率)。VLAD 更进一步,它记录的是:“这些特征不仅属于类 \(c_k\),而且它们偏离类中心的方向和程度是什么。” 这种记录“残差”的方式,包含了比单纯统计数量丰富得多的细节信息。最终,这 \(K\) 个聚类的残差向量会被拼接在一起,形成一个形状为 \(K \times D\) 的全局特征矩阵 \(V\)(通常会被展平为一个长度为 \(K \times D\) 的一维长向量)。由于 \(K\) 和 \(D\) 都是预先设定好的,无论原始图片的特征数 N 是多少,最终输出的特征向量长度永远固定,这就完美解决了前面提到的痛点,可以直接用于快速的向量距离检索。
NetVLAD
\(a_k(x_i)\) 是一个符号函数(硬开关):如果特征属于聚类中心 \(c_k\),值为 1,否则为 0。神经网络依赖于反向传播求导(梯度下降)来更新参数。符号函数(类似阶跃函数)在跳变点不可导,在其他地方梯度全是 0。这意味着如果你把它放进神经网络,网络什么都学不到,直接卡死。
不再说特征 \(x_i\) “100% 属于”或“0% 属于”某个类,而是分配一个 \((0, 1)\) 之间的概率值。\(x_i\) 离中心 \(c_k\) 越近,指数项越大,分配到的权重 \(\bar{a}(x_i)\) 就越接近 1。。
- \(||x_i - c_k||^2 = ||x_i||^2 - 2x_i^T c_k + ||c_k||^2\)
- 此时,\(w_k = 2\alpha c_k\) 可以看作是神经网络这一层的权重 (Weights)。
- \(b_k = -\alpha||c_k||^2\) 可以看作是这一层的偏置 (Biases)。
将推导出的 Softmax 权重 \(\bar{a}(x_i)\) 替换掉传统 VLAD 中的硬开关 \(a_k(x_i)\),就得到了最终的 NetVLAD 聚合公式:

前端(Feature Extractor):普通的 CNN
- 一张图片输入进去,先经过标准的卷积神经网络(常为ResNet)。
- 最后一个卷积层输出的不是一个向量,而是一个三维的特征图(Feature Map),尺寸为 W×H×D(宽 \(\times\) 高 \(\times\) 通道数)。
- 形态转换: NetVLAD 把这个 \(W \times H \times D\) 的特征图,看作是 N 个 D 维的局部特征(其中 \(N = W \times H\))。
后端(Pooling Layer):NetVLAD 层
- 计算聚类得分 → 1×1 卷积 (Conv 1×1×D×K):将原始视觉特征映射到聚类中心
- 公式部分: 需要计算 \(w_k^T x_i + b_k\)。这是在算特征 \(x_i\) 和第 \(k\) 个聚类中心的“匹配得分”。
- 网络实现: 网络使用了一个 1×1 的卷积层来实现这个操作。通道数是 \(D\),一共设置 \(K\) 个卷积核。
- 输出变成了一个 \(W \times H \times K\) 的得分图。
- 转换为软分配权重 → Softmax
- 公式部分: 将上面的得分放入指数并归一化 \(\frac{e^{(\dots)}}{\sum e^{(\dots)}}\)。
- 网络实现: 直接在刚才输出的 \(W \times H \times K\) 得分图上,沿着 \(K\) 这个维度做一个标准的 Soft-max 激活函数。输出的就是 \(\bar{a}\)(软分配权重),表示每个局部特征属于各个类的概率。
- 核心残差聚合 → VLAD core
- 公式部分: 计算残差 \((x_i - c_k)\) 并用前面的权重 \(\bar{a}\) 进行加权求和。
- 网络实现: 这里的输入有两个:一个是原始的局部特征 \(x\),另一个是刚算出来的权重 \(\bar{a}\)。网络在这个模块里,用 \(x\) 减去可学习的参数中心 ck,然后乘上 \(\bar{a}\),最后把属于同一类 \(k\) 的残差累加起来,得到初始的矩阵 \(V\)。
- 双重归一化 → Normalization
- Intra-normalization(内部归一化): 先对每个聚类中心的残差向量(长度为 \(D\))单独做 L2 归一化。这可以防止某个特征极其丰富的区域(比如树叶的杂乱纹理)产生过大的残差,主导整个向量。
- L2 normalization(全局归一化): 把 \(K\) 个向量拼接成 \((K \times D)\) 的超长向量后,再对整体做一次 L2 归一化。这使得最终输出的特征向量长度为 1,可以直接用简单的向量点乘或欧式距离来衡量两张图片的相似度。
