使用视场模型反转鱼眼径向畸变

4
假设我有一张由185º视场的鱼眼相机拍摄的畸变图像。
来自《IEEE国际声学、语音和信号处理会议论文集(ICASSP)》, 2016年3月,中国上海,第1541-1545页的图像。

distorted

我希望使用FoV模型对其进行矫正,该模型在Frederic Devernay, Olivier Faugeras. Straight lines have to be straight: automatic calibration and removal of distortion from scenes of structured enviroments. Machine Vision and Applications, Springer Verlag, 2001, 13 (1), pp.14-24中有详细解释,具体包括13和14两个方程式。
rd = 1 / ω * arctan (2 * ru * tan(ω / 2))   // Equation 13
ru = tan(rd * ω) / (2 * tan(ω / 2))         // Equation 14

我已经在OpenCV中实现了它,但无法使其正常工作。我将rd解释为一个点从光学中心的扭曲距离,将ru解释为新的未扭曲距离。


我给你一个完整的最小化项目。

#include <opencv2/opencv.hpp>

#define W (185*CV_PI/180)

cv::Mat undistortFishEye(const cv::Mat &distorted, const float w)
{
    cv::Mat map_x, map_y;
    map_x.create(distorted.size(), CV_32FC1);
    map_y.create(distorted.size(), CV_32FC1);

    int Cx = distorted.cols/2;
    int Cy = distorted.rows/2;

    for (int x = -Cx; x < Cx; ++x) {
        for (int y = -Cy; y < Cy; ++y) {
            double rd = sqrt(x*x+ y*y);
            double ru = tan(rd*w) / (2*tan(w/2));
            map_x.at<float>(y+Cy,x+Cx) = ru/rd * x + Cx;
            map_y.at<float>(y+Cy,x+Cx) = ru/rd * y + Cy;
        }
    }

    cv::Mat undistorted;
    remap(distorted, undistorted, map_x, map_y, CV_INTER_LINEAR);
    return undistorted;
}

int main(int argc, char **argv)
{
    cv::Mat im_d = cv::imread(<your_image_path>, CV_LOAD_IMAGE_GRAYSCALE);
    cv::imshow("Image distorted", im_d);

    cv::Mat im_u = undistortFishEye(im_d, W);
    cv::imshow("Image undistorted", im_u);

    cv::waitKey(0);
}

我怀疑错误在于FoV模型的输入值。例如,左上角像素点(-575,-543)具有rd = 790.87,而FoV模型返回给我的是ru = 0.012 - Santiago Gil
请检查一下,这可能会对您有所帮助: - Ja_cpp
1
https://github.com/ethz-asl/ethzasl_ptam/blob/e761e802c6382bfe7c09f5f2db342c7a89209348/ptam/src/ATANCamera.cc#L52 - Ja_cpp
@Ja_cpp 是的,这可能会有所帮助。我已经检查过了invrtrans(double r),它返回equation 14。稍后,Unproject执行与我的代码相同的操作:[xu, yu] = ru / rd * [xd, yd]。在光学中心附近,它假设没有畸变,因此仅在(rd > 0.01)时进行校正。我唯一看到的不同之处是它如何计算rd。让我们试试。 - Santiago Gil
@Ja_cpp PTAM 将畸变坐标分配给焦距 f 进行归一化:rd = sqrt((xd/f)^2 + (yd/f)^2)。焦距以像素为单位。数据集 表明相机的 f=1.8mm,图像分辨率为 1088x1088,传感器尺寸为 5.20mm。因此,我将焦距计算为像素单位 1.8 / (5.20/1088)。但是去畸变仍然无法正常工作。您看到了哪些错误步骤吗? - Santiago Gil
好像一切都没问题,我真的不知道! - Ja_cpp
1个回答

3
我只是粗略地浏览了您提供的论文,所以我不确定是否理解正确,但看起来您的实现有三个问题:
  1. 您应该将FOV角度的一半作为W参数(算法在某些径向坐标中运行,计算距离中心的距离,因此角度也应该从中心开始计算,这样可以得到角度的一半)。

  2. 您计算ru和rd时有误:ru应该是距离,然后rd应该按照式(13)计算。这是因为您进行反向映射:您创建一个空图像,然后对于其中的每个(x,y)点,您必须从扭曲的图像中选择一种颜色-您通过扭曲(x,y)并查看它指向扭曲的影像的位置,然后将该颜色映射到原始非扭曲的(x,y)坐标上。进行直接映射(例如,对于扭曲的图像的每个(x,y),将其移动到非扭曲的图像上计算的位置)会产生视觉伪影,因为并非所有目标像素都必定被覆盖。

  3. 您忘记对径向坐标进行归一化,必须分别除以Cx,Cy,执行变换,然后通过乘回来进行反归一化。

