Unity3d和OpenCV的相机模型

这主要是游戏引擎里的相机模型和OpenCV里的相机模型之间的相互印证的实验。

一、Unity的相机模型

考虑u3d里的相机是一个完美的针孔模型的相机,使用3d游戏里的变换方法来作出一个3D点和屏幕点(等于相机画面)的推导。

现在有u3d场景中的一个相机的fov是60度,L相机放在坐标(0,0,0)的位置,R相机放在(0.05,0,0)的位置。左边的相机,fov是60度,近裁剪面是0.01,远裁剪面是100,显示画面是1920x1080,这个相机的现在的基本的信息是:

{
    "worldToCameraMatrix": {
        "m00": 1.0,
        "m10": 0.0,
        "m20": 0.0,
        "m30": 0.0,
        "m01": 0.0,
        "m11": 1.0,
        "m21": 0.0,
        "m31": 0.0,
        "m02": 0.0,
        "m12": 0.0,
        "m22": -1.0,
        "m32": 0.0,
        "m03": 0.0,
        "m13": 0.0,
        "m23": 0.0,
        "m33": 1.0
    },
    "projectionMatrix": {
        "m00": 0.974278569221497,
        "m10": 0.0,
        "m20": 0.0,
        "m30": 0.0,
        "m01": 0.0,
        "m11": 1.73205077648163,
        "m21": 0.0,
        "m31": 0.0,
        "m02": 0.0,
        "m12": 0.0,
        "m22": -1.00020003318787,
        "m32": -1.0,
        "m03": 0.0,
        "m13": 0.0,
        "m23": -0.0200020000338554,
        "m33": 0.0
    },
    "transform": {
        "position": {
            "x": 0.0,
            "y": 0.0,
            "z": 0.0
        },
        "rotation": {
            "x": 0.0,
            "y": 0.0,
            "z": 0.0,
            "w": 1.0
        },
        "name": "Camera1"
    }
}

观察空间对z分量取反了,所以目测worldToCameraMatrix矩阵里有一个-1,worldToCameraMatrix
projectionMatrix
根据3D渲染的变换计算方法,现在已经可以使用c++完成从3D空间中的一个点到屏幕上一点坐标的这个计算,已经核对和U3D中的结果一致。UnityCamera.h

考虑这样一个相机模型在opencv里面会是个啥样的。。。

二、使用U3D渲染的无畸变图片来做标定实验

相机L,R成水平排列,位置和上面的第一节描述一致。现在制作标定板物体:

  • 板子的像素图片是960x1511,预制体大小是0.96x1.511
  • 从88像素到760像素,中间是3个间距,所以一个间距是224像素。
  • 从195像素到1315像素,中间是5个间距,所以一个间距是224像素。
  • 224像素对应的距离就是0.224
    u3d渲染得到的标定图片如下,无畸变。。
    标定用图片
    标定之后计算出来的板子原始位置都很精确,板子很直,误差也很小。。
    根据参数反求的标定板,结果是0.224
    标定板笔直。。
    所以,使用U3D渲染出来的画面可以轻松的标定相机。

参数分析1:统一相机画面在640x360

把1920x1080的图片的点坐标转换成640x360的图片比例计算出来的结果是:

M1: !!opencv-matrix
   rows: 3
   cols: 3
   dt: d
   data: [ 3.1145309294435958e+02, 0., 3.1981847499872049e+02, 0.,
       3.1143397076999474e+02, 1.7979797969812509e+02, 0., 0., 1. ]
D1: !!opencv-matrix
   rows: 1
   cols: 5
   dt: d
   data: [ 1.9639309936262010e-04, -1.0123185609157770e-03, 0., 0.,
       6.8767722366737535e-04 ]
M2: !!opencv-matrix
   rows: 3
   cols: 3
   dt: d
   data: [ 3.1145573222517822e+02, 0., 3.1981447040462308e+02, 0.,
       3.1143841438146063e+02, 1.7979177117828206e+02, 0., 0., 1. ]
D2: !!opencv-matrix
   rows: 1
   cols: 5
   dt: d
   data: [ 1.7158745285466290e-04, -8.7318865791043490e-04, 0., 0.,
       5.5968577019243357e-04 ]
R: !!opencv-matrix
   rows: 3
   cols: 3
   dt: d
   data: [ 9.9999999991701083e-01, 1.2953468591120522e-06,
       1.2817976730929375e-05, -1.2955983624361653e-06,
       9.9999999980666587e-01, 1.9621152483399704e-05,
       -1.2817951312253886e-05, -1.9621169088720433e-05,
       9.9999999972535492e-01 ]
