Math 以编程方式校正鱼眼失真
赏金状态更新: ,从目标坐标到源坐标 如何计算从鱼眼到直线中心的径向距离 一,。实际上,我很难将其反转,并将源坐标映射到目标坐标。在我发布的转换函数风格的代码中,反向是什么? 二,。我也看到我的不失真在一些镜头上是不完美的——大概是那些不是严格线性的镜头。这些镜头的源坐标和目标坐标的等效值是多少?再一次,更多的代码不仅仅是数学公式请。。。 问题如原来所述: 我有一些观点描述了用鱼眼镜头拍摄的照片中的位置 我想把这些点转换成直线坐标。我想取消对图像的干扰 我已经找到了如何产生鱼眼效果的方法,但没有找到如何扭转它的方法 还有一个例子描述了如何使用工具来实现这一点;这些图片来自: 1:来源 输入:原始图像与鱼眼失真修复。 2:目的地 输出:从技术上讲,校正图像还带有透视校正,但这是一个单独的步骤 如何计算从鱼眼到直线中心的径向距离 我的函数存根如下所示:Math 以编程方式校正鱼眼失真,math,graphics,geometry,projection,Math,Graphics,Geometry,Projection,赏金状态更新: ,从目标坐标到源坐标 如何计算从鱼眼到直线中心的径向距离 一,。实际上,我很难将其反转,并将源坐标映射到目标坐标。在我发布的转换函数风格的代码中,反向是什么? 二,。我也看到我的不失真在一些镜头上是不完美的——大概是那些不是严格线性的镜头。这些镜头的源坐标和目标坐标的等效值是多少?再一次,更多的代码不仅仅是数学公式请。。。 问题如原来所述: 我有一些观点描述了用鱼眼镜头拍摄的照片中的位置 我想把这些点转换成直线坐标。我想取消对图像的干扰 我已经找到了如何产生鱼眼效果的方法,但
Point correct_fisheye(const Point& p,const Size& img) {
// to polar
const Point centre = {img.width/2,img.height/2};
const Point rel = {p.x-centre.x,p.y-centre.y};
const double theta = atan2(rel.y,rel.x);
double R = sqrt((rel.x*rel.x)+(rel.y*rel.y));
// fisheye undistortion in here please
//... change R ...
// back to rectangular
const Point ret = Point(centre.x+R*cos(theta),centre.y+R*sin(theta));
fprintf(stderr,"(%d,%d) in (%d,%d) = %f,%f = (%d,%d)\n",p.x,p.y,img.width,img.height,theta,R,ret.x,ret.y);
return ret;
}
或者,我可以在找到点之前,以某种方式将图像从鱼眼转换为直线,但我完全被这一点弄糊涂了。在OpenCV中有没有一种简单的方法可以实现这一点,并且它的性能是否足够好,可以用于实时视频馈送?说明针孔相机的投影(不引入镜头失真的投影)是由
R_u = f*tan(theta)
R_d = 2*f*sin(theta/2)
而普通鱼眼镜头相机的投影,也就是说,畸变是由
R_u = f*tan(theta)
R_d = 2*f*sin(theta/2)
你已经知道R_d和θ,如果你知道相机的焦距由f表示,那么校正图像就等于计算R_d和θ。换句话说,
R_u = f*tan(2*asin(R_d/(2*f)))
就是你要找的配方。估计焦距f可以通过校准相机或其他方式来解决,例如让用户提供关于图像校正程度的反馈或使用来自原始场景的知识
为了使用OpenCV解决同样的问题,您必须获得相机的固有参数和镜头畸变系数。例如,请参阅《别忘了检查》第11章。然后,您可以使用使用Python绑定为OpenCV编写的程序来反转镜头失真:
#!/usr/bin/python
# ./undistort 0_0000.jpg 1367.451167 1367.451167 0 0 -0.246065 0.193617 -0.002004 -0.002056
import sys
import cv
def main(argv):
if len(argv) < 10:
print 'Usage: %s input-file fx fy cx cy k1 k2 p1 p2 output-file' % argv[0]
sys.exit(-1)
src = argv[1]
fx, fy, cx, cy, k1, k2, p1, p2, output = argv[2:]
intrinsics = cv.CreateMat(3, 3, cv.CV_64FC1)
cv.Zero(intrinsics)
intrinsics[0, 0] = float(fx)
intrinsics[1, 1] = float(fy)
intrinsics[2, 2] = 1.0
intrinsics[0, 2] = float(cx)
intrinsics[1, 2] = float(cy)
dist_coeffs = cv.CreateMat(1, 4, cv.CV_64FC1)
cv.Zero(dist_coeffs)
dist_coeffs[0, 0] = float(k1)
dist_coeffs[0, 1] = float(k2)
dist_coeffs[0, 2] = float(p1)
dist_coeffs[0, 3] = float(p2)
src = cv.LoadImage(src)
dst = cv.CreateImage(cv.GetSize(src), src.depth, src.nChannels)
mapx = cv.CreateImage(cv.GetSize(src), cv.IPL_DEPTH_32F, 1)
mapy = cv.CreateImage(cv.GetSize(src), cv.IPL_DEPTH_32F, 1)
cv.InitUndistortMap(intrinsics, dist_coeffs, mapx, mapy)
cv.Remap(src, dst, mapx, mapy, cv.CV_INTER_LINEAR + cv.CV_WARP_FILL_OUTLIERS, cv.ScalarAll(0))
# cv.Undistort2(src, dst, intrinsics, dist_coeffs)
cv.SaveImage(output, dst)
if __name__ == '__main__':
main(sys.argv)
另外请注意,OpenCV使用的镜头畸变模型与您链接到的网页中的镜头畸变模型截然不同。如果您认为公式是精确的,可以使用trig计算精确的公式,如下所示:
Rin = 2 f sin(w/2) -> sin(w/2)= Rin/2f
Rout= f tan(w) -> tan(w)= Rout/f
(Rin/2f)^2 = [sin(w/2)]^2 = (1 - cos(w))/2 -> cos(w) = 1 - 2(Rin/2f)^2
(Rout/f)^2 = [tan(w)]^2 = 1/[cos(w)]^2 - 1
-> (Rout/f)^2 = 1/(1-2[Rin/2f]^2)^2 - 1
然而,正如@jmbr所说,实际的相机失真将取决于镜头和变焦。与其依赖固定公式,不如尝试多项式展开:
Rout = Rin*(1 + A*Rin^2 + B*Rin^4 + ...)
通过先调整A,然后调整高阶系数,可以计算任何合理的局部函数。展开形式利用了问题的对称性。特别是,应该可以计算初始系数来近似上述理论函数
此外,为了获得好的效果,您需要使用插值滤波器来生成校正后的图像。只要失真不是太大,就可以使用将用于线性重新缩放图像的过滤器,而不会有太多问题
编辑:根据您的要求,上述公式的等效比例因子:
(Rout/f)^2 = 1/(1-2[Rin/2f]^2)^2 - 1
-> Rout/f = [Rin/f] * sqrt(1-[Rin/f]^2/4)/(1-[Rin/f]^2/2)
如果将上述公式与tanRin/f一起绘制,可以看到它们的形状非常相似。基本上,在sinw与w大不相同之前,切线的畸变变得严重
逆公式应类似于:
Rin/f = [Rout/f] / sqrt( sqrt(([Rout/f]^2+1) * (sqrt([Rout/f]^2+1) + 1) / 2 )
原始海报,提供替代方案 以下函数将目标直线坐标映射到源鱼眼扭曲坐标。如果能帮我扭转局面,我将不胜感激 我是通过反复试验得出这一点的:我无法从根本上理解这段代码为什么会工作,解释和提高的准确性值得赞赏 当使用系数为3.0时,它成功地消除了用作示例的图像的失真。我没有尝试进行质量插值: 死链 这是博客文章中的内容,用于比较:
我盲目地实现了来自的公式,所以我不能保证它会满足您的需要 使用“自动缩放”获取缩放参数的值
def dist(x,y):
return sqrt(x*x+y*y)
def fisheye_to_rectilinear(src_size,dest_size,sx,sy,crop_factor,zoom):
""" returns a tuple of dest coordinates (dx,dy)
(note: values can be out of range)
crop_factor is ratio of sphere diameter to diagonal of the source image"""
# convert sx,sy to relative coordinates
rx, ry = sx-(src_size[0]/2), sy-(src_size[1]/2)
r = dist(rx,ry)
# focal distance = radius of the sphere
pi = 3.1415926535
f = dist(src_size[0],src_size[1])*factor/pi
# calc theta 1) linear mapping (older Nikon)
theta = r / f
# calc theta 2) nonlinear mapping
# theta = asin ( r / ( 2 * f ) ) * 2
# calc new radius
nr = tan(theta) * zoom
# back to absolute coordinates
dx, dy = (dest_size[0]/2)+rx/r*nr, (dest_size[1]/2)+ry/r*nr
# done
return (int(round(dx)),int(round(dy)))
def fisheye_auto_zoom(src_size,dest_size,crop_factor):
""" calculate zoom such that left edge of source image matches left edge of dest image """
# Try to see what happens with zoom=1
dx, dy = fisheye_to_rectilinear(src_size, dest_size, 0, src_size[1]/2, crop_factor, 1)
# Calculate zoom so the result is what we wanted
obtained_r = dest_size[0]/2 - dx
required_r = dest_size[0]/2
zoom = required_r / obtained_r
return zoom
我找到了这个pdf文件,我已经证明了数学是正确的,除了行vd=*xd**fv+v0应该是vd=**yd**+fv+v0 它并没有使用OpenCV提供的所有最新的协同效率,但我相信它可以相当容易地进行调整
double k1 = cameraIntrinsic.distortion[0];
double k2 = cameraIntrinsic.distortion[1];
double p1 = cameraIntrinsic.distortion[2];
double p2 = cameraIntrinsic.distortion[3];
double k3 = cameraIntrinsic.distortion[4];
double fu = cameraIntrinsic.focalLength[0];
double fv = cameraIntrinsic.focalLength[1];
double u0 = cameraIntrinsic.principalPoint[0];
double v0 = cameraIntrinsic.principalPoint[1];
double u, v;
u = thisPoint->x; // the undistorted point
v = thisPoint->y;
double x = ( u - u0 )/fu;
double y = ( v - v0 )/fv;
double r2 = (x*x) + (y*y);
double r4 = r2*r2;
double cDist = 1 + (k1*r2) + (k2*r4);
double xr = x*cDist;
double yr = y*cDist;
double a1 = 2*x*y;
double a2 = r2 + (2*(x*x));
double a3 = r2 + (2*(y*y));
double dx = (a1*p1) + (a2*p2);
double dy = (a3*p1) + (a1*p2);
double xd = xr + dx;
double yd = yr + dy;
double ud = (xd*fu) + u0;
double vd = (yd*fv) + v0;
thisPoint->x = ud; // the distorted point
thisPoint->y = vd;
我拿了什么
JMBR做到了,并且基本上逆转了这一点。他计算了畸变图像的半径Rd,即距离图像中心的像素距离,并找到了Ru的公式,即未畸变图像的半径
你想走另一条路。对于未失真处理图像中的每个像素,您希望知道失真图像中对应的像素是什么。
换句话说,给定xu,yu->xd,yd。然后用失真图像中的对应像素替换未失真图像中的每个像素
从JMBR做的地方开始,我做相反的事情,发现Rd是Ru的函数。我得到:
Rd = f * sqrt(2) * sqrt( 1 - 1/sqrt(r^2 +1))
其中f是像素的焦距,我将在后面解释,r=Ru/f
我相机的焦距是2.5毫米。我的CCD上每个像素的大小是6平方毫米。因此f为2500/6=417像素。这可以通过反复试验找到
查找Rd允许您使用极坐标在扭曲的图像中查找相应的像素
每个像素与中心点的角度相同:
θ=arctan yu yc/xu xc,其中xc,yc是中心点
那么
确保你知道你在哪个象限
这是我使用的C代码
public class Analyzer
{
private ArrayList mFisheyeCorrect;
private int mFELimit = 1500;
private double mScaleFESize = 0.9;
public Analyzer()
{
//A lookup table so we don't have to calculate Rdistorted over and over
//The values will be multiplied by focal length in pixels to
//get the Rdistorted
mFisheyeCorrect = new ArrayList(mFELimit);
//i corresponds to Rundist/focalLengthInPixels * 1000 (to get integers)
for (int i = 0; i < mFELimit; i++)
{
double result = Math.Sqrt(1 - 1 / Math.Sqrt(1.0 + (double)i * i / 1000000.0)) * 1.4142136;
mFisheyeCorrect.Add(result);
}
}
public Bitmap RemoveFisheye(ref Bitmap aImage, double aFocalLinPixels)
{
Bitmap correctedImage = new Bitmap(aImage.Width, aImage.Height);
//The center points of the image
double xc = aImage.Width / 2.0;
double yc = aImage.Height / 2.0;
Boolean xpos, ypos;
//Move through the pixels in the corrected image;
//set to corresponding pixels in distorted image
for (int i = 0; i < correctedImage.Width; i++)
{
for (int j = 0; j < correctedImage.Height; j++)
{
//which quadrant are we in?
xpos = i > xc;
ypos = j > yc;
//Find the distance from the center
double xdif = i-xc;
double ydif = j-yc;
//The distance squared
double Rusquare = xdif * xdif + ydif * ydif;
//the angle from the center
double theta = Math.Atan2(ydif, xdif);
//find index for lookup table
int index = (int)(Math.Sqrt(Rusquare) / aFocalLinPixels * 1000);
if (index >= mFELimit) index = mFELimit - 1;
//calculated Rdistorted
double Rd = aFocalLinPixels * (double)mFisheyeCorrect[index]
/mScaleFESize;
//calculate x and y distances
double xdelta = Math.Abs(Rd*Math.Cos(theta));
double ydelta = Math.Abs(Rd * Math.Sin(theta));
//convert to pixel coordinates
int xd = (int)(xc + (xpos ? xdelta : -xdelta));
int yd = (int)(yc + (ypos ? ydelta : -ydelta));
xd = Math.Max(0, Math.Min(xd, aImage.Width-1));
yd = Math.Max(0, Math.Min(yd, aImage.Height-1));
//set the corrected pixel value from the distorted image
correctedImage.SetPixel(i, j, aImage.GetPixel(xd, yd));
}
}
return correctedImage;
}
}
我不太明白你在找什么。鱼眼从球体映射到图片平面。反向映射将从图片返回到球体,对吗?你在寻找什么样的直线坐标?@mtrw我的源图像是鱼眼扭曲的,我想让它不失真。你要找的图片是什么?是的,经过校正的图片,例如通过OpenCV,或者一个公式来校正图片中的任何点。威尔,你有没有得到一个结论性的答案?我很想看看你最终得到的任何代码。这取决于你是否可以使用有问题的摄像机。我不知道,我只是在看录像。另外,我突然想到相机是批量生产的,而且不会有太多的变化?原始文章中链接的工具不需要有人拿着棋盘站在相机前面!?仅通过调整缩放,同一摄影机的摄影机参数会有所不同。此外,您可以依赖自动校准技术,而不是使用棋盘。无论如何,我已经编辑了我的答案,以解决你问题的第一部分,为你提供了你想要的公式。谢谢你jmbr!世界开始变得有意义了。实际上,我不能让R_=f*tan2*asinR_d/2*f给我任何东西,但NaN除外。我对三角学一无所知,而这正是我现在的障碍:这是自动接受的-我一直在等待一个更明确的答案来回答我提出的问题。@Will:既然赏金系统已经改进了,你现在也可以接受另一个答案了。你的代码工作的原因是你正在缩放rx,ry乘以系数θ,θ现在是一个比率,而不是一个角度。如果原始镜头有如维基文章所说的从视角到图像偏移的线性映射,我相信atanr/r是正确的。映射的相反方向应该是按tanr'/r'的因子进行缩放,其中r'是未失真图像中心的半径。这两种工作的原因是,如果向量v'=v*k | v |,你想要| v'|=f | v |,你取第一个方程的绝对值:| v'|=|v |*k | v |=f | v |-,所以k | v |=f | v |/| v | comingstorm那么非直线映射的等价物是什么呢?@stkent,所以我在R中做这个,它的工作,除了我看到的叠加或叠加在图像上的因子外。就像在中一样,我得到了想要的桶形失真,但是在它上面有一些浅灰色波浪的假像。想知道这是边界问题还是相移类型的问题?例子: