3D 建模费时费力,Python 让照片秒变模型

3D 建模费时费力,Python 让照片秒变模型
2021年04月14日 17:56 AI科技大本营

引言:

不论是在游戏还是动画中,三维模型的引用始终都很广泛。但是三维模型的搭建却始终存在着较大的难点,一是模型的搭建是个非常费时费力的技术,其次学习三维建模也需要掌握较多的工具,对于日常没有接触过的人而言难度较大。

所以,今天我们将实现一个可以将照片的人体创建成三维模型FBX格式的项目。通过这个项目,可以大量的节省建模工作者的时间,对游戏开发者和动画制作者而言将会是很大的福利。完整代码见文末!

其中生成的图片效果如下可见:

使用3维模型查看效果如下:

模型建立

1.1 环境要求

本次环境使用的是python3.6.5+windows平台,主要用的库

Argparse库是python自带的命令行参数解析包,可以用来方便地读取命令行参数。;

glob获取本地文件,在这里用来快速获取训练数据集;

numpy模块用来矩阵和数据的运算处理,其中也包括和深度学习框架之间的交互等。

Cv2模块是python的opencv模型,用来图像读入,存储和边缘计算等图像处理;

Torch模块是深度学习框架。torch不仅更加灵活,支持动态图,而且提供了Python接口。它是由Torch7团队开发,是一个以Python优先的深度学习框架,不仅能够实现强大的GPU加速,同时还支持动态神经网络,这是很多主流深度学习框架比如Tensorflow等都不支持的。PyTorch既可以看作加入了GPU支持的numpy,同时也可以看成一个拥有自动求导功能的强大的深度神经网络。

Matplotlib模块用来可视化训练效果等数据图的制作。

PIL是python中用来处理图形的pillow库。

1.2 图像处理

对于需要处理成三维模型的图片,我们需要提供照片所对应的骨骼点json文件。Json文件的生成,可以使用openpose生成。其中openpose环境的搭建需要利用cmake工具进行编译,包括大量需要版本对应的C++环境等,较为复杂,这里不再阐述。其中主要使用指令如下:分别是对摄像头、本地图片等生成json文件。

OpenPoseDemo.exe --body 1 --face --face_detector 1  --hand --hand_detector 1

OpenPoseDemo.exe --camera 0 --write_json 1.json  --model_pose COCO

OpenPoseDemo.exe --image_dir json/  --write_json 1.json  --model_pose COCO

OpenPoseDemo.exe --camera 0 --write_json 1.json  --model_pose  BODY_25

OpenPoseDemo.exe --image_dir json/  --write_json 1.json  --model_pose  BODY_25

1.3 算法搭建

(1)本文所使用的是算法是PiFuHD算法。需要分别定义生成模型,模型颜色和模型贴图的函数,代码如下:

def save_obj_mesh(mesh_path, verts, faces=None):

file = open(mesh_path, 'w')

for v in verts:

file.write('v %.4f %.4f %.4f\n' % (v[], v[1], v[2]))

if faces isnot None:

for f in faces:

