VIBE是一个的3D人体姿态预测开源项目,需要基于该项目作一些开发,首先需要能够搭建和是的环境成功复现它。

不过,这个项目的复现的,真的不是一星半点的艰难。

1、系统选择

之前一直用的Windows,最开始想在windows上复现它,经过一周的折腾,我放弃了。

接着尝试了Docker,由于GPU的问题放弃了。

接着尝试了WSL,虽然基本解决了GPU加速问题,但其中的某些方法还是只能使用CPU,导致速度异常慢,在最后的渲染阶段也是不能正常渲染的,最终也放弃了。

一不做二不休,把自己的电脑整个重装系统,变成一台Ubuntu电脑。

所以,如果你也想验证该项目,那么就不要在Windows上纠缠了,也不要去考虑虚拟机什么的,直接换系统吧。

2、硬件

官方是在2080Ti显卡上测试,宣称可达到30FPS,所以这个玩意是真的吃性能的,1060会报显存不够的错误的。

本人做测试时,电脑信息如下

3、准备阶段

  • 显卡驱动、在ubuntu上安装Nvidia的驱动,简直是一件%#@%@¥%¥#%,为此重装了三遍系统。。。
  • 安装Anaconda、VSCode(在ubuntu上不推荐pycharm,pycharm的坑有点多)、以及科学上网工具(如果有能力)

4、克隆项目

git clone https://github.com/mkocabas/VIBE.git

5、开始安装

官方提供了两种安装方式,pip和conda,因为其安装过程是需要创建虚拟环境的,个人习惯使用conda,

所以使用conda的安装:

cd VIBE
# pip
# source scripts/install_pip.sh
# conda
source scripts/install_conda.sh

打住!还是打开该文件,对照着内容一步一步手动来吧

首先创建 名为 vibe-env python=3.7的虚拟环境,接着激活该环境

接着手动使用pip安装numpy==1.17.5 torch==1.4.0 torchvision==0.5.0,(为了避免后面其他坑,这里就按照指定版本安装)

如果torch和torchvision提示找不到对应的版本,可以去pip官方源下载对应的whl文件后手动安装

“pip install git+https:……pytube.git --upgrade”是安装一个下载youtobe视频的开源项目,这个可以跳过。

其次如果需要安装,查看pytube项目主页,可以直接使用“pip install pytube”来安装。

最后就是根据“requirement.txt”来安装依赖项。

pip install -r requirements.txt

(本人在上面这一步折腾了很久,,,有很多坑的,但是由于其中一些项目的更新,已经VIBE项目作者在19天前也对该文件做了更新,目前亲测可以一次成功)

如果不成功,按照文件内所列依赖项逐个手动安装,并逐一解决问题即可。

6、测试

模型与示例视频准备:

source scripts/prepare_data.sh

当然参考该文件内容,要下在vibe_data.zip文件,我上传到了csdn,可以手动下载:https://download.csdn.net/download/Raink_LH/12692105

其他步骤参可照上图中的注释进行操作即可。

最后运行demo.py文件

编译器运行需要配置一定的参数,参照官方示例:

# Run on a local video
python demo.py --vid_file sample_video.mp4 --output_folder output/ --display

7、效果

我下载了B站上的舞蹈视频来作测试

该Demo中对3D-pose的预测分三个步骤:

  1. 首先是使用了ffmpeg将视频解析成一帧一帧的图片,在对每张图片使用Yolo进行人物检测,记录检测到人物及区域(框)的信息。
  2. 按照检测结果进行裁减图像,喂入VIBE pose检测模型进行检测,并记录每一帧所对应的结果(包括预测的虚拟相机位置、关节点、人物三角网格数据等)。
  3. 针对每一帧的预测结果,使用pyrender进行渲染,并将渲染结果合并成一个视频。

对与第一个阶段,GPU基本在70%左右,CPU在60%左右,第二阶段CPU和GPU都在很低和100%直接波动,应该是逐帧检测时,每一帧都比较消耗资源的。第三阶段渲染时,GPU使用一般,但CPU持续在95%以上。

我使用的视频是720P的,人物检测才40FPS,3D-pose检测在25FPS

运行输出如下

输出的结果视频,截取了一点,下过如下

总体来说,效果可以,人物在侧向、多人物重叠时效果不佳。

8、补充说明

vibe模型是基于pytorch的,输出的数据包含多个类别。3D-pose结果使用了smplx

结果展示使用的是pyrender,

上文中展示的是使用了输出结果中的三角人物网格数据(verts)。

更为简单直观的可以使用关节点数据(kp_3d),是归一化后的三位坐标,每个人物包含49个关节坐标。


2021年5月26日补充

因为很久没有用到这个项目了,很多地方都记不清了。之前一次补充,提到了三为节点渲染,但是很多人反馈“joints3d”关键字错误。

想来想去,终于想起来了,实在抱歉,之前忘了一点东西

(源码中是针对每一帧图像预测,在对比前后帧,做人物追踪,然后再把每一帧预测的数据转换成正对每一帧中不同人的数据,在做这个数据整理的过程中,原项目只整理了需要用到的两组数据,其他直接抛弃了,所以后面渲染三维节点或者用其他数据时就会报错)

如果还有哪里有遗漏的,欢迎补充!

因此对下文做了一些修改:

8.1关于三维关节点

在官方demo.py中,有关于输出结果的一个字典:

output_dict = {
                'pred_cam': pred_cam,
                'orig_cam': orig_cam,
                'verts': pred_verts,
                'pose': pred_pose,
                'betas': pred_betas,
                'joints3d': pred_joints3d,
                'joints2d': joints2d,
                'bboxes': bboxes,
                'frame_ids': frames,
               }

