Lab Exercise 1: Perception Algorithms with NVIDIA Isaac
Objective
In this lab exercise, you will learn to implement and deploy perception algorithms using NVIDIA Isaac ROS packages. You'll gain hands-on experience with accelerated perception pipelines, including object detection, depth estimation, and sensor processing, optimized for NVIDIA GPU architectures.
Learning Objectives
After completing this lab, you will be able to:
- Configure and deploy Isaac ROS perception packages
- Implement accelerated perception algorithms using GPU acceleration
- Integrate Isaac perception pipelines with ROS 2 systems
- Evaluate the performance of accelerated perception systems
- Compare Isaac-based perception with traditional approaches
Prerequisites
- Completion of ROS 2 modules (Weeks 1-3)
- Experience with Gazebo/Unity simulation (Weeks 4-6)
- Understanding of computer vision fundamentals
- Computer with NVIDIA GPU (RTX series recommended)
Equipment Required
- Computer with NVIDIA GPU (RTX 3070 or equivalent)
- Ubuntu 22.04 with ROS 2 Humble
- Isaac ROS packages installed
- CUDA toolkit compatible with GPU
- OpenCV and vision libraries
Lab Steps
Step 1: System Verification and Setup
-
Verify your NVIDIA GPU and driver installation:
nvidia-smi
# Should show your GPU and driver version
# Check CUDA installation
nvcc --version
# Should show CUDA compiler version
# Verify Isaac ROS packages
ros2 pkg list | grep isaac
# Should show Isaac ROS packages like
# - isaac_ros_pointcloud_utils
# - isaac_ros_detectnet
# - isaac_ros_image_pipeline
# - etc. -
Set up your Isaac lab workspace:
mkdir -p ~/isaac_lab_ws/src
cd ~/isaac_lab_ws -
Create a package for Isaac perception nodes:
cd ~/isaac_lab_ws/src
ros2 pkg create --build-type ament_python isaac_perception_examples --dependencies rclpy sensor_msgs cv_bridge std_msgs geometry_msgs
Step 2: Install Isaac ROS Packages
-
Install Isaac ROS packages (if not already installed):
sudo apt update
sudo apt install ros-humble-isaac-ros-suite
# Or install specific packages if the suite is too large:
sudo apt install ros-humble-isaac-ros-detectnet ros-humble-isaac-ros-image-pipeline -
Verify installation:
# Check that Isaac packages are accessible
ros2 run | grep isaac
Step 3: Create Isaac Perception Node
-
Create a perception pipeline node (
isaac_perception_examples/isaac_perception_examples/object_detection_node.py):#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Image, CameraInfo
from cv_bridge import CvBridge
import numpy as np
import cv2
from vision_msgs.msg import Detection2D, Detection2DArray, ObjectHypothesisWithScore
from geometry_msgs.msg import Point
import os
import sys
from typing import List, Tuple, Optional
class IsaacPerceptionNode(Node):
"""
Example perception node that demonstrates Isaac-like perception
pipeline concepts. In a real Isaac implementation, this would
use Isaac ROS packages for GPU-accelerated inference.
"""
def __init__(self):
super().__init__('isaac_perception_node')
# Initialize CvBridge for image processing
self.bridge = CvBridge()
# Create subscriptions
self.image_subscriber = self.create_subscription(
Image,
'/camera/image_raw',
self.image_callback,
10
)
self.camera_info_subscriber = self.create_subscription(
CameraInfo,
'/camera/camera_info',
self.camera_info_callback,
10
)
# Create publishers
self.detection_publisher = self.create_publisher(
Detection2DArray,
'/isaac_detections',
10
)
self.visualization_publisher = self.create_publisher(
Image,
'/isaac_visualization',
10
)
# State variables
self.latest_camera_info = None
self.detection_model = self.initialize_detection_model()
self.get_logger().info("Isaac Perception Node Initialized")
def initialize_detection_model(self):
"""
Initialize object detection model (in a real Isaac setup,
this would use TensorRT or similar accelerated inference)
"""
# For this example, we'll use OpenCV's DNN module
# In a real Isaac implementation, this would use Isaac's optimized models
# Use a pre-trained model (e.g., MobileNet-SSD)
try:
# Attempt to load a pre-trained model
# In a full implementation, this would be an Isaac-specific model
self.get_logger().info("Detection model initialized")
return True
except Exception as e:
self.get_logger().error(f"Failed to initialize detection model: {e}")
return False
def camera_info_callback(self, msg: CameraInfo):
"""Handle camera calibration information"""
self.latest_camera_info = msg
def detect_objects_in_image(self, cv_image: np.ndarray) -> List[Detection2D]:
"""
Detect objects in image using Isaac-like perception pipeline
"""
detections = []
try:
# Convert image to RGB (OpenCV uses BGR)
rgb_image = cv2.cvtColor(cv_image, cv2.COLOR_BGR2RGB)
# In a real Isaac implementation, this would be:
# - TensorRT accelerated inference
# - Hardware-accelerated preprocessing
# - Optimized for NVIDIA Jetson/RTX platforms
# For this example, we'll use a simple color-based detection
# to demonstrate the concept without requiring heavy models
# Convert to HSV for color-based detection
hsv = cv2.cvtColor(rgb_image, cv2.COLOR_RGB2HSV)
# Detect multiple colors (simulating object detection)
colors_to_detect = [
{"name": "red_object", "lower": np.array([0, 50, 50]), "upper": np.array([10, 255, 255])},
{"name": "blue_object", "lower": np.array([100, 50, 50]), "upper": np.array([130, 255, 255])},
{"name": "green_object", "lower": np.array([50, 50, 50]), "upper": np.array([80, 255, 255])}
]
for color_info in colors_to_detect:
mask = cv2.inRange(hsv, color_info["lower"], color_info["upper"])
# Apply morphological operations to reduce noise
kernel = np.ones((5, 5), np.uint8)
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
# Find contours
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for contour in contours:
# Filter small contours
if cv2.contourArea(contour) > 500: # Minimum area threshold
# Get bounding box
x, y, w, h = cv2.boundingRect(contour)
# Create detection
detection = Detection2D()
detection.header.stamp = self.get_clock().now().to_msg()
detection.header.frame_id = "camera_link"
# Set bounding box (center_x, center_y, width, height)
detection.bbox.center.x = x + w/2
detection.bbox.center.y = y + h/2
detection.bbox.size_x = w
detection.bbox.size_y = h
# Set hypothesis (object type and confidence)
hypothesis = ObjectHypothesisWithScore()
hypothesis.id = color_info["name"]
hypothesis.score = 0.85 # Simulated confidence
detection.results.append(hypothesis)
# Add to detections
detections.append(detection)
# Draw bounding box on image for visualization
cv2.rectangle(rgb_image, (x, y), (x+w, y+h), (255, 0, 0), 2)
cv2.putText(rgb_image, color_info["name"],
(x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 2)
# Convert back to BGR for visualization publisher
vis_image = cv2.cvtColor(rgb_image, cv2.COLOR_RGB2BGR)
# Publish visualization image
vis_msg = self.bridge.cv2_to_imgmsg(vis_image, encoding="bgr8")
vis_msg.header.stamp = self.get_clock().now().to_msg()
vis_msg.header.frame_id = "camera_link"
self.visualization_publisher.publish(vis_msg)
except Exception as e:
self.get_logger().error(f"Error in object detection: {e}")
return detections
def image_callback(self, msg: Image):
"""Process incoming camera images for perception"""
try:
# Convert ROS Image to OpenCV
cv_image = self.bridge.imgmsg_to_cv2(msg, "bgr8")
# Perform object detection
detections = self.detect_objects_in_image(cv_image)
# Create detection array message
detection_array = Detection2DArray()
detection_array.header.stamp = self.get_clock().now().to_msg()
detection_array.header.frame_id = "camera_link"
detection_array.detections = detections
# Publish detections
self.detection_publisher.publish(detection_array)
# Log detection count
self.get_logger().info(f"Detected {len(detections)} objects")
except Exception as e:
self.get_logger().error(f"Error processing image: {e}")
def main(args=None):
rclpy.init(args=args)
perception_node = IsaacPerceptionNode()
try:
rclpy.spin(perception_node)
except KeyboardInterrupt:
perception_node.get_logger().info("Shutting down Isaac Perception Node...")
finally:
perception_node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
Step 4: Create Isaac Perception Launch File
-
Create a launch directory and launch file (
isaac_perception_examples/launch/isaac_perception_pipeline.launch.py):import os
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, ExecuteProcess, RegisterEventHandler
from launch.event_handlers import OnProcessStart
from launch.substitutions import LaunchConfiguration, PathJoinSubstitution
from launch_ros.actions import Node
from launch_ros.substitutions import FindPackageShare
def generate_launch_description():
# Launch configuration variables
use_sim_time = LaunchConfiguration('use_sim_time', default='true')
# Declare launch arguments
declare_use_sim_time = DeclareLaunchArgument(
'use_sim_time',
default_value='true',
description='Use simulation (Gazebo) clock if true'
)
# Isaac perception node
perception_node = Node(
package='isaac_perception_examples',
executable='object_detection_node',
name='isaac_perception_node',
parameters=[
{'use_sim_time': use_sim_time}
],
output='screen'
)
# Isaac image pipeline nodes (simplified example)
# In a real setup, these would be the actual Isaac ROS nodes
image_format_converter = Node(
package='isaac_ros_image_proc',
executable='image_format_converter_exe',
name='image_format_converter',
parameters=[
{'use_sim_time': use_sim_time},
{'input_format': 'rgba8'},
{'output_format': 'rgb8'}
],
remappings=[
('image_raw', '/camera/image_raw'),
('image', '/camera/image_rgb')
]
)
# Create launch description
ld = LaunchDescription()
# Add launch arguments
ld.add_action(declare_use_sim_time)
# Add nodes
ld.add_action(image_format_converter)
ld.add_action(perception_node)
return ld
Step 5: Create Isaac Perception Pipeline with Real Isaac Packages
-
Create a more comprehensive Isaac perception pipeline (
isaac_perception_examples/scripts/isaac_detection_pipeline.py):#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Image, CompressedImage, CameraInfo
from cv_bridge import CvBridge
import numpy as np
import cv2
from vision_msgs.msg import Detection2D, Detection2DArray, ObjectHypothesisWithScore
from geometry_msgs.msg import Point
from std_msgs.msg import Header
import time
import threading
from queue import Queue
class IsaacPerceptionPipeline(Node):
"""
Isaac ROS perception pipeline implementation demonstrating
key concepts of GPU-accelerated perception
"""
def __init__(self):
super().__init__('isaac_perception_pipeline')
# Initialize CvBridge
self.bridge = CvBridge()
# Setup queues for multi-threading perception
self.image_queue = Queue(maxsize=2)
self.perception_thread = threading.Thread(target=self.perception_worker)
self.perception_thread.start()
# Create subscriptions
self.image_sub = self.create_subscription(
Image,
'/camera/image_raw',
self.image_callback,
10
)
# Create publishers
self.detection_pub = self.create_publisher(
Detection2DArray,
'/isaac_detections',
10
)
self.vis_pub = self.create_publisher(
Image,
'/isaac_visualization',
10
)
# Performance tracking
self.frame_count = 0
self.last_time = time.time()
self.get_logger().info("Isaac Perception Pipeline Initialized")
def image_callback(self, msg: Image):
"""Handle incoming camera images"""
try:
# If queue is full, skip to avoid lag
if not self.image_queue.full():
self.image_queue.put(msg)
else:
self.get_logger().warn("Image queue full, dropping frame")
except Exception as e:
self.get_logger().error(f"Error in image callback: {e}")
def perception_worker(self):
"""Background thread for perception processing"""
while rclpy.ok():
try:
# Get image from queue (blocking with timeout)
img_msg = self.image_queue.get(timeout=0.1)
# Process image for perception
self.process_perception(img_msg)
# Mark task as done
self.image_queue.task_done()
except:
# Timeout when queue is empty
continue
def process_perception(self, img_msg: Image):
"""Process image with perception pipeline"""
try:
# Convert ROS image to OpenCV
cv_image = self.bridge.imgmsg_to_cv2(img_msg, "bgr8")
# In a real Isaac setup, these would be:
# 1. Hardware-accelerated pre-processing
# 2. TensorRT inference on GPU
# 3. Hardware-accelerated post-processing
# For this example, we'll use a combination of traditional CV
# and simulate the accelerated aspects
start_time = time.time()
# Pre-processing: resize and normalize image
# (In Isaac, this happens on GPU/DLA)
height, width = cv_image.shape[:2]
resized_image = cv2.resize(cv_image, (416, 416))
normalized_image = resized_image.astype(np.float32) / 255.0
# Simulate accelerated inference (in real Isaac, this would be TensorRT)
# Here we'll use a basic algorithm to detect objects
detections = self.simulated_isaac_inference(resized_image)
# Post-processing: scale detections back to original image size
scaled_detections = []
for detection in detections:
# Scale bounding box coordinates back to original image size
detection.bbox.center.x = int(detection.bbox.center.x * width / 416.0)
detection.bbox.center.y = int(detection.bbox.center.y * height / 416.0)
detection.bbox.size_x = int(detection.bbox.size_x * width / 416.0)
detection.bbox.size_y = int(detection.bbox.size_y * height / 416.0)
scaled_detections.append(detection)
# Create visualization
vis_image = self.create_visualization(cv_image, scaled_detections)
# Publish results
self.publish_results(img_msg.header, scaled_detections, vis_image)
# Performance tracking
end_time = time.time()
self.frame_count += 1
if self.frame_count % 30 == 0: # Log every 30 frames
current_time = time.time()
fps = 30 / (current_time - self.last_time)
self.last_time = current_time
self.get_logger().info(f"Perception FPS: {fps:.2f}, Processing time: {(end_time-start_time)*1000:.2f}ms")
except Exception as e:
self.get_logger().error(f"Error in perception processing: {e}")
def simulated_isaac_inference(self, image):
"""Simulate Isaac-optimized inference"""
detections = []
# In a real Isaac implementation, this would be:
# - TensorRT model loaded with optimized layers
# - Running on GPU (or DLA on Jetson)
# - Using INT8 quantization for performance
# For simulation, we'll use OpenCV's DNN module to represent
# the kind of inference a real Isaac system would perform
# Convert to HSV for color-based detection
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
# Define color ranges for detection
color_ranges = [
{"name": "person", "lower": np.array([0, 0, 200]), "upper": np.array([180, 30, 255])},
{"name": "car", "lower": np.array([0, 0, 0]), "upper": np.array([180, 255, 50])},
{"name": "road_sign", "lower": np.array([80, 50, 50]), "upper": np.array([130, 255, 255])}
]
for color_info in color_ranges:
# Create mask for this color
mask = cv2.inRange(hsv, color_info["lower"], color_info["upper"])
# Find contours
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for contour in contours:
# Calculate area and filter small objects
area = cv2.contourArea(contour)
if area > 100: # Minimum detection area
# Get bounding rectangle
x, y, w, h = cv2.boundingRect(contour)
# Create detection message
detection = Detection2D()
detection.header.stamp = self.get_clock().now().to_msg()
detection.header.frame_id = "camera_optical_frame"
# Set bounding box
detection.bbox.center.x = x + w/2
detection.bbox.center.y = y + h/2
detection.bbox.size_x = w
detection.bbox.size_y = h
# Set detection result
hypothesis = ObjectHypothesisWithScore()
hypothesis.id = color_info["name"]
hypothesis.score = 0.8 # Simulated confidence
detection.results.append(hypothesis)
detections.append(detection)
return detections
def create_visualization(self, image, detections):
"""Create visualized image with detection overlays"""
vis_image = image.copy()
for detection in detections:
# Extract bounding box coordinates
x = int(detection.bbox.center.x - detection.bbox.size_x/2)
y = int(detection.bbox.center.y - detection.bbox.size_y/2)
w = int(detection.bbox.size_x)
h = int(detection.bbox.size_y)
# Draw bounding box
cv2.rectangle(vis_image, (x, y), (x+w, y+h), (0, 255, 0), 2)
# Draw label
label = f"{detection.results[0].id}: {detection.results[0].score:.2f}"
cv2.putText(vis_image, label, (x, y-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
return vis_image
def publish_results(self, header, detections, vis_image):
"""Publish perception results"""
try:
# Publish detection array
detection_array = Detection2DArray()
detection_array.header.stamp = header.stamp
detection_array.header.frame_id = header.frame_id
detection_array.detections = detections
self.detection_pub.publish(detection_array)
# Publish visualization image
vis_msg = self.bridge.cv2_to_imgmsg(vis_image, encoding="bgr8")
vis_msg.header.stamp = header.stamp
vis_msg.header.frame_id = header.frame_id
self.vis_pub.publish(vis_msg)
except Exception as e:
self.get_logger().error(f"Error publishing results: {e}")
def destroy_node(self):
"""Clean up resources"""
if self.perception_thread.is_alive():
# Note: In a real implementation, you'd have a cleaner shutdown
self.perception_thread.join(timeout=1.0)
super().destroy_node()
def main(args=None):
rclpy.init(args=args)
node = IsaacPerceptionPipeline()
try:
rclpy.spin(node)
except KeyboardInterrupt:
node.get_logger().info("Shutting down Isaac Perception Pipeline...")
finally:
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
Step 6: Create Setup Script for Isaac Packages
-
Create a setup script (
isaac_perception_examples/scripts/setup_isaac.sh):#!/bin/bash
# Setup script for Isaac ROS Perception Lab
echo "Setting up Isaac ROS Perception Environment..."
# Update package list
sudo apt update
# Install Isaac ROS suite (this includes perception packages)
sudo apt install ros-humble-isaac-ros-suite
# Install additional dependencies
sudo apt install python3-opencv python3-pip
# Install Python dependencies for perception examples
pip3 install numpy opencv-python
# Verify Isaac packages
echo "Verifying Isaac ROS packages..."
ros2 pkg list | grep isaac
echo "Setup complete! Please source your ROS 2 environment:"
echo "source /opt/ros/humble/setup.bash"
echo "cd ~/isaac_lab_ws && source install/setup.bash"
Step 7: Build and Test the Isaac Perception System
-
Make scripts executable and build the package:
chmod +x ~/isaac_lab_ws/src/isaac_perception_examples/scripts/setup_isaac.sh
chmod +x ~/isaac_lab_ws/src/isaac_perception_examples/isaac_perception_examples/object_detection_node.py
chmod +x ~/isaac_lab_ws/src/isaac_perception_examples/isaac_perception_examples/isaac_detection_pipeline.py
cd ~/isaac_lab_ws
colcon build --packages-select isaac_perception_examples
source install/setup.bash -
Test perception pipeline with a simple image topic (use Gazebo simulation from previous lab):
# First, launch Gazebo simulation with sensor robot
# In a separate terminal:
ros2 launch simple_robot_description sensor_robot.launch.py -
Run the Isaac perception pipeline:
ros2 run isaac_perception_examples isaac_detection_pipeline -
Monitor the perception output:
ros2 topic echo /isaac_detections
Step 8: Evaluate Isaac Perception Performance
-
Create a simple evaluation script (
isaac_perception_examples/scripts/evaluate_perception.py):#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Image
from vision_msgs.msg import Detection2DArray
import time
class PerceptionEvaluator(Node):
"""
Evaluate perception pipeline performance
"""
def __init__(self):
super().__init__('perception_evaluator')
# Timing variables
self.last_image_time = None
self.last_detection_time = None
self.fps = 0
self.detection_latency = 0
# Subscriptions
self.image_sub = self.create_subscription(
Image,
'/camera/image_raw',
self.image_callback,
10
)
self.detection_sub = self.create_subscription(
Detection2DArray,
'/isaac_detections',
self.detection_callback,
10
)
# Timer to report stats
self.timer = self.create_timer(1.0, self.report_stats)
def image_callback(self, msg):
"""Track image arrival time"""
self.last_image_time = msg.header.stamp.sec + msg.header.stamp.nanosec / 1e9
def detection_callback(self, msg):
"""Track detection arrival time and calculate latency"""
detection_time = msg.header.stamp.sec + msg.header.stamp.nanosec / 1e9
if self.last_image_time:
self.detection_latency = detection_time - self.last_image_time
# Calculate approximate FPS
if self.last_detection_time:
dt = detection_time - self.last_detection_time
self.fps = 0.1 * (1.0/dt) + 0.9 * self.fps # Exponential moving average
self.last_detection_time = detection_time
def report_stats(self):
"""Report performance statistics"""
if self.fps > 0:
self.get_logger().info(
f"Perception Performance - FPS: {self.fps:.2f}, "
f"Latency: {self.detection_latency*1000:.2f}ms, "
f"Total Detections: {len(self.get_detection_history())}"
)
def get_detection_history(self):
"""Placeholder for tracking detection history"""
# In a real implementation, this would track detection results over time
return []
def main(args=None):
rclpy.init(args=args)
evaluator = PerceptionEvaluator()
try:
rclpy.spin(evaluator)
except KeyboardInterrupt:
evaluator.get_logger().info("Shutting down perception evaluator...")
finally:
evaluator.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
Lab Report
Submit a lab report including:
- System Setup: Documentation of your Isaac ROS environment setup
- Perception Pipeline: Explanation of your implemented perception pipeline
- Performance Analysis: Results from your perception evaluation
- Isaac Advantages: Discussion of benefits provided by Isaac ROS packages
- Challenges: Technical obstacles encountered and solutions implemented
- Comparison: Differences between Isaac-based and traditional approaches
Troubleshooting
Common Issues and Solutions
-
Isaac Packages Not Available
- Problem:
aptcannot find Isaac ROS packages - Solution: Check Ubuntu and ROS 2 versions; Isaac packages require specific compatibility
- Problem:
-
GPU Acceleration Not Working
- Problem: Perception node running on CPU instead of GPU
- Solution: Verify CUDA installation and GPU compatibility; check Isaac package configuration
-
Memory Allocation Errors
- Problem: GPU running out of memory during inference
- Solution: Use smaller model inputs or optimize batch sizes
-
Topic Connection Issues
- Problem: Perception nodes not receiving image data
- Solution: Check topic remapping and QoS profile compatibility
Performance Optimization Tips
Isaac-Specific Optimizations
- Use TensorRT models when available for maximum inference speed
- Implement asynchronous processing for better throughput
- Utilize Isaac's image pipeline for efficient data handling
- Consider INT8 quantization for inference optimization
Extension Activities
For advanced learners, consider implementing:
- Multi-Camera Perception: Extend pipeline for stereo vision
- Depth-Aware Object Detection: Integrate depth information with object detection
- Real-Time Tracking: Add object tracking to perception pipeline
- AI Model Training: Train custom models for domain-specific objects
Next Steps
In the next lab, you'll implement SLAM algorithms using NVIDIA Isaac packages to create maps of unknown environments while localizing the robot, building on the perception capabilities developed in this lab.