【SMPL简介】SMPL: A Skinned Multi-Person Linear Model【源码复现】
- 一、前言
 - 环境搭建
 - 运行demo.py
 
- 参考链接
 
一、前言
SMPL是一种3D人体建模方法.在数字人或者人物角色三维重建领域有着广泛应用
支持人体的各种形状及动作
可以简单理解为通过训练获取的人物模型
常用的模型有 smpl(身体模型),mano(手部模型),smplh(身体+手部),flame(脸部),smplx(身体+手部+脸部) 官网:https://smpl-x.is.tue.mpg.de/index.html https://smpl.is.tue.mpg.de
SMPL 输入是:姿态shape vector  
     
      
       
       
         β 
        
       
      
        \beta 
       
      
    β + 影响动作Pose的参数  
     
      
       
       
         θ 
        
       
      
        \theta 
       
      
    θ
 输出是: 各种变化后vertex顶点的数据
 作者:Loper, Matthew and Mahmood, Naureen and Romero, Javier and Pons-Moll, Gerard and Black, Michael J.
【Paper】 > 【Code】 > 【Project】
SMPL 是基于蒙皮和混合形状的真实人体 3D 模型,是从数千个 3D 人体扫描中学习得到的。
a. T-pose姿态.常用的模版Pose有T-Pose,A-Pose,Da-Pose 颜色表示身体每个部位受到身体关节点影响的权重 颜色表示身体每个部位受到身体关节点影响的权重 颜色表示身体每个部位受到身体关节点影响的权重
b. 改变β参数的姿态,J关节点的变化 β参数可以理解为影响人体高矮胖瘦的一个参数
c. 改变β跟θ参数的姿态,此时还没有发生变化
d. 加上W权重通过W的LBS蒙皮算法的姿态
SMPL-X
【Paper】 > 【Code】 > 【Project】
感兴趣的可以看看原文
环境搭建
- 代码下载
 
git clone https://github.com/vchoutas/smplx.git 
cd smplx
 
- conda 虚拟环境搭建
 
conda create -n smplx python=3.8 -y 
conda activate smplx
 
- torch
 
conda install -y pytorch==1.13.1 torchvision==0.14.1 torchaudio==0.13.1 pytorch-cuda=11.7 -c pytorch -c nvidia
pip install smplx[all]
 
- matplotlib y轴朝上
 
ax.view_init(azim=-90, elev=100)
 
- 下载models,先要注册登录
SMPL下载:



将这三个模型上传到项目中,需要改名,KaTeX parse error: Double subscript at position 13: basicmodel_f_̲lbs_10_207_0_v1…,其余类似
SMPL-X 下载:



将所需6个文件上传到项目中 
models
├── smpl
│   ├── SMPL_FEMALE.pkl
│   └── SMPL_MALE.pkl
│   └── SMPL_NEUTRAL.pkl
├── smplh
│   ├── SMPLH_FEMALE.pkl
│   └── SMPLH_MALE.pkl
├── mano
|   ├── MANO_RIGHT.pkl
|   └── MANO_LEFT.pkl
└── smplx
    ├── SMPLX_FEMALE.npz
    ├── SMPLX_FEMALE.pkl
    ├── SMPLX_MALE.npz
    ├── SMPLX_MALE.pkl
    ├── SMPLX_NEUTRAL.npz
    └── SMPLX_NEUTRAL.pkl
 

运行demo.py
安装 smplx 包并下载模型参数后,您应该能够运行 demo.py 脚本来可视化结果。对于此步骤,您必须安装pyrender 和trimesh 软件包。
python examples/demo.py --model-folder $SMPLX_FOLDER --plot-joints=True --gender="neutral"
 
我们可以先用 --plotting-module matplotlib 来先用matplotlib渲染。pip install matplotlib
 可以得到对应的输出,远程环境可以保存为png图像查看
elif plotting_module == 'matplotlib':
        from matplotlib import pyplot as plt
        from mpl_toolkits.mplot3d import Axes3D
        from mpl_toolkits.mplot3d.art3d import Poly3DCollection
        fig = plt.figure()
        ax = fig.add_subplot(111, projection='3d')
        mesh = Poly3DCollection(vertices[model.faces], alpha=0.1)
        face_color = (1.0, 1.0, 0.9)
        edge_color = (0, 0, 0)
        mesh.set_edgecolor(edge_color)
        mesh.set_facecolor(face_color)
        ax.add_collection3d(mesh)
        ax.scatter(joints[:, 0], joints[:, 1], joints[:, 2], color='r')
        ax.view_init(azim=-90, elev=100) #  y轴朝上
        if plot_joints:
            ax.scatter(joints[:, 0], joints[:, 1], joints[:, 2], alpha=0.1)
        plt.show()
        plt.savefig("./outputs_zzk/ok"+".png") # 保存观看
        plt.close()
 
