Skip to content

Commit 01ed5bc

Browse files
committed
[update] update realtime retargeting example
1 parent 1c27155 commit 01ed5bc

File tree

5 files changed

+94
-76
lines changed

5 files changed

+94
-76
lines changed

example/vector_retargeting/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
# Examples
22
/example/vector_retargeting/data/
33
!example/vector_retargeting/data/human_hand_video.mp4
4+
!example/vector_retargeting/data/realtime_example.mp4

example/vector_retargeting/README.md

+25-3
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,34 @@ python3 render_robot_hand.py \
3636

3737
This command uses the data saved from the previous step to create a rendered video.
3838

39-
### Record a video of your own hand
39+
### Capture a Video Using Your Webcam
40+
41+
*The following instructions assume that your computer has a webcam connected.*
4042

4143
```bash
4244
python3 capture_webcam.py --video-path example/vector_retargeting/data/my_human_hand_video.mp4
45+
```
46+
47+
This command enables you to use your webcam to record a video saved in MP4 format. To stop recording, press `q` on your
48+
keyboard.
49+
50+
### Real-time Visualization of Hand Retargeting via Webcam
4351

52+
```bash
53+
pip install loguru
54+
python3 show_realtime_retargeting.py \
55+
--robot-name allegro \
56+
--retargeting-type dexpilot \
57+
--hand-type right
4458
```
4559

