前言

​ 最近由于一些机会,接触到了一系列深度学习对称形状和纹理和篇文章,并尝试做了一些实践。以Learning to Predict 3D Objects with an Interpolation-based Differentiable Renderer举例,他的思想主要是通过相机视角的RGB图,用类似于Auto Encoder的模型输出三维点和纹理信息。然后通过一个渲染器得到在固定角度下的RGB图像,与之前的输入形成主要的损失。当然论文还介绍了其他光照什么的,细节就不在讨论了,我们主要用他这个可微分渲染器。这里给出这篇文章的开源链接:https://github.com/nv-tlabs/DIB-R

Differentiable Renderer

​ 由于自己不是专门做计算机图形学的。所以,对于这篇文章中的可微分渲染的理解将表述的很通俗。

​ 在NN输出空间中的三维点的时候,我们希望也知道这些点的RGB像素值是多少。经常,我们可以看到深度相机中带有色彩的点云,它们描述了几何特征和纹理特征。但是,这不是连续的。我们可能还希望点能连成面,而色彩则可以也可以连续过渡。所以那篇论文中最有价值的部分莫过于他的基于CUDA的渲染器。有时,我们想轻量级的使用一个渲染器,且希望接口简单清晰,速度快,易于集成。DIB-R可以是一个很好的选择。或者有小伙伴,也想从事于深度学习中渲染于几何物体的相关领域,这就是一个很好的选择。

​ 具体原理的可以看文章中3.2的可微分光栅化。这里主要讲述了代码接口解析,并附上一小段测试的渲染程序以作参考。

环境

​ 环境至关重要。我是在Ubuntu18.04上实现的,基于python3.6。python包的依赖可以看github中的requirments.txt 。

​ 其次就是CUDA环境,这点我装可好久,此前我想在Colab上装合适版本的CUDA(版本为11),然后测试。但是发现demo写好后,包一些莫名其妙的错误。然而,我朋友却用同样的demo在极客云上用cuda10.2.89的版本跑了起来。后面,干脆直接在我本地上装合适的CUDA好了。

​ 由 lspci | grep -i nvidia 可以看到我的显卡是GeForce GTX 1050 Ti Mobile,在https://zh.wikipedia.org/wiki/CUDA 查的自己显卡计算能力为6.1, 架构是Pascal) 。查表后可以看到支持的cuda版本很多,为了安全起见,我也选择了10.2.89。

​ 由于之前装了一个低版本的CUDA,所以要进行升级。首先要卸载原来的。

sudo apt-get --purge remove cuda nvidia* libnvidia-*
sudo dpkg -l | grep cuda- | awk '{print $2}' | xargs -n1 dpkg --purge
sudo apt-get remove cuda-*
sudo apt autoremove
sudo apt-get update

​ 然后进行安装新版本的CUDA。

wget  --no-clobber https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/cuda-repo-ubuntu1804_10.2.89-1_amd64.deb
sudo dpkg -i cuda-repo-ubuntu1804_10.2.89-1_amd64.deb
sudo apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/7fa2af80.pub
sudo apt-get update
sudo apt-get install cuda-10-2

​ 确定自己安装的版本可以由以下方式

cat /usr/local/cuda-10.2/version.txt

​ 在安装好CUDA之后,便可以编译和下载(按照github上)

cd dib-render/cuda_dib_render
python build.py install

​ 注意,如果你之前用了其他版本的cuda编译过,那么就需要将编译后生成的文件全部删去后,在进行编译。上述编译会在 cuda_dib_render 中多几个文件夹。

接口及测试

​ 编译后我们甚至可以不考虑那几个文件中所做了什么,只要直接使用他的接口即可。比较坑的是,他这个代码没有像想象那样进行全部开源,而是给出渲染器和一个小的测试demo。总的工程在 test-all 中进行描述。它总的描述了一个训练的流程是怎样的,具有借鉴和学习意义。这里我们主要剥离出渲染的主要成分即可。

​ 我写可以一个渲染测试文件,并对一些内容进行了注释(原来的demo文件几乎没有什么注释)。测试的效果还行,具体如下

​ 从图中可以看出,颜色以一定的块(条纹块)进行分布。车子的几何特征有点粗糙,这是由于所用的obj文件中的点集是NN训练得到的,点集数量不多,且与原来形状有点小差异。对于代码的解释,已经全部放在,下面的代码中。在配置好环境后,修改路径即可运行。

# Test file written by JWC

import numpy as np
import os
import sys
sys.path.append('/home/jame/Desktop/deguo/DIB-R-master/utils/')
sys.path.append('/home/jame/Desktop/deguo/DIB-R-master/dib-render/render_cuda')
sys.path.append('/home/jame/Desktop/deguo/DIB-R-master/utils/render')
import torch
import torchvision.utils as vutils
from model.modelcolor import Ecoder
from utils.utils_mesh import loadobj, \
   face2edge, edge2face, face2pneimtx, mtx2tfsparse, savemesh, savemeshcolor

from utils.utils_perspective import camera_info_batch, perspectiveprojectionnp
from renderfunc_cluster import rendermeshcolor as rendermesh
from utils_render_color2 import linear
import cv2

# Automatic GPU/CPU device placement
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')

# 加载相机参数
def load_cam( pkl_path):
    rotntxname = pkl_path
    rotmtx_4x4 = np.load(rotntxname).astype(np.float32)
    rotmx = rotmtx_4x4[:3, :3]
    transmtx = rotmtx_4x4[:3, 3:4]
    transmtx = -np.matmul(rotmx.T, transmtx)
    renderparam = (rotmx, transmtx)
    return  renderparam

