1. NTU_RGBD数据集解析实战第一次接触NTU_RGBD数据集时我被它庞大的数据量和复杂的文件结构弄得晕头转向。这个由56880个动作样本组成的庞然大物每个样本都包含RGB视频、深度图序列、3D骨架数据和红外视频四种模态数据。今天我们就重点聊聊其中最实用的3D骨架数据。数据集的文件命名很有讲究比如S010C001P019R001A010.skeleton这个文件名就包含了丰富的信息S010表示第10组设置C001表示1号相机拍摄P019是第19位参与者R001代表第一次表演A010对应第10类动作打开.skeleton文件你会发现数据排列非常规整。以我最近分析的拍手动作为例文件开头明确标注了71帧数据。每帧数据包含body数量、关节信息等关键参数。特别要注意的是每个关节的12维数据其中前3维的x、y、z坐标就是我们做可视化最需要的。实际解析时我建议先用小样本测试。比如先处理前5帧数据确认关节连接顺序是否正确。这里有个坑要注意25个关节的编号是从0开始的而且不同部位关节的连线顺序需要预先定义好。比如手臂的连线顺序应该是23→11→10→9→8→20→4→5→6→7→21。2. 骨架数据读取与处理技巧写数据读取代码时我习惯先用Python字典来组织数据。下面这个read_skeleton函数经过多次优化现在用起来很顺手def read_skeleton(file): with open(file, r) as f: skeleton_sequence {} skeleton_sequence[numFrame] int(f.readline()) skeleton_sequence[frameInfo] [] for t in range(skeleton_sequence[numFrame]): frame_info {} frame_info[numBody] int(f.readline()) frame_info[bodyInfo] [] for m in range(frame_info[numBody]): body_info {} body_info_key [ bodyID, clipedEdges, handLeftConfidence, handLeftState, handRightConfidence, handRightState, isResticted, leanX, leanY, trackingState ] body_info { k: float(v) for k, v in zip( body_info_key, f.readline().split() ) } body_info[numJoint] int(f.readline()) body_info[jointInfo] [] for v in range(body_info[numJoint]): joint_info_key [ x, y, z, depthX, depthY, colorX, colorY, orientationW, orientationX, orientationY, orientationZ, trackingState ] joint_info { k: float(v) for k, v in zip( joint_info_key, f.readline().split() ) } body_info[jointInfo].append(joint_info) frame_info[bodyInfo].append(body_info) skeleton_sequence[frameInfo].append(frame_info) return skeleton_sequence处理多body情况时要特别注意。有些动作比如握手会有两个body数据而拍手可能只有一个。我建议先用max_body参数做限制避免数组越界。数据归一化也很重要不同动作的坐标范围差异很大可以试试这样的处理def normalize_data(data): # 减去均值 data - np.mean(data, axis(1,2,3), keepdimsTrue) # 除以标准差 data / np.std(data, axis(1,2,3), keepdimsTrue) return data3. 2D动态可视化实现用Matplotlib做2D动态可视化是我最推荐新手尝试的方案。先准备好关节连接关系arms [23,11,10,9,8,20,4,5,6,7,21] rightHand [11,24] leftHand [7,22] legs [19,18,17,16,0,12,13,14,15] body [3,2,20,1,0]动态展示的核心是plt.ion()开启交互模式然后在循环中更新数据。这里分享几个优化技巧使用plt.cla()而不是plt.clf()这样只清除当前轴而保留其他设置提前计算好坐标范围避免画面抖动添加帧数显示方便调试控制plt.pause()的时间间隔0.001秒比较流畅完整代码如下def visualize_2d(point, connections, num_frame): plt.figure(figsize(10,8)) plt.ion() # 预计算坐标范围 x_min, x_max np.min(point[0]), np.max(point[0]) y_min, y_max np.min(point[1]), np.max(point[1]) for i in range(num_frame): plt.cla() # 绘制关节点 plt.scatter(point[0,i,:,0], point[1,i,:,0], cred, s50) # 绘制连接线 for connection in connections: x point[0,i,connection,0] y point[1,i,connection,0] plt.plot(x, y, cblue, lw2) plt.xlim(x_min-0.5, x_max0.5) plt.ylim(y_min-0.5, y_max0.5) plt.title(fFrame {i}/{num_frame}) plt.pause(0.001) plt.ioff() plt.show()4. 3D可视化进阶技巧3D可视化虽然酷炫但坑也不少。首先是视角问题默认视角可能不理想建议这样设置ax.view_init(elev20, azim-60) # 仰角20度方位角-60度其次是比例问题z轴的范围通常和x、y轴不同会导致图形变形。我的解决方案是def adjust_axes(ax, points): # 获取各轴范围 x_min, x_max np.min(points[0]), np.max(points[0]) y_min, y_max np.min(points[1]), np.max(points[1]) z_min, z_max np.min(points[2]), np.max(points[2]) # 计算最大范围 max_range max(x_max-x_min, y_max-y_min, z_max-z_min) # 设置各轴范围 ax.set_xlim((x_maxx_min)/2 - max_range/2, (x_maxx_min)/2 max_range/2) ax.set_ylim((y_maxy_min)/2 - max_range/2, (y_maxy_min)/2 max_range/2) ax.set_zlim((z_maxz_min)/2 - max_range/2, (z_maxz_min)/2 max_range/2)对于多body的情况建议用不同颜色区分colors [red, green, blue] for body_idx in range(num_bodies): ax.scatter( points[0,:,:,body_idx], points[1,:,:,body_idx], points[2,:,:,body_idx], ccolors[body_idx], s40 )5. 常见问题解决方案在实际项目中我遇到过几个典型问题问题1关节连线错乱这是因为关节连接顺序定义错误。建议先用静态帧测试确认每个连接线是否正确。特别是脊柱和四肢的连接点容易搞混。问题2动画闪烁严重可能是没有正确清除上一帧的绘图。确保使用了plt.cla()并且所有绘图元素都在清除后重新绘制。问题33D视角不理想多试试不同的view_init参数也可以添加交互式控件from matplotlib.widgets import Slider ax_elev plt.axes([0.1, 0.05, 0.8, 0.02]) slider_elev Slider(ax_elev, Elevation, -180, 180, valinit20)问题4性能瓶颈对于长序列可以考虑降低帧率使用blitting技术预计算所有帧的图像6. 实战案例手势识别可视化最近用NTU_RGBD数据做了一个手势识别项目这里分享关键代码。首先定义手部关节hand_joints { wrist: 0, thumb: [1,2,3,4], index: [5,6,7,8], middle: [9,10,11,12], ring: [13,14,15,16], pinky: [17,18,19,20] }然后专门写一个手部可视化函数def visualize_hand(hand_points, frame_idx): fig plt.figure(figsize(10,8)) ax fig.add_subplot(111, projection3d) # 绘制关节 ax.scatter( hand_points[0,frame_idx,:,0], hand_points[1,frame_idx,:,0], hand_points[2,frame_idx,:,0], cred, s50 ) # 绘制手指连线 for finger in hand_joints.values(): if isinstance(finger, list): x hand_points[0,frame_idx,finger,0] y hand_points[1,frame_idx,finger,0] z hand_points[2,frame_idx,finger,0] ax.plot(x, y, z, cblue, lw2) adjust_axes(ax, hand_points) plt.show()7. 性能优化与扩展当需要处理大量数据时原始方法可能不够高效。我总结了几种优化方案使用OpenGL加速from vispy import scene canvas scene.SceneCanvas(keysinteractive) view canvas.central_widget.add_view()预处理数据 将.skeleton文件转换为更高效的二进制格式读取速度能提升10倍以上。多线程渲染 使用Python的threading模块分离数据加载和渲染线程。交互功能增强 添加暂停、回放、速度调节等控件提升用户体验。对于更复杂的应用可以考虑整合到PyQt等GUI框架中。我最近做的一个项目就把可视化做成了独立面板还能实时显示关节角度等信息。
NTU_RGB+D骨架数据解析与动态可视化实战
发布时间:2026/6/11 21:49:49
1. NTU_RGBD数据集解析实战第一次接触NTU_RGBD数据集时我被它庞大的数据量和复杂的文件结构弄得晕头转向。这个由56880个动作样本组成的庞然大物每个样本都包含RGB视频、深度图序列、3D骨架数据和红外视频四种模态数据。今天我们就重点聊聊其中最实用的3D骨架数据。数据集的文件命名很有讲究比如S010C001P019R001A010.skeleton这个文件名就包含了丰富的信息S010表示第10组设置C001表示1号相机拍摄P019是第19位参与者R001代表第一次表演A010对应第10类动作打开.skeleton文件你会发现数据排列非常规整。以我最近分析的拍手动作为例文件开头明确标注了71帧数据。每帧数据包含body数量、关节信息等关键参数。特别要注意的是每个关节的12维数据其中前3维的x、y、z坐标就是我们做可视化最需要的。实际解析时我建议先用小样本测试。比如先处理前5帧数据确认关节连接顺序是否正确。这里有个坑要注意25个关节的编号是从0开始的而且不同部位关节的连线顺序需要预先定义好。比如手臂的连线顺序应该是23→11→10→9→8→20→4→5→6→7→21。2. 骨架数据读取与处理技巧写数据读取代码时我习惯先用Python字典来组织数据。下面这个read_skeleton函数经过多次优化现在用起来很顺手def read_skeleton(file): with open(file, r) as f: skeleton_sequence {} skeleton_sequence[numFrame] int(f.readline()) skeleton_sequence[frameInfo] [] for t in range(skeleton_sequence[numFrame]): frame_info {} frame_info[numBody] int(f.readline()) frame_info[bodyInfo] [] for m in range(frame_info[numBody]): body_info {} body_info_key [ bodyID, clipedEdges, handLeftConfidence, handLeftState, handRightConfidence, handRightState, isResticted, leanX, leanY, trackingState ] body_info { k: float(v) for k, v in zip( body_info_key, f.readline().split() ) } body_info[numJoint] int(f.readline()) body_info[jointInfo] [] for v in range(body_info[numJoint]): joint_info_key [ x, y, z, depthX, depthY, colorX, colorY, orientationW, orientationX, orientationY, orientationZ, trackingState ] joint_info { k: float(v) for k, v in zip( joint_info_key, f.readline().split() ) } body_info[jointInfo].append(joint_info) frame_info[bodyInfo].append(body_info) skeleton_sequence[frameInfo].append(frame_info) return skeleton_sequence处理多body情况时要特别注意。有些动作比如握手会有两个body数据而拍手可能只有一个。我建议先用max_body参数做限制避免数组越界。数据归一化也很重要不同动作的坐标范围差异很大可以试试这样的处理def normalize_data(data): # 减去均值 data - np.mean(data, axis(1,2,3), keepdimsTrue) # 除以标准差 data / np.std(data, axis(1,2,3), keepdimsTrue) return data3. 2D动态可视化实现用Matplotlib做2D动态可视化是我最推荐新手尝试的方案。先准备好关节连接关系arms [23,11,10,9,8,20,4,5,6,7,21] rightHand [11,24] leftHand [7,22] legs [19,18,17,16,0,12,13,14,15] body [3,2,20,1,0]动态展示的核心是plt.ion()开启交互模式然后在循环中更新数据。这里分享几个优化技巧使用plt.cla()而不是plt.clf()这样只清除当前轴而保留其他设置提前计算好坐标范围避免画面抖动添加帧数显示方便调试控制plt.pause()的时间间隔0.001秒比较流畅完整代码如下def visualize_2d(point, connections, num_frame): plt.figure(figsize(10,8)) plt.ion() # 预计算坐标范围 x_min, x_max np.min(point[0]), np.max(point[0]) y_min, y_max np.min(point[1]), np.max(point[1]) for i in range(num_frame): plt.cla() # 绘制关节点 plt.scatter(point[0,i,:,0], point[1,i,:,0], cred, s50) # 绘制连接线 for connection in connections: x point[0,i,connection,0] y point[1,i,connection,0] plt.plot(x, y, cblue, lw2) plt.xlim(x_min-0.5, x_max0.5) plt.ylim(y_min-0.5, y_max0.5) plt.title(fFrame {i}/{num_frame}) plt.pause(0.001) plt.ioff() plt.show()4. 3D可视化进阶技巧3D可视化虽然酷炫但坑也不少。首先是视角问题默认视角可能不理想建议这样设置ax.view_init(elev20, azim-60) # 仰角20度方位角-60度其次是比例问题z轴的范围通常和x、y轴不同会导致图形变形。我的解决方案是def adjust_axes(ax, points): # 获取各轴范围 x_min, x_max np.min(points[0]), np.max(points[0]) y_min, y_max np.min(points[1]), np.max(points[1]) z_min, z_max np.min(points[2]), np.max(points[2]) # 计算最大范围 max_range max(x_max-x_min, y_max-y_min, z_max-z_min) # 设置各轴范围 ax.set_xlim((x_maxx_min)/2 - max_range/2, (x_maxx_min)/2 max_range/2) ax.set_ylim((y_maxy_min)/2 - max_range/2, (y_maxy_min)/2 max_range/2) ax.set_zlim((z_maxz_min)/2 - max_range/2, (z_maxz_min)/2 max_range/2)对于多body的情况建议用不同颜色区分colors [red, green, blue] for body_idx in range(num_bodies): ax.scatter( points[0,:,:,body_idx], points[1,:,:,body_idx], points[2,:,:,body_idx], ccolors[body_idx], s40 )5. 常见问题解决方案在实际项目中我遇到过几个典型问题问题1关节连线错乱这是因为关节连接顺序定义错误。建议先用静态帧测试确认每个连接线是否正确。特别是脊柱和四肢的连接点容易搞混。问题2动画闪烁严重可能是没有正确清除上一帧的绘图。确保使用了plt.cla()并且所有绘图元素都在清除后重新绘制。问题33D视角不理想多试试不同的view_init参数也可以添加交互式控件from matplotlib.widgets import Slider ax_elev plt.axes([0.1, 0.05, 0.8, 0.02]) slider_elev Slider(ax_elev, Elevation, -180, 180, valinit20)问题4性能瓶颈对于长序列可以考虑降低帧率使用blitting技术预计算所有帧的图像6. 实战案例手势识别可视化最近用NTU_RGBD数据做了一个手势识别项目这里分享关键代码。首先定义手部关节hand_joints { wrist: 0, thumb: [1,2,3,4], index: [5,6,7,8], middle: [9,10,11,12], ring: [13,14,15,16], pinky: [17,18,19,20] }然后专门写一个手部可视化函数def visualize_hand(hand_points, frame_idx): fig plt.figure(figsize(10,8)) ax fig.add_subplot(111, projection3d) # 绘制关节 ax.scatter( hand_points[0,frame_idx,:,0], hand_points[1,frame_idx,:,0], hand_points[2,frame_idx,:,0], cred, s50 ) # 绘制手指连线 for finger in hand_joints.values(): if isinstance(finger, list): x hand_points[0,frame_idx,finger,0] y hand_points[1,frame_idx,finger,0] z hand_points[2,frame_idx,finger,0] ax.plot(x, y, z, cblue, lw2) adjust_axes(ax, hand_points) plt.show()7. 性能优化与扩展当需要处理大量数据时原始方法可能不够高效。我总结了几种优化方案使用OpenGL加速from vispy import scene canvas scene.SceneCanvas(keysinteractive) view canvas.central_widget.add_view()预处理数据 将.skeleton文件转换为更高效的二进制格式读取速度能提升10倍以上。多线程渲染 使用Python的threading模块分离数据加载和渲染线程。交互功能增强 添加暂停、回放、速度调节等控件提升用户体验。对于更复杂的应用可以考虑整合到PyQt等GUI框架中。我最近做的一个项目就把可视化做成了独立面板还能实时显示关节角度等信息。