Files
lerobot/test_zmq_camera_server.py
T
Martino Russi 8f06c02c17 add robot models xml etc
remove isaaclab, add test scripts

added simulator to the hub

remove mujoco env
2025-11-21 14:13:05 +01:00

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()