草庐IT

c++ - 使用 OpenCV 特征检测器匹配热成像/非热成像图像

coder 2023-06-03 原文

我目前正在开发可以匹配使用热像仪从固定点拍摄的红外和非红外图像的软件。

用例如下:使用红外热像仪和标准相机使用固定点的三脚架拍摄照片。拍摄完照片后,摄影师想要匹配来自每台相机的图像。在某些情况下,由于不需要其他图像类型,因此仅使用一台相机拍摄图像。是的,可以使用时间戳来匹配图像,但最终用户要求使用计算机视觉来匹配它们。

我看过 StackOverflow 上的其他图像匹配帖子——他们经常关注使用直方图匹配和特征检测器。直方图匹配在这里不是一个选项,因为我们无法在两种图像类型之间匹配颜色。因此,我开发了一个进行特征检测的应用程序。除了标准特征检测之外,我还添加了一些逻辑,说明如果两个关键点不在彼此的特定边距内,则它们无法匹配(查询图像最左侧的关键点无法匹配上的关键点)候选图像的最右侧)——这个过程发生在下面代码的第 3 阶段。

为了让您了解当前的输出,这里生成了一个有效和无效的匹配——请注意热成像图像在左侧 .我的目标是提高匹配过程的准确性。

有效匹配:


无效匹配:


