鱼眼相机模型与去畸变实现

news2025/2/4 3:56:35

1.坐标系说明

鱼眼相机模型涉及到世界坐标系、相机坐标系、图像坐标系、像素坐标系之间的转换关系。对于分析鱼眼相机模型,假定世界坐标系下的坐标点P_W(x_w,y_w,z_w),经过外参矩阵的变换转到相机坐标系P(x_c,y_c,z_c),相机坐标再经过内参转换到像素坐标,具体如下

进一步进行变换得到如下

坐标(i, j)位置对应的就是无畸变图中的像素坐标。

那么在已知像素坐标时,根据上述表达式就能得到归一化的相机坐标 (\bar{x_c},\bar{y_c},1).实际计算时,可以用内参矩阵求逆也可以直接变换得到。

 xw = K_matrix_inv.dot(np.array([i, j, 1], dtype=float))
 x_d = xw[0]
 y_d = xw[1]
 x = float(i)
 y = float(j)
 x1 = (x - cx) / fx  # 求出ud ==> x1
 y1 = (y - cy) / fy  # 求出vd ==> y1
 # x == x1, y == y1

2.opencv实现

分析opencv鱼眼矫正最重要的函数是fisheye::initUndistortRectifyMap(),它能得到map1矩阵。对应opencv-python,

map1, map2 = cv2.initUndistortRectifyMap(camera_matrix, dist_coeffs, None, new_camera_matrix, (w, h), cv2.CV_32FC1)

map1是一个2通道矩阵,它在(i, j)处的二维向量元素(u, v) = (map1(i, j)[0], map1(i, j)[1])的意义如下:
将畸变图像中(u, v) = (map1(i, j)[0], map1(i, j)[1])的元素,复制到(i, j)处,就得到了无畸变图像。

 opencv官方给出的实现过程如下:

3.去畸变理论分析

鱼眼相机的入射与反射示意图如下图所示。对于相机坐标系下有一点 P(x,y,z),如果按照针孔相机模型投影,则不存在畸变,像点为P_0(a,b),发生畸变后的像点坐标为p'

在图中,r=\parallel OP_0\parallel,r_d=\parallel Op'\parallel.

在上图中不妨假设 f = 1,最终可以求得r_d和 r 的比值(与 f无关),从而可求得去畸变后的P_0点坐标(a,b) 以及入射角 \theta. 这里的(a,b,1)实际就是对应于P_0的齐次坐标。

实际的鱼眼镜头因为各种原因并不会精确的符合投影模型,为了方便鱼眼相机的标定,一般取r_d关于\theta泰勒展开式的前5项来近似鱼眼镜头的实际投影函数。具体来说,该近似结果最早由

Juho Kannala 和 Sami S. Brandt在《A Generic Camera Model and Calibration Method for Conventional, Wide-Angle, and Fish-Eye Lenses》论文中提出了一种一般的鱼眼模型,也是opencv和一般通常使用的模型,用入射角 \theta 的奇数次泰勒展开式来进行鱼眼模型的通用表示:

                        r_d =\theta(k_0+k_1\theta^2+k_2\theta^4+k_3\theta^6+k_4\theta^8)

通常设置k_0=1,使得相应的变化在后续的含k_1,k_2,k_3,k_4高次项目中体现,由此得到

                        r_d =\theta(1+k_1\theta^2+k_2\theta^4+k_3\theta^6+k_4\theta^8)

