一开始想使用nmslib的,conan中引用后发现nmslib的C++示例代码太难写了,改为使用flann去实现,opencv中已纳入了flann,使用起来更方便了。
下面这段代码实现了使用 KD-Tree 算法进行 K 近邻搜索的函数 knn_nmslib,其中调用了 normalizeFeatures 函数用于对特征向量进行标准化处理。最终目的是计算每个特征的k近邻是谁,余弦距离是多少。
具体来说,该函数输入一个特征向量集合 feats,输出每个特征向量的前 k 个最近邻索引和对应的距离,存储在一个 vector<vector> 和一个 vector<vector> 中,分别表示索引和距离。
在函数中,首先复制一份输入特征向量 feats 到 feats_copy,然后对 feats_copy 中的每个特征向量进行标准化处理,即将其 L2 范数归一化为 1。标准化后,将 feats_copy 转换为一个 cv::Mat 对象 features,作为 K 近邻搜索的数据集。
接着,使用 cv::flann::Index 创建一个 KD-Tree 索引,使用欧几里得距离作为距离度量。然后,将 features 作为查询数据进行 K 近邻搜索,返回每个查询数据的前 k 个最近邻的索引和距离,存储在 cv::Mat 对象 indices 和 dists 中。
最后,对于每个查询数据,将其前 k 个最近邻的索引和距离存储到 neighbours 中,同时计算每个最近邻之间的余弦距离,并保存到 distance22 中,方便后续的计算。函数最后释放 KD-Tree 索引的内存。
c#include <opencv2/opencv.hpp>
#include <opencv2/flann.hpp>
#include <utility>
float findCosineSimilarity(const std::vector<float>& source_representation,
const std::vector<float>& test_representation) {
return 1.0f - findCosineDistance(source_representation, test_representation);
}
float findCosineDistance(const std::vector<float>& source_representation,
const std::vector<float>& test_representation) {
cv::Mat source(source_representation);
cv::Mat test(test_representation);
double dist = 1.0 - source.dot(test) / (cv::norm(source) * cv::norm(test, cv::NORM_L2) + 0.000001);
return static_cast<float>(dist);
}
void normalizeFeatures(std::vector<std::vector<float>>& features) {
for (auto& feature : features) {
cv::Mat mat(feature);
double norm = cv::norm(mat, cv::NORM_L2);
mat = mat / norm;
for (int i = 0; i < mat.cols; ++i) {
feature[i] = mat.at<float>(0, i);
}
}
}
void knn_nmslib(const std::vector<std::vector<float>>& feats,
std::vector<std::pair<std::vector<int>, std::vector<float>>>& neighbours,
int k) {
// 复制一份feats出来
std::vector<std::vector<float>> feats_copy(feats);
// 标准化
normalizeFeatures(feats_copy);
cv::Mat features(feats_copy.size(), feats_copy[0].size(), CV_32F);
features.forEach<float>(
[&](float& pixel, const int* position) -> void { pixel = feats_copy[position[0]][position[1]]; });
// 设置搜索算法为KD-Tree,使用欧几里得距离
cv::flann::Index flannIndex(features, cv::flann::KDTreeIndexParams(4), cvflann::FLANN_DIST_EUCLIDEAN);
// 设置查询数据
cv::Mat query = features;
// 进行k近邻搜索
cv::Mat indices(query.rows, k, CV_32S);
cv::Mat dists(query.rows, k, CV_32F);
flannIndex.knnSearch(query, indices, dists, k, cv::flann::SearchParams(256));
std::map<std::pair<int, int>, float> distance22;
float dist_tmp;
for (int i = 0; i < query.rows; i++) {
std::vector<int> index;
std::vector<float> dist;
for (int j = 0; j < k; j++) {
index.push_back(indices.at<int>(i, j));
// 查询distance22中是否存在(i,j)
if (distance22.find(std::make_pair(i, indices.at<int>(i, j))) == distance22.end()) {
dist_tmp = findCosineDistance(feats[i], feats[indices.at<int>(i, j)]);
distance22[std::make_pair(i, indices.at<int>(i, j))] = dist_tmp;
distance22[std::make_pair(indices.at<int>(i, j), i)] = dist_tmp;
} else {
dist_tmp = distance22[std::make_pair(i, indices.at<int>(i, j))];
}
dist.push_back(dist_tmp); // 距离保存
}
neighbours.push_back(std::make_pair(index, dist));
}
//释放内存
flannIndex.release();
}
本文作者:Dong
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC。本作品采用《知识共享署名-非商业性使用 4.0 国际许可协议》进行许可。您可以在非商业用途下自由转载和修改,但必须注明出处并提供原作者链接。 许可协议。转载请注明出处!