将检测到的 2D 地图调整为参考 2D 地图

Posted

技术标签:

【中文标题】将检测到的 2D 地图调整为参考 2D 地图【英文标题】:Adjust a detected 2D map to a reference 2D map 【发布时间】:2019-09-16 10:53:25 【问题描述】:

我有一张地图,其中一些参考位置对应于一些对象的中心(小十字),如下所示:

我拍照是为了找到我的对象,但在图片中我有一些噪音,所以我不能总是找到所有的对象,可能是这样的:

从少数找到的位置,我需要知道图片中其他未找到的对象应该在哪里。在过去的几天里,我一直在阅读这方面的内容并进行试验,但我找不到合适的方法来做到这一点。在某些示例中,它们首先计算质心并将它们一起平移,然后旋转,其他一些示例使用最小二乘法最小化并从旋转开始。我不能使用 OpenCV 或任何其他 API,只能使用普通 C++。如果有帮助,我可以使用 Eigen 库。谁能给我一些指示?

编辑: 我已经解决了点之间的对应关系,图片从不会与参考有很大不同,因此对于每个找到的位置,我都可以搜索其对应的参考。简而言之,我有一个带有参考点的二维矩阵和另一个带有找到点的二维矩阵。在找到的点矩阵中,将未找到的点保存为 NaN 只是为了保持矩阵大小相同,计算中不使用 NaN 点。

【问题讨论】:

所以你有参考图像顶部,它保证对象的位置在查询图像中彼此之间的关系是准确的? @Timo 是的,我有完美图像中的参考位置 我正在研究“仿射变换”,但它的数学运算量很大,而且我现在有点超载... @Timo 我有参考位置,在处理新图像后我找到了一些位置,我的目标是知道丢失的对象中心可能在哪里。显然,这可以使用仿射变换来完成。我正在使用 2D 位置。 仿射变换是两个空间之间保持相关性的变换(在您的情况下:点之间的距离和旋转)。找到这种转变就是问题所在。由于您的第二个点集(嘈杂的图像)不完整,因此您无法将质心等内在函数相互比较。您必须将检测到的点与参考点进行匹配才能找到转换。搜索 Iterative Closest Point 算法,也许看看实现。 【参考方案1】:

由于您已经将点相互匹配,因此查找变换很简单:

Eigen::Affine2d findAffine(Eigen::Matrix2Xd const& refCloud, Eigen::Matrix2Xd const& targetCloud)

    // get translation
    auto refCom = centerOfMass(refCloud);
    auto refAtOrigin = refCloud.colwise() - refCom;

    auto targetCom = centerOfMass(targetCloud);
    auto targetAtOrigin = targetCloud.colwise() - targetCom;

    // get scale
    auto scale = targetAtOrigin.rowwise().norm().sum() / refAtOrigin.rowwise().norm().sum();

    // get rotation
    auto covMat = refAtOrigin * targetAtOrigin.transpose();
    auto svd = covMat.jacobiSvd(Eigen::ComputeFullU | Eigen::ComputeFullV);
    auto rot = svd.matrixV() * svd.matrixU().transpose();   

    // combine the transformations
    Eigen::Affine2d trans = Eigen::Affine2d::Identity();
    trans.translate(targetCom).scale(scale).rotate(rot).translate(-refCom);
    return trans;

refCloud 是您的参考点集,targetCloud 是您在图像中找到的点集。云匹配索引很重要,所以refCloud[n] 必须是targetCloud[n] 的对应点。这意味着您必须从矩阵中删除所有 NaN,然后​​在您的参考点集中挑选对应项。

这是一个完整的例子。我正在使用 OpenCV 来绘制这些东西:

#include <Eigen/Dense>
#include <opencv2/opencv.hpp>

#include <vector>
#include <iostream>

using Point = Eigen::Vector2d;

template <typename TMatrix>
Point centerOfMass(TMatrix const& points)

    return points.rowwise().sum() / points.cols();


