exoskeleton/code/test/test_sew.py

159 lines
5.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import os
import sys
import numpy as np
import pinocchio as pin
import matplotlib.pyplot as plt
from omegaconf import OmegaConf
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from core.sew_mapper import SEWMapper, fk_update, pose_of_frame, rot_error_deg
# ...(这里保留你之前的 safe_normalize、random_qm、几何绘图函数等...
def random_qm(mapper, N=50, margin=0.2):
lb = []; ub = []
for j in mapper.m_model.joints[1:]: # skip universe
if j.nq == 1: # 1-DoF revolute
jid = j.id
iq = j.idx_q
lb.append(mapper.m_model.lowerPositionLimit[iq])
ub.append(mapper.m_model.upperPositionLimit[iq])
lb = np.array(lb); ub = np.array(ub)
rng = (ub - lb)
lb2 = lb + margin * rng
ub2 = ub - margin * rng
qs = []
for _ in range(N):
v = lb2 + np.random.rand(len(lb2)) * (ub2 - lb2)
q_full = pin.neutral(mapper.m_model)
k = 0
for j in mapper.m_model.joints[1:]:
if j.nq == 1:
q_full[j.idx_q] = v[k]; k += 1
qs.append(q_full)
return qs
def evaluate_sew_statistics(mapper, conf, N=500, margin=0.1, save_dir="assets"):
"""
统计验证 SEW retargeting 的精度,并输出论文用图:
(1) 腕点位置误差直方图
(2) 腕点姿态误差直方图
(3) (可选)位置误差 vs 肩-腕距离散点
"""
os.makedirs(save_dir, exist_ok=True)
# 随机采样 master 关节
qs_m = random_qm(mapper, N=N, margin=margin)
pos_errs = [] # mm
rot_errs = [] # deg
dists_sw = [] # shoulderwrist distance (m)
clipped_flags = []
for q_m in qs_m:
# SEW 映射
q_s, dbg = mapper.retargetting(q_m)
# master FK
pS_m, _ = pose_of_frame(mapper.m_model, mapper.m_data, conf.m_shoulder_frame)
pE_m, RE_m = pose_of_frame(mapper.m_model, mapper.m_data, conf.m_ee_frame)
# slave FK
fk_update(mapper.s_model, mapper.s_data, q_s)
pE_s, RE_s = pose_of_frame(mapper.s_model, mapper.s_data, conf.s_ee_frame)
# 误差(改成 EE
e_p = np.linalg.norm(pE_s - pE_m) * 1000.0 # 末端位置误差 [mm]
e_R = rot_error_deg(RE_m, RE_s) # 末端姿态误差 [deg]
pos_errs.append(e_p)
rot_errs.append(e_R)
d_m = np.linalg.norm(pE_m - pS_m) # m
dists_sw.append(d_m)
# 是否发生 reach 裁剪(根据 d_s vs master 原始 d_m、以及 L1+L2 区间)
d_s = dbg["d_s"]
clipped = (d_s < d_m - 1e-9) or (d_s > mapper.L1 + mapper.L2 - mapper.eps_clip + 1e-9)
clipped_flags.append(clipped)
pos_errs = np.asarray(pos_errs)
rot_errs = np.asarray(rot_errs)
dists_sw = np.asarray(dists_sw)
clipped_flags = np.asarray(clipped_flags, dtype=bool)
# --------- 打印统计量 (你可以把结果手工抄进论文表格) ----------
def summary_str(x):
return f"mean={x.mean():.3f}, std={x.std():.3f}, max={x.max():.3f}"
print("=== SEW retargeting statistics over {} samples ===".format(N))
print("Wrist position error [mm]:", summary_str(pos_errs))
print("Wrist orientation error [deg]:", summary_str(rot_errs))
print("Clipped configurations: {} / {} ({:.1f}%)"
.format(clipped_flags.sum(), N, 100.0 * clipped_flags.mean()))
# --------- 图 1: 位置 & 姿态误差直方图 ----------
fig, axes = plt.subplots(1, 2, figsize=(10, 4))
axes[0].hist(pos_errs, bins=30, color='C0', alpha=0.8)
axes[0].set_xlabel("Wrist position error $e_p$ [mm]")
axes[0].set_ylabel("Count")
axes[0].set_title("(a) Distribution of $e_p$")
axes[1].hist(rot_errs, bins=30, color='C1', alpha=0.8)
axes[1].set_xlabel("Wrist orientation error $e_R$ [deg]")
axes[1].set_ylabel("Count")
axes[1].set_title("(b) Distribution of $e_R$")
plt.tight_layout()
fig.savefig(os.path.join(save_dir, "sew_error_hist.png"), dpi=300)
# plt.show()
# --------- 图 2: 位置误差 vs 肩-腕距离 (可视化剪裁区) ----------
fig2, ax2 = plt.subplots(figsize=(5, 4))
ax2.scatter(dists_sw[~clipped_flags], pos_errs[~clipped_flags],
s=15, c='C0', label="Unclipped")
ax2.scatter(dists_sw[clipped_flags], pos_errs[clipped_flags],
s=25, c='C3', marker='x', label="Clipped")
ax2.set_xlabel("Shoulderwrist distance $d_m$ [m]")
ax2.set_ylabel("Position error $e_p$ [mm]")
ax2.set_title("SEW error vs. reach distance")
ax2.grid(True, linestyle='--', linewidth=0.5)
ax2.legend(loc="upper left")
fig2.tight_layout()
fig2.savefig(os.path.join(save_dir, "sew_error_scatter.png"), dpi=300)
plt.show()
def main():
conf = OmegaConf.load("./config/config.yaml")
master_model, _, _ = pin.buildModelsFromUrdf(str(conf.master_urdf))
slave_model, _, _ = pin.buildModelsFromUrdf(str(conf.slave_urdf))
mapper = SEWMapper(
master_model=master_model,
slave_model=slave_model,
m_shoulder_frame=conf.m_shoulder_frame,
m_elbow_frame=conf.m_elbow_frame,
m_wrist_frame=conf.m_wrist_frame,
m_ee_frame=conf.m_ee_frame,
s_shoulder_frame=conf.s_shoulder_frame,
s_elbow_frame=conf.s_elbow_frame,
s_wrist_frame=conf.s_wrist_frame,
s_ee_frame=conf.s_ee_frame,
slave_joint_names=conf.sew_mapper.slave_joint_names,
up_dir=np.array(conf.sew_mapper.up_dir),
eps_clip=conf.sew_mapper.eps_clip,
)
# 1) 如果要画几何示意图(方法部分),可以单独写一个函数;这里我们只跑统计
evaluate_sew_statistics(mapper, conf, N=500, margin=0.1, save_dir="assets")
if __name__ == "__main__":
main()