T: !!opencv-matrix
   rows: 3
   cols: 1
   dt: d
   data: [ 4.9996304610299518e-02, 4.9630650415325490e-08,
       2.2170213223960013e-05 ]
E: !!opencv-matrix
   rows: 3
   cols: 3
   dt: d
   data: [ 2.8087528687204077e-11, -2.2170214193485139e-05,
       4.9195645267437877e-08, 2.2811063420407559e-05,
       9.8101466468592521e-07, -4.9996304312391000e-02,
       -1.1440578079217043e-07, 4.9996304600569239e-02,
       9.8098448020066426e-07 ]
F: !!opencv-matrix
   rows: 3
   cols: 3
   dt: d
   data: [ 8.3735822730697772e-10, -6.6098912885294780e-04,
       1.1930103267696988e-01, 6.8009165111491325e-04,
       2.9249879144060730e-05, -4.6447335282452690e+02,
       -1.2333743779072519e-01, 4.6446334959129268e+02, 1. ]
R1: !!opencv-matrix
   rows: 3
   cols: 3
   dt: d
   data: [ -9.9999989591309058e-01, -2.2793319751775300e-06,
       -4.5625498635018042e-04, 2.2838083322875222e-06,
       -9.9999999994926858e-01, -9.8105656814498609e-06,
       -4.5625496396549788e-04, -9.8116066592378730e-06,
       9.9999989586756477e-01 ]
R2: !!opencv-matrix
   rows: 3
   cols: 3
   dt: d
   data: [ -9.9999990168131858e-01, -9.9268627796705807e-07,
       -4.4343699433431108e-04, 9.8833600549133372e-07,
       -9.9999999995138811e-01, 9.8105698327887659e-06,
       -4.4343700405157280e-04, 9.8101306034788079e-06,
       9.9999990163368768e-01 ]
P1: !!opencv-matrix
   rows: 3
   cols: 4
   dt: d
   data: [ 2.4910197018477044e+02, 0., 3.2047301483154297e+02, 0., 0.,
       2.4910197018477044e+02, 1.8020493888854980e+02, 0., 0., 0., 1.,
       0. ]
P2: !!opencv-matrix
   rows: 3
   cols: 4
   dt: d
   data: [ 2.4910197018477044e+02, 0., 3.2046898651123047e+02,
       -1.2454179204862012e+01, 0., 2.4910197018477044e+02,
       1.8020493888854980e+02, 0., 0., 0., 1., 0. ]
Q: !!opencv-matrix
   rows: 4
   cols: 4
   dt: d
   data: [ 1., 0., 0., -3.2047301483154297e+02, 0., 1., 0.,
       -1.8020493888854980e+02, 0., 0., 0., 2.4910197018477044e+02, 0.,
       0., 2.0001476298616534e+01, -8.0572353253704293e-02 ]

上面的标定结果数据里看,首先L相机和R相机的M矩阵基本是完全相等的,它们都是:

//M矩阵311.45     0      319.8  丨
丨 0        311.43   179.79 丨
丨 0          0        1

fx和fy是311.45。cx是319.9(640的一半是320),cy是179.79(360的一半是180),即c是在正中心。
两个相机的D不太一致,但是K1,k2,k3都是10^-4的级别,很小(目前实际的相机一般K1,K2,K3在10^-1,-2的级别),可以认为它们基本上不需要畸变的校正。

验证内参矩阵中焦距

假设现在在z等1的位置有一个物体(0.5, 0.3, 1),使用u3d渲染相机模型计算当640x360的画面的时候渲染结果点在 (475.88, 86.469)。
红色物体坐标为(0.5, 0.3, 1)
使用前面写的u3d相机模型计算一个精确结果:

    dxlib::UnityCamera cam;
    cam.position = {0, 0, 0};
    cam.setEulerAngle(0, 0, 0);
    cam.screenSize = {640, 360};
    cam.updateW2C();
    cam.updateProj();
    Eigen::Vector2d screenPoint;
    bool success = cam.point2Screen({0.5, 0.3, 1}, screenPoint);
    //计算结果是 screenPoint = (475.88, 86.469)

使用opencv里相机内参的M矩阵乘以这个物体点,得到的结果是:

{ 475.54502147090028, 273.22817092912351, 1.0000000000000000 }

这个结果x轴坐标对应无误,y轴上opencv的相机模型结果是273.22(360-86.469=273.3,所以它的画面uv坐标系是以左下角为原点的,可以理解当x=0,y=0的时候,对应屏幕点的值是2分之1屏幕宽和2分之1屏幕高的正值,屏幕中心点在坐标系中都是正值,所以原点在左下角)。所以相机的内参矩阵中的f的物理含义可以理解为在1米远处的一米长度的物体在画面上代表了多少像素,然后再结合画面图像的大小也可以确定出相机的视野范围。

