3. 特征提取与匹配:颜色、纹理、深度特征与匹配方法

大家好,我是老陈。今天咱们聊聊目标跟踪里最核心的一环——特征提取与匹配。说白了,就是让机器知道它盯着的那个目标长什么样,以及怎么在下一帧里把它认出来。

我刚开始做嵌入式视觉那会儿,总觉得特征提取就是调个库函数的事。后来在项目里栽过跟头,才明白这里面的门道有多深。嗯,咱们一个一个来看。

3.1 颜色特征:直方图

颜色特征是最直观的。你想想看,一个红色的小球在绿色草地上跑,人眼一眼就能盯住。机器也一样,它通过颜色直方图来记住目标的颜色分布。

直方图怎么做? 就是把图像从RGB转到HSV空间,然后统计H(色调)和S(饱和度)通道的分布。为什么要转HSV?因为RGB受光照影响太大,而HSV更稳定。

核心要点: 颜色直方图对旋转、缩放不敏感,但对光照变化很敏感。我建议在室外场景里,把直方图的bin数设小一点(比如16×16),这样能抗一点光照干扰。

我的经验: 在无人机跟踪项目中,我遇到过目标颜色和背景颜色相近的情况。这时候单纯靠颜色直方图,跟踪器很容易跟丢。我的做法是:把颜色直方图和位置信息结合起来,给靠近目标中心的像素更高权重。

// 颜色直方图计算示例(简化版)
// 输入:HSV图像,输出:2D直方图
void calcColorHistogram(Mat& hsvImg, Mat& hist) {
    int hbins = 16, sbins = 16;
    int histSize[] = {hbins, sbins};
    float hranges[] = {0, 180};
    float sranges[] = {0, 256};
    const float* ranges[] = {hranges, sranges};
    int channels[] = {0, 1};
    
    calcHist(&hsvImg, 1, channels, Mat(), 
             hist, 2, histSize, ranges, true, false);
    normalize(hist, hist, 0, 1, NORM_MINMAX);
}

3.2 纹理特征:HOG 与 LBP

颜色靠不住的时候,就得看纹理了。纹理特征描述的是图像的局部结构信息,说白了就是「这块区域的明暗变化模式」。

HOG(方向梯度直方图)

HOG的核心思想:物体的外观和形状,可以通过局部梯度方向分布来描述。它把图像分成小格子(cell),在每个格子里统计梯度方向,然后归一化。

HOG的坑在哪里? 计算量大!在嵌入式设备上跑HOG,帧率经常掉到个位数。我建议:

  • 把cell size设大一点(比如8×8改成16×16)
  • 只计算目标区域附近的HOG,别全图算
  • 用积分图加速梯度计算

注意: HOG对遮挡很敏感。如果目标被遮挡超过30%,HOG特征会剧烈变化。我曾经在行人跟踪项目里吃过这个亏——目标被树挡了一半,HOG匹配直接崩了。

LBP(局部二值模式)

LBP比HOG轻量得多。它的原理很简单:把每个像素和它周围的像素比较,大于中心像素的记为1,否则记为0,形成一个二进制数。

我个人习惯用圆形LBP(Circular LBP),因为它对旋转有更好的鲁棒性。在嵌入式平台上,LBP的运算速度比HOG快3-5倍。

// 基本LBP计算
Mat calcLBP(Mat& src) {
    Mat dst = Mat::zeros(src.size(), CV_8UC1);
    for(int i=1; i<src.rows-1; i++) {
        for(int j=1; j<src.cols-1; j++) {
            uchar center = src.at<uchar>(i,j);
            uchar code = 0;
            code |= (src.at<uchar>(i-1,j-1) > center) << 7;
            code |= (src.at<uchar>(i-1,j)   > center) << 6;
            code |= (src.at<uchar>(i-1,j+1) > center) << 5;
            code |= (src.at<uchar>(i,j+1)   > center) << 4;
            code |= (src.at<uchar>(i+1,j+1) > center) << 3;
            code |= (src.at<uchar>(i+1,j)   > center) << 2;
            code |= (src.at<uchar>(i+1,j-1) > center) << 1;
            code |= (src.at<uchar>(i,j-1)   > center) << 0;
            dst.at<uchar>(i,j) = code;
        }
    }
    return dst;
}

3.3 深度特征:CNN特征

这几年深度学习火起来之后,CNN特征成了主流。它通过多层卷积网络自动学习出对跟踪任务最有利的特征表示。

为什么CNN特征强? 因为它能同时捕捉颜色、纹理、形状、语义信息。浅层网络关注边缘和纹理,深层网络关注物体部件和整体结构。

但注意,在嵌入式设备上跑CNN特征提取,有两个大坑:

  • 模型太大: 一个VGG16有138M参数,嵌入式设备根本跑不动
  • 推理太慢: 即使是用MobileNet,在树莓派上也只能跑5-10fps

我的建议: 在嵌入式平台上,用轻量级网络(如MobileNetV3、ShuffleNetV2)提取特征。如果算力实在不够,可以只在第一帧用CNN提取一次特征模板,后续帧用传统方法匹配。这叫「混合特征策略」。

3.4 特征匹配方法

特征提取完了,怎么比?常用的有两种:余弦相似度和欧氏距离。

方法 公式 特点 适用场景
余弦相似度 cos(θ) = (A·B) / (||A|| ||B||) 关注方向,忽略幅度 光照变化大的场景
欧氏距离 d = √(Σ(Ai-Bi)²) 关注绝对差异 特征已经归一化的场景

选哪个? 我个人习惯:如果特征向量已经做过L2归一化,用欧氏距离和余弦相似度是等价的。但如果没归一化,余弦相似度更稳定。

避坑指南: 我曾经在特征匹配时直接用欧氏距离,结果光照一变,匹配分数剧烈波动。后来改成余弦相似度,稳定性提升了不少。记住:特征匹配前,一定要做归一化处理。

3.5 知识体系总览

下面这张图总结了本章的核心逻辑。你可以看到,特征提取和匹配是一个完整的链路:从原始图像出发,经过不同的特征提取方法,最后通过匹配方法找到目标。

特征提取与匹配知识体系 输入图像 颜色特征(直方图) 纹理特征(HOG/LBP) 深度特征(CNN) 特征向量 A 特征向量 B 特征向量 C 特征匹配:余弦相似度 / 欧氏距离 匹配结果

3.6 实战建议

最后,给大家几个实战中的小建议:

  1. 特征融合: 别只用一种特征。我习惯把颜色直方图和LBP特征拼接起来,效果比单独用任何一种都好。
  2. 自适应权重: 光照好的时候,颜色特征权重高一点;光照差的时候,纹理特征权重高一点。这个权重可以动态调整。
  3. 缓存特征模板: 在嵌入式设备上,每次重新提取特征很费时。我建议每隔5-10帧更新一次特征模板,而不是每帧都更新。
  4. 阈值设置: 匹配分数低于0.6(余弦相似度)时,基本可以认为目标丢失了。这时候需要启动重检测机制。

曾经踩过的坑: 我在一个安防监控项目里,用了纯颜色特征做跟踪。结果一到傍晚,光照变化剧烈,跟踪器直接放飞自我了。后来改成颜色+纹理特征融合,才稳住了。记住:单一特征在复杂环境下就是定时炸弹。

好了,特征提取与匹配就聊到这儿。记住一句话:没有最好的特征,只有最合适的特征组合。下一章咱们聊聊目标检测和跟踪的协同工作,到时候见。

可可精品资料库