图像是:(y轴朝上)
 
 没改代码之前是:
 
 pyrender 离线渲染包安装
 然后就可以跑demo.py了,改为–plotting-module pyrender
 需要改demo.py的代码,加上renderer.py,在examples下面:【直接复制过去即可】结果在最下面
 demo.py
# -*- coding: utf-8 -*-
import os
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
# holder of all proprietary rights on this computer program.
# You can only use this computer program if you have closed
# a license agreement with MPG or you get the right to use the computer
# program from someone who is authorized to grant you that right.
# Any use of the computer program without a valid license is prohibited and
# liable to prosecution.
#
# Copyright©2019 Max-Planck-Gesellschaft zur Förderung
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
# for Intelligent Systems. All rights reserved.
#
# Contact: ps-license@tuebingen.mpg.de
import os.path as osp
import argparse
import numpy as np
import torch
import smplx
from renderer import Renderer
import cv2
def main(model_folder,
         model_type='smplx',
         ext='npz',
         gender='neutral',
         plot_joints=False,
         num_betas=10,
         sample_shape=True,
         sample_expression=True,
         num_expression_coeffs=10,
         plotting_module='pyrender',
         use_face_contour=False):
    model = smplx.create(model_folder, model_type=model_type,
                         gender=gender, use_face_contour=use_face_contour,
                         num_betas=num_betas,
                         num_expression_coeffs=num_expression_coeffs,
                         ext=ext)
    print(model)
    betas, expression = None, None
    if sample_shape:
        betas = torch.randn([1, model.num_betas], dtype=torch.float32)
    if sample_expression:
        expression = torch.randn(
            [1, model.num_expression_coeffs], dtype=torch.float32)
    pose = torch.zeros([1,21*3], dtype=torch.float32)
    pose[:,2] = 1
    pose[:,5] = -1
    output = model(betas=betas, expression=expression, body_pose=pose,
                   return_verts=True)
    # output = model(betas=betas, expression=expression,
    #                return_verts=True)
    vertices = output.vertices.detach().cpu().numpy().squeeze()
    joints = output.joints.detach().cpu().numpy().squeeze()
    print('Vertices shape =', vertices.shape)
    print('Joints shape =', joints.shape)
    if plotting_module == 'pyrender':
        import pyrender
        import trimesh
        renderer = Renderer(resolution=(512, 512), orig_img=True, faces=model.faces)
        img = renderer.render(
            vertices,
        )
        cv2.imwrite('demo.jpg',img)
    elif plotting_module == 'matplotlib':
        from matplotlib import pyplot as plt
        from mpl_toolkits.mplot3d import Axes3D
        from mpl_toolkits.mplot3d.art3d import Poly3DCollection
        fig = plt.figure()
        ax = fig.add_subplot(111, projection='3d')
        mesh = Poly3DCollection(vertices[model.faces], alpha=0.1)
        face_color = (1.0, 1.0, 0.9)
        edge_color = (0, 0, 0)
        mesh.set_edgecolor(edge_color)
        mesh.set_facecolor(face_color)
        ax.add_collection3d(mesh)
        ax.scatter(joints[:, 0], joints[:, 1], joints[:, 2], color='r')
        ax.view_init(azim=-90, elev=100)
        if plot_joints:
            ax.scatter(joints[:, 0], joints[:, 1], joints[:, 2], alpha=0.1)
        plt.show()
    elif plotting_module == 'open3d':
        import open3d as o3d
        mesh = o3d.geometry.TriangleMesh()
        mesh.vertices = o3d.utility.Vector3dVector(
            vertices)
        mesh.triangles = o3d.utility.Vector3iVector(model.faces)
        mesh.compute_vertex_normals()
        mesh.paint_uniform_color([0.3, 0.3, 0.3])
        geometry = [mesh]
        if plot_joints:
            joints_pcl = o3d.geometry.PointCloud()
            joints_pcl.points = o3d.utility.Vector3dVector(joints)
            joints_pcl.paint_uniform_color([0.7, 0.3, 0.3])
            geometry.append(joints_pcl)
        o3d.visualization.draw_geometries(geometry)
    else:
        raise ValueError('Unknown plotting_module: {}'.format(plotting_module))