同时在其下文,能找到如下的代码:

# prepare results for rendering
frame_results = prepare_rendering_results(vibe_results, num_frames)

吧vibe_result,转换成frame_results,找到这个方法的定义prepare_rendering_results(),

可以看到原项目中只把verts和cam转换过来了,其他的没用到。

所以需要手动增加之后要用的数据,比如joints3d。

def prepare_rendering_results(vibe_results, nframes):
    frame_results = [{} for _ in range(nframes)]
    for person_id, person_data in vibe_results.items():
        for idx, frame_id in enumerate(person_data['frame_ids']):
            frame_results[frame_id][person_id] = {
                'verts': person_data['verts'][idx],
                'cam': person_data['orig_cam'][idx],
                'joints3d': person_data['joints3d'][idx],  # ---需要把每一帧预测出的结果加入到针对每个人的结果中!需要什么加什么!
            }

 

8.2关于关节点渲染图

官方使用pyrender渲染人物,使用的是'verts': pred_verts。

主要代码在demo.py的结尾处:

for frame_idx in tqdm(range(len(image_file_names))):
        img_fname = image_file_names[frame_idx]
        img = cv2.imread(img_fname)
        # …… # 个人省略了一些代码
        for person_id, person_data in frame_results[frame_idx].items():
            # 这里 拿到每个人的verts,camera参数,以及每个人的颜色
            frame_verts = person_data['verts']
            frame_cam = person_data['cam']
            mc = mesh_color[person_id]
            mesh_filename = None
            # …… # 个人省略了一些代码
            # 这里就是基于结果对每一帧图像进行渲染的
            img = renderer.render(
                img,
                frame_verts,
                cam=frame_cam,
                color=mc,
                mesh_filename=mesh_filename,
            )            
            # …… # 个人省略了一些代码

那么如果需要3D的关节点渲染效果,就需要传入3D节点数据,并重写渲染方法。

首先对上面做小小修改:

for frame_idx in tqdm(range(len(image_file_names))):
    img_fname = image_file_names[frame_idx]
    img = cv2.imread(img_fname)
    # …… # 个人省略了一些代码
    for person_id, person_data in frame_results[frame_idx].items():
        # 这里拿到3维关节点数据
        frame_joints3d = person_data['joints3d']
        frame_cam = person_data['cam']
        mesh_filename = None
        # …… # 个人省略了一些代码
        # 重写一个渲染方法,传入3D的关节点数据
        img = renderer.render_s(
                                img,
                                frame_joints3d,
                                cam=frame_cam,
                                mesh_filename=mesh_filename,
                                )
        # …… # 个人省略了一些代码

接着在render.py中,仿照原有的,实现一个render_s()方法

def render_s(self, img, joints, cam, angle=None, axis=None, mesh_filename=None):    
    tfs = np.tile(np.eye(4), (len(joints), 1, 1))
    tfs[:, :3, 3] = joints
    sx, sy, tx, ty = cam

    camera = WeakPerspectiveCamera(
        scale=[sx, -sy],
        translation=[tx, -ty],
        zfar=1000.
    )
    # 点的半径和颜色
    sm = trimesh.creation.uv_sphere(radius=0.02)
    sm.visual.vertex_colors = [0.2, 0.9, 0.2, 1.0]
    mesh = pyrender.Mesh.from_trimesh(sm, poses=tfs)

    mesh_node = self.scene.add(mesh, 'mesh')

    camera_pose = np.eye(4)
    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
    image = output_img.astype(np.uint8)

    self.scene.remove_node(mesh_node)
    self.scene.remove_node(cam_node)

    return image

以上,只是我当时测试的时候临时写的,没有研究过pyrender,参照这官方教程算是勉强能跑,颜色写死的,有需要的可以自己研究对这个方法进行优化。

8.2 关于二维关节点

二维的关节点数据其实在预测接结果中也是有的,

参考8.1中提到的output_dict,其中有:'joints2d': joints2d,以及'bboxes': bboxes两个项,一个是2维关节点数据,一个是检测到人的框的数据。

这两个数据有个特点,都是基于框的中心点的,框的四个数据中,前两个是框中心点,后两个是宽高,关节点坐标貌似也是基于中心点的相对坐标

将二维关节点数据,翻译成图像中绝对坐标:

def get_js2d(image, vibe_results):
    persons = [] # 当前帧中的所有检测到人的结果
    i = 0 # 当前帧中检测到几个人,和这个人的person_id是两回事
    for person_id, pose_data in vibe_results.items():
        pose = {}    
        pose["person_id"] = person_id
        pose["j2ds"] = []          
        joints_2d = pose_data['joints2d'][i]
        bbox = pose_data['bboxes'][i]
        # ---------记录一些常用关节序号
        # 2、3、4是右胳膊, 5、6、7是左胳膊
        # 9-右髋关节, 10-右膝盖, 11-右脚, 12-左髋关节, 13-左膝盖, 14-左脚
        # 37-头顶, 38-脖子
        # 39-肚脐(12), 40-脖下(13), 41-胸前(14)
        # -------------------------------------------------------
        # 可以过滤一些只保留需要的关节点
        #joints_2d = np.vstack((joints_2d[2:5, :], joints_2d[5:8, :], joints_2d[9:12, :], joints_2d[12:15, :], joints_2d[39:42, :]))   
        # 
        for j2d in joints_2d:
            pt = (int(bbox[0] + j2d[0] * (bbox[2] / 2.0)), int(bbox[1] + j2d[1] * (bbox[3] / 2.0)))
            pose["j2ds"].append(pt)
        persons.append(pose)
        i += 1
    return persons

以上,如有错误和建议,还请告诉我

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