如何在opencv中找到单个图像的关键点之间的欧几里得距离

Posted

技术标签:

【中文标题】如何在opencv中找到单个图像的关键点之间的欧几里得距离【英文标题】:How to find euclidean distance between keypoints of a single image in opencv 【发布时间】:2014-10-24 08:10:08 【问题描述】:

我想得到图像中每个关键点的距离向量 d。距离向量应包含从该关键点到该图像中所有其他关键点的距离。 注意:使用 SIFT 找到关键点。

我对 opencv 很陌生。是否有 C++ 中的库函数可以让我的任务变得简单?

【问题讨论】:

那么,对于 N 个关键点,您将有 N 个距离向量到其他 N-1 个点?另外,你能解释一下你想要达到什么目的吗? 什么阻止了你?这看起来是一个简单的问题;两个循环。 对于 N 个关键点,我应该有 N 个距离向量。每个距离向量的形式为 d=d1,d2,d3,.d(n-1)。我正在尝试匹配同一图像中的两个关键点。 您的问题是如何访问关键点描述符或如何计算两个描述符之间的距离或两者兼而有之?!? 两者。关键点存储在 vector 数据类型中。 【参考方案1】:

如果你对位置距离不感兴趣,但描述符距离不感兴趣,你可以使用这个:

cv::Mat SelfDescriptorDistances(cv::Mat descr)

    cv::Mat selfDistances = cv::Mat::zeros(descr.rows,descr.rows, CV_64FC1);
    for(int keyptNr = 0; keyptNr < descr.rows; ++keyptNr)
    
        for(int keyptNr2 = 0; keyptNr2 < descr.rows; ++keyptNr2)
        
            double euclideanDistance = 0;
            for(int descrDim = 0; descrDim < descr.cols; ++descrDim)
            
                double tmp = descr.at<float>(keyptNr,descrDim) - descr.at<float>(keyptNr2, descrDim);
                euclideanDistance += tmp*tmp;
            

            euclideanDistance = sqrt(euclideanDistance);
            selfDistances.at<double>(keyptNr, keyptNr2) = euclideanDistance;
        

    
    return selfDistances;

这将为您提供一个 N x N 矩阵(N = 关键点数),其中 Mat_i,j = 关键点 i 和 j 之间的欧式距离。

使用此输入:

我得到了这些输出:

    标记关键点且距离小于 0.05 的图像

    对应于矩阵的图像。白色像素距离

备注:你可以在矩阵的计算中优化很多东西,因为距离是对称的!

更新:

这是另一种方法:

从你的聊天中我知道你需要 13GB 的内存来保存 41381 个关键点的距离信息(你尝试过)。如果您只想要 N 个最佳匹配项,请尝试以下代码:

// choose double here if you are worried about precision!
#define intermediatePrecision float
//#define intermediatePrecision double
// 
void NBestMatches(cv::Mat descriptors1, cv::Mat descriptors2, unsigned int n, std::vector<std::vector<float> > & distances, std::vector<std::vector<int> > & indices)

    // TODO: check whether descriptor dimensions and types are the same for both!

    // clear vector
    // get enough space to create n best matches
    distances.clear();
    distances.resize(descriptors1.rows);
    indices.clear();
    indices.resize(descriptors1.rows);

    for(int i=0; i<descriptors1.rows; ++i)
    
        // references to current elements:
        std::vector<float> & cDistances = distances.at(i);
        std::vector<int>  & cIndices = indices.at(i);
        // initialize:
        cDistances.resize(n,FLT_MAX);
        cIndices.resize(n,-1);  // for -1 = "no match found"

        // now find the 3 best matches for descriptor i:
        for(int j=0; j<descriptors2.rows; ++j)
        
            intermediatePrecision euclideanDistance = 0;
            for( int dim = 0; dim < descriptors1.cols; ++dim)
            
                intermediatePrecision tmp = descriptors1.at<float>(i,dim) - descriptors2.at<float>(j, dim);
                euclideanDistance += tmp*tmp;
            
            euclideanDistance = sqrt(euclideanDistance);

            float tmpCurrentDist = euclideanDistance;
            int tmpCurrentIndex = j;

            // update current best n matches:
            for(unsigned int k=0; k<n; ++k)
            
                if(tmpCurrentDist < cDistances.at(k))
                
                    int tmpI2 = cIndices.at(k);
                    float tmpD2 = cDistances.at(k);

                    // update current k-th best match
                    cDistances.at(k) = tmpCurrentDist;
                    cIndices.at(k) = tmpCurrentIndex;

                    // previous k-th best should be better than k+1-th best //TODO: a simple memcpy would be faster I guess.
                    tmpCurrentDist = tmpD2;
                    tmpCurrentIndex =tmpI2;
                
            


        
    