结合发生畸变后对应的归一化相机坐标(x',y',1),可以求出(a,b).

                                \left\{\begin{matrix} u=f_xx'+c_x \\ \\ v=f_yy'+c_y \end{matrix}\right. \Rightarrow \left\{\begin{matrix} x'=(u-c_x)/f_x \\ \\ y'=(v-c_y)/f_y\end{matrix}\right.

                                                \left\{\begin{matrix} \frac{a}{r}= \frac{x'}{r_d} \\ \\ \frac{b}{r}= \frac{y'}{r_d}\end{matrix}\right.\Rightarrow \left\{\begin{matrix} a=\frac{r}{r_d}x' \\ \\ b=\frac{r}{r_d}y' \end{matrix}\right.

注意,这里的r_d\neq \theta_d,根据示意图可知,无论采用通用模型还是等距投影模型,都严格存在如下

                                                \left\{\begin{matrix} tan(\theta)= \frac{OP_0}{f}=\frac{r}{f} \\ \\ tan(\theta_d)= \frac{Op'}{f}=\frac{r_d}{f}\end{matrix}\right.

对于 f=1,则有:

                                                        \left\{\begin{matrix}\theta = arctan(r) \\ \\ \theta_d = arctan(r_d)\end{matrix}\right.

实际计算过程,都是已知无畸变的像素坐标(i,j) 推导得到畸变后的像素坐标(u,v),再借助remap函数完成像素插值。当需要通过已知的畸变像素坐标反向投影得到无畸变点的像素时,也就是已知(u,v),采用上述关系得到 (x',y') ,此时已知r_d, 需要求出对应的\theta。所以畸变矫正的本质问题是求解关于\theta 的一元高次方程

                                        r_d =\theta(1+k_1\theta^2+k_2\theta^4+k_3\theta^6+k_4\theta^8)

常见求解一元高次方程的方法有二分法、不动点迭代、牛顿迭代法。这里采用牛顿迭代法求解。

                                令 f(\theta) =\theta+k_1\theta^3+k_2\theta^5+k_3\theta^7+k_4\theta^9-r_d,

                                                                \theta_0=r_d

                                                        \theta_{n+1}=\theta_{n}-\frac{f(\theta_n)}{f'(\theta_{n})}

循环迭代直到f(\theta)\approx 0(具体精度根据需要自行设置,比如设置阈值1e-6),或达到迭代次数上限。求得 \theta 之后,未畸变像点 P_0的坐标满足

                                                        \left\{\begin{matrix} a=\frac{r}{r_d}x' \\ \\ b=\frac{r}{r_d}y' \end{matrix}\right.

详见下文4.3代码。

4.代码实现

4.1 调用opencv

def undistort_imgs_fisheye(camera_matrix, dist_coeffs,img):
    # 注意:OpenCV 没有直接提供逆畸变的函数,但我们可以使用 cv2.initUndistortRectifyMap 和 cv2.remap 来模拟
    w = int(img.shape[1])
    h = int(img.shape[0])
    border_width  = int(w/4)
    border_height = int(h/4)

    img_bordered = cv2.copyMakeBorder(img, border_height, border_height, border_width, border_width, cv2.BORDER_ISOLATED)
    h_new, w_new = img_bordered.shape[:2]

    new_camera_matrix1, roi = cv2.getOptimalNewCameraMatrix(camera_matrix, dist_coeffs[:4], (w_new, h_new), 0.5, (w, h))


    # 计算去畸变和逆畸变的映射
    map1, map2 = cv2.fisheye.initUndistortRectifyMap(camera_matrix, dist_coeffs[:4], np.eye(3), new_camera_matrix1, (w_new, h_new), cv2.CV_16SC2)

    #根据CV_16SC2, map1此时是一个2通道的矩阵,每个点(i, j)都是一个2维向量, u = map1(i, j)[0], v= map1(i, j)[1],畸变图中坐标为(u, v)的像素点,在无畸变图中应该处于(i, j)位置。
    
    undistort_img = cv2.remap(img_bordered, map1, map2, cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT)
   
    return undistort_img

4.2 表达式实现

def undistort_imgs_fisheye_equid(params_matrix, distort, img):
    
    fx = params_matrix[0][0]
    fy = params_matrix[1][1]
    cx = params_matrix[0][2]
    cy = params_matrix[1][2]
    
    distortion_params = distort
    kk = distortion_params

    width  = int(img.shape[1] * 1)
    height = int(img.shape[0] * 1)
    print("w is: {}, h is: {}".format(width,height))

    mapx = np.zeros((width, height), dtype=np.float32)
    mapy = np.zeros((width, height), dtype=np.float32)
    
    for i in tqdm(range(0, width), desc="calculate_maps"):
        for j in range(0, height):
            x = float(i)  #x是去畸变后的像素坐标
            y = float(j)  #y是去畸变后的像素坐标
            a = (x - cx) / fx  # x ==> a
            b = (y - cy) / fy  # y ==> b
            r = np.sqrt(a**2 + b**2)
            theta = np.arctan2(r,1)
            rd = (1.0 *theta + kk[0] * theta**3 + kk[1] * theta**5 + kk[2] * theta**7 + kk[3] * theta**9)
            scale = rd/r if r!=0 else 1.0
            x2 = fx * a * scale + cx # width // 2
            y2 = fy *b * scale + cy # height // 2
            mapx[i, j] = x2
            mapy[i, j] = y2

    distorted_image = cv2.remap(
        img,
        mapx.T,
        mapy.T,
        interpolation=cv2.INTER_LINEAR,
        borderMode=cv2.BORDER_CONSTANT,
    )
    return distorted_image, params_matrix

4.3 反向投影


def diff(k2, k3, k4, k5, theta):
        theta_2 = theta * theta
        theta_4 = theta_2 * theta_2
        theta_6 = theta_4 * theta_2
        theta_8 = theta_6 * theta_2
        rd_diff = 1 + 3 * k2 * theta_2 + 5 * k3 * theta_4 + 7 * k4 * theta_6 + 9 * k5 * theta_8
        return rd_diff
    
def distort_theta(k2, k3, k4, k5, theta):
    theta_2 = theta * theta
    theta_3 = theta * theta_2
    theta_5 = theta_3 * theta_2
    theta_7 = theta_5 * theta_2
    theta_9 = theta_7 * theta_2
    theta_d = theta + k2 * theta_3 + k3 * theta_5 + k4 * theta_7 + k5 * theta_9
    return theta_d

def newton_itor_theta(k2, k3, k4, k5, r_d):
    theta = r_d
    max_iter = 500
    for i in range(max_iter):
        diff_t0 = diff(k2, k3, k4, k5, theta)
        f_t0 = distort_theta(k2, k3, k4, k5, theta) - r_d
        theta = theta - f_t0 / diff_t0
        if abs(f_t0) < 1e-6:
            break
    return theta

def distort_imgs_fisheye_new(params_matrix, distort, img):
    undistorted_image = np.zeros((img.shape))

    fx = params_matrix[0][0]
    fy = params_matrix[1][1]
    cx = params_matrix[0][2]
    cy = params_matrix[1][2]

    K_matrix_inv = np.linalg.inv(params_matrix)
    
    width  = int(img.shape[1] * 1)
    height = int(img.shape[0] * 1)
    mapx = np.zeros((width, height), dtype=np.float32)
    mapy = np.zeros((width, height), dtype=np.float32)
    
    for i in tqdm(range(0, width), desc="calculate_maps"):
        for j in range(0, height):
            xw = K_matrix_inv.dot(np.array([i, j, 1], dtype=float))
            x_d = xw[0]
            y_d = xw[1]
            x = float(i)
            y = float(j)
            x1 = (x - cx) / fx  # 求出ud ==> x1
            y1 = (y - cy) / fy  # 求出vd ==> y1

            phi = np.arctan2(y_d, x_d)

            r_d = np.sqrt(x_d ** 2 + y_d ** 2)
            theta = newton_itor_theta(distort[0],distort[1],distort[2],distort[3],r_d)
            r = np.tan(theta)
            # r_d = np.tan(theta_d)
            a = x_d * r/r_d
            b = y_d * r/r_d
            u = a*fx + cx
            v = b*fy + cy

            mapx[i, j] = u
            mapy[i, j] = v

    return mapx, mapy

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2268351.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于eBPF的微服务网络安全(Cilium 1)

一些开源的kubernetes工具已经开始使用eBPF&#xff0c;这些工具大多数与网络&#xff0c;监控和安全相关。 本文不会涵盖eBPF的方方面面&#xff0c;只作为一个入门指南&#xff0c;包括Linux内核的BPF概念&#xff0c;到将该功能加入到微服务环境的优势&#xff0c;以及当前…

我的JAVA-Web进阶--Maven

1.Maven功能 依赖管理和项目构建 2.Maven仓库 3.Maven项目构建 4.Maven依赖管理 依赖配置 依赖冲突 理解&#xff1a;层级指的是dependency的每一个包的树状图的深度&#xff0c;每一个包都向右延伸一个树状图&#xff0c;层级越深优先级越低&#xff0c;层级越浅&#xff0…

020-spring-整合web

引入web包。通过 ContextLoaderListener 在启动的时候加载spring.xml 加载spring.xml 之后&#xff0c;把dao层等对象放在容器中 <!DOCTYPE web-app PUBLIC"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN""http://java.sun.com/dtd/web-app_2_3.…

Kotlin Multiplatform 新纪元:klibs.io 与鸿蒙支持解锁跨平台开发新潜力

Kotlin Multiplatform 新纪元:klibs.io 与鸿蒙支持解锁跨平台开发新潜力 在软件开发日益多元化的今天,Kotlin Multiplatform(KMP) 正凭借其跨平台代码复用能力迅速成为开发者的首选工具之一。2024年,KMP生态系统的库数量激增了35%,标志着这一领域的强劲增长。然而,随着…

Qt从入门到入土(七)-实现炫酷的登录注册界面(下)

前言 Qt从入门到入土&#xff08;六&#xff09;-实现炫酷的登录注册界面&#xff08;上&#xff09;主要讲了如何使用QSS样式表进行登录注册的界面设计&#xff0c;本篇文章将介绍如何对登录注册界面进行整体控件的布局&#xff0c;界面的切换以及实现登录、记住密码等功能。…

【阅读笔记】《基于区间梯度的联合双边滤波图像纹理去除方法》

一、联合双边滤波背景 联合双边滤波&#xff08;Joint Bilateral Filter, JBF&#xff09;是一种图像处理技术&#xff0c;它在传统的双边滤波&#xff08;Bilateral Filter, BF&#xff09;基础上进行了改进&#xff0c;通过引入一个引导图&#xff08;guidance image&#x…

Dockerfile基础指令

1.FROM 基于基准镜像&#xff08;建议使用官方提供的镜像作为基准镜像&#xff0c;相对安全一些&#xff09; 举例&#xff1a; 制作基准镜像&#xff08;基于centos:lastest&#xff09; FROM cenots 不依赖于任何基准镜像 FROM scratch 依赖于9.0.22版本的tomcat镜像 FROM…

Linux实验报告9-进程管理

目录 一&#xff1a;实验目的 二&#xff1a;实验内容 (1)列出当前系统中的所有进程,如何观察进程的优先级? (2)查看当前终端运行的 bash 进程的 PID,在当前终端启动 vim 编辑器并让其在后台执行,然后列出在当前终端中执行的进程的家族树。 (3)请自行挂载U盘或光盘,然后…

活动预告 |【Part1】Microsoft Azure 在线技术公开课:数据基础知识

课程介绍 参加“Azure 在线技术公开课&#xff1a;数据基础知识”活动&#xff0c;了解有关云环境和数据服务中核心数据库概念的基础知识。通过本次免费的介绍性活动&#xff0c;你将提升在关系数据、非关系数据、大数据和分析方面的技能。 活动时间&#xff1a;01 月 07 日…

笔记本电脑驱动下载并安装

自己的电脑是鸡哥家的&#xff0c;经常出现没有声音、炸麦、麦克风没有音频输入等问题 最快的解决办法就是更新驱动 但是现在市面上好多驱动检测安装软件都已经开始收费了&#xff08;xx人生、xx精灵&#xff09;很恶心 所以不妨自己寻找驱动并重新安装 1.驱动下载 最好是去…

手机实时提取SIM卡打电话的信令声音-双卡手机来电如何获取哪一个卡的来电

手机实时提取SIM卡打电话的信令声音 --双卡手机来电如何获取哪一个卡的来电 一、前言 前面的篇章《手机实时提取SIM卡打电话的信令声音-智能拨号器的双SIM卡切换方案》中&#xff0c;我们论述了局域网SIP坐席通过手机外呼出去时&#xff0c;手机中主副卡的呼叫调度策略。 但…

人工智能及深度学习的一些题目(二)

1、【单选题】 不属于语音识别预处理的步骤是哪个&#xff1f; 去重 2、HSV&#xff0c;H是色调&#xff0c;S是饱和度&#xff0c;V是亮度。 3、【填空题】 语音信号预处理中&#xff08; 预加重 &#xff09;的目的是为了对语音的高频部分进行加重&#xff0c;去除口唇辐射的…

关于今天发现的一个bug

一个输入框&#xff0c;定义只能输入0-100的数字 经过测试没有问题。 在回归的时候偶然发现&#xff0c;在输入数字7&#xff0c;点击保存以后&#xff0c;再次打开&#xff0c;发现竟然显示 经过查资料发现&#xff1a; // 关于js失精算法你都遇到哪些&#xff0c;让我们一起来…

力扣-数据结构-7【算法学习day.78】

前言 ###我做这类文章一个重要的目的还是给正在学习的大家提供方向&#xff08;例如想要掌握基础用法&#xff0c;该刷哪些题&#xff1f;建议灵神的题单和代码随想录&#xff09;和记录自己的学习过程&#xff0c;我的解析也不会做的非常详细&#xff0c;只会提供思路和一些关…

oscp备战系列-Kioptrix2014

文章目录 一、信息收集二、漏洞探测三、漏洞利用四、后渗透 一、信息收集 主机探测 nmap 192.168.30.0/24 -sP端口及版本探测 nmap 192.168.30.199 -sV可以看到开放了80&#xff0c;8080端口&#xff0c;采用apache 2.2.21 mod_ssl2.2.21 openssl0.9.8q WebDAV2 php5.3.8 O…

SonarQube相关的maven配置及使用

一、maven 全局配置 <settings><pluginGroups><pluginGroup>org.sonarsource.scanner.maven</pluginGroup></pluginGroups><profiles><profile><id>sonar</id><activation><activeByDefault>true</acti…

mac系统vsCode中使用Better Comments在.vue文件里失效

问题&#xff1a;关于Better Comments默认在html、TS、JS中有效&#xff0c;在vue中无效,需要单独进行配置 windows系统可以参考友链Better Comments&#xff08;注释高亮&#xff09;在vue文件里失效的问题 关于Better Comments电脑的配置路径&#xff1a; Windows系统&…

Maven项目中不修改 pom.xml 状况下直接运行OpenRewrite的配方

在Java 的Maven项目中&#xff0c;可以在pom.xml 中配置插件用来运行OpenRewrite的Recipe&#xff0c;但是有一些场景是希望不修改pom.xml 文件就可以运行Recipe&#xff0c;比如&#xff1a; 因为不需要经常运行 OpenRewrite&#xff0c;所以不想在pom.xml 加入不常使用的插件…

命令行之巅:Linux Shell编程的至高艺术(上)

文章一览 前言一、shell概述1.1 shell的特点和类型1.1.1 **shell的特点&#xff1a;**1.1.2 常用shell类型 1.2 shell脚本的建立和执行1.2.1 建立shell脚本1.2.2 执行shell脚本的方式1.2.3 shell程序实例 二、shell变量与算数运算2.1 简单shell变量2.1.1 简单变量定义和赋值2.1…

深度学习:基于MindSpore NLP的数据并行训练

什么是数据并行&#xff1f; 数据并行&#xff08;Data Parallelism, DP&#xff09;的核心思想是将大规模的数据集分割成若干个较小的数据子集&#xff0c;并将这些子集分配到不同的 NPU 计算节点上&#xff0c;每个节点运行相同的模型副本&#xff0c;但处理不同的数据子集。…