Welcome to MicroROS V2 Robot Car repository
3. Object Detection
Object Detection
1. Experiment Objective
Use YOLO for object detection — recognize objects in the frame and annotate bounding boxes and classes. It supports three input sources: images, videos, and a real-time camera feed.
2. Experiment Principle
1. Glossary
| Term | Description |
|---|---|
| YOLO | You Only Look Once, a real-time object detection algorithm that completes detection and localization in a single inference, balancing speed and accuracy |
| Ultralytics | The official Python library for YOLO models, providing training, inference, and export interfaces; supports the YOLOv8/v11 series |
| COCO | The standard dataset for general object detection (Common Objects in Context), containing 80 classes; it is the benchmark for YOLO pretrained models |
| mAP | mean Average Precision, the core evaluation metric for object detection model accuracy, computed as the average precision across different IoU thresholds |
| Bounding Box | A rectangular box (x1, y1, x2, y2) that marks an object's location, accompanied by a class label and confidence |
| NMS | Non-Maximum Suppression, which removes duplicate detection boxes and keeps the results with the highest confidence |
2. Technical Architecture
YOLO vision recognition starts inference nodes through ROS2 launch files, supporting three input sources:
- Image/Video mode: The node reads local files for offline inference; it does not require micro_ros_agent or the state estimation layer. You can start the YOLO launch directly.
- Real-time camera mode: The node subscribes to the
/camera/image_rawtopic to get real-time camera frames. It requires three processes to work together: micro_ros_agent (firmware communication) → state estimation layer (camera data publishing) → YOLO detection node.
Real-time mode data flow:
ESP32 camera → micro_ros_agent → /camera/image_raw → YOLO detection node → /yolo/detect/image
YOLO inference depends on the ultralytics + PyTorch libraries installed in the current Python environment; the ROS2 and workspace environment must be sourced before starting.
3. Core Topic Quick Reference
ROS2 Internal Topics
| Topic | Message Type | QoS | Source | Description |
|---|---|---|---|---|
/camera/image_raw | sensor_msgs/Image | best_effort | State estimation layer | Raw camera image, subscribed by the YOLO node |
/yolo/detect/image | sensor_msgs/Image | reliable | YOLO detection node | Annotated detection result image |
3. Experiment Steps
1. Hardware Requirements
| Hardware | Requirement |
|---|---|
| PTZ version | ✅ Supported |
| No-PTZ version | ✅ Supported |
| Required peripheral | ESP32 camera (realtime mode) |
2. Start the YOLO Environment
All YOLO ROS2 nodes depend on the ultralytics + PyTorch packages in the current Python environment. Before starting, the ROS2 and workspace environment must be sourced:
xxxxxxxxxxyolo_env3. Image Mode (Single Image Inference)
Image mode does not depend on the robot hardware or micro_ros_agent, so inference can be started directly:
xxxxxxxxxxros2 launch microros_v2_yolo_pkg yolo_image_detect.launch.py

4. Video Mode (Frame-by-Frame Inference on a Video File)
xxxxxxxxxxros2 launch microros_v2_yolo_pkg yolo_video_detect.launch.py

