Color Map Optimization for 3D Reconstruction with Consumer Depth Cameras核心算法翻译及理解,附python open3d实现

方案概述

输入

RGB-D

网格重建

KinectFusion

关键帧选取

由于相机是手持的,会导致运动模糊。
为了最大化纹理质量,需要从输入图像序列中选取一个子集。
模糊评估:使用Crete【2007】提出的方法量化模糊程度。
然后,在选定的子集中进一步选取。
选定一帧后,在该帧后的 ( t − , t + ) (t_-,t_+) (t,t+)时间间隔内选取模糊程度最低的一帧(实际中使用 ( t − , t + ) = ( 1 , 5 ) s (t_-,t_+)=(1,5)s (t,t+)=(1,5)s)。关键帧表示为 { I i } \{I_i\} {Ii}
对于每个关键帧 I i I_i Ii,将mesh网格渲染到 I i I_i Ii的图像平面上,使用距离 I i I_i Ii最近的深度图算出的相机pose。通过比较每个顶点的深度值和相应的深度进行可见性测试,由此可以确定关键帧 I i I_i Ii对应的顶点子集,这个顶点子集进一步通过投影点到图像边缘的距离(实际中阈值为9个像素)和深度不连续性进行滤除。保留的顶点表示为 { P i } \{\mathbf P_i\} {Pi}

相机姿态优化

相机姿态优化本质上是一个非线性最小二乘问题,下面对其目标函数和优化方法进行说明。

目标函数

输入:关键帧选取中确定的纹理图片集合 { I i } \{I_i\} {Ii}以及相关联的顶点子集 { P i } \{\mathbf P_i\} {Pi}
目标:优化相机姿态矩阵 T i \mathbf T_i Ti,通过 T i \mathbf T_i Ti可以将顶点映射到对应的纹理图片上。 ∀ p ∈ P \forall \mathbf p\in \mathbf P pP,最大化 p \mathbf p p在所有相关联图像 I p = { I i : p ∈ P } I_p=\{I_i:\mathbf p\in \mathbf P\} Ip={Ii:pP}中的颜色一致性

采用齐次坐标(homogeneous coordinates)进行计算,因此 P i ⊂ P 3 \mathbf P_i\subset\Bbb P^3 PiP3 T i \mathbf T_i Ti 4 × 4 4\times4 4×4的矩阵。

齐次坐标(homogeneous coordinates):在投影空间中,两条平行线会相交于无限远处的一点,在笛卡尔坐标 ( X , Y ) (X,Y) (X,Y)的基础上增加一个新的分量 w w w,变成 ( x , y , w ) (x,y,w) (x,y,w) X = x / w , Y = y / w X=x/w,Y=y/w X=x/w,Y=y/w