Eigen::Affine2d findAffine(Eigen::Matrix2Xd const& refCloud, Eigen::Matrix2Xd const& targetCloud)

    // get translation
    auto refCom = centerOfMass(refCloud);
    auto refAtOrigin = refCloud.colwise() - refCom;

    auto targetCom = centerOfMass(targetCloud);
    auto targetAtOrigin = targetCloud.colwise() - targetCom;

    // get scale
    auto scale = targetAtOrigin.rowwise().norm().sum() / refAtOrigin.rowwise().norm().sum();

    // get rotation
    auto covMat = refAtOrigin * targetAtOrigin.transpose();
    auto svd = covMat.jacobiSvd(Eigen::ComputeFullU | Eigen::ComputeFullV);
    auto rot = svd.matrixV() * svd.matrixU().transpose();   

    // combine the transformations
    Eigen::Affine2d trans = Eigen::Affine2d::Identity();
    trans.translate(targetCom).scale(scale).rotate(rot).translate(-refCom);
    return trans;


void drawCloud(cv::Mat& img, Eigen::Matrix2Xd const& cloud, Point const& origin, Point const& scale, cv::Scalar const& color, int thickness = cv::FILLED)

    for (int c = 0; c < cloud.cols(); c++)
    
        auto p = origin + cloud.col(c).cwiseProduct(scale);
        cv::circle(img, int(p.x()), int(p.y()), 5, color, thickness, cv::LINE_AA);
    


int main()

    // generate sample reference
    std::vector<Point> points = 4, 9, 4, 4, 6, 9, 6, 4, 8, 9, 8, 4, 10, 9, 10, 4, 12, 9, 12, 4;
    Eigen::Matrix2Xd fullRefCloud(2, points.size());
    for (int i = 0; i < points.size(); i++)
        fullRefCloud.col(i) = points[i];

    // generate sample target
    Eigen::Matrix2Xd refCloud = fullRefCloud.leftCols(fullRefCloud.cols() * 0.6);
    Eigen::Affine2d refTransformation = Eigen::Affine2d::Identity();
    refTransformation.translate(Point(8, -4)).rotate(4.3).translate(-centerOfMass(refCloud)).scale(1.5);
    Eigen::Matrix2Xd targetCloud = refTransformation * refCloud;

    // find the transformation
    auto transform = findAffine(refCloud, targetCloud);
    std::cout << "Original: \n" << refTransformation.matrix() << "\n\nComputed: \n" << transform.matrix() << "\n";

    // apply the computed transformation
    Eigen::Matrix2Xd queryCloud = fullRefCloud.rightCols(fullRefCloud.cols() - refCloud.cols());
    queryCloud = transform * queryCloud;

    // draw it
    Point scale = 15, 15, origin = 100, 300;
    cv::Mat img(600, 600, CV_8UC3);
    cv::line(img, 0, int(origin.y()), 800, int(origin.y()), );
    cv::line(img, int(origin.x()), 0, int(origin.x()), 800, );    
    drawCloud(img, refCloud, origin, scale, 0, 255, 0);
    drawCloud(img, fullRefCloud, origin, scale, 255, 0, 0, 1);
    drawCloud(img, targetCloud, origin, scale, 0, 0, 255);
    drawCloud(img, queryCloud, origin, scale, 255, 0, 255, 1);

    cv::flip(img, img, 0);
    cv::imshow("img", img);
    cv::waitKey();

    return 0;

【讨论】:

我们几乎同时发布了一个解决方案 :-) 您的代码与我找到的其他解决方案非常相似,非常感谢您的帮助!【参考方案2】:

我设法使它与这里的代码一起工作:https://github.com/oleg-alexandrov/projects/blob/master/eigen/Kabsch.cpp

我正在调用 Find3DAffineTransform 函数并将我的 2D 地图传递给它,因为该函数需要 3D 地图,我已将所有 z 坐标设置为 0 并且它可以工作。如果我有时间,我会尝试将其调整为 2D。 同时,一位程序员同事(Regis :-) 也发现了这个,应该可以工作:https://eigen.tuxfamily.org/dox/group__Geometry__Module.html#gab3f5a82a24490b936f8694cf8fef8e60 它的函数 umeyama() 返回两个点集之间的转换。它是 Eigen 库的一部分。没有时间测试这个。

【讨论】:

以上是关于将检测到的 2D 地图调整为参考 2D 地图的主要内容,如果未能解决你的问题,请参考以下文章

“等一下,我碰!”——常见的2D碰撞检测

安卓开发 百度地图怎么自动设置缩放比例

c#如何将2D数组拆分为较小的2D数组(块)列表?

窗口调整大小后将鼠标位置转换为 2D openGL 中的世界坐标

如何在osmdroid中将指南针调整为地图

为啥unity3d中我无法创建瓦片地图?