使用视野模型反向鱼眼径向畸变
Reverse fisheye radial distortion using Field of View model
假设我用 185º FoV 的鱼眼相机拍摄了这张扭曲的图像。
我想使用 Frederic Devernay、Olivier Faugeras 中解释的 FoV 模型来消除失真。直线必须是直线:自动校准
并消除结构化环境场景中的失真。 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角度的一半作为W
参数(算法在一些径向坐标上运行,计算到中心的距离,所以角度也应该从中心,给出一半的角度)。
你计算的ru
和rd
是错误的:ru
应该是距离,那么rd
应该是根据式(13) .这是因为你进行了逆向映射:你创建了一个空图像,然后对于它的每个 (x, y)
点,你必须从扭曲的图像中选择一种颜色 - 你通过扭曲 (x, y)
来做到这一点,然后看它指向扭曲的图像,然后将该颜色映射到原始的非扭曲 (x, y)
坐标。进行直接映射(例如,对于失真图像的每个 (x, y)
将其移动到非失真图像上的计算位置)会产生视觉伪影,因为并非所有目标像素都必须被覆盖。
你忘了对径向坐标进行归一化,必须分别除以Cx
、Cy
,做变换,再乘回去去归一化。
也可能存在一些从 double
到 int
的隐式转换,但我不确定 - 永远记不起关于那个的规则,我只是尽量不要混合 int
和 double
在同一个等式中,如果对您有用,请随意将 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);
}
原始图像的大部分在转换过程中丢失了 - 应该是这样吗?还是算法仍应将它们映射到某个地方?我尝试将它转换为更大的目标图像,它的边缘真的被拉伸了:
假设我用 185º FoV 的鱼眼相机拍摄了这张扭曲的图像。
我想使用 Frederic Devernay、Olivier Faugeras 中解释的 FoV 模型来消除失真。直线必须是直线:自动校准 并消除结构化环境场景中的失真。 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角度的一半作为
W
参数(算法在一些径向坐标上运行,计算到中心的距离,所以角度也应该从中心,给出一半的角度)。你计算的
ru
和rd
是错误的:ru
应该是距离,那么rd
应该是根据式(13) .这是因为你进行了逆向映射:你创建了一个空图像,然后对于它的每个(x, y)
点,你必须从扭曲的图像中选择一种颜色 - 你通过扭曲(x, y)
来做到这一点,然后看它指向扭曲的图像,然后将该颜色映射到原始的非扭曲(x, y)
坐标。进行直接映射(例如,对于失真图像的每个(x, y)
将其移动到非失真图像上的计算位置)会产生视觉伪影,因为并非所有目标像素都必须被覆盖。你忘了对径向坐标进行归一化,必须分别除以
Cx
、Cy
,做变换,再乘回去去归一化。
也可能存在一些从 double
到 int
的隐式转换,但我不确定 - 永远记不起关于那个的规则,我只是尽量不要混合 int
和 double
在同一个等式中,如果对您有用,请随意将 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);
}
原始图像的大部分在转换过程中丢失了 - 应该是这样吗?还是算法仍应将它们映射到某个地方?我尝试将它转换为更大的目标图像,它的边缘真的被拉伸了: