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

5

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

我对opencv还不太熟悉。在C ++中是否有库函数可以使我的任务变得更加容易?


2
什么阻止了你?这看起来是一个简单的问题;两个循环。 - MSalters
对于N个关键点,我应该有N个距离向量。每个距离向量的形式为d={d1,d2,d3,.d(n-1)}。我正在尝试匹配同一图像中的两个关键点。 - sparkle
2
你的问题是如何访问关键点描述符还是如何计算两个描述符之间的距离,还是两者都有?!? - Micka
关于编程的相关内容:将关键点存储在vector<keypoint>数据类型中。 - sparkle
你对关键点的欧几里得距离感兴趣,是指关键点的位置还是描述符(关键点邻域的相似性)的欧几里得距离?我最初考虑的是描述符,但再次阅读您的问题后,可能是位置距离(这由a-Jays回答)。 - Micka
显示剩余4条评论
3个回答

11

如果您对位置距离不感兴趣而是对描述符距离感兴趣,您可以使用以下方法:

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之间的欧几里得距离。

对于这个输入:

enter image description here

我得到了以下输出:

  1. 标记有距离小于0.05的关键点的图像。

enter image description here

  1. 与矩阵相对应的图像。白色像素是dist < 0.05。

enter image description here

备注:您可以在计算矩阵时优化许多内容,因为距离是对称的!

更新:

从您的聊天记录中,我知道您需要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,如下所示。记住:函数不知道两个描述符集是相同的,因此第一个最佳匹配(或至少一个)将始终是关键点本身,距离为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;
}

1
很抱歉听到这个。尺寸应该是正确的,但我不确定SIFT描述符元素是否真的是浮点类型。虽然样本输出也是SIFT...描述符维度:行=关键点。列=描述符维度。 - Micka
@Micka,我无法使您在答案中提供的代码正常工作,它只能显示找到的关键点,但不能在相同区域之间绘制任何线条? - MLMLTL
@MLMLTL 如果你不改变代码,它是否可以工作(包括绘制的线条)?你想要绘制哪些关键点? - Micka
@Micka 我最后使用了一个不同于你的图像,但类似于你的图像,它的一些部分被复制了,但还是没有成功。我只能将原始内容进行复制。 - MLMLTL
更改后的代码是否有效?使用原始图像可以正常工作吗? - Micka
显示剩余7条评论

2

keypoint类有一个成员称为pt,它本身具有xy作为其自己的成员 [表示点的(x,y)位置]。

给定两个关键点kp1kp2,那么计算欧几里得距离就很容易了:

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

在您的情况下,需要使用双重循环来迭代所有关键点。

2
  1. 创建一个二维向量(大小为NXN)--> std::vector< std::vector< float > > item;
  2. 创建两个for循环,循环次数为您拥有的关键点数(N)
  3. 按照a-Jays的建议计算距离

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接