以编程方式纠正鱼眼失真

Wil*_*ill 49 math graphics geometry projection

BOUNTY STATUS UPDATE:

我发现了如何绘制线性镜头,从destination坐标到source坐标.

你如何计算从中心到鱼眼到直线的径向距离?

  • 1).我实际上很难逆转它,并将源坐标映射到目标坐标.在我发布的转换函数风格的代码中有什么反转?

  • 2).我也看到我的失真在一些镜头上是不完美的 - 大概是那些不是严格线性的镜头.这些镜头的源和目标坐标是等价的?再说一遍,代码不仅仅是数学公式,请...


最初提出的问题:

我有一些观点描述用鱼眼镜头拍摄的照片中的位置.

我想将这些点转换为直线坐标.我想不要失真的形象.

我发现这个描述的如何生成鱼眼效果,但不知道如何扭转这种局面.

还有一篇博客文章描述了如何使用工具来完成它; 这些照片来自:

(1):SOURCE 原始照片链接

输入:修复鱼眼失真的原始图像.

(2):DESTINATION 原始照片链接

输出:校正图像(技术上也有透视校正,但这是一个单独的步骤).

你如何计算从中心到鱼眼到直线的径向距离?

我的函数存根看起来像这样:

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;
}
Run Code Online (Sandbox Code Playgroud)

或者,我可以在找到点之前以某种方式将图像从鱼眼转换为直线,但我完全被OpenCV文档迷惑了.有没有一种直接的方法在OpenCV中做到这一点,它是否能够很好地运行到实时视频源?

jmb*_*mbr 33

您提到描述表明,针孔相机(不引入镜头失真的投影)的投影是通过建模的

R_u = f*tan(theta)
Run Code Online (Sandbox Code Playgroud)

普通鱼眼镜头相机(即失真)的投影由模拟

R_d = 2*f*sin(theta/2)
Run Code Online (Sandbox Code Playgroud)

你已经知道了R_d和theta,如果你知道相机的焦距(用f表示)那么校正图像将相当于用R_d和theta计算R_u.换一种说法,

R_u = f*tan(2*asin(R_d/(2*f)))
Run Code Online (Sandbox Code Playgroud)

是你正在寻找的公式.估计焦距f可以通过校准相机或其他装置来解决,例如让用户提供关于图像校正程度的反馈或使用来自原始场景的知识的反馈.

为了使用OpenCV解决相同的问题,您必须获得相机的内部参数和镜头失真系数.例如,参见学习OpenCV的第11章(不要忘记检查校正).然后你可以使用像这样的程序(用OpenCV的Python绑定编写)来反转镜头失真:

#!/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)
Run Code Online (Sandbox Code Playgroud)

另请注意,OpenCV使用与您链接的网页中的镜头失真模型截然不同的镜头失真模型.

  • 通过调整变焦,相机参数在同一相机之间变化.此外,您可以依靠自动校准技术而不是使用棋盘.无论如何,我已经编辑了我的答案,以解决您问题的第一部分,为您提供您正在寻找的公式. (2认同)

Wil*_*ill 8

(原创海报,提供替代方案)

以下函数将目标(直线)坐标映射到源(鱼眼失真)坐标. (我很感激帮助扭转它)

我通过反复试验达到了这一点:我没有从根本上理解为什么这个代码有效,解释和提高准确性赞赏!

def dist(x,y):
    return sqrt(x*x+y*y)

def correct_fisheye(src_size,dest_size,dx,dy,factor):
    """ returns a tuple of source coordinates (sx,sy)
        (note: values can be out of range)"""
    # convert dx,dy to relative coordinates
    rx, ry = dx-(dest_size[0]/2), dy-(dest_size[1]/2)
    # calc theta
    r = dist(rx,ry)/(dist(src_size[0],src_size[1])/factor)
    if 0==r:
        theta = 1.0
    else:
        theta = atan(r)/r
    # back to absolute coordinates
    sx, sy = (src_size[0]/2)+theta*rx, (src_size[1]/2)+theta*ry
    # done
    return (int(round(sx)),int(round(sy)))
Run Code Online (Sandbox Code Playgroud)

当使用因子3.0时,它成功地不使用作为示例的图像(我没有尝试质量插值):

死链接

(这是来自博客文章,为了比较:)

使用Panotools

  • 映射的相反步骤是按tan(r')/ r'的比例缩放,其中r'是距未失真图像中心的半径。 (2认同)
  • 而且这两个原因都是因为,如果您有向量v'= v * k(| v |),并且想要| v'| = f(| v |),则可以取第一个方程的绝对值:| v'| = | v | * k(| v |)= f(| v |)-这样k(| v |)= f(| v |)/ | v | (2认同)