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的预测分三个步骤:
- 首先是使用了ffmpeg将视频解析成一帧一帧的图片,在对每张图片使用Yolo进行人物检测,记录检测到人物及区域(框)的信息。
- 按照检测结果进行裁减图像,喂入VIBE pose检测模型进行检测,并记录每一帧所对应的结果(包括预测的虚拟相机位置、关节点、人物三角网格数据等)。
- 针对每一帧的预测结果,使用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
以上,如有错误和建议,还请告诉我