//使用这个内参矩阵的公式进行计算,计算完了之后的最终结果应该要除以z311.45     0      319.8  丨  丨0.5丨
丨 0        311.43   179.79*0.3= {475.545, 273.228, 1 }0         0         1    丨  丨 1

所以结论:使用内参矩阵计算和3D引擎里面的投影矩阵的计算结果是一致的,在3D游戏引擎里面使用一个4x4的投影矩阵,然后进行除w的计算。在opencv中这一步可以直接使用内参矩阵来确定。如果是在3D游戏引擎的相机中,那么相机模型就是一个完美的针孔相机,cx和cy是画面中心,内参数矩阵实际上只有一个fx=fy未知数需要确定,那么如果知道投影矩阵应该只要一对3d点和屏幕2d点即可以求出结果。另外如果已知FOV也可以直接根据FOV的定义求出焦距fx和fy。

///根据FOV和纵横比计算投影矩阵和OpenCV相机模型的内参矩阵. 
void updateProj()
{
       //这里横纵比实际上还是需要一个Viewport Rect中的W和H属性共同决定
       double aspect = screenSize.x() / (double)screenSize.y();

       //公式推导见冯乐乐的Shader书P79,第一个公式M_frustum,书中图2有笔误
       projectionMatrix << 
       (1 / tan((FOV / 180 * M_PI) / 2)) / aspect, 0, 0, 0,
       0, 1 / tan((FOV / 180 * M_PI) / 2), 0, 0,
       0, 0, -(Far + Near) / (Far - Near), -2 * Near * Far / (Far - Near),
       0, 0, -1, 0;

       //根据FOV计算相机内参矩阵,内参矩阵中的f的物理含义可以理解为:
       //在1米远处的一米长度的物体在画面上代表了多少像素
       double fx = (screenSize.y() / 2) / tan((FOV / 180 * M_PI) / 2);
       ocvCameraMatrix << 
       fx, 0, (double)screenSize.x() / 2,
       0, fx, (double)screenSize.y() / 2,
       0, 0, 1;
}

关于立体标定结果的RT

RT参数没什么悬念,R矩阵结果是1和2相机之间的旋转矩阵,其中目前相对没有旋转.

@param R Output rotation matrix between the 1st and the 2nd camera coordinate systems.
@param T Output translation vector between the coordinate systems of the cameras.

  • R参数的矩阵是:
    1  0  0 丨
    丨 0  1  0 丨
    丨 0  0  1
  • T是(0.05, 0, 0),在u3d场景中R相机确实是在L相机的左边0.05的位置,嗯它没有闹什么幺蛾子。

本征矩阵E和基本矩阵F

书上说,本征矩阵E包含关于物理空间中两个摄像机的平移和旋转的信息,除了包含E中相同的信息外,矩阵F还包含两个相机的内参数。由于F包含内参数信息,它可以在像素坐标系上将两台相机关联。但是E矩阵是一个3x3的矩阵,它是如何包含平移旋转。。F也是一个3x3的矩阵,不懂。。

R和P矩阵

R1,R2是左右摄像机平面间的行对准的3x3标定旋转矩阵(未仔细验证含义,在上面的例子中它几乎是无旋转的单位矩阵)。
p1矩阵的左边3列是newCameraMatrix:

| 249.10     0     320.473     0  |
|  0      249.10   180.205     0  |
|  0         0        1        0  |

因为这个标定的相机是完美的相机,误差全来自标记点的中心计算,所以p2矩阵和p1矩阵数值上几乎完全一样,实际标定的相机还是有待确认一下。

参数分析2:相机画面是1920x1080

把1920x1080的图片的点坐标不作转换的图片比例计算出来的结果是:

M1: !!opencv-matrix #三倍关系(和640x360的比较)
   rows: 3
   cols: 3
   dt: d
   data: [ 9.3435903472785969e+02, 0., 9.5945537660556943e+02, 0.,
       9.3430167531283428e+02, 5.3939395064402299e+02, 0., 0., 1. ]
D1: !!opencv-matrix
   rows: 1
   cols: 5
   dt: d
   data: [ 1.9657924861968512e-04, -1.0128202942453653e-03, 0., 0.,
       6.8803465135148568e-04 ]
M2: !!opencv-matrix #三倍关系
   rows: 3
   cols: 3
   dt: d
   data: [ 9.3436697656807200e+02, 0., 9.5944337092771116e+02, 0.,
       9.3431501493472899e+02, 5.3937529316686357e+02, 0., 0., 1. ]
