OpenCV - Surf算法 - 大量误报

3
我正在学习OpenCV,并开始探索SURF算法进行图像匹配。我通过修改Microsoft Windows 7的默认图像库创建了一个样本图像库。
每个图像在同一个文件夹中都有旋转、缩放、模糊和扭曲版本。
我的代码用于查找匹配的图像如下所示。从代码中可以看出,距离是通过线dis/objectDescriptors->total测量的,进一步相似性则通过100-(dis/objectDescriptors->total)*100计算。
不幸的是,这给了我一些奇怪的误报。例如,它将image1与完全不同的image2匹配(85%相似度),但只会显示与图像1稍微模糊的版本60%的相似度。
我如何消除误报?
以下代码来自网站:http://opencvuser.blogspot.in/2012/07/surf-source-code-part-2.html
#include <cv.h>
#include <highgui.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h> 
#include <iostream>
#include <vector>

using namespace std;

static double dis=0;//For calculating the distance


IplImage *image = 0;

double
compareSURFDescriptors( const float* d1, const float* d2, double best, int length )
{
    double total_cost = 0;
    assert( length % 4 == 0 );
    for( int i = 0; i < length; i += 4 )
    {
        double t0 = d1[i] - d2[i];
        double t1 = d1[i+1] - d2[i+1];
        double t2 = d1[i+2] - d2[i+2];
        double t3 = d1[i+3] - d2[i+3];
        total_cost += t0*t0 + t1*t1 + t2*t2 + t3*t3;

      if( total_cost > best )
            break;
    }
    return total_cost;
}


int
naiveNearestNeighbor( const float* vec, int laplacian,
                      const CvSeq* model_keypoints,
                      const CvSeq* model_descriptors )
{
    int length = (int)(model_descriptors->elem_size/sizeof(float));
    int i, neighbor = -1;
    double d, dist1 = 1e6, dist2 = 1e6;
    CvSeqReader reader, kreader;
    cvStartReadSeq( model_keypoints, &kreader, 0 );
    cvStartReadSeq( model_descriptors, &reader, 0 );

    for( i = 0; i < model_descriptors->total; i++ )
    {
        const CvSURFPoint* kp = (const CvSURFPoint*)kreader.ptr;
        const float* mvec = (const float*)reader.ptr;
     CV_NEXT_SEQ_ELEM( kreader.seq->elem_size, kreader );
        CV_NEXT_SEQ_ELEM( reader.seq->elem_size, reader );
        if( laplacian != kp->laplacian )
            continue;
        d = compareSURFDescriptors( vec, mvec, dist2, length );

        if( d < dist1 )
        {
            dist2 = dist1;
            dist1 = d;
            neighbor = i;
        }
        else if ( d < dist2 )
            dist2 = d;
    }
dis=dis+dist1;

/*We are finding the distance from every descriptor of probe image to every descriptor of the galley image. Finally in the findpairs function, we divide this distance with the total no. of descriptors to get the average of all the distances
*/
    if ( dist1 < 0.6*dist2 )
        return neighbor;
    return -1;
}

void
findPairs( const CvSeq* objectKeypoints, const CvSeq* objectDescriptors,
           const CvSeq* imageKeypoints, const CvSeq* imageDescriptors, vector<int>& ptpairs )
{
    int i;
    CvSeqReader reader, kreader;
    cvStartReadSeq( objectKeypoints, &kreader );
    cvStartReadSeq( objectDescriptors, &reader );
    ptpairs.clear();

    for( i = 0; i < objectDescriptors->total; i++ )
    {
        const CvSURFPoint* kp = (const CvSURFPoint*)kreader.ptr;
        const float* descriptor = (const float*)reader.ptr;
        CV_NEXT_SEQ_ELEM( kreader.seq->elem_size, kreader );
        CV_NEXT_SEQ_ELEM( reader.seq->elem_size, reader );
        int nearest_neighbor = naiveNearestNeighbor( descriptor, kp->laplacian, imageKeypoints, imageDescriptors);
//For every descriptor, we are trying to find it's nearest neighbour in the probe image
        if( nearest_neighbor >= 0 )
        {
            ptpairs.push_back(i);
            ptpairs.push_back(nearest_neighbor);
        }
    }

printf("\n%lf\n",(dis/objectDescriptors->total));////Here's where I am outputting the distance between the images
/*Dileep: If you are using this for recognition, write this distance to a file along with the name of the image you are matching against. After doing this for several images, you can then sort them in ascending order to find the best possible match - the one with the smallest distance. Here, I am outputting the distance to stdout
*/
}