if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='SMPL-X Demo')
    parser.add_argument('--model-folder', required=True, type=str,
                        help='The path to the model folder')
    parser.add_argument('--model-type', default='smplx', type=str,
                        choices=['smpl', 'smplh', 'smplx', 'mano', 'flame'],
                        help='The type of model to load')
    parser.add_argument('--gender', type=str, default='neutral',
                        help='The gender of the model')
    parser.add_argument('--num-betas', default=10, type=int,
                        dest='num_betas',
                        help='Number of shape coefficients.')
    parser.add_argument('--num-expression-coeffs', default=10, type=int,
                        dest='num_expression_coeffs',
                        help='Number of expression coefficients.')
    parser.add_argument('--plotting-module', type=str, default='pyrender',
                        dest='plotting_module',
                        choices=['pyrender', 'matplotlib', 'open3d'],
                        help='The module to use for plotting the result')
    parser.add_argument('--ext', type=str, default='npz',
                        help='Which extension to use for loading')
    parser.add_argument('--plot-joints', default=False,
                        type=lambda arg: arg.lower() in ['true', '1'],
                        help='The path to the model folder')
    parser.add_argument('--sample-shape', default=True,
                        dest='sample_shape',
                        type=lambda arg: arg.lower() in ['true', '1'],
                        help='Sample a random shape')
    parser.add_argument('--sample-expression', default=True,
                        dest='sample_expression',
                        type=lambda arg: arg.lower() in ['true', '1'],
                        help='Sample a random expression')
    parser.add_argument('--use-face-contour', default=False,
                        type=lambda arg: arg.lower() in ['true', '1'],
                        help='Compute the contour of the face')
    args = parser.parse_args()
    model_folder = osp.expanduser(osp.expandvars(args.model_folder))
    model_type = args.model_type
    plot_joints = args.plot_joints
    use_face_contour = args.use_face_contour
    gender = args.gender
    ext = args.ext
    plotting_module = args.plotting_module
    num_betas = args.num_betas
    num_expression_coeffs = args.num_expression_coeffs
    sample_shape = args.sample_shape
    sample_expression = args.sample_expression
    main(model_folder, model_type, ext=ext,
         gender=gender, plot_joints=plot_joints,
         num_betas=num_betas,
         num_expression_coeffs=num_expression_coeffs,
         sample_shape=sample_shape,
         sample_expression=sample_expression,
         plotting_module=plotting_module,
         use_face_contour=use_face_contour)
 
renderer.py
import os
#os.environ['PYOPENGL_PLATFORM'] = 'egl'
import math
import trimesh
import pyrender
import numpy as np
from pyrender.constants import RenderFlags
class WeakPerspectiveCamera(pyrender.Camera):
    def __init__(self,
                 scale,
                 translation,
                 znear=pyrender.camera.DEFAULT_Z_NEAR,
                 zfar=None,
                 name=None):
        super(WeakPerspectiveCamera, self).__init__(
            znear=znear,
            zfar=zfar,
            name=name,
        )
        self.scale = scale
        self.translation = translation
    def get_projection_matrix(self, width=None, height=None):
        P = np.eye(4)
        P[0, 0] = self.scale[0]
        P[1, 1] = self.scale[1]
        P[0, 3] = self.translation[0] * self.scale[0]
        P[1, 3] = -self.translation[1] * self.scale[1]
        P[2, 2] = -1
        return P
class PerspectiveCamera(pyrender.Camera):
    def __init__(self,
                 yfov=None,
                 aspectRatio=1):
        super(PerspectiveCamera, self).__init__(
            znear=0.05,
            zfar=100.0
        )
        self.znear = 0.05
        self.zfar = 100.0
        self.yfov = yfov
        self.aspectRatio = aspectRatio
    def get_projection_matrix(self, width=None, height=None):
        znear = 0.05
        zfar = 100.0
        fovX = self.yfov
        tanHalfFovY = math.tan((self.yfov / 2))
        tanHalfFovX = math.tan((fovX / 2))
        top = tanHalfFovY * znear
        bottom = -top
        right = tanHalfFovX * znear
        left = -right
        P = np.zeros((4, 4))
        z_sign = 1.0
        P[0, 0] = 2.0 * znear / (right - left)
        P[1, 1] = 2.0 * znear / (top - bottom)
        P[0, 2] = (right + left) / (right - left)
        P[1, 2] = (top + bottom) / (top - bottom)
        P[3, 2] = -z_sign
        P[2, 2] = z_sign * zfar / (znear-zfar)
        P[2, 3] = 2*(zfar * znear) / (znear-zfar)
        return P
class Renderer:
    def __init__(self, resolution=(224,224), orig_img=False, wireframe=False, faces=None):
        self.resolution = resolution
        self.faces = faces
        self.orig_img = orig_img
        self.wireframe = wireframe
        self.renderer = pyrender.OffscreenRenderer(
            viewport_width=self.resolution[0],
            viewport_height=self.resolution[1],
            point_size=1.0
        )
        # set the scene
        self.scene = pyrender.Scene(bg_color=[0.0, 0.0, 0.0, 0.0], ambient_light=(0.3, 0.3, 0.3))
        # light = pyrender.PointLight(color=[1.0, 1.0, 1.0], intensity=0.8)
        light = pyrender.DirectionalLight(color=[1.0, 1.0, 1.0], intensity=0.8)
        light_pose = np.eye(4)
        light_pose[:3, 3] = [0, -1, 1]
        self.scene.add(light, pose=light_pose)
        light_pose[:3, 3] = [0, 1, 1]
        self.scene.add(light, pose=light_pose)
        light_pose[:3, 3] = [1, 1, 2]
        self.scene.add(light, pose=light_pose)
    def render(self, verts, cam=None, angle=None, axis=None, mesh_filename=None, color=[1.0, 1.0, 0.9], rotate=False):
        mesh = trimesh.Trimesh(vertices=verts, faces=self.faces, process=False)
        Rx = trimesh.transformations.rotation_matrix(math.radians(180), [1, 0, 0])
        mesh.apply_transform(Rx)
        if rotate:
            rot = trimesh.transformations.rotation_matrix(
                np.radians(60), [0, 1, 0])
            mesh.apply_transform(rot)
        if mesh_filename is not None:
            mesh.export(mesh_filename)
        angle = 180
        axis = [1, 0, 0]
        if angle and axis:
            R = trimesh.transformations.rotation_matrix(math.radians(angle), axis)
            mesh.apply_transform(R)
        camera = PerspectiveCamera(yfov=0.5, aspectRatio=1)
        material = pyrender.MetallicRoughnessMaterial(
            metallicFactor=0.0,
            alphaMode='OPAQUE',
            smooth=True,
            wireframe=True,
            roughnessFactor=1.0,
            emissiveFactor=(0.1, 0.1, 0.1),
            baseColorFactor=(color[0], color[1], color[2], 1.0)
        )
        mesh = pyrender.Mesh.from_trimesh(mesh, material=material)
        mesh_node = self.scene.add(mesh, 'mesh')
        camera_pose = np.eye(4)
        camera_pose[:3, 3] = np.array([0, 0, 5])
        cam_node = self.scene.add(camera, pose=camera_pose)
        if self.wireframe:
            render_flags = RenderFlags.RGBA | RenderFlags.ALL_WIREFRAME
        else:
            render_flags = RenderFlags.RGBA
        rgb, _ = self.renderer.render(self.scene, flags=render_flags)
        # valid_mask = (rgb[:, :, -1] > 0)[:, :, np.newaxis]
        # #output_img = rgb[:, :, :-1] * valid_mask + (1 - valid_mask) * img
        # output_img = rgb * valid_mask + (1 - valid_mask) * img
        # image = output_img.astype(np.uint8)
        self.scene.remove_node(mesh_node)
        self.scene.remove_node(cam_node)
        return rgb
 


参考链接
- https://www.bilibili.com/video/BV1jkVeeJEDn/?spm_id_from=333.788&vd_source=5413f4289a5882463411525768a1ee27
 - https://github.com/heawon-yoon/smpl_gaussian_tutorial/blob/main/smpl.ipynb
 



















![C语言学习笔记[21]:分支语句if...else](https://img-blog.csdnimg.cn/direct/1043b9e05d9a47acb17511d8dcbd8b2e.png)
