如何结合SIFT描述符与KNN算法实现OpenCV图像分类?
用OpenCV结合SIFT与KNN实现图像分类
因为单张图像的SIFT特征是多个变长的描述子向量,而KNN要求每个样本是固定长度的特征向量,核心解决思路是用**词袋模型(BoW)**把单张图像的所有SIFT描述子转换成全局固定长度特征,再输入KNN进行训练和分类。具体步骤如下:
一、构建视觉词汇表
先从所有训练图像的SIFT描述子中聚类出一组"视觉单词",作为特征基准:
- 收集所有训练图像的SIFT描述子,合并成一个大矩阵
- 用K-Means对这些描述子聚类,聚类数量就是视觉词汇的大小(比如100、200,可根据数据集调整)
二、将图像转换为BoW特征向量
对每张图像,统计其SIFT描述子匹配到各个视觉单词的次数,得到固定长度向量:
- 提取图像的SIFT描述子
- 对每个描述子,找到距离最近的视觉单词
- 统计每个单词的出现频次,归一化后作为该图像的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); }
对你现有代码的修正说明
- 原SIFT代码中
i变量未定义,且vconcat(descriptors, descriptors, descriptors)是错误操作,应将单张图像的描述子追加到全局描述子矩阵中 - 原KNN代码直接用图像像素展平作为特征,替换为上述BoW特征即可适配SIFT特征
内容的提问来源于stack exchange,提问作者cvstudent
相关产品推荐
相关产品推荐