D2: !!opencv-matrix 
   rows: 1
   cols: 5
   dt: d
   data: [ 1.7176203195778768e-04, -8.7369521074086797e-04, 0., 0.,
       5.6006093191186845e-04 ]
R: !!opencv-matrix #一致
   rows: 3
   cols: 3
   dt: d
   data: [ 9.9999999991701327e-01, 1.2966020610656008e-06,
       1.2817655546288390e-05, -1.2968539905038601e-06,
       9.9999999980600174e-01, 1.9654889959296845e-05,
       -1.2817630059233700e-05, -1.9654906580291701e-05,
       9.9999999972469655e-01 ]
T: !!opencv-matrix #一致
   rows: 3
   cols: 1
   dt: d
   data: [ 4.9996288610474726e-02, 4.8792677635307373e-08,
       2.2200907182445024e-05 ]
E: !!opencv-matrix #一致
   rows: 3
   cols: 3
   dt: d
   data: [ 2.8165928580830823e-11, -2.2200908137153606e-05,
       4.8356321234207064e-08, 2.2841741112346385e-05,
       9.8270116774219296e-07, -4.9996288312146996e-02,
       -1.1363056402613507e-07, 4.9996288600712271e-02,
       9.8267092560439191e-07 ]
F: !!opencv-matrix #值为三分之一
   rows: 3
   cols: 3
   dt: d
   data: [ 9.3212132929986778e-11, -7.3476045373119066e-05,
       3.9781970510988203e-02, 7.5596507090177478e-05,
       3.2525251129856912e-06, -1.5467947904930654e+02,
       -4.1126344642009365e-02, 1.5467614324418986e+02, 1. ]
R1: !!opencv-matrix #一致
   rows: 3
   cols: 3
   dt: d
   data: [ -9.9999989563291225e-01, -2.2637999788616374e-06,
       -4.5686873345384115e-04, 2.2682900638376881e-06,
       -9.9999999994913813e-01, -9.8274361473115049e-06,
       -4.5686871118324910e-04, -9.8284714324625051e-06,
       9.9999989558718538e-01 ]
R2: !!opencv-matrix #一致
   rows: 3
   cols: 3
   dt: d
   data: [ -9.9999990140884654e-01, -9.7592589726803222e-07,
       -4.4405106080175951e-04, 9.7156210776719249e-07,
       -9.9999999995123878e-01, 9.8274403025738818e-06,
       -4.4405107037095537e-04, 9.8270079104873008e-06,
       9.9999990136103345e-01 ]
P1: !!opencv-matrix #数值为3倍
   rows: 3
   cols: 4
   dt: d
   data: [ 7.4730558351663683e+02, 0., 9.6142030334472656e+02, 0., 0.,
       7.4730558351663683e+02, 5.4061483764648438e+02, 0., 0., 0., 1.,
       0. ]
P2: !!opencv-matrix #数值为3倍
   rows: 3
   cols: 4
   dt: d
   data: [ 7.4730558351663683e+02, 0., 9.6140821838378906e+02,
       -3.7362509317329881e+01, 0., 7.4730558351663683e+02,
       5.4061483764648438e+02, 0., 0., 0., 1., 0. ]
Q: !!opencv-matrix #数值为3倍
   rows: 4
   cols: 4
   dt: d
   data: [ 1., 0., 0., -9.6142030334472656e+02, 0., 1., 0.,
       -5.4061483764648438e+02, 0., 0., 0., 7.4730558351663683e+02, 0.,
       0., 2.0001482694044149e+01, -2.4171713704960579e-01 ]

M矩阵是:

934.35     0      959.4  丨
丨 0        934.30   539.39 丨
丨 0          0        1

fx和fy基本相等为934,于是现在f是640时候画面的3倍。cx为959.4(1920的一半为960),cy为539.39(1080的一半为540)。


   转载规则


《Unity3d和OpenCV的相机模型》 daixian 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
使用systemd设置开机启动服务 使用systemd设置开机启动服务
买了一个友善的nanopiM4玩,smb服务和bt下载和内网穿透搭起来之后,日思夜想终于又买了一个nas的扩展板…现在有些软件希望它开机就自动启动。图中小机器的bt远程下载 开机启动:挂载硬盘准备一个sh脚本文件这个硬盘需要设置它自动休眠,
2019-09-04
本篇 
Unity3d和OpenCV的相机模型 Unity3d和OpenCV的相机模型
这主要是游戏引擎里的相机模型和OpenCV里的相机模型之间的相互印证的实验。 一、Unity的相机模型考虑u3d里的相机是一个完美的针孔模型的相机,使用3d游戏里的变换方法来作出一个3D点和屏幕点(等于相机画面)的推导。 现在有u3d场景中
2019-07-19
  目录