引用 常用相机投影及畸变模型(针孔|广角|鱼眼)_相机畸变模型-CSDN博客https://blog.csdn.net/qq_28087491/article/details/107965151
针孔相机投影
习惯上将z 轴指向相机前方,x 轴向右,y 轴向下,O是摄像机的光心,也是针孔模型的针孔,现实世界空间点P,经过小孔O投影后,落在O‘-x’-y'上,成像点P‘。
#注意这里的 ( c x , c y ) 写的是 光心 或 principle point(PP),不是畸变中心(COD)
缩略词和术语
item | desc |
COD | 畸变中心(Center of Distortion), 在传感器上的 ( x , y )表示拟合的最佳对称中心 (unit:pixel) |
DM | 畸变模型(Distortion Model) |
EFL | 有效焦距(Effective Focal Length) |
Pinhole Model FL | 针孔模型焦距(Pinhole Model Focal Length) - 图像去畸变后的模型焦距 (unit:pixel) |
FOV | 视野(Field of View)。FOV的大小取决于相机镜头的焦距 和传感器尺寸 。较短的焦距和较大的传感器尺寸会导致更大的FOC,即能够捕捉到更广阔的画面范围。 |
PP | 主点(Principle Point) - 透镜光轴与传感器表面的交点 |
LPP | 透镜主平面(Lens Primary Plane) |
世界坐标系到像素坐标系
世界坐标系中三维点 M=【X,Y,Z】^T 和像素坐标系中二维点 m=[u,v]^T的关系:
其中,s为缩放因子(Z c),A为相机的内参矩阵,【R T】是相机的外参矩阵
具体可表示为:
针孔相机模型
相机中三维世界中坐标点m 映射到 二维图像平面pixel 的过程能够用一个几何模型来描述,其中最简单的称之为 针孔相机模型(pinhole camera model)
像素坐标:像素点在图像中的位置,以像素点左上角为原点
图像坐标:图像中某个特征点在整幅图像的位置,此时原点位于图像的中心
世界坐标系到相机坐标系:
相机坐标系到像素坐标系:
其中,f是镜头焦距,单位为米,α、β 的单位为像素/米,fx,fy是x,y方向的焦距,单位为像素。最终形成矩阵的形式为:
从另一个角度看,我们可以把一个世界坐标点先转换到相机坐标系,再除掉它最后一维的数值(即该点距离相机成像平面的深度),这相当于把最后一维进行归一化处理,得到点 P 在相机归一化平面上的投影:
归一化坐标可以看成相机前方 z = 1处的平面上的一个点,这个 z = 1平面也称为归一化平面。归一化坐标再左乘内参就得到了像素坐标,所以我们可以把像素坐标 [ u , v ] T看成对归一化平面上的点进行量化测量的结果。从这个模型中也可以看出,如果对相机坐标同时乘以任意非零常数,归一化坐标都是一样的,这说明点的深度在投影过程中被丢失了,所以单目视觉中没法得到像素点的深度值。
内参标定是找到一个相机的物理参数的过程,标定结果是一个相机内参的结合
畸变模型
普通相机畸变包括 径向畸变和切向畸变
切向畸变表达式沿半径方向的偏移量,径向畸变的形成原因是镜头制造工艺不完美,使得镜头形状存在缺陷,通常又分为桶性畸变和枕形畸变,分别代表 dr 往外偏和往里偏。
[x distorted,y distorted]T是畸变后点的归一化坐标
。
径向畸变:
真实的摄像头使用的并不是针孔,它们使用的是能一次性聚焦大量光线的镜头,这使得其能迅速生成图像,但是,镜头仍会产生失真,光线通常会在摄像机镜头的边缘出现较大或较小幅度的弯曲,这会产生图像边缘扭曲的效果,因此,线条或者物体会比真实情况呈现出或多或少的弯曲。这种失真被称为径向畸变,是最常见的失真类型。
桶形畸变(Barrel Distortion):又称桶形失真,是指光学系统引起的成像画面呈桶形膨胀状的失真现象。 桶形畸变在摄影镜头成像尤其是广角镜头成像时较为常见。
枕形畸变(Pincushion Distortion):又称枕形失真,它是指光学系统引起的成像画面向中间“收缩”的现象。 枕形畸变在长焦镜头成像时较为常见
实际情况中我们常用 r = 0 r=0r=0 处的泰勒级数展开的前几项来近似描述径向畸变,径向畸变后的归一化坐标为:
切向畸变:是由于透镜和CMOS或者CCD的安装位置误差导致(相机的组装过程中由于不能使得透镜和成像面严格平行),切向畸变需要两个额外的畸变参数来描述,切向畸变后的归一化坐标为:
其中,r2=x2+y2
对于相机坐标系中的一点 P PP,我们能够通过5个畸变系数找到这个点在像素平面上的正确位置:
由于参数过多导致数值求解不稳定, 通常只使用 k 1 , k 2 , k 3 , p 1 , p 2,我们一共需要5个畸变参数( k 1 , k 2 , k 3 , p 1 , p 2 ) 来描述透镜畸变。
将畸变后的点通过内参数矩阵投影到像素平面,得到该点在图像上的正确位置:
歪斜:图像传感器图像原尺寸在制造过程可能不是正方形,传感器歪斜和不是正方形主要对相机 x和 y方向的焦距产生影响。
在不考虑畸变的情况下,考虑主点偏移、图像传感器的特性,3D 目标点成像数学模型用以下公式可完全表达。这就是相机内部参数对成像的影响,因此 K称为内参矩阵,相机内参标定主要是标定相机的焦距、主点、歪斜(下面的s 、)等内部参数
畸变矫正
for (int v = 0; v < height; v++) {
for (int u = 0; u < width; u++) {
double u_distorted = 0, v_distorted = 0;
// 计算点(u,v)对应到畸变图像中的坐标(u_distorted, v_distorted)
double x = (u-cx)/fx;
double y = (v-cy)/fy;
double x2 = x*x, y2 = y*y, xy = x*y, r2 = x2 + y2;
double x_radial = x * (1 + k1*r2 + k2*r2*r2);
double y_radial = y * (1 + k1*r2 + k2*r2*r2);
double x_tangential = 2*p1*xy + p2*(r2 + 2*x2);
double y_tangential = 2*p2*xy + p1*(r2 + 2*y2);
double xd = x_radial + x_tangential;
double yd = y_radial + y_tangential;
u_distorted = xd*fx + cx;
v_distorted = yd*fy + cy;
// 赋值(最近邻插值)
if (u_distorted >= 0 && v_distorted >= 0 && u_distorted < width && v_distorted < height)
img_dst(v, u) = (*this)((int) v_distorted, (int) u_distorted);
else
img_dst(v, u) = 0;
}
}
畸变矫正是计算机视觉和摄影领域中的一个常见任务,特别是在使用广角镜头时。以下是代码的逐行解释:
-
for (int v = 0; v < height; v++) {
和for (int u = 0; u < width; u++) {
: 这两个嵌套的循环遍历图像的每个像素点。v
和u
分别代表像素点的行(高度)和列(宽度)坐标。 -
double u_distorted = 0, v_distorted = 0;
: 初始化畸变校正后的像素坐标。 -
double x = (u-cx)/fx;
和double y = (v-cy)/fy;
: 将像素坐标(u, v)
转换为相机坐标系中的归一化坐标(x, y)
。这里cx
和cy
是图像中心(通常是光学中心),fx
和fy
是焦距。 -
double x2 = x*x, y2 = y*y, xy = x*y, r2 = x2 + y2;
: 计算x
和y
的平方、乘积以及x
和y
平方和的平方,这些值将用于后续的畸变模型计算。 -
double x_radial = x * (1 + k1*r2 + k2*r2*r2);
和double y_radial = y * (1 + k1*r2 + k2*r2*r2);
: 计算径向畸变。k1
和k2
是畸变系数,它们决定了径向畸变的程度。这部分代码应用了畸变模型来校正x
和y
坐标。 -
double x_tangential = 2*p1*xy + p2*(r2 + 2*x2);
和double y_tangential = 2*p2*xy + p1*(r2 + 2*y2);
: 计算切向畸变。p1
和p2
是切向畸变系数,它们影响图像边缘的像素。 -
double xd = x_radial + x_tangential;
和double yd = y_radial + y_tangential;
: 计算校正后的归一化坐标。 -
u_distorted = xd*fx + cx;
和v_distorted = yd*fy + cy;
: 将校正后的归一化坐标转换回像素坐标。 -
if (u_distorted >= 0 && v_distorted >= 0 && u_distorted < width && v_distorted < height)
: 检查校正后的像素坐标是否在图像范围内。 -
img_dst(v, u) = (*this)((int) v_distorted, (int) u_distorted);
: 使用最近邻插值将畸变校正后的像素值赋给输出图像img_dst
。(*this)
表示当前对象的调用,这里应该是一个图像对象,用于获取原始图像中对应像素的值。 -
else img_dst(v, u) = 0;
: 如果校正后的像素坐标超出图像范围,则将输出图像中对应的像素值设置为0(通常是黑色)。
这段代码的目的是将畸变图像中的每个像素映射到校正后的图像中,通过应用畸变模型来校正每个像素的位置。最终,img_dst
将包含畸变校正后的图像。
COD和PP
COD图片上两点在透镜中心相交,而PP的理论上垂直于传感器表面。也就是说在透镜装歪了的时候,COD本身也会跟着歪过来,但是PP理论上是垂直的,可能因为标定误差的原因导致它偏离了原位置
展示了切向失真,这会导致 PP 和 COD 不同
在自动驾驶和计算机视觉领域,将激光雷达(LiDAR)点云投影到相机图像平面是一个常见的需求,用于多传感器数据融合和3D场景理解。这个过程通常涉及以下步骤:
-
坐标系转换:首先需要将点云从激光雷达坐标系转换到相机坐标系。这通常通过一个外参矩阵(包含旋转和平移信息)来实现,该矩阵描述了激光雷达和相机之间的空间关系。
-
相机模型:使用相机的内参矩阵(包含焦距、主点坐标等)和针孔相机模型将3D点投影到2D图像平面。这个模型描述了3D点到2D像素点的几何关系。
-
畸变校正:由于相机镜头的畸变,可能需要对投影的2D点进行畸变校正,以获得准确的像素坐标。
-
深度信息融合:将投影到图像平面的点云与相机捕获的RGB图像结合,形成带有深度信息的RGB-D图像,这对于后续的目标检测、跟踪和场景理解等任务至关重要。
在实际操作中,可以使用如OpenCV这样的计算机视觉库来执行上述步骤。例如,使用cv::projectPoints
函数可以将3D点云投影到2D图像平面,并进行畸变校正。
3D可视化点云数据及投影数据
Open3D是一个开源库,用于处理3D数据。它提供了许多用于3D几何数据的工具,包括点云、三维重建、表面重建、体积渲染等。以下是一些基本的步骤和概念,可以帮助你开始使用Open3D进行点云可视化学习
下载包:
pip install open3d
读取点云
import open3d as o3d
# 读取点云文件
pcd = o3d.io.read_point_cloud("path_to_your_pcd_file.pcd")
点云数据可视化
#点云数据转换为 Open3D 的 Vector3dVector
#点云数据转换为 Open3D 的 Vector3dVector
img_points_vector0 = o3d.utility.Vector3dVector(pcd.points)
# 绘制投影点创建一个 Open3D PointCloud 对象
pcd = o3d.geometry.PointCloud()
# 正确地赋值
pcd.points = img_points_vector0
#创建可视化实例
vis = o3d.visualization.Visualizer()
vis.create_window(window_name="pcd project camera image",width=800, height=600)
#向视图中添加几何对象
vis.add_geometry(pcd)
#启动可视化
vis.run()
销毁窗口
vis.destroy_window()
或者也可以写成
o3d.visualization.draw_geometries([pcd])
前者提供了根本更多的定制化选项,允许创建一个可视化窗口,可以添加多个几何对象(点云、网格等)还可以进行视图的控制和交互 。后者是一个更简单快捷的方法,可视化一个或者多个几何对象,自动创建一个窗口,不需要手动调用创建和销毁窗口的方法。
可视化之前可以对点云进行一些处理:滤波、下采样、估计法线等
eg:体素下采样减少点的数量
voxel_size = 0.05 # 体素的大小
downpcd = pcd.voxel_down_sample(voxel_size)
eg:估计点云中每个点的法线
downpcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.1, max_nn=30))
eg:点云变换-平移、旋转等
# 创建一个4x4的变换矩阵
transformation = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]])
# 应用变换
pcd.transform(transformation)
eg:点云分割。如半径分割、平面分割
radii = [0.05, 0.1, 0.2]
segmented_pcd = pcd.segment_radial_search(radii)
eg:保存文件
o3d.io.write_point_cloud("path_to_save_pcd_file.pcd", segmented_pcd)
然后我们就进入到了点云到相机投影的过程啦!
首先需要齐次坐标,这是一种表示点的方法,它将传统坐标系统中的二维或三维坐标扩展到更高维度。这种方法在处理几何变换(如平移、旋转、缩放)时非常有用,因为它允许将这些变换统一表示为矩阵乘法。做这一步的目的是和标定参数维度一致可以进行矩阵乘法操作,在二维空间中,一个普通的点 P(x,y) 可以表示为齐次坐标 P′(x,y,1)。在三维空间中,点 P(x,y,z)可以表示为齐次坐标 P′(x,y,z,1)。
pcd_points= np.asarray(pcd.points)
# 将点云扩展到齐次坐标
points_homogeneous = np.hstack((pcd_points, np.ones((pcd_points.shape[0], 1))))
第一行是第一个点的坐标,第二行是第二个点的坐标,...,最后一行是最后一个点的坐标。
加入有一个相机的外参矩阵
T_w_c = np.array([
[-0.007111, -0.009311, -0.999931, -5.764661],
[0.99992, 0.0122, -0.007232, 0.085112],
[0.012073, -0.999883, 0.009233, -125.417163],
[0.0, 0.0, 0.0, 1.0]
])
左上角的3x3子矩阵 [-0.007111, -0.009311, -0.999931]
, [0.99992, 0.0122, -0.007232]
, [0.012073, -0.999883, 0.009233]
表示相机坐标系xyz相对于世界坐标系的旋转。
第四列的前三个数字 -5.76466
, 0.08511
, -125.41716
表示世界坐标系原点在相机坐标系中的位置,即相机坐标系原点相对于世界坐标系的平移。最后一行 [0.0, 0.0, 0.0, 1.0]
确保了齐次坐标的正确应用。
T_w_c
矩阵的每一列代表相机坐标系的基向量在世界坐标系中的表示,而最后一行通常用于处理齐次坐标,确保变换可以应用于非齐次坐标(即普通的三维坐标)。
在举证乘法中,能否相乘取决于他们的相撞,行数相同就可以相乘,如果 A 是一个 m×n 矩阵,B 是一个 n×p 矩阵,那么它们的乘积 AB 将是一个 m×p 矩阵。
points_camera = T_w_c @ points_homogeneous.T
points_camera = points_camera.T
这里齐次坐标后的点云是N*4的矩阵,外参矩阵是4*4的矩阵,所以要先对齐次坐标转置后才能相乘,每个点的齐次坐标(x, y, z, 1)现在变成了行向量。points_camera = points_camera.T
这一行代码将 points_camera
转置回 Nx4 的矩阵,其中 N 是点的数量。这样,每个点的齐次坐标再次变成了列向量。最终得到N *4的矩阵。
计算投影-内参-转换为笛卡尔坐标:
首先我们需要知道内参矩阵,这是一个描述相机内部特性的一个重要参数,包含了相机镜头的几何和光学特性,通常用于将三维坐标系中的点转换为相机坐标系中的二维图像。fx,fy 是沿图像 x轴和 y轴的焦距(以像素为单位),u0,v0 是图像的主点坐标(通常是图像的中心)
要将相机坐标系下的点投影到二维图像平面上,你需要执行以下步骤:
-
提取 x,y,z坐标:从
points_camera
矩阵中提取每个点的 x,y,z 坐标。 -
应用内参矩阵:将内参矩阵与 x,y,z标相乘,得到齐次坐标下的图像平面上的点。
-
从齐次坐标转换为笛卡尔坐标:将得到的齐次坐标转换为笛卡尔坐标,这通常涉及到除以齐次坐标的第三个分量(ww)。
#内参矩阵
#焦距(f_x,f_y),主点坐标(u_0,v_0)
intrinsic = np.array([
[f_x, 0, u_0],
[0, f_y, v_0],
[0, 0, 1]
])
img_points_homogeneous = intrinsic @ points_camera[:, :3].T
img_points_homogeneous = img_points_homogeneous.T
img_points = img_points_homogeneous[:, :2] / img_points_homogeneous[:, 2].reshape(-1, 1)
相机的内参矩阵,它是一个 3x3 的矩阵,poins_camera相机坐标系是一个N*4(x,y,z,w)的矩阵,两者相乘需要取前三列相机坐标xyz即N*3矩阵再转置变为3*N,这样3*3的内参矩阵就可以@3*N的相机坐标系了,得到3*N的矩阵,N表示点数。
再转置回来得到N*3的矩阵了。得到图像平面上的二维齐次坐标。(x',y',w'),w′ 仍然是一个缩放因子,用于将齐次坐标转换回普通的笛卡尔坐标。
最后将齐次坐标转换为二维笛卡尔坐标,得到二维图像平面上的点坐标。
- 首先【:,:2】取前列的坐标;
img_points_homogeneous[:, 2].reshape(-1, 1)
将第三列(w
坐标)转换为一个列向量,以便可以与(x, y)
坐标进行逐元素的除法。- 除法
(x, y) / w
将齐次坐标转换为笛卡尔坐标 - 最后我们会得到点1的二维图像坐标,点2的二维图像坐标。。。,得到【N,2】矩阵坐标。这些坐标现在是以像素为单位的二维图像坐标,可以直接用于在图像上标记这些点的位置。
在投影过程中,深度信息实际上是由三维点的 zz 坐标(在相机坐标系中)表示的,它描述了点到相机光心的距离。在透视投影中,点的 x′ 和 y′坐标会随着 z 坐标的变化而变化,从而在二维图像上产生透视效果。在将齐次坐标 (x′,y′,w′)转换为笛卡尔坐标 (u,v)时,我们通过除以 w′ 来“去除齐次化”.