CMU Mocap
CMU Mocap 是一个广为人知的运动捕捉数据库
其中 ASF 文件用于描述骨架,AMC 文件用于描述运动
在官网的 FAQ 界面给出了一些可视化 ASF AMC 文件的工具
在这里介绍了一点关于 ASF 文件的组织
其中引人注意的是骨架层级
可见,hierarchy 的一行代表一个父节点和若干个直接相连的子节点
在这里介绍了 AMC 文件如何表示某一帧骨架的位置
research.cs.wisc.edu/graphics/Co…
CalciferZh/AMCParser
光看公式可能还是不懂,可以看看别人是怎么解析的
这个解析器的使用示例是:
if __name__ == '__main__':
test_all()
asf_path = './133.asf'
amc_path = './133_01.amc'
joints = parse_asf(asf_path)
motions = parse_amc(amc_path)
frame_idx = 0
joints['root'].set_motion(motions[frame_idx])
joints['root'].draw()
其中 parse_asf 和 parse_amc 都很好懂,就是一行一行地读取文件
关键在于 set_motion
def set_motion(self, motion):
if self.name == 'root':
self.coordinate = np.reshape(np.array(motion['root'][:3]), [3, 1])
rotation = np.deg2rad(motion['root'][3:])
self.matrix = self.C.dot(euler2mat(*rotation)).dot(self.Cinv)
else:
idx = 0
rotation = np.zeros(3)
for axis, lm in enumerate(self.limits):
if not np.array_equal(lm, np.zeros(2)):
rotation[axis] = motion[self.name][idx]
idx += 1
rotation = np.deg2rad(rotation)
self.matrix = self.parent.matrix.dot(self.C).dot(euler2mat(*rotation)).dot(self.Cinv)
self.coordinate = self.parent.coordinate + self.length * self.matrix.dot(self.direction)
for child in self.children:
child.set_motion(motion)
Numpy 测试
array = np.array([1, 2, 3, 4, 5, 6])
print(array[0])
print(array[:3])
print(array[3:])
输出
1
[1 2 3]
[4 5 6]
所以说他这个取数据写法简单,但是直觉上有点怪
[:] 表示取全部数据
[:idx] 表示取 0~idx-1 的数据,[idx:] 表示取 idx~length-1 的数据
毕竟我还以为要不就是一直不包含 [idx] 要不就是一直包含 [idx]
其中 Cinv 是 C 的转置,在初始化 Joint 的时候计算:
这个 axis 读的是 root 骨的 axis,由于是用的 euler2mat,可知 ASF 文件里的角度都是欧拉角
那么现在应该没有问题了,已知公式是:
vM = vXYZ
L = CinvMCB
对于 root 来说,它的坐标是给定的,它也没有父级,所以不需要计算父级相关的矩阵,所以他的计算里面没有 B,那么他的 rotation = np.deg2rad(motion['root'][3:]) 对应公式中的 M
对于其他节点来说,变换要以父级为基础,所以多出来的那个就是父级相关的 B,所以发现:
self.matrix 对应公式中的 L
self.parent.matrix 对应公式中的 B
这么看的话,原公式不如写成:
Rot = euler2mat(np.deg2rad([X, Y, Z]))
B = self.parent.L if self.parent != None else E
L = Cinv·Rot·C·B
其中 X Y Z 是三个轴的欧拉角,文件中用角度表示
有些骨骼只在某个方向上有自由度,那么其他方向上的旋转自然就是 0 了
如果在那些没有自由度的方向上有一个固定的旋转角度怎么办?那些是写在 axis 里面的,也就是由 C 计算
jutanke/mocap
这个人的脚本可以将 Human3.6M 和 CMU 数据集的骨架归一化,简化,看上去很有用……?
虽然介绍中大部分使用 Human3.6M 数据集,但是用 CMU 是差不多的
比如在根目录下创建如下测试脚本:
import mocap.datasets.cmu as CMU
all_subjects = CMU.ALL_SUBJECTS
# different subjects have different actions:
action_for_subject_01 = CMU.GET_ACTIONS('01')
from mocap.visualization.sequence import SequenceVisualizer
ds = CMU.CMU(['01'])
seq = ds[0]
vis_dir = './visualization/'
vis_name = 'test_visualization'
vis = SequenceVisualizer(vis_dir, vis_name, # mandatory parameters
plot_fn=None, # TODO
vmin=-1, vmax=1, # min and max values of the 3D plot scene
to_file=False, # if True writes files to the given directory
subsampling=1, # subsampling of sequences
with_pauses=False, # if True pauses after each frame
fps=20, # fps for visualization
mark_origin=False) # if True draw cross at origin
# plot single sequence
vis.plot(seq,
seq2=None,
parallel=False,
plot_fn1=None, plot_fn2=None, # defines how seq/seq2 are drawn
views=[(45, 45)], # [(elevation, azimuth)] # defines the view(s)
lcolor='#099487', rcolor='#F51836',
lcolor2='#E1C200', rcolor2='#5FBF43',
noaxis=False, # if True draw person against white background
noclear=False, # if True do not clear the scene for next frame
toggle_color=False, # if True toggle color after each frame
plot_cbc=None, # alternatve plot function: fn(ax{matplotlib}, seq{n_frames x dim}, frame:{int})
last_frame=60,
definite_cbc=None, # fn(ax{matplotlib}, iii{int}|enueration, frame{int})
name='',
plot_jid=False,
create_video=False,
video_fps=25,
if_video_keep_pngs=False)
我在使用的时候遇到了这样的报错
0%| | 0/60 [00:00<?, ?it/s]
Traceback (most recent call last):
File "MyWorkSpace\plugins\python\helpers\pydev\pydevconsole.py", line 364, in runcode
coro = func()
File "<input>", line 20, in <module>
File "MyWorkSpace\mocap-version2\mocap\visualization\sequence.py", line 183, in plot
if t < n:
UnboundLocalError: local variable 'n' referenced before assignment
一看报错的脚本,确实是没有声明局部变量 n 就直接用了
这是因为源代码只考虑了 last_frame is None 的情况
if last_frame is None:
if seq2 is None or parallel:
last_frame = len(seq1)
n = last_frame
if parallel:
assert seq2 is not None
assert len(seq2) == last_frame
else:
n = len(seq1)
seq1 = np.concatenate([seq1, seq2], axis=0)
last_frame = len(seq1)
如果给 last_frame 赋了值,就会跳过这个 if,局部变量 n 就没有在这里赋值,之后也没有赋值语句,也就相当于没有定义了
最简单的解决方法就是在代码前面声明一下
但是更好的方法是把他这个 if...else... 补全
但是我看不懂他的代码……所以提了一个 issue