Skip to content

Commit 2211209

Browse files
- Added base gym env class for the real robot environment.
- Added several wrappers around the base gym env robot class. - Including: time limit, reward classifier, crop images, preprocess observations. - Added an interactive script crop_roi.py where the user can interactively select the roi in the observation images and return the correct crop values that will improve the policy and reward classifier performance. Co-authored-by: Adil Zouitine <[email protected]>
1 parent 506821c commit 2211209

File tree

2 files changed

+528
-0
lines changed

2 files changed

+528
-0
lines changed

Diff for: lerobot/scripts/server/crop_roi.py

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import cv2
2+
3+
from lerobot.common.robot_devices.cameras.opencv import OpenCVCamera
4+
5+
6+
def select_square_roi(img):
7+
"""
8+
Allows the user to draw a square ROI on the image.
9+
10+
The user must click and drag to draw the square.
11+
- While dragging, the square is dynamically drawn.
12+
- On mouse button release, the square is fixed.
13+
- Press 'c' to confirm the selection.
14+
- Press 'r' to reset the selection.
15+
- Press ESC to cancel.
16+
17+
Returns:
18+
A tuple (top, left, height, width) representing the square ROI,
19+
or None if no valid ROI is selected.
20+
"""
21+
# Create a working copy of the image
22+
clone = img.copy()
23+
working_img = clone.copy()
24+
25+
roi = None # Will store the final ROI as (top, left, side, side)
26+
drawing = False
27+
ix, iy = -1, -1 # Initial click coordinates
28+
29+
def mouse_callback(event, x, y, flags, param):
30+
nonlocal ix, iy, drawing, roi, working_img
31+
32+
if event == cv2.EVENT_LBUTTONDOWN:
33+
# Start drawing: record starting coordinates
34+
drawing = True
35+
ix, iy = x, y
36+
37+
elif event == cv2.EVENT_MOUSEMOVE:
38+
if drawing:
39+
# Compute side length as the minimum of horizontal/vertical drags
40+
side = min(abs(x - ix), abs(y - iy))
41+
# Determine the direction to draw (in case of dragging to top/left)
42+
dx = side if x >= ix else -side
43+
dy = side if y >= iy else -side
44+
# Show a temporary image with the current square drawn
45+
temp = working_img.copy()
46+
cv2.rectangle(temp, (ix, iy), (ix + dx, iy + dy), (0, 255, 0), 2)
47+
cv2.imshow("Select ROI", temp)
48+
49+
elif event == cv2.EVENT_LBUTTONUP:
50+
# Finish drawing
51+
drawing = False
52+
side = min(abs(x - ix), abs(y - iy))
53+
dx = side if x >= ix else -side
54+
dy = side if y >= iy else -side
55+
# Normalize coordinates: (top, left) is the minimum of the two points
56+
x1 = min(ix, ix + dx)
57+
y1 = min(iy, iy + dy)
58+
roi = (y1, x1, side, side) # (top, left, height, width)
59+
# Draw the final square on the working image and display it
60+
working_img = clone.copy()
61+
cv2.rectangle(working_img, (ix, iy), (ix + dx, iy + dy), (0, 255, 0), 2)
62+
cv2.imshow("Select ROI", working_img)
63+
64+
# Create the window and set the callback
65+
cv2.namedWindow("Select ROI")
66+
cv2.setMouseCallback("Select ROI", mouse_callback)
67+
cv2.imshow("Select ROI", working_img)
68+
69+
print("Instructions for ROI selection:")
70+
print(" - Click and drag to draw a square ROI.")
71+
print(" - Press 'c' to confirm the selection.")
72+
print(" - Press 'r' to reset and draw again.")
73+
print(" - Press ESC to cancel the selection.")
74+
75+
# Wait until the user confirms with 'c', resets with 'r', or cancels with ESC
76+
while True:
77+
key = cv2.waitKey(1) & 0xFF
78+
# Confirm ROI if one has been drawn
79+
if key == ord("c") and roi is not None:
80+
break
81+
# Reset: clear the ROI and restore the original image
82+
elif key == ord("r"):
83+
working_img = clone.copy()
84+
roi = None
85+
cv2.imshow("Select ROI", working_img)
86+
# Cancel selection for this image
87+
elif key == 27: # ESC key
88+
roi = None
89+
break
90+
91+
cv2.destroyWindow("Select ROI")
92+
return roi
93+
94+
95+
def select_square_roi_for_images(images: dict) -> dict:
96+
"""
97+
For each image in the provided dictionary, open a window to allow the user
98+
to select a square ROI. Returns a dictionary mapping each key to a tuple
99+
(top, left, height, width) representing the ROI.
100+
101+
Parameters:
102+
images (dict): Dictionary where keys are identifiers and values are OpenCV images.
103+
104+
Returns:
105+
dict: Mapping of image keys to the selected square ROI.
106+
"""
107+
selected_rois = {}
108+
109+
for key, img in images.items():
110+
if img is None:
111+
print(f"Image for key '{key}' is None, skipping.")
112+
continue
113+
114+
print(f"\nSelect square ROI for image with key: '{key}'")
115+
roi = select_square_roi(img)
116+
117+
if roi is None:
118+
print(f"No valid ROI selected for '{key}'.")
119+
else:
120+
selected_rois[key] = roi
121+
print(f"ROI for '{key}': {roi}")
122+
123+
return selected_rois
124+
125+
126+
if __name__ == "__main__":
127+
# Example usage:
128+
# Replace 'image1.jpg' and 'image2.jpg' with valid paths to your image files.
129+
fps = [5, 30]
130+
cameras = [OpenCVCamera(i, fps=fps[i], width=640, height=480, mock=False) for i in range(2)]
131+
[camera.connect() for camera in cameras]
132+
133+
image_keys = ["image_" + str(i) for i in range(len(cameras))]
134+
135+
images = {image_keys[i]: cameras[i].read() for i in range(len(cameras))}
136+
137+
# Verify images loaded correctly
138+
for key, img in images.items():
139+
if img is None:
140+
raise ValueError(f"Failed to load image for key '{key}'. Check the file path.")
141+
142+
# Let the user select a square ROI for each image
143+
rois = select_square_roi_for_images(images)
144+
145+
# Print the selected square ROIs
146+
print("\nSelected Square Regions of Interest (top, left, height, width):")
147+
for key, roi in rois.items():
148+
print(f"{key}: {roi}")

0 commit comments

Comments
 (0)