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 = [] # shoulder–wrist 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("Shoulder–wrist 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()