if f[] == f[1or f[1] == f[2or f[] == f[2]:

continue

            f_plus = f + 1

file.write('f %d %d %d\n' % (f_plus[], f_plus[2], f_plus[1]))

file.close()

def save_obj_mesh_with_color(mesh_path, verts, faces, colors):

file = open(mesh_path, 'w')

for idx, v in enumerate(verts):

        c = colors[idx]

file.write('v %.4f %.4f %.4f %.4f %.4f %.4f\n' % (v[], v[1], v[2], c[], c[1], c[2]))

for f in faces:

        f_plus = f + 1

file.write('f %d %d %d\n' % (f_plus[], f_plus[2], f_plus[1]))

file.close()

def save_obj_mesh_with_uv(mesh_path, verts, faces, uvs):

file = open(mesh_path, 'w')

for idx, v in enumerate(verts):

        vt = uvs[idx]

file.write('v %.4f %.4f %.4f\n' % (v[], v[1], v[2]))

file.write('vt %.4f %.4f\n' % (vt[], vt[1]))

for f in faces:

        f_plus = f + 1

file.write('f %d/%d %d/%d %d/%d\n' % (f_plus[], f_plus[],

                                              f_plus[2], f_plus[2],

                                              f_plus[1], f_plus[1]))

file.close()

(2)PiFuHD算法是使用图片中的上下文信息,通过对原始不清晰图像先进行了超分辨率重建获得,其中高分辨率详细信息是通过在相似的轻量级PIFu网络中使用这些第一个3D输出作为高分辨率输入来添加的。

defgen_mesh(res, net, cuda, data, save_path, thresh=0.5, use_octree=True, components=False):

    image_tensor_global = data['img_512']

    image_tensor = data['img']

    calib_tensor = data['calib']

    net.filter_global(image_tensor_global)

    net.filter_local(image_tensor[:,None])

try:

if net.netG.netF isnotNone:

            image_tensor_global = torch.cat([image_tensor_global, net.netG.nmlF], )

if net.netG.netB isnotNone:

            image_tensor_global = torch.cat([image_tensor_global, net.netG.nmlB], )

except:

pass

    b_min = data['b_min']

    b_max = data['b_max']

try:

        save_img_path = save_path[:-4] + '.png'

        save_img_list = []

for v in range(image_tensor_global.shape[]):

            save_img = (np.transpose(image_tensor_global[v].detach().cpu().numpy(), (12, )) * 0.5 + 0.5)[:, :, ::-1] * 255.0

            save_img_list.append(save_img)

        save_img = np.concatenate(save_img_list, axis=1)

        cv2.imwrite(save_img_path, save_img)

        verts, faces, _, _ = reconstruction(

            net, cuda, calib_tensor, res, b_min, b_max, thresh, use_octree=use_octree, num_samples=50000)

        verts_tensor = torch.from_numpy(verts.T).unsqueeze().to(device=cuda).float()

# if 'calib_world' in data:

#     calib_world = data['calib_world'].numpy()[0]

#     verts = np.matmul(np.concatenate([verts, np.ones_like(verts[:,:1])],1), inv(calib_world).T)[:,:3]

        color = np.zeros(verts.shape)

        interval = 50000

for i in range(len(color) // interval + 1):

            left = i * interval

if i == len(color) // interval:

                right = -1

else:

                right = (i + 1) * interval

            net.calc_normal(verts_tensor[:, None, :, left:right], calib_tensor[:,None], calib_tensor)

            nml = net.nmls.detach().cpu().numpy()[] * 0.5 + 0.5

            color[left:right] = nml.T

        save_obj_mesh_with_color(save_path, verts, faces, color)

except Exception as e:

        print(e)

(3)人体表面颜色重建。基于上一步的重建深度特征,这是模型只需要关注于模型各点的颜色即可,而不需要关注潜在的3D信息。

defgen_mesh_imgColor(res, net, cuda, data, save_path, thresh=0.5, use_octree=True, components=False):

    image_tensor_global = data['img_512'].to(device=cuda)

    image_tensor = data['img'].to(device=cuda)

    calib_tensor = data['calib'].to(device=cuda)

    net.filter_global(image_tensor_global)

    net.filter_local(image_tensor[:,None])

try:

if net.netG.netF isnotNone:

            image_tensor_global = torch.cat([image_tensor_global, net.netG.nmlF], )

if net.netG.netB isnotNone:

            image_tensor_global = torch.cat([image_tensor_global, net.netG.nmlB], )

except:

pass

    b_min = data['b_min']

    b_max = data['b_max']

try:

        save_img_path = save_path[:-4] + '.png'

        save_img_list = []

for v in range(image_tensor_global.shape[]):

            save_img = (np.transpose(image_tensor_global[v].detach().cpu().numpy(), (12, )) * 0.5 + 0.5)[:, :, ::-1] * 255.0

            save_img_list.append(save_img)

        save_img = np.concatenate(save_img_list, axis=1)

        cv2.imwrite(save_img_path, save_img)

        verts, faces, _, _ = reconstruction(

            net, cuda, calib_tensor, res, b_min, b_max, thresh, use_octree=use_octree, num_samples=100000)

        verts_tensor = torch.from_numpy(verts.T).unsqueeze().to(device=cuda).float()

# if this returns error, projection must be defined somewhere else

        xyz_tensor = net.projection(verts_tensor, calib_tensor[:1])

        uv = xyz_tensor[:, :2, :]

        color = index(image_tensor[:1], uv).detach().cpu().numpy()[].T

        color = color * 0.5 + 0.5

if'calib_world'in data:

            calib_world = data['calib_world'].numpy()[]

            verts = np.matmul(np.concatenate([verts, np.ones_like(verts[:,:1])],1), inv(calib_world).T)[:,:3]

        save_obj_mesh_with_color(save_path, verts, faces, color)

except Exception as e:

        print(e)

模型文件的生成

2.1 模型参数的定义

其中模型参数的定义主要是使用Argparse库,其中参数包括输入图像、读取的尺寸、模式、学习率等定义。代码如下:

class BaseOptions():

    def __init__(self):

        self.initialized = False

        self.parser = None

    def initialize(self, parser):

# Datasets related

        g_data = parser.add_argument_group('Data')

        g_data.add_argument('--dataset', type=str, default='renderppl', help='dataset name')

        g_data.add_argument('--dataroot', type=str, default='./data',

help='path to images (data folder)')

        g_data.add_argument('--loadSize'type=intdefault=512help='load size of input image')

# Experiment related

        g_exp = parser.add_argument_group('Experiment')

        g_exp.add_argument('--name'type=strdefault='',

help='name of the experiment. It decides where to store samples and models')

        g_exp.add_argument('--debug'action='store_true'help='debug mode or not')

        g_exp.add_argument('--mode'type=strdefault='inout'help='inout || color')

# Training related

        g_train = parser.add_argument_group('Training')

        g_train.add_argument('--tmp_id'type=intdefault=, help='tmp_id')

        g_train.add_argument('--gpu_id'type=intdefault=, help='gpu id for cuda')

        g_train.add_argument('--batch_size'type=intdefault=32help='input batch size')

        g_train.add_argument('--num_threads'default=1type=inthelp='# sthreads for loading data')

        g_train.add_argument('--serial_batches'action='store_true',

help='if true, takes images in order to make batches, otherwise takes them randomly')

        g_train.add_argument('--pin_memory'action='store_true'help='pin_memory')

        g_train.add_argument('--learning_rate'type=floatdefault=1e-3help='adam learning rate')

        g_train.add_argument('--num_iter'type=intdefault=30000help='num iterations to train')

        g_train.add_argument('--freq_plot'type=intdefault=100help='freqency of the error plot')

        g_train.add_argument('--freq_mesh'type=intdefault=20000help='freqency of the save_checkpoints')

        g_train.add_argument('--freq_eval'type=intdefault=5000help='freqency of the save_checkpoints')

        g_train.add_argument('--freq_save_ply'type=intdefault=5000help='freqency of the save ply')

        g_train.add_argument('--freq_save_image'type=intdefault=100help='freqency of the save input image')

        g_train.add_argument('--resume_epoch'type=intdefault=-1help='epoch resuming the training')

        g_train.add_argument('--continue_train'action='store_true'help='continue training: load the latest model')

        g_train.add_argument('--finetune'action='store_true'help='fine tuning netG in training C')

# Testing related

        g_test = parser.add_argument_group('Testing')

        g_test.add_argument('--resolution'type=intdefault=512help='# of grid in mesh reconstruction')

        g_test.add_argument('--no_numel_eval'action='store_true'help='no numerical evaluation')

        g_test.add_argument('--no_mesh_recon'action='store_true'help='no mesh reconstruction')

# Sampling related

        g_sample = parser.add_argument_group('Sampling')

        g_sample.add_argument('--num_sample_inout'type=intdefault=6000help='# of sampling points')

        g_sample.add_argument('--num_sample_surface'type=intdefault=, help='# of sampling points')

        g_sample.add_argument('--num_sample_normal'type=intdefault=, help='# of sampling points')

        g_sample.add_argument('--num_sample_color'type=intdefault=, help='# of sampling points')

        g_sample.add_argument('--num_pts_dic'type=intdefault=1help='# of pts dic you load')

        g_sample.add_argument('--crop_type'type=strdefault='fullbody'help='Sampling file name.')

        g_sample.add_argument('--uniform_ratio'type=floatdefault=0.1help='maximum sigma for sampling')

        g_sample.add_argument('--mask_ratio'type=floatdefault=0.5help='maximum sigma for sampling')

        g_sample.add_argument('--sampling_parts'action='store_true'help='Sampling on the fly')

        g_sample.add_argument('--sampling_otf'action='store_true'help='Sampling on the fly')

        g_sample.add_argument('--sampling_mode'type=strdefault='sigma_uniform'help='Sampling file name.')

        g_sample.add_argument('--linear_anneal_sigma'action='store_true'help='linear annealing of sigma')

        g_sample.add_argument('--sigma_max'type=floatdefault=0.0help='maximum sigma for sampling')

        g_sample.add_argument('--sigma_min'type=floatdefault=0.0help='minimum sigma for sampling')

        g_sample.add_argument('--sigma'type=floatdefault=1.0help='sigma for sampling')

        g_sample.add_argument('--sigma_surface'type=floatdefault=1.0help='sigma for sampling')

2.1 模型测试的定义

设立模型测试基本参数和使用方法。

from .recon import reconWrapper

import argparse

parser = argparse.ArgumentParser()

parser.add_argument('-i''--input_path'type=str, default='./sample_images')

parser.add_argument('-o''--out_path'type=str, default='./results')

parser.add_argument('-c''--ckpt_path'type=str, default='./checkpoints/pifuhd.pt')

parser.add_argument('-r''--resolution'type=intdefault=512)

parser.add_argument('--use_rect', action='store_true', help='use rectangle for cropping')

args = parser.parse_args()

resolution = str(args.resolution)

start_id = -1

end_id = -1

cmd = ['--dataroot', args.input_path, '--results_path', args.out_path,\

'--loadSize''1024''--resolution', resolution, '--load_netMR_checkpoint_path', \

       args.ckpt_path,\

'--start_id''%d' % start_id, '--end_id''%d' % end_id]

reconWrapper(cmd, args.use_rect)

使用方法

进入到项目文件夹,执行“python -m apps.simple_test ”即可运行。

源码

完整代码下载链接:

https://pan.baidu.com/s/1dVo57yp89GiNExSfCisPMA

提取码:tetg

财经自媒体联盟更多自媒体作者

新浪首页 语音播报 相关新闻 返回顶部