You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何结合SIFT描述符与KNN算法实现OpenCV图像分类?

用OpenCV结合SIFT与KNN实现图像分类

因为单张图像的SIFT特征是多个变长的描述子向量,而KNN要求每个样本是固定长度的特征向量,核心解决思路是用**词袋模型(BoW)**把单张图像的所有SIFT描述子转换成全局固定长度特征,再输入KNN进行训练和分类。具体步骤如下:

一、构建视觉词汇表

先从所有训练图像的SIFT描述子中聚类出一组"视觉单词",作为特征基准:

  1. 收集所有训练图像的SIFT描述子,合并成一个大矩阵
  2. 用K-Means对这些描述子聚类,聚类数量就是视觉词汇的大小(比如100、200,可根据数据集调整)

二、将图像转换为BoW特征向量

对每张图像,统计其SIFT描述子匹配到各个视觉单词的次数,得到固定长度向量:

  1. 提取图像的SIFT描述子
  2. 对每个描述子,找到距离最近的视觉单词
  3. 统计每个单词的出现频次,归一化后作为该图像的BoW特征

三、用BoW特征训练KNN并分类

把BoW向量作为KNN的输入样本,搭配对应图像标签完成训练;测试时对测试图像重复BoW特征提取步骤,再用KNN预测类别


修正后的完整代码示例

1. 工具函数:提取单张图像的SIFT描述子

cv::Mat extractSIFTDescriptors(const cv::Mat& img) {
    cv::Ptr<cv::SiftFeatureDetector> detector = cv::SiftFeatureDetector::create();
    cv::Ptr<cv::SiftDescriptorExtractor> descriptor = cv::SiftDescriptorExtractor::create();
    std::vector<cv::KeyPoint> keypoints;
    cv::Mat descriptors;
    detector->detectAndCompute(img, cv::Mat(), keypoints, descriptors);
    return descriptors;
}

2. 构建视觉词汇表

// 假设trainImages是训练图像列表,trainLabels是对应标签
cv::Mat allDescriptors;
for (const auto& img : trainImages) {
    cv::Mat desc = extractSIFTDescriptors(img);
    if (!desc.empty()) {
        allDescriptors.push_back(desc);
    }
}

// K-Means聚类构建词汇表,这里设词汇大小为100
int vocabSize = 100;
cv::TermCriteria criteria(cv::TermCriteria::EPS + cv::TermCriteria::MAX_ITER, 10, 0.01);
cv::Mat vocab;
cv::kmeans(allDescriptors, vocabSize, vocab, criteria, 3, cv::KMEANS_RANDOM_CENTERS);

3. 生成BoW特征向量

cv::Mat getBoWFeature(const cv::Mat& descriptors, const cv::Mat& vocab) {
    cv::Mat bowFeature = cv::Mat::zeros(1, vocab.rows, CV_32F);
    if (descriptors.empty()) return bowFeature;

    // 对每个描述子,找到最近的视觉单词
    for (int i = 0; i < descriptors.rows; i++) {
        cv::Mat desc = descriptors.row(i);
        double minDist = DBL_MAX;
        int bestIdx = 0;
        for (int j = 0; j < vocab.rows; j++) {
            double dist = cv::norm(desc, vocab.row(j), cv::NORM_L2);
            if (dist < minDist) {
                minDist = dist;
                bestIdx = j;
            }
        }
        bowFeature.at<float>(0, bestIdx)++;
    }

    // 归一化特征
    bowFeature /= cv::sum(bowFeature)[0];
    return bowFeature;
}

// 生成所有训练样本的BoW特征
cv::Mat trainSamples;
for (const auto& img : trainImages) {
    cv::Mat desc = extractSIFTDescriptors(img);
    cv::Mat bow = getBoWFeature(desc, vocab);
    trainSamples.push_back(bow);
}

4. KNN训练与分类

// 转换标签格式(假设trainLabels是vector<int>)
cv::Mat labelsMat(trainLabels.size(), 1, CV_32S);
for (int i = 0; i < trainLabels.size(); i++) {
    labelsMat.at<int>(i, 0) = trainLabels[i];
}

// 训练KNN
cv::Ptr<cv::ml::KNearest> knn = cv::ml::KNearest::create();
knn->setAlgorithmType(cv::ml::KNearest::BRUTE_FORCE);
knn->train(trainSamples, cv::ml::ROW_SAMPLE, labelsMat);

// 测试图像分类
std::vector<float> predictions;
for (const auto& testImg : testImages) {
    cv::Mat desc = extractSIFTDescriptors(testImg);
    cv::Mat bow = getBoWFeature(desc, vocab);
    float response = knn->findNearest(bow, 1, cv::noArray());
    predictions.push_back(response);
}

对你现有代码的修正说明

  1. 原SIFT代码中i变量未定义,且vconcat(descriptors, descriptors, descriptors)是错误操作,应将单张图像的描述子追加到全局描述子矩阵中
  2. 原KNN代码直接用图像像素展平作为特征,替换为上述BoW特征即可适配SIFT特征

内容的提问来源于stack exchange,提问作者cvstudent

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 02:52:56