mirror of
https://github.com/huggingface/lerobot.git
synced 2026-05-16 09:09:48 +00:00
8f06c02c17
remove isaaclab, add test scripts added simulator to the hub remove mujoco env
251 lines
9.0 KiB
Python
251 lines
9.0 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
ZMQ Camera Server - Publishes camera frames via ZeroMQ
|
|
|
|
This script captures frames from a camera (or generates test patterns) and
|
|
publishes them as JPEG-encoded images over a ZMQ PUB socket.
|
|
|
|
Usage:
|
|
# Using a webcam (default camera index 0)
|
|
python test_zmq_camera_server.py --port 5554
|
|
|
|
# Using a specific camera
|
|
python test_zmq_camera_server.py --camera 1 --port 5555
|
|
|
|
# Using test pattern (no camera required)
|
|
python test_zmq_camera_server.py --test-pattern --port 5554
|
|
|
|
# Specify IP address and resolution
|
|
python test_zmq_camera_server.py --address 192.168.1.100 --width 1280 --height 720
|
|
"""
|
|
|
|
import argparse
|
|
import time
|
|
from datetime import datetime
|
|
|
|
import cv2
|
|
import numpy as np
|
|
import zmq
|
|
|
|
|
|
def create_test_pattern(width: int = 640, height: int = 480, frame_count: int = 0) -> np.ndarray:
|
|
"""
|
|
Creates an animated test pattern image.
|
|
|
|
Args:
|
|
width: Image width
|
|
height: Image height
|
|
frame_count: Current frame number for animation
|
|
|
|
Returns:
|
|
BGR image array
|
|
"""
|
|
# Create a colorful test pattern
|
|
img = np.zeros((height, width, 3), dtype=np.uint8)
|
|
|
|
# Animated gradient background
|
|
for i in range(height):
|
|
color_shift = int(((i + frame_count) % height) / height * 255)
|
|
img[i, :] = [color_shift, 128, 255 - color_shift]
|
|
|
|
# Moving circle
|
|
center_x = int(width // 2 + 200 * np.sin(frame_count * 0.05))
|
|
center_y = int(height // 2 + 100 * np.cos(frame_count * 0.05))
|
|
cv2.circle(img, (center_x, center_y), 50, (0, 255, 0), -1)
|
|
|
|
# Add text with timestamp
|
|
text = f"ZMQ Test Pattern - Frame {frame_count}"
|
|
cv2.putText(img, text, (20, 40), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
|
|
|
|
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
|
|
cv2.putText(img, timestamp, (20, height - 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
|
|
|
|
return img
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="ZMQ Camera Server - Publishes camera frames via ZeroMQ")
|
|
parser.add_argument("--address", type=str, default="*",
|
|
help="IP address to bind to (* for all interfaces)")
|
|
parser.add_argument("--port", type=int, default=5550,
|
|
help="Port number to publish on (default: 5554)")
|
|
parser.add_argument("--camera", type=int, default=0,
|
|
help="Camera device index (default: 0)")
|
|
parser.add_argument("--test-pattern", action="store_true",
|
|
help="Use animated test pattern instead of camera")
|
|
parser.add_argument("--width", type=int, default=640,
|
|
help="Frame width (default: 640)")
|
|
parser.add_argument("--height", type=int, default=480,
|
|
help="Frame height (default: 480)")
|
|
parser.add_argument("--fps", type=int, default=30,
|
|
help="Target frames per second (default: 30)")
|
|
parser.add_argument("--quality", type=int, default=80,
|
|
help="JPEG quality 1-100 (default: 80)")
|
|
parser.add_argument("--display", action="store_true",
|
|
help="Display the video feed locally")
|
|
|
|
args = parser.parse_args()
|
|
|
|
# Initialize ZMQ
|
|
print(f"Initializing ZMQ publisher on tcp://{args.address}:{args.port}")
|
|
context = zmq.Context()
|
|
socket = context.socket(zmq.PUB)
|
|
socket.bind(f"tcp://{args.address}:{args.port}")
|
|
|
|
# Give ZMQ time to establish connections
|
|
print("Waiting for subscribers to connect...")
|
|
time.sleep(1)
|
|
|
|
# Initialize camera or test pattern
|
|
cap = None
|
|
if not args.test_pattern:
|
|
print(f"Opening camera {args.camera}...")
|
|
cap = cv2.VideoCapture(args.camera)
|
|
|
|
if not cap.isOpened():
|
|
print(f"ERROR: Could not open camera {args.camera}")
|
|
print("Falling back to test pattern mode")
|
|
args.test_pattern = True
|
|
else:
|
|
# Set camera properties
|
|
cap.set(cv2.CAP_PROP_FRAME_WIDTH, args.width)
|
|
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, args.height)
|
|
cap.set(cv2.CAP_PROP_FPS, args.fps)
|
|
|
|
# Read actual properties (camera may not support requested values)
|
|
actual_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
|
actual_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
|
actual_fps = cap.get(cv2.CAP_PROP_FPS)
|
|
|
|
print(f"Camera opened successfully")
|
|
print(f" Resolution: {actual_width}x{actual_height}")
|
|
print(f" FPS: {actual_fps}")
|
|
|
|
if args.test_pattern:
|
|
print(f"Using test pattern mode")
|
|
print(f" Resolution: {args.width}x{args.height}")
|
|
print(f" FPS: {args.fps}")
|
|
|
|
print("\n" + "="*60)
|
|
print("SERVER RUNNING - Press Ctrl+C to stop")
|
|
print("="*60)
|
|
print(f"Publishing JPEG frames on tcp://{args.address}:{args.port}")
|
|
if args.address == "*":
|
|
print("\nClients can connect using your machine's IP address")
|
|
print("Run 'hostname -I' or 'ipconfig' to find your IP")
|
|
print("\nPress Ctrl+C to stop the server")
|
|
print("="*60 + "\n")
|
|
|
|
frame_count = 0
|
|
start_time = time.time()
|
|
last_print_time = start_time
|
|
frame_times = []
|
|
|
|
# JPEG encoding parameters
|
|
encode_params = [int(cv2.IMWRITE_JPEG_QUALITY), args.quality]
|
|
|
|
try:
|
|
while True:
|
|
loop_start = time.time()
|
|
|
|
# Capture or generate frame
|
|
if args.test_pattern:
|
|
frame = create_test_pattern(args.width, args.height, frame_count)
|
|
ret = True
|
|
else:
|
|
ret, frame = cap.read()
|
|
|
|
if not ret:
|
|
print("ERROR: Failed to capture frame")
|
|
break
|
|
|
|
# Resize if needed
|
|
if frame.shape[1] != args.width or frame.shape[0] != args.height:
|
|
frame = cv2.resize(frame, (args.width, args.height))
|
|
|
|
# Encode frame as JPEG
|
|
encode_start = time.time()
|
|
_, buffer = cv2.imencode('.jpg', frame, encode_params)
|
|
encode_time = (time.time() - encode_start) * 1000
|
|
|
|
# Publish frame
|
|
publish_start = time.time()
|
|
socket.send(buffer.tobytes())
|
|
publish_time = (time.time() - publish_start) * 1000
|
|
|
|
frame_count += 1
|
|
|
|
# Display locally if requested
|
|
if args.display:
|
|
# Add stats overlay
|
|
display_frame = frame.copy()
|
|
stats_text = f"Frame: {frame_count} | FPS: {len(frame_times):.1f}"
|
|
cv2.putText(display_frame, stats_text, (10, 30),
|
|
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
|
|
cv2.imshow('ZMQ Camera Server', display_frame)
|
|
|
|
# Exit on 'q' key
|
|
if cv2.waitKey(1) & 0xFF == ord('q'):
|
|
print("\nStopping server (user pressed 'q')...")
|
|
break
|
|
|
|
# Calculate timing statistics
|
|
loop_time = time.time() - loop_start
|
|
frame_times.append(loop_time)
|
|
|
|
# Keep only last second of frame times for FPS calculation
|
|
if len(frame_times) > args.fps * 2:
|
|
frame_times = frame_times[-args.fps:]
|
|
|
|
# Print statistics every 2 seconds
|
|
current_time = time.time()
|
|
if current_time - last_print_time >= 2.0:
|
|
elapsed = current_time - start_time
|
|
actual_fps = len(frame_times) / sum(frame_times) if frame_times else 0
|
|
avg_loop_time = np.mean(frame_times) * 1000 if frame_times else 0
|
|
|
|
print(f"[{elapsed:6.1f}s] Frames: {frame_count:5d} | "
|
|
f"FPS: {actual_fps:5.1f} | "
|
|
f"Loop: {avg_loop_time:5.1f}ms | "
|
|
f"Encode: {encode_time:4.1f}ms | "
|
|
f"Publish: {publish_time:4.1f}ms")
|
|
|
|
last_print_time = current_time
|
|
|
|
# Control frame rate
|
|
target_loop_time = 1.0 / args.fps
|
|
sleep_time = target_loop_time - loop_time
|
|
if sleep_time > 0:
|
|
time.sleep(sleep_time)
|
|
|
|
except KeyboardInterrupt:
|
|
print("\n\nStopping server (Ctrl+C pressed)...")
|
|
|
|
finally:
|
|
# Cleanup
|
|
if cap is not None:
|
|
cap.release()
|
|
|
|
if args.display:
|
|
cv2.destroyAllWindows()
|
|
|
|
socket.close()
|
|
context.term()
|
|
|
|
# Print final statistics
|
|
elapsed = time.time() - start_time
|
|
avg_fps = frame_count / elapsed if elapsed > 0 else 0
|
|
|
|
print("\n" + "="*60)
|
|
print("SERVER STOPPED")
|
|
print("="*60)
|
|
print(f"Total frames published: {frame_count}")
|
|
print(f"Total time: {elapsed:.2f}s")
|
|
print(f"Average FPS: {avg_fps:.2f}")
|
|
print("="*60)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
|