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

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 ==> x1y1 = (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 = distortkk = distortion_paramswidth  = 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 ==> ab = (y - cy) / fy  # y ==> br = 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.0x2 = fx * a * scale + cx # width // 2y2 = fy *b * scale + cy # height // 2mapx[i, j] = x2mapy[i, j] = y2distorted_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 * thetatheta_4 = theta_2 * theta_2theta_6 = theta_4 * theta_2theta_8 = theta_6 * theta_2rd_diff = 1 + 3 * k2 * theta_2 + 5 * k3 * theta_4 + 7 * k4 * theta_6 + 9 * k5 * theta_8return rd_diffdef distort_theta(k2, k3, k4, k5, theta):theta_2 = theta * thetatheta_3 = theta * theta_2theta_5 = theta_3 * theta_2theta_7 = theta_5 * theta_2theta_9 = theta_7 * theta_2theta_d = theta + k2 * theta_3 + k3 * theta_5 + k4 * theta_7 + k5 * theta_9return theta_ddef newton_itor_theta(k2, k3, k4, k5, r_d):theta = r_dmax_iter = 500for i in range(max_iter):diff_t0 = diff(k2, k3, k4, k5, theta)f_t0 = distort_theta(k2, k3, k4, k5, theta) - r_dtheta = theta - f_t0 / diff_t0if abs(f_t0) < 1e-6:breakreturn thetadef 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 ==> x1y1 = (y - cy) / fy  # 求出vd ==> y1phi = 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_db = y_d * r/r_du = a*fx + cxv = b*fy + cymapx[i, j] = umapy[i, j] = vreturn mapx, mapy

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

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

相关文章

[图形渲染]【Unity Shader】【游戏开发】 Shader数学基础17-法线变换基础与应用

在计算机图形学中,法线(normal) 是表示表面方向的向量。它在光照、阴影、碰撞检测等领域有着重要作用。本文将介绍如何在模型变换过程中正确变换法线,确保其在光照计算中的正确性,特别是法线与顶点的变换问题。 1. 法线与切线的基本概念 法线(Normal Vector) 法线(或…

我的2024年度总结2025展望

2025已经到来&#xff0c;下面是我对2024年的个人总结&#xff0c;以及对2025年的未来展望。 2024总结——峰回路转 2024大概是我大学这几年收获最满的一年&#xff0c;我不仅收获了丰富的技术内容&#xff0c;也提高了例如情商、管理能力、团队协作、开源思想等技术之外的事…

Windows 下安装 triton 教程

目录 背景解决方法方法一&#xff1a;&#xff08;治标不治本&#xff09;方法二&#xff1a;&#xff08;triton-windows&#xff09;- 安装 MSVC 和 Windows SDK- vcredist 安装- whl 安装- 验证 背景 triton 目前官方只有Linux 版本&#xff0c;若未安装&#xff0c;则会出…

如何使用网络工具进行网络性能评估

网络评估是对IT基础设施的系统评估&#xff0c;以确保它能够很好地满足企业的核心运营需求&#xff0c;确定了基础设施中需要改进的领域&#xff0c;并定义了改进的范围。 网络评估工具分析IT基础设施的各个方面&#xff0c;它通过评估网络设备、网络性能和安全威胁来仔细检查…

Vue.js组件开发-实现动态切换菜单简单示例

在Vue.js中&#xff0c;实现动态切换菜单通过组件化开发和Vue的响应式数据绑定来实现。 示例&#xff1a; 展示如何创建一个可以动态切换菜单的Vue组件。 首先&#xff0c;需要定义一个Vue组件&#xff0c;该组件将包含菜单项和用于切换菜单的状态。 1. 创建Vue组件 <t…

【笔记_连续发请求的问题】

疑问&#xff1a; 连续给同一个账号下的同一个应用发送三次启动应用请求&#xff0c;顺序是&#xff1a;订单1、订单2、订单3&#xff0c;但是有时候的执行顺序是&#xff1a;订单1、订单3、订单2 不管用不用队列&#xff0c;用不用webhook&#xff0c;都会出现这种情况 回答…

Vue项目整合与优化

前几篇文章&#xff0c;我们讲述了 Vue 项目构建的整体流程&#xff0c;从无到有的实现了单页和多页应用的功能配置&#xff0c;但在实现的过程中不乏一些可以整合的功能点及可行性的优化方案&#xff0c;就像大楼造完需要进行最后的项目验收改进一样&#xff0c;有待我们进一步…

Python、R用深度学习神经网络组合预测优化能源消费总量时间序列预测及ARIMA、xgboost对比...

全文链接&#xff1a;https://tecdat.cn/?p38726 分析师&#xff1a;Qingxia Wang 在能源领域&#xff0c;精准预测能源消费总量对制定合理能源战略至关重要。当前&#xff0c;能源消费预测分析主要运用单一模型&#xff08;如灰色预测法、时间序列分析法等&#xff09;和组合…

STM32使用UART发送字符串与printf输出重定向

首先我们先看STM32F103C8T6的电路图 由图可知&#xff0c;其PA9和PA10引脚分别为UART的TX和RX(注意&#xff1a;这个电路图是错误的&#xff0c;应该是PA9是X而PA9是RX&#xff0c;我们看下图的官方文件可以看出)&#xff0c;那么接下来我们应该找到该引脚的定义是什么&#xf…

Kotlin在医疗大健康域的应用实例探究与编程剖析(下)

四、Kotlin医疗编程实例分析 4.1 移动医疗应用实例 4.1.1 患者健康监测应用 在当今数字化医疗时代,患者健康监测应用为人们提供了便捷的健康管理方式。利用Kotlin开发的患者健康监测应用,能够实时采集患者的各类生理数据,如心率、血压、血氧饱和度等,并通过直观的可视化…

探索数据之美,Plotly引领可视化新风尚

在数据如潮的今天&#xff0c;如何精准捕捉信息的脉搏&#xff0c;让数据说话&#xff1f;Plotly&#xff0c;这款强大的数据可视化工具&#xff0c;正以其卓越的性能和丰富的功能&#xff0c;成为数据分析师、科学家及工程师们的得力助手。 Plotly不仅仅是一个绘图库&#xf…

Redis 5设计与源码分析读书笔记

目录 引言Redis 5.0的新特性Redis源码概述Redis安装与调试 简单动态字符串数据结构基本操作创建字符串释放字符串拼接字符串扩容策略 其余API 本章小结兼容C语言字符串、保证二进制安全sdshdr5的特殊之处是什么SDS是如何扩容的 跳跃表简介跳跃表节点与结构跳跃表节点跳跃表结构…

Golang学习历程【第五篇 复合数据类型:数组切片】

Golang学习历程【第五篇 复合数据类型&#xff1a;数组&切片】 1. 数组&#xff08;Array&#xff09;1.1 数组的定义1.2 初始化数组1.3 数据的循环遍历1.4 多维数组 2. 切片&#xff08;Slice&#xff09;2.1 切片声明、初始化2.2 基于数组创建切片2.2 切片的长度(len)和容…

【Unity】 HTFramework框架(五十七)通过Tag、Layer批量搜索物体

更新日期&#xff1a;2024年12月30日。 Github源码&#xff1a;[点我获取源码] Gitee源码&#xff1a;[点我获取源码] 索引 问题再现通过Tag搜索物体&#xff08;SearchByTag&#xff09;打开SearchByTag窗口搜索标记指定Tag的所有物体批量修改Tag搜索Undefined状态的所有物体 …

基于feapder爬虫与flask前后端框架的天气数据可视化大屏

# 最近又到期末了&#xff0c;有需要的同学可以借鉴。 一、feapder爬虫 feapder是国产开发的新型爬虫框架&#xff0c;具有轻量且数据库操作方便、异常提醒等优秀特性。本次设计看来利用feapder进行爬虫操作&#xff0c;可以加快爬虫的速率&#xff0c;并且简化数据入库等操作…

PCL点云库入门——PCL库点云滤波算法之统计滤波(StatisticalOutlierRemoval)

1、算法原理 统计滤波算法是一种利用统计学原理对点云数据进行处理的方法。它主要通过计算点云中每个点的统计特性&#xff0c;如均值、方差等&#xff0c;来决定是否保留该点。算法首先会设定一个统计阈值&#xff0c;然后对点云中的每个点进行分析。如果一个点的统计特性与周…

CentOS7 解决ping:www.baidu.com 未知的名称或服务

CentOS7 解决ping&#xff1a;www.baidu.com“未知的名称或服务 在VM查看网络配置 查看虚拟网络编辑器 编辑网络配置文件 vi /etc/sysconfig/network-scripts/ifcfg-ens33注意&#xff1a;不同机器的配置文件名可能不相同&#xff0c;通过 ip addr 命令查看 将 ONBOOT 从 no 改…

aws(学习笔记第二十一课) 开发lambda应用程序

aws(学习笔记第二十一课) 开发lambda应用程序 学习内容&#xff1a; lambda的整体概念开发lambda应用程序 1. lambda的整体概念 借助AWS Lambda&#xff0c;无需预置或管理服务器即可运行代码。只需为使用的计算时间付费。借助 Lambda&#xff0c;可以为几乎任何类型的应用进…

【Leetcode】3280. 将日期转换为二进制表示

文章目录 题目思路代码复杂度分析时间复杂度空间复杂度 结果总结 题目 题目链接&#x1f517; 给你一个字符串 date&#xff0c;它的格式为 yyyy-mm-dd&#xff0c;表示一个公历日期。 date 可以重写为二进制表示&#xff0c;只需要将年、月、日分别转换为对应的二进制表示&a…

docker compose部署kafka集群

先部署zookeeper集群&#xff0c;启动 参考&#xff1a;docker compose部署zookeeper集群-CSDN博客 再部署kafka集群 networks: net: external: true services: kafka1: restart: always image: wurstmeister/kafka:2.13_2.8.1 container_name: kafka1 …