也可能存在将double隐式转换为int的情况,但我不确定-从未记得这方面的规则,我只是尽量避免在同一个等式中混合int's和double's,如果对您有用,请将Cx、Cy转换回int。无论如何,这似乎有效(undistortFishEye函数的两个版本都产生相同的结果,因此使用您喜欢的任何一个版本):

#include <opencv2/opencv.hpp>

#define W (185/2*CV_PI/180)

cv::Mat undistortFishEye(const cv::Mat &distorted, const float w)
{
    cv::Mat map_x, map_y;
    map_x.create(distorted.size(), CV_32FC1);
    map_y.create(distorted.size(), CV_32FC1);

    double Cx = distorted.cols / 2.0;
    double Cy = distorted.rows / 2.0;

    for (double x = -1.0; x < 1.0; x += 1.0/Cx) {
        for (double y = -1.0; y < 1.0; y += 1.0/Cy) {
            double ru = sqrt(x*x + y*y);
            double rd = (1.0 / w)*atan(2.0*ru*tan(w / 2.0));

            map_x.at<float>(y*Cy + Cy, x*Cx + Cx) = rd/ru * x*Cx + Cx;
            map_y.at<float>(y*Cy + Cy, x*Cx + Cx) = rd/ru * y*Cy + Cy;
        }
    }

    cv::Mat undistorted;
    remap(distorted, undistorted, map_x, map_y, CV_INTER_LINEAR);
    return undistorted;
}

cv::Mat undistortFishEye2(const cv::Mat &distorted, const float w)
{
    cv::Mat map_x, map_y;
    map_x.create(distorted.size(), CV_32FC1);
    map_y.create(distorted.size(), CV_32FC1);

    double cx = distorted.cols / 2.0;
    double cy = distorted.rows / 2.0;

    for (int x = 0; x < distorted.cols; ++x)
    {
        for (int y = 0; y < distorted.rows; ++y)
        {
            double rx = (x - cx) / cx;
            double ry = (y - cy) / cy;
            double ru = sqrt(rx*rx + ry*ry);
            //TODO: check for ru == 0.0

            double rd = (1.0 / w)*atan(2.0*ru*tan(w/2.0));
            double coeff = rd / ru;
            rx *= coeff;
            ry *= coeff;
            map_x.at<float>(y, x) = rx*cx + cx;
            map_y.at<float>(y, x) = ry*cy + cy;
        }
    }

    cv::Mat undistorted;
    remap(distorted, undistorted, map_x, map_y, CV_INTER_LINEAR);
    return undistorted;
}

int main(int argc, char **argv)
{
    cv::Mat im_d = cv::imread("C:/projects/test_images/fisheye/library.jpg", CV_LOAD_IMAGE_GRAYSCALE);
    cv::imshow("Image distorted", im_d);

    cv::Mat im_u = undistortFishEye(im_d, W);
    cv::imshow("Image undistorted", im_u);

    cv::waitKey(0);
}

在此输入图片描述

在转换过程中,原始图像的大部分信息都丢失了 - 这是正常现象吗?还是算法应该仍然将它们映射到某个位置?我尝试将其转换到更大的目标图像上,但边缘部分被拉伸得非常严重:

在此输入图片描述

听起来很不错,这三个更正都有意义。我会使用第一种实现方式,它更聪明,而且肯定会更快一些。关于信息的丢失,是的...remap返回的图像与原始图像大小相同。对我来说这不是问题,我想在图像中使用无畸变函数来处理一组特征。无畸变图像的可视化是检查无畸变是否有效的一种方式。 - Santiago Gil
我认为第一次实现中有 bug,for (double y = -1.0; y < 1.0; y += 1.0/Cx) 应该是 1.0/Cy,然后 rd/ru * y*Cx + Cy 应该是 rd/ru * y*Cy + Cy。之后会进行检查并修复我的答案。 - Headcrab

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