将检测到的 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 地图的主要内容,如果未能解决你的问题,请参考以下文章