46-
This command will access your webcam (which should be connected to your computer) and record the video stream in mp4
47-
format. To end video recording, press `q` on the keyboard.
60+
This process integrates the tasks described above. It involves capturing your hand movements through the webcam and
61+
instantaneously displaying the retargeting outcomes in the SAPIEN viewer. Special thanks
62+
to [@xbkaishui](https://github.com/xbkaishui) for contributing the initial pull request.
63+
64+
![realtime_example](data/realtime_example.mp4)
65+
66+
67+
68+
69+
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,37 @@
1-
from pathlib import Path
21
import multiprocessing
32
import time
3+
from pathlib import Path
4+
from queue import Empty
5+
from typing import Optional
6+
47
import cv2
5-
from loguru import logger
68
import numpy as np
79
import sapien
10+
import tyro
11+
from loguru import logger
812
from sapien.asset import create_dome_envmap
913
from sapien.utils import Viewer
1014

1115
from dex_retargeting.constants import RobotName, RetargetingType, HandType, get_default_config_path
1216
from dex_retargeting.retargeting_config import RetargetingConfig
13-
from dex_retargeting.seq_retarget import SeqRetargeting
1417
from single_hand_detector import SingleHandDetector
1518

1619

17-
def start_retargeting(queue: multiprocessing.Queue, robot_dir:str, config_path: str, robot_name:str):
20+
def start_retargeting(queue: multiprocessing.Queue, robot_dir: str, config_path: str):
1821
RetargetingConfig.set_default_urdf_dir(str(robot_dir))
1922
logger.info(f"Start retargeting with config {config_path}")
2023
retargeting = RetargetingConfig.load_from_file(config_path).build()
21-
detector = SingleHandDetector(hand_type="Right", selfie=False)
22-
23-
# init show ui
24-
meta_data = {
25-
"dof": len(retargeting.optimizer.robot.dof_joint_names),
26-
"joint_names":retargeting.optimizer.robot.dof_joint_names,
27-
}
28-
29-
headless = False
30-
if not headless:
31-
sapien.render.set_viewer_shader_dir("default")
32-
sapien.render.set_camera_shader_dir("default")
33-
else:
34-
sapien.render.set_viewer_shader_dir("rt")
35-
sapien.render.set_camera_shader_dir("rt")
36-
sapien.render.set_ray_tracing_samples_per_pixel(16)
37-
sapien.render.set_ray_tracing_path_depth(8)
38-
sapien.render.set_ray_tracing_denoiser("oidn")
39-
24+
25+
hand_type = "Right" if "right" in config_path.lower() else "Left"
26+
detector = SingleHandDetector(hand_type=hand_type, selfie=False)
27+
28+
sapien.render.set_viewer_shader_dir("default")
29+
sapien.render.set_camera_shader_dir("default")
30+
4031
config = RetargetingConfig.load_from_file(config_path)
4132

4233
# Setup
4334
scene = sapien.Scene()
44-
45-
# Ground
4635
render_mat = sapien.render.RenderMaterial()
4736
render_mat.base_color = [0.06, 0.08, 0.12, 1]
4837
render_mat.metallic = 0.0
@@ -61,15 +50,14 @@ def start_retargeting(queue: multiprocessing.Queue, robot_dir:str, config_path:
6150
cam = scene.add_camera(name="Cheese!", width=600, height=600, fovy=1, near=0.1, far=10)
6251
cam.set_local_pose(sapien.Pose([0.50, 0, 0.0], [0, 0, 0, -1]))
6352

64-
6553
viewer = Viewer()
6654
viewer.set_scene(scene)
6755
viewer.control_window.show_origin_frame = False
6856
viewer.control_window.move_speed = 0.01
6957
viewer.control_window.toggle_camera_lines(False)
7058
viewer.set_camera_pose(cam.get_local_pose())
71-
72-
# Load robot and set it to a good pose to take picture
59+
60+
# Load robot and set it to a good pose to take picture
7361
loader = scene.create_urdf_loader()
7462
filepath = Path(config.urdf_path)
7563
robot_name = filepath.stem
@@ -89,14 +77,13 @@ def start_retargeting(queue: multiprocessing.Queue, robot_dir:str, config_path:
8977
elif "svh" in robot_name:
9078
loader.scale = 1.5
9179

92-
9380
if "glb" not in robot_name:
9481
filepath = str(filepath).replace(".urdf", "_glb.urdf")
9582
else:
9683
filepath = str(filepath)
97-
84+
9885
robot = loader.load(filepath)
99-
86+
10087
if "ability" in robot_name:
10188
robot.set_pose(sapien.Pose([0, 0, -0.15]))
10289
elif "shadow" in robot_name:
@@ -111,70 +98,79 @@ def start_retargeting(queue: multiprocessing.Queue, robot_dir:str, config_path:
11198
robot.set_pose(sapien.Pose([0, 0, -0.15]))
11299
elif "svh" in robot_name:
113100
robot.set_pose(sapien.Pose([0, 0, -0.13]))
114-
101+
115102
# Different robot loader may have different orders for joints
116103
sapien_joint_names = [joint.get_name() for joint in robot.get_active_joints()]
117-
retargeting_joint_names = meta_data["joint_names"]
104+
retargeting_joint_names = retargeting.joint_names
118105
retargeting_to_sapien = np.array([retargeting_joint_names.index(name) for name in sapien_joint_names]).astype(int)
119-
120-
for _ in range(2):
121-
viewer.render()
122106

123107
while True:
124-
rgb = queue.get()
125-
if rgb is None:
126-
time.sleep(.1)
127-
continue
108+
try:
109+
rgb = queue.get(timeout=5)
110+
except Empty:
111+
logger.error(f"Fail to fetch image from camera in 5 secs. Please check your web camera device.")
112+
return
128113

129114
_, joint_pos, _, _ = detector.detect(rgb)
130-
logger.info("join pos {}", joint_pos)
131115
if joint_pos is None:
132-
continue
133-
134-
retargeting_type = retargeting.optimizer.retargeting_type
135-
indices = retargeting.optimizer.target_link_human_indices
136-
if retargeting_type == "POSITION":
137-
indices = indices
138-
ref_value = joint_pos[indices, :]
116+
logger.warning(f"{hand_type} hand is not detected.")
139117
else:
140-
origin_indices = indices[0, :]
141-
task_indices = indices[1, :]
142-
ref_value = joint_pos[task_indices, :] - joint_pos[origin_indices, :]
143-
qpos = retargeting.retarget(ref_value)
144-
logger.info(f"qpos {qpos}")
145-
robot.set_qpos((qpos)[retargeting_to_sapien])
146-
118+
retargeting_type = retargeting.optimizer.retargeting_type
119+
indices = retargeting.optimizer.target_link_human_indices
120+
if retargeting_type == "POSITION":
121+
indices = indices
122+
ref_value = joint_pos[indices, :]
123+
else:
124+
origin_indices = indices[0, :]
125+
task_indices = indices[1, :]
126+
ref_value = joint_pos[task_indices, :] - joint_pos[origin_indices, :]
127+
qpos = retargeting.retarget(ref_value)
128+
robot.set_qpos(qpos[retargeting_to_sapien])
129+
147130
for _ in range(2):
148131
viewer.render()
149-
150132

151-
def produce_frame(queue: multiprocessing.Queue):
152-
cap = cv2.VideoCapture(0)
133+
134+
def produce_frame(queue: multiprocessing.Queue, camera_path: Optional[str] = None):
135+
if camera_path is None:
136+
cap = cv2.VideoCapture(4)
137+
else:
138+
cap = cv2.VideoCapture(camera_path)
139+
153140
while cap.isOpened():
154141
success, image = cap.read()
155142
if not success:
156143
continue
157144
frame = image
158145
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
159146
queue.put(image)
160-
time.sleep(.2)
161-
cv2.imshow('demo', frame)
162-
163-
if cv2.waitKey(1) & 0xFF == ord('q'):
164-
break
147+
time.sleep(1 / 30.0)
148+
cv2.imshow("demo", frame)
165149

150+
if cv2.waitKey(1) & 0xFF == ord("q"):
151+
break
166152

167-
def main():
168-
robot_name: RobotName = RobotName.allegro
169-
retargeting_type: RetargetingType = RetargetingType.vector
170-
hand_type: HandType = HandType.right
171153

154+
def main(
155+
robot_name: RobotName, retargeting_type: RetargetingType, hand_type: HandType, camera_path: Optional[str] = None
156+
):
157+
"""
158+
Detects the human hand pose from a video and translates the human pose trajectory into a robot pose trajectory.
159+
160+
Args:
161+
robot_name: The identifier for the robot. This should match one of the default supported robots.
162+
retargeting_type: The type of retargeting, each type corresponds to a different retargeting algorithm.
163+
hand_type: Specifies which hand is being tracked, either left or right.
164+
Please note that retargeting is specific to the same type of hand: a left robot hand can only be retargeted
165+
to another left robot hand, and the same applies for the right hand.
166+
camera_path: the device path to feed to opencv to open the web camera. It will use 0 by default.
167+
"""
172168
config_path = get_default_config_path(robot_name, retargeting_type, hand_type)
173169
robot_dir = Path(__file__).absolute().parent.parent.parent / "assets" / "robots" / "hands"
174170

175171
queue = multiprocessing.Queue(maxsize=1000)
176-
producer_process = multiprocessing.Process(target=produce_frame, args=(queue,))
177-
consumer_process = multiprocessing.Process(target=start_retargeting, args=(queue, str(robot_dir), str(config_path), robot_name))
172+
producer_process = multiprocessing.Process(target=produce_frame, args=(queue, camera_path))
173+
consumer_process = multiprocessing.Process(target=start_retargeting, args=(queue, str(robot_dir), str(config_path)))
178174

179175
producer_process.start()
180176
consumer_process.start()
@@ -186,5 +182,5 @@ def main():
186182
print("done")
187183

188184

189-
if __name__ == '__main__':
190-
main()
185+
if __name__ == "__main__":
186+
tyro.cli(main)

setup.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
"anytree",
2626
"pyyaml",
2727
"lxml",
28-
"loguru",
2928
]
3029

3130
# Check whether you have torch installed
@@ -61,7 +60,7 @@
6160
"mypy",
6261
]
6362

64-
example_requirements = ["tyro", "tqdm", "opencv-python", "mediapipe", "sapien==3.0.0b0"]
63+
example_requirements = ["tyro", "tqdm", "opencv-python", "mediapipe", "sapien==3.0.0b0", "loguru"]
6564

6665
classifiers = [
6766
"Development Status :: 3 - Alpha",

0 commit comments

Comments
 (0)