则目标函数为:
E ( C , T ) = ∑ i ∑ p ∈ P ( C ( p ) − Γ i ( p , T i ) ) 2 E(\mathbf C,\mathbf T)=\sum_i\sum_{\mathbf p\in \mathbf P}(\mathbf C(\mathbf p)-\Gamma_i(\mathbf p,\mathbf T_i))^2 E(C,T)=ipP(C(p)Γi(p,Ti))2
其中, Γ i ( p , T i ) \Gamma_i(\mathbf p,\mathbf T_i) Γi(p,Ti)是点 p \mathbf p p在图像 I i I_i Ii对应投影点的颜色值, C ( p ) \mathbf C(\mathbf p) C(p)是集合 { Γ i ( p , T i ) } I i ∈ I p \{\Gamma_i(\mathbf p,\mathbf T_i)\}_{I_i\in I_\mathbf p} {Γi(p,Ti)}IiIp(所有和 p \mathbf p p相关联的纹理图像)的平均值。
为了进一步优化,下面阐述一下 Γ i ( p , T i ) \Gamma_i(\mathbf p,\mathbf T_i) Γi(p,Ti)的计算过程:该过程由三步组成,一次刚体变换 g \mathbf g g,一次投影变换 u \mathbf u u,以及一次颜色变换 Γ i \Gamma_i Γi,即 Γ i ( u ( g ( p , T i ) ) ) \Gamma_i(\mathbf u(\mathbf g(\mathbf p,\mathbf T_i))) Γi(u(g(p,Ti)))
g ( p , T i ) = T i p u ( g x , g y , g z , g w ) = ( g x f x g z + c x , g y f y g z + c y ) T \mathbf g(\mathbf p,\mathbf T_i)=\mathbf T_i\mathbf p \\ \mathbf u(g_x,g_y,g_z,g_w)=(\frac{g_xf_x}{g_z}+c_x,\frac{g_yf_y}{g_z}+c_y)^\mathbf T g(p,Ti)=Tipu(gx,gy,gz,gw)=(gzgxfx+cx,gzgyfy+cy)T
其中, f x , f y f_x,f_y fx,fy表示焦距, ( c x , c y ) T (c_x,c_y)^\mathbf T (cx,cy)T表示焦点。则 Γ i ( u x , u y ) \Gamma_i(u_x,u_y) Γi(ux,uy)就图像 I i I_i Ii是相应坐标点处的灰度值(双线性插值)。
最后,目标函数可以写作:
E ( C , T ) = ∑ i ∑ p ∈ P r i , p 2 E(\mathbf C,\mathbf T)=\sum_i\sum_{\mathbf p\in \mathbf P}r_{i,p}^2 E(C,T)=ipPri,p2
残差项 r i , p r_{i,p} ri,p为:
r i , p = C ( p ) − Γ i ( u ( g ( p , T i ) ) r_{i,p}=\mathbf C(\mathbf p)-\Gamma_i(\mathbf u(\mathbf g(\mathbf p,\mathbf T_i)) ri,p=C(p)Γi(u(g(p,Ti))

解释:我们有一个重建好的网格和一组关键帧数据,以及每一张关键帧相应的相机姿态。由于重建过程中估计的相机姿态可能存在一定的误差,直接进行纹理映射会产生重影模糊等问题,因此要根据这些数据对相机姿态进行优化。优化的大体思路是:首先根据网格顶点3D坐标和关键帧的相机姿态,将3D顶点其投影到2D平面,得到若干张投影图,根据每个顶点投影后的uv坐标获取到关键帧相应位置的像素值,每个顶点在每个关键帧(这里就用到了之前的可见性,只需要“可见”的关键帧)中都可以得到一个对应的像素值 Γ i ( p , T i ) \Gamma_i(\mathbf p,\mathbf T_i) Γi(p,Ti),将这些像素值求平均就是 C ( p ) \mathbf C(\mathbf p) C(p),优化的目标就是让 C ( p ) \mathbf C(\mathbf p) C(p)和每一个 Γ i ( p , T i ) \Gamma_i(\mathbf p,\mathbf T_i) Γi(p,Ti)尽可能的接近。

高斯牛顿法

非线性最小二乘问题可以通过高斯牛顿法进行优化求解。我们将上面的目标函数中需要优化的参数(即 C , T \mathbf C, \mathbf T C,T包含的所有参数)表示为 x 0 = [ C 0 , T 0 ] \mathbf x^0=[\mathbf C^0,\mathbf T^0] x0=[C0,T0]
对于每个 i i i T i 0 \mathbf T_i^0 Ti0是由距离 I i I_i Ii最近的深度图提供的相机姿态,
对于每个 p \mathbf p p C 0 ( p ) C^0(\mathbf p) C0(p) { Γ i ( p , T i 0 ) } I i ∈ I p \lbrace\Gamma_i(\mathbf p,\mathbf T_i^0)\rbrace_{I_i \in I_\mathbf p} {Γi(p,Ti0)}IiIp的平均值。
在每次迭代中,对参数 x \mathbf x x进行更新:
x k + 1 = x k + Δ x \mathbf x^{k+1}=\mathbf x^k+\Delta \mathbf x xk+1=xk+Δx
其中, Δ x \Delta \mathbf x Δx是如下线性方程的解:
J r T J r Δ x = − J r T r \mathbf J_\mathbf r^\mathbf T\mathbf J_\mathbf r\Delta \mathbf x=-\mathbf J_\mathbf r^\mathbf T\mathbf r JrTJrΔx=JrTr
其中, r = r ( x ) \mathbf r=\mathbf r(\mathbf x) r=r(x)是目标函数中的残差项, J r = J r ( x ) \mathbf J_\mathbf r=\mathbf J_\mathbf r(\mathbf x) Jr=Jr(x) r \mathbf r r的雅可比矩阵(Jacobian):
r = [ r i , p ( x ) ∣ x = x k ] ( i , p ) , J r = [ ∇ r i , p ( x ) ∣ x = x k ] ( i , p ) \mathbf r=[r_{i,\mathbf p}(\mathbf x)\mid _{\mathbf x=\mathbf x^k}]_{(i,\mathbf p)},\\ \mathbf J_\mathbf r=[\nabla r_{i,\mathbf p}(\mathbf x)\mid _{\mathbf x=\mathbf x^k}]_{(i,\mathbf p)} r=[ri,p(x)x=xk](i,p),Jr=[ri,p(x)x=xk](i,p)
在每次迭代中,根据目标函数的定义计算残差项r, r i , p r_{i,\mathbf p} ri,p关于 C \mathbf C C T j ∣ j ≠ i \mathbf T_j\mid _{j\neq i} Tjj=i的偏导都是平凡解。为了计算 r i , p r_{i,\mathbf p} ri,p关于 T i \mathbf T_i Ti的偏导,我们将 T i \mathbf T_i Ti局部线性化近似为 T i k \mathbf T_i^k Tik
T i ≈ ( 1 − γ i β i α i γ i 1 − α i b i − β i α i 1 c i 0 0 0 1 ) T i k \mathbf T_i \approx \begin{pmatrix} 1 & -\gamma_i & \beta_i & \alpha_i \\ \gamma_i & 1 & -\alpha_i & b_i \\ -\beta_i & \alpha_i & 1 & c_i \\ 0 & 0 & 0 & 1 \end{pmatrix}\mathbf T_i^k Ti1γiβi0γi1αi0βiαi10αibici1Tik
ξ i = ( α i , β i , γ i , a i , b i , c i ) T \xi_i=(\alpha_i,\beta_i,\gamma_i,a_i,b_i,c_i)^\mathbf T ξi=(αi,βi,γi,ai,bi,ci)T Δ x \Delta \mathbf x Δx则为 C ( p ) {C(\mathbf p)} C(p) ξ i \xi_i ξi的组合.由链式法则,可得:
∇ r i , p ( ξ i ) ∣ x = x k = − ∂ ∂ ξ i ( Γ i ∘ u ∘ g ) ∣ x = x k = − ∇ Γ i ( u ) J u ( g ) J g ( ξ i ) ∣ x = x k \nabla r_{i,\mathbf p}(\xi_i)\mid _{\mathbf x=\mathbf x^k}=-\frac{\partial}{\partial \xi_i}(\Gamma_i \circ \mathbf u \circ \mathbf g)\mid _{\mathbf x=\mathbf x^k} =-\nabla \Gamma_i(\mathbf u)\mathbf J_\mathbf u(\mathbf g)\mathbf J_\mathbf g(\xi_i)\mid _{\mathbf x=\mathbf x^k} ri,p(ξi)x=xk=ξi(Γiug)x=xk=Γi(u)Ju(g)Jg(ξi)x=xk
其中, ∇ Γ i \nabla \Gamma_i Γi Γ i \Gamma_i Γi的梯度,使用Scharr核计算。 J u ( g ) \mathbf J_\mathbf u(\mathbf g) Ju(g)是u的Jacobian矩阵, J g ( ξ i ) \mathbf J_\mathbf g(\xi_i) Jg(ξi)是g关于 ξ i \xi_i ξi的Jacobian矩阵.

交替优化法

高斯牛顿法计算复杂度很高,因此作者提出了一种交替优化法,每次迭代交替优化C和T。
当T固定时,非线性最小二乘问题就变成了线性最小而成问题,并且有以下闭解:
C ( p ) = 1 n P ∑ Γ i ( p , T i ) C(\mathbf p)=\frac{1}{n_\mathbf P}\sum \Gamma_i(\mathbf p,\mathbf T_i) C(p)=nP1Γi(p,Ti)
其中 n P n_\mathbf P nP是与顶点p相关的图片的个数,这样就相当于只需计算p在各个与其相关的图片上的投影点的平均灰度值。
当C固定时,目标函数分解为多个独立的目标函数,每个 T i \mathbf T_i Ti对应一个:
E i ( T ) = ∑ p ∈ P i r i , p 2 E_i(\mathbf T)=\sum_{\mathbf p \in \mathbf P_i}r_{i, \mathbf p}^2 Ei(T)=pPiri,p2
每一项 E i ( T ) E_i(\mathbf T) Ei(T)只和 ξ i \xi_i ξi的六个变量有关,在每次迭代中,使用高斯牛顿法迭代更新这六个变量,每个 T i \mathbf T_i Ti都是相互独立的,因此可以并行计算。这样的计算是非常高效的。

非刚体校正

非刚体校正是为了修复不准确的Mesh和镜头畸变等导致的误差。

目标函数

对每一张图片 I i I_i Ii,非刚体校正可以用一个变形函数 F i F_i Fi表示。
F i ( v i , l ) = v i , l + f i , l \mathbf F_i(\mathbf v_{i,l})=\mathbf v_{i,l}+\mathbf f_{i,l} Fi(vi,l)=vi,l+fi,l
其中, v i , l ∈ V i \mathbf v_{i,l} \in\mathbf V_i vi,lVi,表示变形的控制范围。 f i , l \mathbf f_{i,l} fi,l是一个二维向量,该图像平面上的所有投影点 u \mathbf u u校正为:
F i ( u ) = u + ∑ i θ l ( u ) f i , l \mathbf F_i(\mathbf u)=\mathbf u+\sum_i \theta_l(\mathbf u)\mathbf f_{i,l} Fi(u)=u+iθl(u)fi,l
θ l \theta_l θl为双线性插值函数,则 F i ( u ) \mathbf F_i(\mathbf u) Fi(u) { F i ( v i , l ) } v i , l ∈ V i \{\mathbf F_i(\mathbf v_{i,l})\}_{\mathbf v_{i,l}\in\mathbf V_i} {Fi(vi,l)}vi,lVi的线性组合。 V i \mathbf V_i Vi是一个 20 × 16 20\times16 20×16的网格( 21 × 17 21\times17 21×17个控制点)。
之前的目标函数可纳入非刚体变换:
E c ( C , T , F ) = ∑ i ∑ p ∈ P i r i , p 2 E_c(\mathbf C,\mathbf T,\mathbf F)=\sum_i\sum_{\mathbf p\in \mathbf P_i}r_{i,\mathbf p}^2 Ec(C,T,F)=ipPiri,p2
残差项为
r i , p = C ( p ) − Γ i ( F i ( u ( g ( p , T i ) ) ) ) r_{i,\mathbf p}=C(\mathbf p)-\Gamma_i(\mathbf F_i(\mathbf u(\mathbf g(\mathbf p,\mathbf T_i)))) ri,p=C(p)Γi(Fi(u(g(p,Ti))))
为了防止校正函数漂移,加入 L 2 L^2 L2正则项:
E r ( F ) = ∑ i ∑ l f i , l T f i , l E_r(\mathbf F)=\sum_i\sum_l\mathbf f_{i,l}^T\mathbf f_{i,l} Er(F)=ilfi,lTfi,l
则完整的目标函数为:
E ( C , T , F ) = E c ( C , T , F ) + λ E r ( F ) E(\mathbf C,\mathbf T,\mathbf F)=E_c(\mathbf C,\mathbf T,\mathbf F)+\lambda Er(\mathbf F) E(C,T,F)=Ec(C,T,F)+λEr(F)
实际中作者使用 λ = 0.1 \lambda=0.1 λ=0.1

优化

目标函数引入了 m + 720 n m+720n m+720n个变量,直接使用高斯牛顿法计算量非常大,而交替优化法可以将优化问题转换为独立线性问题,只需720个变量。
在第k论迭代中,首先固定T和F,优化C。
然后固定C,优化T和F。

python实现

在open3d开源库中,集成了这篇论文的算法。作者所用的数据库可以点击这里下载。
在open3d中,给出了demo代码如下:

# examples/Python/Advanced/o3d.color_map.color_map_optimization.py

import open3d as o3d
from trajectory_io import *
import os, sys
sys.path.append("../Utility")
from file import *

path = "[path_to_fountain_dataset]"
debug_mode = False

if __name__ == "__main__":
    o3d.utility.set_verbosity_level(o3d.utility.VerbosityLevel.Debug)

    # Read RGBD images
    rgbd_images = []
    depth_image_path = get_file_list(os.path.join(path, "depth/"),
                                     extension=".png")
    color_image_path = get_file_list(os.path.join(path, "image/"),
                                     extension=".jpg")
    assert (len(depth_image_path) == len(color_image_path))
    for i in range(len(depth_image_path)):
        depth = o3d.io.read_image(os.path.join(depth_image_path[i]))
        color = o3d.io.read_image(os.path.join(color_image_path[i]))
        rgbd_image = o3d.geometry.RGBDImage.create_from_color_and_depth(
            color, depth, convert_rgb_to_intensity=False)
        if debug_mode:
            pcd = o3d.geometry.PointCloud.create_from_rgbd_image(
                rgbd_image,
                o3d.camera.PinholeCameraIntrinsic(
                    o3d.camera.PinholeCameraIntrinsicParameters.
                    PrimeSenseDefault))
            o3d.visualization.draw_geometries([pcd])
        rgbd_images.append(rgbd_image)

    # Read camera pose and mesh
    camera = o3d.io.read_pinhole_camera_trajectory(
        os.path.join(path, "scene/key.log"))
    mesh = o3d.io.read_triangle_mesh(
        os.path.join(path, "scene", "integrated.ply"))

    # Before full optimization, let's just visualize texture map
    # with given geometry, RGBD images, and camera poses.
    option = o3d.color_map.ColorMapOptimizationOption()
    option.maximum_iteration = 0
    o3d.color_map.color_map_optimization(mesh, rgbd_images, camera, option)
    o3d.visualization.draw_geometries([mesh])
    o3d.io.write_triangle_mesh(
        os.path.join(path, "scene", "color_map_before_optimization.ply"), mesh)

    # Optimize texture and save the mesh as texture_mapped.ply
    # This is implementation of following paper
    # Q.-Y. Zhou and V. Koltun,
    # Color Map Optimization for 3D Reconstruction with Consumer Depth Cameras,
    # SIGGRAPH 2014
    option.maximum_iteration = 300
    option.non_rigid_camera_coordinate = True
    o3d.color_map.color_map_optimization(mesh, rgbd_images, camera, option)
    o3d.visualization.draw_geometries([mesh])
    o3d.io.write_triangle_mesh(
        os.path.join(path, "scene", "color_map_after_optimization.ply"), mesh)

使用作者的数据跑出来效果还是很不错的,但是在实测中使用自己扫描的数据,效果就差了一些。

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值