# 得到 obj格式点集的 点和面 描述集
pointnp_px3, facenp_fx3 = loadobj('/home/jame/Desktop/deguo/DIB-R-master/mm/off/car_20.obj')

edge_ex2 = face2edge(facenp_fx3)
# edgef_ex2 = edge2face(facenp_fx3, edge_ex2)
# pneimtx = face2pneimtx(facenp_fx3)
tff_fx3 = torch.from_numpy(facenp_fx3).to(device)

# 获得 点,面和边的数量
pnum = pointnp_px3.shape[0]
fnum = facenp_fx3.shape[0]
enum = edge_ex2.shape[0]
# print(pnum, fnum, enum)

# 设定相机FOV, 可以由相机内参矩阵获得
camfovy = np.arctan(2*64/140)

# 这里转化 相机的FOV向量
camprojmtx = perspectiveprojectionnp(camfovy, 1.0)
tfcamproj = torch.from_numpy(camprojmtx).to(device)

# print(tfcamproj.shape)
# 这里增加一个维度
tfp_1xpx3 = torch.from_numpy(pointnp_px3).to(device).view(1, pnum, 3)
# print(tfp_1xpx3)
# print(tfcamproj)

# 以下将设定一个batch中有两个 mesh 和 颜色集, 以此来做测试
meshes = []
meshcolors = []

# 重复添加两个
meshes.append(tfp_1xpx3)
meshes.append(tfp_1xpx3)

# 设定颜色
temp = []
cout = 0
# 颜色变化处理
for i in range(pnum):
    if cout==0:
        temp.append([  1,  0, 0 ])
    elif cout ==1:
        temp.append([  0., 1, 0 ])
    elif cout ==2:
        temp.append([  0., 0,  1 ])
    if i%300 ==0:  # 300 决定颜色分布块大小
        cout+=1
        if cout ==3:
            cout =0

# mesh color 转化为向量
mc_bxp3 = np.array(temp,dtype=np.float32)
# print(mc_bxp3)
# 转化cuda tensor 后增加一个维度
mc_bxpx3 = torch.from_numpy(mc_bxp3).to(device).view(1, -1, 3)
# print(mc_bxpx3)

# 和之前mesh一致, 也重复添加两个
meshcolors.append(mc_bxpx3)
meshcolors.append(mc_bxpx3)
# print(meshes.shape,meshcolors.shape )

# 转化为tensor
meshesvv = meshes
mcvv = meshcolors
mesh_vvbxpx3 = torch.cat(meshesvv)
mc_vvbxpx3 = torch.cat(mcvv)
# print(mesh_vvbxpx3.shape,  mc_vvbxpx3.shape)

# 加载相机参数,其次变换矩阵保存在 .npy文件中, 注意,物体和相机关系需要转换
rotat, trans = load_cam("/home/jame/Desktop/deguo/DIB-R-master/mm/datasets/camera_settings/cam_npy/cam_RT_004.npy")
# rotat = np.array( [[[1.,0.,0.], [0.,1.,0.], [0.,0.,1.] ]], dtype=np.float32 )
# trans = np.array(  [[.0,0.,2]], dtype=np.float32 )

rotat = np.array( [rotat], dtype=np.float32 )
trans = np.array(  [trans], dtype=np.float32 )
# print(rotat, trans)

# 相机坐标参数 转化为 cuda 计算
tfcamrot = torch.from_numpy(rotat).to(device)
tfcampos = torch.from_numpy(trans).to(device)

# 相机参数添加到一个集合中,可能有点冗余,这是为了和 test-all.py中保持一致
tfcams=[[tfcamrot,tfcampos,tfcamproj]]
tfcamrot_bx3x3, tfcampos_bx3, _ = tfcams[0]

# 由于 前面批量了两个,所以这里需要重读添加
tfcamsvv = [[], [], tfcamproj]
tfcamsvv[0].append(tfcamrot_bx3x3)
tfcamsvv[1].append(tfcampos_bx3)
tfcamsvv[0].append(tfcamrot_bx3x3)
tfcamsvv[1].append(tfcampos_bx3)

tfcamsvv[0] = torch.cat(tfcamsvv[0])
tfcamsvv[1] = torch.cat(tfcamsvv[1])
# print(tfcamsvv[0].shape,  tfcamsvv[1].shape)

# DIB-R渲染接口,至关重要
tmp, _ = rendermesh(mesh_vvbxpx3, mc_vvbxpx3, tff_fx3, tfcamsvv, linear)
# 第一个就是渲染后视野中的图像
impre_vvbxhxwx3, silpred_vvbxhxwx1 = tmp

# 转化到 RGB 值
img = impre_vvbxhxwx3.cpu().numpy().astype(np.float32) *255.0
# print(np.shape( img ))

\# opencv 图像显示
cv2.imshow('image', img[0])
cv2.imwrite("./image.jpg",img[0])
cv2.waitKey(0)
print("OVER")

​ 以上是作者做的一个大项目中的一个小章节,验证可微分渲染以此来进行后面深度学习-学习mesh的纹理。此处为今后学习这个小伙伴提供一个参考。

​ 另外,这个项目在后续将实践一个pytorch深度学习学习对称几何物体纹理的案例,后面有机会将持续跟进。