将检测到的二维地图调整为参考二维地图

Adjust a detected 2D map to a reference 2D map

我有一张地图,上面有一些参考位置,这些参考位置对应于一些对象的中心(小十字):

我拍照是为了找到我的物品,但在照片中我有一些噪点,所以我不能总是找到所有的物品,它可能是这样的:

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

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

我设法让它与这里的代码一起工作:
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
它是 returns 两个点集之间的转换的函数 umeyama()。它是 Eigen 库的一部分。没有时间测试这个。

由于您已经将这些点相互匹配,找到变换很简单:

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;
}