Trong bài 3, chúng ta đã có output 4D HOI: người SMPL-X, object pose 6-DoF, contact và visualization. Trong bài 4, chúng ta tách rõ hai kiểu dữ liệu: manipulation với object động và locomotion/sitting với scene tĩnh. Bài 5 là cầu nối từ thế giới "người trong reconstruction" sang "robot Unitree G1 trong motion library".
Điểm mấu chốt: policy downstream không train trực tiếp trên SMPL-X. SMPL-X là body model của người; Unitree G1 có topology, joint limit, tay, chân, khối lượng và kinematics khác. Vì vậy GRAIL cần một bước retargeting để biến trajectory người-object thành trajectory robot-object. Script trung tâm là grail/retargeting/scripts/retarget_pipeline.sh. Nó không làm phép màu trong một lệnh duy nhất; nó gọi ba stage rõ ràng: grail.retargeting.retarget, grail.retargeting.process, và grail.retargeting.compute_bps.
Nguồn kỹ thuật chính dùng trong bài:
- GRAIL project page
- GRAIL GitHub README
- GRAIL retargeting docs
retarget_pipeline.shretarget.shprocess.shcompute_bps.sh- GMR: General Motion Retargeting, Basis Point Sets paper
Roadmap series
- Tạo asset 3D và terrain cho GRAIL: hai nhánh asset, prompt object, sharding và chuẩn bị file cho downstream.
- Sinh video 2D HOI bằng Blender và Kling: render frame điều kiện, camera/depth và video foundation model.
- Tái dựng 4D HOI: GEM, SAM2, MoGe: pose người, object tracking, optimization, filtering và visualization.
- Locomotion trên terrain tĩnh: curb, slope, stairs và sitting như static-scene 4D HOI.
- Retarget trajectory sang Unitree G1: bài hiện tại.
- Train policy và export dữ liệu: đóng gói demonstration, train tracker/policy và chuẩn bị sim-to-real.
Nếu bạn cần bối cảnh rộng hơn về whole-body policy, đọc thêm Humanoid loco-manipulation và G1 đi terrain bằng reinforcement learning. Hai bài đó giải thích vì sao retargeting không chỉ là đổi tên joint: dữ liệu phải giữ được balance, timing, contact và quan hệ tay-object.
Mục tiêu của bài
Sau bài này, bạn sẽ biết:
- Input hợp lệ từ
4dhoi_recon_validnằm ở đâu và đi vàoretarget_pipeline.shnhư thế nào. - Vì sao stage 1 dùng
grail.retargeting.retargetđể chuyển SMPL-X sangunitree_g1. - Stage 2
grail.retargeting.processthêmhand_action_left,hand_action_right, table geometry và lọc contact theo frame lift ra sao. --lift_thresholdvà--grasp_anticipation_framesảnh hưởng trực tiếp đến lúc tay đóng như thế nào.- Stage 3
grail.retargeting.compute_bpstạo object shape embedding trongbps/để policy có observation cố định. - Khi nào nên dùng
--zero_out_wristvà khi nào nên bỏ quaprocess.sh.
Lệnh end-to-end được tài liệu GRAIL khuyến nghị có dạng:
conda activate sonic
export DISPLAY=:1
bash grail/retargeting/scripts/retarget_pipeline.sh \
data/genhoi/benchmark_v3/generation/4dhoi_recon_valid/Hunyuan \
benchmark_v3_0203
Tham số thứ nhất là folder reconstruction hợp lệ. Tham số thứ hai là tên motion library dưới data/motion_lib/. Nếu đặt output là benchmark_v3_0203, stage đầu tiên sẽ ghi vào:
data/motion_lib/benchmark_v3_0203/
robot/
objects/
object_usd/
meta/
Sau stage process, GRAIL tạo thêm một bản đã có hand action:
data/motion_lib/benchmark_v3_0203_ha/
robot/
objects/
meta/
Nếu dataset có nhiều object USD, stage BPS tạo:
data/motion_lib/benchmark_v3_0203/
bps/
_basis.npy
<object_name>.npy
Bức tranh input-output
Từ góc nhìn beginner, hãy nghĩ pipeline này như một bộ chuyển đổi format có kiểm tra vật lý nhẹ. 4dhoi_recon_valid là output của reconstruction. Nó còn mang ngôn ngữ của human-object interaction: SMPL-X pose, object pose, mesh object, contact points và metadata scene. data/motion_lib/<name> là output robot-ready hơn: G1 joint trajectory, object trajectory đã đóng gói, USD asset dùng được trong Isaac Lab, và metadata mà task-general tracker cần đọc.
| Giai đoạn | Input chính | Output chính | Ý nghĩa |
|---|---|---|---|
retarget |
4dhoi_recon_valid/... |
data/motion_lib/<name>/{robot,objects,object_usd,meta} |
Chuyển pose người SMPL-X sang motion Unitree G1 và convert object mesh sang USD |
process |
data/motion_lib/<name> |
data/motion_lib/<name>_ha/{robot,objects,meta} |
Thêm hand action, table pose/size, contact đã lọc, output per-motion |
compute_bps |
data/motion_lib/<name>/object_usd/*.usd |
data/motion_lib/<name>/bps/*.npy |
Mã hóa shape object thành vector cố định cho policy observation |
Tên motion_lib rất quan trọng. Đừng đặt tên chung chung như test nếu bạn chạy nhiều batch. Nên dùng tên chứa dataset, ngày hoặc config: comasset_pickup_g1_20260607, terrain_v6_zero_wrist, benchmark_v3_0203. Khi lỗi xảy ra, bạn sẽ biết folder nào đến từ config nào.
retarget_pipeline.sh thật sự làm gì?
Script retarget_pipeline.sh là wrapper ngắn nhưng hữu ích. Nó nhận:
bash grail/retargeting/scripts/retarget_pipeline.sh \
<DATA_DIR> \
<OUTPUT_FOLDER> \
[extra_retarget_args...]
Trong đó:
DATA_DIR: folder chứa reconstruction đã qua filter, ví dụdata/genhoi/.../generation/4dhoi_recon_valid/Hunyuan.OUTPUT_FOLDER: tên thư mục dướidata/motion_lib/, ví dụbenchmark_v3_0203.- Các argument còn lại thường được chuyển cho stage retarget.
Có một ngoại lệ đáng nhớ: --treat_hands_equally hoặc --treat-hands-equally được wrapper tách riêng và chuyển sang stage process, vì đây là flag xử lý hand action chứ không phải flag IK retargeting. Các flag khác, ví dụ --zero_out_wrist, đi vào grail.retargeting.retarget.
Flow rút gọn:
# 1. SMPL-X -> Unitree G1
bash grail/retargeting/scripts/retarget.sh \
"${DATA_DIR}" "${OUTPUT_FOLDER}" "${RETARGET_ARGS[@]}"
# 2. Hand actions + table geometry
bash grail/retargeting/scripts/process.sh \
"${OUTPUT_FOLDER}" "${PROCESS_ARGS[@]}"
# 3. BPS nếu có nhiều object USD
USD_COUNT=$(find "data/motion_lib/${OUTPUT_FOLDER}/object_usd" -name "*.usd" | wc -l)
if [[ "${USD_COUNT}" -gt 1 ]]; then
bash grail/retargeting/scripts/compute_bps.sh "${OUTPUT_FOLDER}"
fi
Điều kiện USD_COUNT > 1 có chủ ý. Với dataset một object duy nhất, BPS không thêm nhiều tín hiệu phân biệt object vì shape không thay đổi giữa motions. Với dataset nhiều object như ComAsset hoặc RoboCasa-style data, policy cần biết nó đang pick object dạng chai, hộp, drill hay vật khác; BPS giúp đưa shape vào observation bằng vector cố định.
Stage 1: grail.retargeting.retarget
Stage đầu tiên được gọi qua retarget.sh:
python -m grail.retargeting.retarget \
--data_dir "${DATA_DIR}" \
--all \
--robot unitree_g1 \
--output_dir "data/motion_lib/${OUTPUT_FOLDER}" \
--no_viewer
Nó đọc SMPL-X human pose và object trajectory từ output grail.pipelines.recon_4dhoi, rồi tạo motion library cho G1. Theo docstring của retarget.py, pipeline này có ba việc chính.
Thứ nhất, nó có thể bake tỷ lệ cơ thể G1 vào SMPL-X thông qua G1ProportionSMPLX. Đây là bước giúp giảm mismatch giữa người trong video và robot thật. Nếu một clip dùng human body quá cao, quá dài tay, hoặc tỷ lệ không giống G1, IK solver phải "đuổi theo" target không khả thi. GRAIL đã thiết kế các bước trước để character gần tỷ lệ G1 hơn, nên retargeting có đầu vào dễ chịu hơn so với video in-the-wild.
Thứ hai, nó gọi GMR thông qua grail.adapters.gmr. GMR là kinematics-based retargeting: lấy motion ở source embodiment, giải inverse kinematics trên target robot, rồi làm mượt theo thời gian để motion không giật. Với G1, target không chỉ là vị trí bàn tay. Retargeting phải giữ pelvis/root, chân, đầu gối, cổ chân, torso và tay ở quan hệ hợp lý để motion còn có thể được tracker bám trong simulation.
Thứ ba, nó convert object mesh sang USD và ghép object trajectory. Đây là phần dễ bị bỏ qua. Nếu chỉ có robot joint trajectory mà không có object USD, policy không biết object hình gì, ở đâu, và tương tác với gì. Output object_usd/ là asset sẵn sàng cho Isaac Lab; output objects/ giữ object motion 6-DoF theo thời gian.
Sau stage này, mỗi motion thường có ba loại pkl hoặc asset tương ứng:
data/motion_lib/<name>/
robot/<motion_id>.pkl # G1 root, pose_aa, dof, root_rot, smpl_joints, fps
objects/<motion_id>.pkl # object root_pos, root_quat, contact points, fps
meta/<motion_id>.pkl # object_name, table info nếu có
object_usd/<object>.usd # mesh object đã convert cho simulator
Bạn nên kiểm tra số lượng file trước khi đi tiếp:
find data/motion_lib/benchmark_v3_0203/robot -name "*.pkl" | wc -l
find data/motion_lib/benchmark_v3_0203/objects -name "*.pkl" | wc -l
find data/motion_lib/benchmark_v3_0203/meta -name "*.pkl" | wc -l
find data/motion_lib/benchmark_v3_0203/object_usd -name "*.usd" | wc -l
Nếu robot/ có ít file hơn kỳ vọng, lỗi nằm ở retargeting hoặc input reconstruction. Nếu object_usd/ rỗng, stage convert mesh không thành công hoặc input không có mesh object đúng chỗ.
--zero_out_wrist cho terrain và sitting
Không phải mọi dataset đều cần hand IK. Với pickup hoặc tabletop manipulation, wrist và hand contact là tín hiệu task chính. Nhưng với stairs, curb, slope và nhiều clip sitting, object/scene là môi trường lớn; tay không điều khiển object cầm nắm. Tài liệu GRAIL hướng dẫn dùng --zero_out_wrist cho terrain/sitting để skip hand IK:
bash grail/retargeting/scripts/retarget.sh \
data/genhoi/results_terrain_v6/generation/4dhoi_recon_valid/Hunyuan \
terrain_v6 \
--zero_out_wrist
Với terrain, bạn cũng thường bỏ qua process.sh, vì không cần sinh hand_action_left/right. Policy locomotion quan tâm root, chân, terrain geometry và contact với mặt đất hơn là open/close hand. Nếu vẫn chạy process.sh, nó có thể tìm lift frame của object; terrain không "lift" khỏi vị trí ban đầu, nên --skip_no_lift sẽ loại motion hoặc làm bạn debug sai hướng.
Một quy tắc thực dụng:
| Dataset | Có cần wrist/hand action? | Nên chạy gì? |
|---|---|---|
| Pick up object từ bàn | Có | retarget_pipeline.sh đầy đủ |
| Pick up object từ ground | Có | retarget_pipeline.sh đầy đủ, kiểm tra lift threshold |
| Carry/push object động | Có, nhưng có thể cần config riêng | Retarget + process, xem contact kỹ |
| Stairs/curb/slope | Không | retarget.sh --zero_out_wrist, bỏ process.sh |
| Sitting trên ghế đứng yên | Thường không | retarget.sh --zero_out_wrist, cân nhắc bỏ process.sh |
Stage 2: grail.retargeting.process
Stage process biến motion đã retarget thành bản có hand action và table geometry sạch hơn:
python -m grail.retargeting.process \
--input data/motion_lib/benchmark_v3_0203 \
--output data/motion_lib/benchmark_v3_0203_ha \
--meta_pkl data/g1_smplx/g1_skeleton_meta.pkl \
--include_contact_points \
--grasp_from_lift \
--lift_threshold 0.02 \
--grasp_anticipation_frames 10 \
--skip_no_lift \
--per_object
Tên _ha có thể hiểu là "hand action". Trong robot/<motion_id>.pkl, stage này thêm:
hand_action_left # shape (T,), thường -1.0 là open, 1.0 là closed
hand_action_right # shape (T,), thường -1.0 là open, 1.0 là closed
Với default legacy path, pipeline giả định pickup chủ yếu bằng tay phải. Nó zero out left arm bằng một pose mặc định, giữ hand_action_left mở, rồi đóng hand_action_right từ thời điểm grasp. Nếu bạn thêm --treat_hands_equally, pipeline giữ cả hai tay và suy ra hand action trái/phải đối xứng từ contact của từng tay. Flag này hữu ích khi video có hai tay cùng tham gia, hoặc khi data generation không đảm bảo object luôn được nhấc bằng tay phải.
So sánh nhanh:
| Mode | Left arm | hand_action_left |
hand_action_right |
Khi dùng |
|---|---|---|---|---|
| Mặc định | Bị đưa về pose mặc định | Mở | Đóng theo lift/contact | Dataset pickup tay phải, legacy path |
--treat_hands_equally |
Được giữ lại | Suy ra từ contact tay trái | Suy ra từ contact tay phải | Bimanual, không chắc tay nào grasp, muốn giữ cả hai arm |
Stage process cũng xử lý table geometry. Nếu meta/<motion_id>.pkl có table_pos và table_quat, output _ha/meta/ sẽ giữ thêm table_pos, table_quat, table_size. Default --table_size trong parser là [1.5, 0.7, 0.04] mét. Code còn có logic điều chỉnh table để tránh leg intersection trong một số trường hợp: nó dùng forward kinematics từ skeleton metadata, tìm đoạn lower-body giao với mặt phẳng bàn, rồi dịch table theo trục y nếu cần. Với beginner, bạn không cần chỉnh phần này ngay; chỉ cần biết table không phải decoration. Nó là geometry dùng cho task pickup-from-table, nên sai table pose sẽ làm robot học grasp trên một mặt bàn không khớp object.
--lift_threshold: khi nào object được xem là đã nhấc?
Flag --lift_threshold mặc định trong wrapper là 0.02, tức 2 cm. Khi bật --grasp_from_lift, process.py đọc object_motion[f]["root_pos"][:, 0, 2], lấy z ban đầu, rồi tìm frame đầu tiên có:
obj_z > initial_z + lift_threshold
Frame đó là lift_frame. Nếu không có frame nào vượt ngưỡng và --skip_no_lift đang bật, motion bị skip. Đây là lý do log có thể báo "object never lifted above 0.02m".
Ngưỡng 2 cm là lựa chọn hợp lý cho pickup: nó tránh việc noise rất nhỏ ở object z bị hiểu nhầm là lift thật. Nhưng nó không phải hằng số cho mọi asset. Nếu object rất nhỏ, reconstruction hơi thấp, hoặc motion chỉ lăn/trượt chứ không nhấc lên, bạn cần hiểu task trước khi giảm threshold.
| Triệu chứng | Nguyên nhân có thể | Cách xử lý |
|---|---|---|
| Nhiều motion bị skip "no lift" | Object không được nhấc quá 2 cm hoặc reconstruction z bị thấp | Xem video/recon, thử --lift_threshold 0.01 nếu task đúng là pickup nhẹ |
| Hand đóng quá muộn | Lift frame xảy ra sau contact thật | Tăng --grasp_anticipation_frames hoặc dùng contact trực tiếp với --treat_hands_equally |
| Hand đóng quá sớm | Contact/lift bị detect sớm do noise | Giữ threshold 0.02, giảm anticipation, kiểm tra contact points |
| Terrain bị skip | Terrain không phải object được lift | Không chạy process.sh; dùng --zero_out_wrist ở retarget |
Đừng giảm --lift_threshold chỉ để tăng số lượng file output. Nếu object chưa bao giờ rời bàn, motion đó có thể không phải pickup thành công. Giữ dữ liệu lỗi sẽ làm policy học sai: tay đóng nhưng object không đi theo, hoặc object tự trượt mà không có grasp đáng tin.
--grasp_anticipation_frames: đóng tay trước lift
Robot không thể chờ object đã bay lên rồi mới đóng tay. Trong dữ liệu pickup, tay phải đóng trước hoặc gần thời điểm lift. --grasp_anticipation_frames giải quyết timing này. Wrapper process.sh lấy giá trị từ biến môi trường GRAIL_GRASP_ANTICIPATION_FRAMES, mặc định là 10.
Logic đơn giản là:
grasp_start_frame = lift_frame - grasp_anticipation_frames
hand_action_right[grasp_start_frame:] = 1.0
Nếu dùng --treat_hands_equally, mỗi tay có action riêng dựa trên contact của tay đó trước lift. Nếu một tay không có contact ở hoặc trước lift, tay đó có thể giữ open.
Ví dụ ở 50 FPS:
grasp_anticipation_frames |
Tương đương thời gian | Tác động |
|---|---|---|
| 0 | 0 ms | Tay đóng đúng tại lift frame, thường quá muộn |
| 5 | 100 ms | Đóng hơi sớm, phù hợp motion ngắn |
| 10 | 200 ms | Default cân bằng cho pickup thường |
| 20 | 400 ms | Đóng sớm hơn, có thể hợp object lớn nhưng dễ tạo grasp giả |
Với beginner, hãy bắt đầu bằng default 10. Chỉ chỉnh khi bạn xem visualization hoặc replay và thấy hand command lệch rõ so với contact.
Stage 3: grail.retargeting.compute_bps
BPS là viết tắt của Basis Point Set. Ý tưởng chung từ paper của Prokudin và cộng sự: thay vì đưa toàn bộ point cloud/mesh object vào policy, ta chọn một tập basis point cố định và mã hóa object bằng khoảng cách từ mỗi basis point đến bề mặt object gần nhất. GRAIL dùng phiên bản rất nhỏ: mặc định --num_basis 10, sample surface object, normalize quanh centroid và scale về unit bounding sphere, rồi lưu vector khoảng cách 10-D.
Lệnh wrapper:
python -m grail.retargeting.compute_bps \
--object_usd_dir data/motion_lib/benchmark_v3_0203/object_usd \
--output_dir data/motion_lib/benchmark_v3_0203/bps
Output:
data/motion_lib/benchmark_v3_0203/bps/
_basis.npy
cordless_drill.npy
mug.npy
bottle.npy
...
_basis.npy giúp tái lập cùng hệ basis point. Mỗi <object>.npy là embedding shape của object tương ứng. Trong training, policy có thể nhận object pose, robot state và BPS embedding để phân biệt vật nhỏ/dài/tròn/hộp mà không cần parse USD mesh trong vòng lặp RL.
BPS không thay thế collision geometry. USD vẫn cần cho simulation và contact. BPS là observation compact cho network. Nếu bạn chỉ có một object duy nhất trong dataset, wrapper skip stage này vì shape embedding không giúp phân biệt object giữa motions.
Debug theo folder
Khi pipeline lỗi, đừng đọc log từ trên xuống trong hoảng loạn. Hãy debug theo folder output:
| Folder | Nếu thiếu | Nên kiểm tra |
|---|---|---|
robot/ |
Retarget SMPL-X sang G1 thất bại | Input 4dhoi_recon_valid, GMR install, --robot unitree_g1, viewer/display |
objects/ |
Object trajectory không được đóng gói | Reconstruction có object pose không, file input có đúng category không |
object_usd/ |
Mesh conversion thất bại | Mesh source, IsaacLab/USD dependencies, quyền ghi output |
meta/ |
Thiếu metadata task | Input meta từ reconstruction, table/object name |
<name>_ha/robot/ |
Process skip motion | --lift_threshold, --skip_no_lift, contact points |
bps/ |
Không có BPS | Có nhiều hơn một USD không, pxr/usd-core import được không |
Một smoke test tốt là chọn 3-5 motions, chạy end-to-end, rồi mở pkl bằng joblib để kiểm tra key:
import joblib
motion = joblib.load("data/motion_lib/benchmark_v3_0203_ha/robot/<motion_id>.pkl")
key = list(motion.keys())[0]
print(motion[key].keys())
obj = joblib.load("data/motion_lib/benchmark_v3_0203_ha/objects/<motion_id>.pkl")
print(obj[key].keys())
Bạn muốn thấy robot có dof, pose_aa, root_trans_offset, root_rot, fps, và nếu là manipulation thì có hand_action_left/right. Object nên có root_pos, root_quat, fps, và nếu bật contact thì có contact points đã lọc.
Kết luận
retarget_pipeline.sh là điểm chuyển đổi quan trọng nhất trước training. Nó lấy output 4dhoi_recon_valid của reconstruction và tạo motion library theo layout mà tracker/policy hiểu được: robot, objects, object_usd, meta, và với multi-object dataset là bps. Stage retarget giải bài toán morphology SMPL-X sang Unitree G1. Stage process biến contact/lift thành hand_action_left/right, thêm table geometry và loại motion không có lift thật. Stage compute_bps biến object shape thành embedding cố định cho observation.
Quyết định quan trọng nhất là phân loại dataset trước khi chạy. Với pickup/manipulation, chạy full pipeline và kiểm tra --lift_threshold, --grasp_anticipation_frames, --treat_hands_equally. Với terrain/sitting, dùng --zero_out_wrist ở retarget và thường bỏ qua hand-action processing. Bước tiếp theo trong bài 6 là dùng motion library này để train tracker/policy và export dữ liệu phục vụ sim-to-real.