int main(int argc, char** argv)
{
    const char* object_filename = argc == 3 ? argv[1] : "box.png";
    const char* scene_filename = argc == 3 ? argv[2] : "box_in_scene.png";
//Dileep:When you are excuting the object file, please write Command:./objectfile probe_image Gallery_image
/*Dileep:
Probe_image - This is the image for which you need to find the match
Gallery_image - This is one of the set of images, you use for matching

You keep the same probe image same, repeatedly changing the gallery image and outputting the distance in the format
<Gallery_name distance> into a file
Finally you can sort the distances in ascending order. And the one with the shortest distance - You can output it's name as the best possible match

It may become tedious to continually write the same command multiple times, changing the gallery file name. Try to use shell script with a for loop
*/
    CvMemStorage* storage = cvCreateMemStorage(0);





    IplImage* object = cvLoadImage( object_filename, CV_LOAD_IMAGE_GRAYSCALE );
    IplImage* image = cvLoadImage( scene_filename, CV_LOAD_IMAGE_GRAYSCALE );
    if( !object || !image )
    {
        fprintf( stderr, "Can not load %s and/or %s\n"
            "Usage: find_obj [<object_filename> <scene_filename>]\n",
            object_filename, scene_filename );
        exit(-1);
    }

    CvSeq *objectKeypoints = 0, *objectDescriptors = 0;
    CvSeq *imageKeypoints = 0, *imageDescriptors = 0;
    int i;
    CvSURFParams params = cvSURFParams(500, 1);

    double tt = (double)cvGetTickCount();
    cvExtractSURF( object, 0, &objectKeypoints, &objectDescriptors, storage, params );
    printf("Object Descriptors: %d\n", objectDescriptors->total);
    cvExtractSURF( image, 0, &imageKeypoints, &imageDescriptors, storage, params );
    printf("Image Descriptors: %d\n", imageDescriptors->total);
    tt = (double)cvGetTickCount() - tt;
    printf( "Extraction time = %gms\n", tt/(cvGetTickFrequency()*1000.));




    vector<int> ptpairs;

    findPairs( objectKeypoints, objectDescriptors, imageKeypoints, imageDescriptors, ptpairs );


    return 0;
}
3个回答

6
请记住通常的流程如下:
  1. 从两张图像中提取特征。
  2. 使用邻居比率(像您所做的那样)或交叉匹配(@user2746401)计算假设对应点。
  3. 找到一组一致的对应点,支持图像之间的某些几何关系。
你已经完成了步骤1和2,但缺少步骤3。请注意,假设对应点非常嘈杂。您会得到许多对应点,其中许多是错误的,但有些是正确的。仅通过绘图很难确定它们是否被正确计算。因此,看起来似乎有些奇怪的对应点是正常的。
要执行第3步,在您的情况下,您可以使用RANSAC找到单应性,就像@user3481173所说的那样。这很容易,因为OpenCV已经提供了同时执行两个功能的cvFindHomography函数。在您处理平面图像的透视变换时,单应性应该很有效。
顺便说一下,将来如果您使用OpenCV的C++ API,这将更加容易。相同的代码可能只占用您目前代码行数的一半。

你的答案是100%有效的。不幸的是,我对单应性和RANSAC的知识几乎为零。这就是我在寻找一些捷径的原因。请查看我在其他答案中的评论,了解我如何几乎解决了这个问题。 - milan m
1
您可以通过更严格的计算方法(如邻居比率左右匹配)获取较少的虚假对应关系。但是在一般情况下,您将永远无法获得100%完美的推测对应关系。这是因为所谓的“感知混淆”:互不相关的点可能在特征描述符上看起来完全相同。毕竟,它们是从图像的小补丁计算出来的。重要的是您总需要一些额外的步骤。 - ChronoTrigger
看看这个例子:http://docs.opencv.org/doc/tutorials/features2d/feature_homography/feature_homography.html - ChronoTrigger
谢谢提供链接。我之前已经查看过了。然而,我被分配了计算相似度百分比的任务。那个链接没有任何计算方法。谢谢。 - milan m

1
抱歉,我不认为有“一个”答案可以回答你的问题。我可以提供一些建议来降低误报率:
  • 进行交叉匹配。在这种方案中,您可以在图像A中找到特征的最近匹配并在图像B中匹配该特征,然后在图像A中找到特征的最近邻居。如果这些匹配都是双向的,则将其视为匹配。

  • 进行比率匹配。在此方案中,您可以跟踪特征描述符之间的最近距离和第二最近距离。如果最近距离与第二最近距离的比率满足一定阈值(例如0.8),则将其保留为良好的匹配项。

它们应该使您的特征匹配仅匹配图像之间的“好”特征。一旦您有了这些良好的特征匹配项,您就可以看到哪些图像之间的特征平均距离最佳。您可以设置阈值(正如您现在所做的)或再次进行比率测试,以确保选择的图像比其他图像更好。

如果您想要特征匹配来进行图像检索,则可以搜索有关此主题的论文...这是一个非常开放的问题,所以继续尝试吧!


我认为有一个答案。在几个小时前研究这个问题时,我发现假阳性总是出现在第二张图像的探测器非常少的情况下。因此,我在findpairs函数中交换了位置并实施了它,并发现它可以正常工作。 - milan m
1
很不幸,现在在某些情况下它会给我错误的负面结果。因此,问题仍然悬而未决。 - milan m

1

我认为只用简单的对配对进行匹配,很难将两个图像完全匹配。这样做的成本是巨大的。这就是为什么您可能需要考虑使用RANSAC的原因。

我可以推荐Matlab中Zisserman和Kovesi的示例。

您将需要Peter Kovesi的RANSAC函数。


请问,“做这件事的成本是巨大的”是什么意思?我能在不到一秒钟的时间内完成100张图像的匹配。 - milan m
1
假设你有一张图像有2000个关键点,而第二张图像只有1800个关键点。现在尝试将它们匹配以找到适合的对。每个关键点都有128个维度。这意味着你需要执行18002000128次乘法和减法来完成任务。 - Yonatan Simson

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