5. Real-time Camera Mode
Launch the proxy + camera + joint model (Terminal 1)
xxxxxxxxxxros2 launch microros_v2_bringup car_base.launch.py
Terminal 2 — Launch YOLO real-time detection:
xxxxxxxxxxros2 launch microros_v2_yolo_pkg yolo_realtime_detect.launch.py
6. Operating Instructions
After startup, the YOLO model runs inference on the input frames, and the detection results are annotated on the frame as bounding boxes. Each box is accompanied by a class name and confidence score.
- Image mode: The program reads a single image and shows the annotated results after inference. Press
qorESCto close the window and exit. - Video mode: The program reads a video file frame by frame, runs inference independently on each frame, and shows the annotated results. Press
qorESCto stop playback and exit. - Real-time camera mode: The program subscribes to the
/camera/image_rawtopic to receive real-time camera frames for inference. PressqorESCto exit. - Common interaction: All modes support pressing
qto exit; the first model load takes a few seconds, and subsequent inference runs in real time.
7. How to Stop
Press Ctrl+C in each terminal, or press q or ESC to exit.
4. Experimental Phenomena
1. Image Mode
- After a single image is fed in, the objects in the frame are annotated with colored rectangular boxes.
- Above each box, the class name (e.g. person, car, dog) and confidence (e.g. 0.85) are displayed.
- The result window stays open; press
qto close it.
2. Video Mode
- Frame-by-frame inference; the detection results are displayed in real time on each frame.
- Detection boxes keep updating as objects move, which is suitable for observing dynamic scenes.
3. Real-time Camera Mode
- The real-time camera feed is annotated; smoothness depends on CPU performance and model size.
- Tracking mode is supported (
use_tracking:=true), which assigns a stable ID to the same object.
4. Edge Cases
- When there are no objects in the scene, the frame stays as-is without any boxes.
- Low-confidence objects can be shown by lowering the
confthreshold, but this also increases false detections.
5. Program Analysis
Source code paths:
- Launch (real-time):
~/microros_ws/src/microros_v2_yolo_pkg/launch/yolo_realtime_detect.launch.py- Launch (image):
~/microros_ws/src/microros_v2_yolo_pkg/launch/yolo_image_detect.launch.py- Launch (video):
~/microros_ws/src/microros_v2_yolo_pkg/launch/yolo_video_detect.launch.py- Node source:
~/microros_ws/src/microros_v2_yolo_pkg/microros_v2_yolo_pkg/yolo_realtime_detect_node.py
1. Core Node Analysis
The real-time detection node YoloRealtimeDetectNode is the ROS2 entry point for the object detection feature. It subscribes to the raw camera image topic, calls the YOLO model for inference, and publishes the annotated results.
Constructor (__init__): Declares all parameters, initializes CvBridge, loads the model, and sets up ROS2 communication.
x# source file: ~/microros_ws/src/microros_v2_yolo_pkg/microros_v2_yolo_pkg/yolo_realtime_detect_node.pyclass YoloRealtimeDetectNode(Node): def __init__(self): super().__init__("yolo_realtime_detect_node") self.declare_parameter("model_name", "yolo11n") self.declare_parameter("model_path", "") self.declare_parameter("image_topic", "/camera/image_raw") self.declare_parameter("output_topic", "/yolo/detect/image") self.declare_parameter("conf", 0.05) self.declare_parameter("iou", 0.7) self.declare_parameter("imgsz", 960) self.declare_parameter("device", "cpu") self.declare_parameter("max_det", 1000) self.declare_parameter("use_tracking", True) self.declare_parameter("tracker", "bytetrack.yaml") self.declare_parameter("persist", True) self.declare_parameter("process_every_n_frames", 1) self.declare_parameter("show_window", True) self.declare_parameter("window_name", "YOLO Realtime Tracking") self.declare_parameter("display_width", 960) self.declare_parameter("display_height", 540) self.declare_parameter("save_video", False) self.declare_parameter("output_dir", str(OUTPUT_VIDEO_DIR)) self.declare_parameter("output_video_name", "realtime_yolo_track.mp4") self.declare_parameter("save_video_fps", 15.0) self.declare_parameter("fourcc", "mp4v") self.bridge = CvBridge() self.model = None self.frame_index = 0 self.processed_count = 0 self.start_time = time.time() self.video_writer = None self.video_output_path = None self._load_parameters() self._load_model() self._setup_ros_io()Image callback (image_callback): Receives the raw image, runs YOLO inference, and publishes the annotated result. Core flow: Image → cv2 image → YOLO inference → annotation drawing → publish Image message. Supports frame-skipping (process_every_n_frames) and optional video saving.
xxxxxxxxxx# source file: ~/microros_ws/src/microros_v2_yolo_pkg/microros_v2_yolo_pkg/yolo_realtime_detect_node.py def image_callback(self, msg: Image): self.frame_index += 1 if self.frame_index % self.process_every_n_frames != 0: return try: frame = self.bridge.imgmsg_to_cv2(msg, desired_encoding="bgr8") except Exception as exc: self.get_logger().error(f"cv_bridge convert failed: {exc}") return try: results = self._run_yolo(frame) if results: annotated = results[0].plot() else: annotated = frame except Exception as exc: self.get_logger().error(f"YOLO inference failed: {exc}") return try: out_msg = self.bridge.cv2_to_imgmsg(annotated, encoding="bgr8") out_msg.header = msg.header self.publisher.publish(out_msg) except Exception as exc: self.get_logger().error(f"publish result image failed: {exc}") self._init_video_writer_if_needed(annotated) if self.video_writer is not None: self.video_writer.write(annotated) if self.show_window: if self.display_width > 0 and self.display_height > 0: show_img = cv2.resize(annotated, (self.display_width, self.display_height)) else: show_img = annotated cv2.imshow(self.window_name, show_img) key = cv2.waitKey(1) & 0xFF if key in (ord("q"), ord("Q"), 27): self.get_logger().info("q/ESC pressed, shutting down.") os._exit(0) self.processed_count += 1YOLO inference (_run_yolo): Selects model.track() (object tracking) or model.predict() (object detection) based on the use_tracking parameter. In tracking mode, ByteTrack is used to keep cross-frame ID consistency.
xxxxxxxxxx# source file: ~/microros_ws/src/microros_v2_yolo_pkg/microros_v2_yolo_pkg/yolo_realtime_detect_node.py def _run_yolo(self, frame): if self.use_tracking: return self.model.track( source=frame, persist=self.persist, tracker=self.tracker, conf=self.conf, iou=self.iou, imgsz=self.imgsz, device=self.device, max_det=self.max_det, verbose=False, ) return self.model.predict( source=frame, conf=self.conf, iou=self.iou, imgsz=self.imgsz, device=self.device, max_det=self.max_det, verbose=False, )2. Key Parameters
Parameter definition files:
- Launch:
~/microros_ws/src/microros_v2_yolo_pkg/launch/yolo_realtime_detect.launch.py- Node:
~/microros_ws/src/microros_v2_yolo_pkg/microros_v2_yolo_pkg/yolo_realtime_detect_node.py
| Parameter | Type | Launch Default | Node Default | Description |
|---|---|---|---|---|
model_name | string | yolo11n | yolo11n | YOLO model name; other options such as yolo11s / yolo11m are available |
conf | float | 0.12 | 0.05 | Detection confidence threshold; results below this value are filtered |
iou | float | 0.45 | 0.7 | IoU threshold for NMS; the smaller the value, the stricter the deduplication of overlapping boxes |
imgsz | int | 960 | 960 | Inference input image size (pixels) |
device | string | cpu | cpu | Inference device; cpu or cuda:0 |
max_det | int | 100 | 1000 | Maximum number of detected objects per image |
use_tracking | bool | false | true | Whether to enable ByteTrack object tracking |
tracker | string | bytetrack.yaml | bytetrack.yaml | Tracker configuration file |
persist | bool | true | true | Whether to keep cross-frame ID consistency when tracking |
show_window | bool | true | true | Whether to show the OpenCV inference window |
display_width | int | 960 | 960 | Display window width |
display_height | int | 720 | 540 | Display window height |
save_video | bool | false | false | Whether to save the output video |
process_every_n_frames | int | 1 | 1 | Process every N frames (frame-skipping inference; 1 = process every frame) |
Note: The launch file defaults (e.g.
conf:=0.12) override the node source defaults (e.g.conf=0.05). At runtime, the launch defaults or the values overridden on the command line take effect.
3. Node Description
| Node | Executable Name | Description |
|---|---|---|
yolo_realtime_detect_node | yolo_realtime_detect_node | Real-time detection node; subscribes to /camera/image_raw and publishes /yolo/detect/image |
yolo_image_detect_node | yolo_image_detect_node | Image detection node; reads a local image and outputs annotated results |
yolo_video_detect_node | yolo_video_detect_node | Video detection node; reads a video file frame by frame and outputs an annotated video |
6. Common Problems
| Problem | Cause | Solution |
|---|---|---|
| No detection boxes at all | Confidence threshold too high, or no objects the model recognizes in the scene | Lower the conf parameter (e.g. conf:=0.05), or check whether the camera feed is normal |
| Too many heavily overlapping boxes | IoU threshold too low or max_det too large | Raise iou appropriately (e.g. iou:=0.7) or lower max_det |
| Real-time feed stutters | Insufficient CPU inference performance, the model too large, or the input resolution too high | Switch to the lightweight yolo11n model, lower imgsz, or increase process_every_n_frames to skip frames |
| Blank/no image displayed | Camera not connected properly or topic name mismatch | Check the ESP32 camera WiFi configuration and confirm /camera/image_raw has data |
ModuleNotFoundError: ultralytics | The current Python environment lacks the dependencies | Run python3 -m pip install ultralytics torch torchvision opencv-python, or activate an existing virtual environment first |