这是代码:

    // for each candidate image specified on the command line, compare it against the query image
        Mat img1 = imread(argv[1], CV_LOAD_IMAGE_GRAYSCALE); // loading query image
        for(int candidateImage = 0; candidateImage < (argc - 2); candidateImage++) {
            Mat img2 = imread(argv[candidateImage + 2], CV_LOAD_IMAGE_GRAYSCALE); // loading candidate image
            if(img1.empty() || img2.empty())
            {
                printf("Can't read one of the images\n");
                return -1;
            }

            // detecting keypoints
            SiftFeatureDetector detector;
            vector<KeyPoint> keypoints1, keypoints2;
            detector.detect(img1, keypoints1);
            detector.detect(img2, keypoints2);

            // computing descriptors
            SiftDescriptorExtractor extractor;
            Mat descriptors1, descriptors2;
            extractor.compute(img1, keypoints1, descriptors1);
            extractor.compute(img2, keypoints2, descriptors2);

            // matching descriptors
            BFMatcher matcher(NORM_L1);
            vector< vector<DMatch> > matches_stage1;
            matcher.knnMatch(descriptors1, descriptors2, matches_stage1, 2);

            // use nndr to eliminate weak matches
            float nndrRatio = 0.80f;
            vector< DMatch > matches_stage2;
            for (size_t i = 0; i < matches_stage1.size(); ++i)
            {
                if (matches_stage1[i].size() < 2)
                    continue;
                const DMatch &m1 = matches_stage1[i][0];
                const DMatch &m2 = matches_stage1[i][3];
                if(m1.distance <= nndrRatio * m2.distance)
                    matches_stage2.push_back(m1);
            }

            // eliminate points which are too far away from each other
            vector<DMatch> matches_stage3;
            for(int i = 0; i < matches_stage2.size(); i++) {
                Point queryPt = keypoints1.at(matches_stage2.at(i).queryIdx).pt;
                Point trainPt = keypoints2.at(matches_stage2.at(i).trainIdx).pt;

                // determine the lowest number here
                int lowestXAxis;
                int greaterXAxis;
                if(queryPt.x <= trainPt.x) { lowestXAxis = queryPt.x; greaterXAxis = trainPt.x; }
                else { lowestXAxis = trainPt.x; greaterXAxis = queryPt.x; }

                int lowestYAxis;
                int greaterYAxis;
                if(queryPt.y <= trainPt.y) { lowestYAxis = queryPt.y; greaterYAxis = trainPt.y; }
                else { lowestYAxis = trainPt.y; greaterYAxis = queryPt.y; }

                // determine if these points are acceptable
                bool acceptable = true;
                if( (lowestXAxis + MARGIN) < greaterXAxis) { acceptable = false; }
                if( (lowestYAxis + MARGIN) < greaterYAxis) { acceptable = false; }
                if(acceptable == false) { continue; }

                //// it's acceptable -- provide details, perform input
                matches_stage3.push_back(matches_stage2.at(i));
            }

            // output how many individual matches were found for this training image
            cout << "good matches found for candidate image # " << (candidateImage+1) << " = " << matches_stage3.size() << endl;

我用过 this sites code举个例子。我遇到的问题是特征检测不可靠,而且我似乎错过了 NNDR 比率的目的。我知道我正在为查询图像中的每个点找到 K 个可能的匹配项,并且我有 K = 2。但我不明白示例代码中这部分的目的:
vector< DMatch > matches_stage2;
for (size_t i = 0; i < matches_stage1.size(); ++i)
{
    if (matches_stage1[i].size() < 2)
        continue;
    const DMatch &m1 = matches_stage1[i][0];
    const DMatch &m2 = matches_stage1[i][1];
    if(m1.distance <= nndrRatio * m2.distance)
        matches_stage2.push_back(m1);
}

关于如何进一步改进这一点的任何想法?任何建议将一如既往地受到赞赏。

最佳答案

您当前使用的验证

第一阶段

首先说一下代码中你不明白的部分。这个想法是只保留“强匹配”。其实,你的电话knnMatch对于每个描述符,找到关于欧几里德距离“L2”的最佳两个对应关系 (*) .这并不意味着这些在现实中是很好的匹配,而只是那些特征点非常相似。

现在让我尝试解释您的验证,仅考虑图像 A 中的一个特征点(它推广到所有特征点):

  • 您将这一点的描述符与图像 B
  • 进行匹配
  • 你得到关于欧几里德距离的两个最佳对应(即你得到图像 B 中两个最相似的点)
  • 如果您的点到最佳对应的距离远小于您的点到次佳对应的距离,则您认为这是一个很好的匹配。换句话说,图像 B 中只有一个点与图像 A 中的点非常相似(即具有很小的欧几里德距离)。
  • 如果两个匹配项太相似(即 !(m1.distance <= nndrRatio * m2.distance) ),那么您无法真正区分它们并且您不考虑匹配项。

  • 正如您可能已经观察到的那样,此验证有一些主要弱点:
  • 首先,如果您从 knnMatch 获得最佳匹配两者都非常糟糕,那么无论如何都可以接受其中最好的。
  • 它不考虑几何。因此,图像左侧远处的点可能与右侧远处的点相似,即使实际上它们显然不匹配。

  • * 编辑:使用 SIFT,您可以使用浮点 vector 描述图像中的每个特征点。通过计算两个 vector 之间的欧几里德距离,您就知道它们有多相似。如果两个 vector 完全相同,则距离为零。距离越小,点越相似。但这不是几何形状:图像左侧的点可能看起来与右侧的点相似。所以你首先找到看起来相似的点对(即“A中的这个点看起来与B中的这个点相似,因为它们的特征向量之间的欧几里德距离很小”)然后你需要验证这个匹配是连贯的(即“这些相似点可能实际上是相同的,因为它们都在我图像的左侧”或“它们看起来相似,但这是不连贯的,因为我知道它们必须位于图像的同一侧他们没有”)。

    第二阶段

    您在第二阶段所做的事情很有趣,因为它考虑了几何:知道两个图像都是从同一点(或几乎同一点?)拍摄的,您消除了两个图像中不在同一区域的匹配项。

    我看到的问题是,如果两个图像不是在完全相同的位置以完全相同的角度拍摄的,那么它将无法工作。

    进一步改进验证的提议

    我个人会在第二阶段工作。尽管两个图像不一定完全相同,但它们描述了相同的场景。您可以利用它的几何形状。

    这个想法是你应该能够找到从第一个图像到第二个图像的转换(即一个点从图像 A 移动到图像 B 的方式实际上与所有点移动的方式相关联)。在你的情况下,我敢打赌,一个简单的 homography 是适应的。

    这是我的提议:
  • 使用 knnMatch 计算匹配,并保留第 1 阶段(您可能想稍后尝试删除它并观察结果)
  • 使用 cv::findHomography 计算这些匹配之间可能的最佳单应变换(选择 RANSAC 算法)。
  • findHomography有一个 mask输出将为您提供内点(即用于计算单应变换的匹配)。

  • 内点很可能是很好的匹配,因为在几何上会有连贯性。

    编辑:我刚刚找到了一个使用 findHomography 的例子here .

    关于c++ - 使用 OpenCV 特征检测器匹配热成像/非热成像图像,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16597324/

    有关c++ - 使用 OpenCV 特征检测器匹配热成像/非热成像图像的更多相关文章

    1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

      我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

    2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

      我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

    3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

      类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

    4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

      很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

    5. ruby - 在 Ruby 中使用匿名模块 - 2

      假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

    6. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

      我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

    7. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

      关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

    8. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

      我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

    9. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

      我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

    10. ruby - 在 64 位 Snow Leopard 上使用 rvm、postgres 9.0、ruby 1.9.2-p136 安装 pg gem 时出现问题 - 2

      我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po

    随机推荐