它计算第一个描述符的每个关键点与第二个描述符的 N 个最佳匹配。因此,如果您想对相同的关键点执行此操作,您将设置为descriptors1 = descriptors2 ion 您的呼叫,如下所示。 记住:该函数不知道两个描述符集是相同的,因此第一个最佳匹配(或至少一个)将始终是距离为 0 的关键点本身!如果使用结果,请记住这一点!

以下是生成与上述类似的图像的示例代码:

int main()

    cv::Mat input = cv::imread("../inputData/MultiLena.png");

    cv::Mat gray;
    cv::cvtColor(input, gray, CV_BGR2GRAY);

    cv::SiftFeatureDetector detector( 7500 );
    cv::SiftDescriptorExtractor describer;

    std::vector<cv::KeyPoint> keypoints;

    detector.detect( gray, keypoints );

    // draw keypoints
    cv::drawKeypoints(input,keypoints,input);



    cv::Mat descriptors;
    describer.compute(gray, keypoints, descriptors);

    int n = 4;
    std::vector<std::vector<float> > dists;
    std::vector<std::vector<int> > indices;

    // compute the N best matches between the descriptors and themselves.
    // REMIND: ONE best match will always be the keypoint itself in this setting!
    NBestMatches(descriptors, descriptors, n, dists, indices);

    for(unsigned int i=0; i<dists.size(); ++i)
    
        for(unsigned int j=0; j<dists.at(i).size(); ++j)
        
            if(dists.at(i).at(j) < 0.05)
                cv::line(input, keypoints[i].pt, keypoints[indices.at(i).at(j)].pt, cv::Scalar(255,255,255) );
        
    

    cv::imshow("input", input);
    cv::waitKey(0);

    return 0;

【讨论】:

非常感谢这个米卡。我可以问一个疑问吗? Mat descr 的尺寸是多少?我有大小为 4300x128 [SIFT] 的描述符矩阵。当我尝试运行您的代码时,我在运行时出现内存不足异常。 很抱歉听到这个消息。尺寸应该是正确的,但我不确定 SIFT 描述符元素是否真的是浮点类型。虽然样本输出也是 SIFT...descr 维度:Row = keypt。列 = 描述符维度 我调试了代码,发现在创建零矩阵时出现异常。对于具有大量关键点的图像,当程序尝试创建大 NxN 矩阵时,会发生异常。 啊好吧...您的描述符2描述符距离垫应该使用 4300*4300*64 位 = 大约 140 MB。您可以使用 float 而不是 double 将值减半。通过假设对称垫和 0 对角线,您可以再次减半。 你是怎么得到输出的?您使用的是哪个 IDE,系统配置是什么?【参考方案2】:
    创建一个二维向量(大小为 NXN)--> std::vector&lt; std::vector&lt; float &gt; &gt; item; 创建 2 个 for 循环,直到您拥有的关键点数 (N)

    按照 a-Jays 的建议计算距离

    Point diff = kp1.pt - kp2.pt; float dist = std::sqrt( diff.x * diff.x + diff.y * diff.y );

    对每个关键点使用 push_back 将其添加到向量中 --> N 次。

【讨论】:

【参考方案3】:

keypoint 类有一个名为 pt 的成员,而 xy [点的 (x,y) 位置] 作为其自己的成员。

给定两个关键点kp1kp2,然后很容易计算欧几里得距离:

Point diff = kp1.pt - kp2.pt;
float dist = std::sqrt( diff.x * diff.x + diff.y * diff.y )

在你的情况下,这将是一个遍历所有关键点的双循环。

【讨论】:

以上是关于如何在opencv中找到单个图像的关键点之间的欧几里得距离的主要内容,如果未能解决你的问题,请参考以下文章

[OpenCV实战]12 使用深度学习和OpenCV进行手部关键点检测

使用 OpenCV 查找图像中两点之间的角度

OpenCV如何在使用单个静态图像时将速度矢量绘制为箭头

使用 OpenCV Cuda ORB 特征检测器

如何在不使用 numpy 或 zip 的情况下找到两个列表之间的欧几里得距离?

通过将单个查询图像与多个图像的列表匹配来存储关键点索引