> ## Documentation Index
> Fetch the complete documentation index at: https://docs.edgeimpulse.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Build a mobile inspection system with Edge Impulse and ROS 2

> Design a wheeled rover inspection workflow with ROS 2, Edge Impulse detection, anomaly scoring, gimbal control, logging, and safety layers.

This tutorial describes the design of an industrial inspection robot that moves along an inspection path, runs perception locally, and logs a structured record of each inspection. The example uses a Waveshare UGV rover with a pan/tilt camera and runs Edge Impulse models on a Qualcomm QCS6490-class device.

![Wheeled inspection rover with a camera gimbal for Edge AI inspection](https://mintcdn.com/edgeimpulse/b5-ul7shu7FpiXoC/.assets/images/robotics/robot_in_the_show-2.webp?fit=max\&auto=format\&n=b5-ul7shu7FpiXoC\&q=85\&s=1d846cfa820c6c234e68922704727992)

Inspection rover with a Waveshare UGV base and a pan/tilt gimbal

> [!NOTE]
>
> This tutorial is based on Moe Sani's inspection robot demo at Embedded World 2026. For the full build story, read [A look inside my edge AI inspection robot (ROS 2-native)](https://www.edgeimpulse.com/blog/edge-ai-inspection-robot-ros-2-native/).

## Problem statement

Inspection at pipelines, refineries, utilities, and other industrial sites has these constraints:

* **Hazardous access.** Manual inspection can put people in unsafe or hard-to-reach places.
* **Inconsistent data.** Operators capture images from different angles, distances, and lighting.
* **Limited connectivity.** Field sites often have restricted, intermittent, or isolated networks.
* **Audit requirements.** Every decision must be traceable and reviewable after the run.

![Human inspection in an industrial environment](https://mintcdn.com/edgeimpulse/b5-ul7shu7FpiXoC/.assets/images/robotics/human_inspection-1.png?fit=max\&auto=format\&n=b5-ul7shu7FpiXoC\&q=85\&s=44150ad34089d8ec5a49263fb5d553ee)

Manual inspection in a hazardous industrial environment

Running inference on the robot addresses these constraints. Decisions don't wait on the network, the robot sends only compact results, and raw images stay on the device unless an operator asks for them.

## System architecture

### Hardware minimum

* **Mobile base.** A rover with reliable velocity control, such as a Waveshare UGV.
* **Gimbal.** A two-axis pan/tilt mount that aims the close-up camera.
* **Cameras.** A wide-view camera for search and a close-up camera for inspection.
* **Compute.** Linux hardware that can run ROS 2 and Edge Impulse Linux inference.
* **Safety controls.** An emergency stop, a command watchdog, speed limits, and a manual override.

### Perception pipeline

Perception runs in two stages, search and inspection:

| Stage | Camera | Task | Model | Output |
| - | - | - | - | - |
| Search | Wide view | Find candidate inspection points | Object detector | Bounding boxes and confidence scores |
| Inspect | Close-up | Evaluate quality or damage | Anomaly detector or classifier | Anomaly score or class result |

The detector runs continuously or in short search windows. The higher-resolution inspection model runs only after the gimbal points at a candidate.

### ROS 2 architecture

Perception nodes publish standard ROS 2 messages. The mission manager runs long tasks as ROS 2 actions. A safety gate sits between the mission manager and the motor driver, and the dashboard only reads topics.

![Diagram of ROS 2 nodes in which detectors feed a critical point aggregator and mission manager, which drive gimbal and motion servers through a cmd\_vel watchdog](https://mintcdn.com/edgeimpulse/b5-ul7shu7FpiXoC/.assets/images/robotics/Gemini_Generated_Image_7d8ucb7d8ucb7d8u.png?fit=max\&auto=format\&n=b5-ul7shu7FpiXoC\&q=85\&s=930c2bae53dd81e83cc35f06b2367b72)

ROS 2 node architecture for the inspection rover

| Node | Role |
| - | - |
| **DET: Object Detector** | Runs the wide-view detection model and publishes `vision_msgs/Detection2DArray`. |
| **ANOM: Anomaly Detector** | Runs the close-up inspection model and publishes an anomaly score or class result. |
| **AGG: Critical Point Aggregator** | Groups detections into stable candidate points for inspection. |
| **MM: Inspection Mission Manager** | Runs the move-inspect-move loop as ROS 2 actions. |
| **GIM: Point Gimbal Server** | Controls pan and tilt to aim the close-up camera at a candidate point. |
| **STEP: Step Motion Server** | Advances the rover one controlled step along the inspection path. |
| **WDG: cmd\_vel Watchdog** | Gates motor commands and stops the robot if control messages stop arriving. |
| **DRV: UGV Driver** | Translates `/cmd_vel` into serial or hardware-specific motor commands. |
| **VIZ: Vizanti Dashboard** | Shows overlays and mission state to the operator in a browser. |
| **LOG: Inspection Logger** | Writes structured per-point inspection records. |

The main ROS interfaces:

| Interface | Message type | Purpose |
| - | - | - |
| `/cmd_vel` | `geometry_msgs/Twist` | Motion control for the rover base. |
| `/joint_states` | `sensor_msgs/JointState` | Gimbal pan/tilt state. |
| `/edgeimpulse_detector/detections` | `vision_msgs/Detection2DArray` | Wide-view candidate detections from `edgeimpulse_ros`. |
| Inspection result topic | `std_msgs/String` or custom message | Structured anomaly result for each inspected point. |

## Mission loop

The rover repeats a move-inspect-move loop along the inspection path:

1. **STEP** advances the rover one position along the inspection path.
2. **DET** runs the object detector on the wide-view camera.
3. **AGG** aggregates detections into stable critical point candidates.
4. For each candidate:
   * **GIM** aims the gimbal at the target location.
   * **ANOM** captures a close-up after the camera settles and produces an anomaly score or class result.
   * **LOG** records the detection, position, score, timestamp, and model version.
5. The mission manager decides whether to continue, retry, alert, or stop.

Each stop produces a record of what the robot saw, why it inspected that point, and the resulting score. You can review and debug each decision from these records.

## Integrating Edge Impulse models

Run the search detector with [`edgeimpulse_ros`](https://github.com/edgeimpulse/edgeimpulse-ros). It loads a Linux `.eim` model and subscribes to the wide-view camera's `sensor_msgs/Image` topic. For details, see [Run Edge Impulse object detection with ROS 2](https://docs.edgeimpulse.com/tutorials/topics/robotics/edgeimpulse-ros2-integration.md).

### Installation

Install ROS 2 message dependencies and OpenCV:

```bash theme={"system"}
sudo apt update
sudo apt install -y ros-$ROS_DISTRO-vision-msgs ros-$ROS_DISTRO-diagnostic-msgs \
                    ros-$ROS_DISTRO-v4l2-camera python3-opencv python3-numpy portaudio19-dev
```

Create or reuse a ROS 2 workspace:

```bash theme={"system"}
mkdir -p ~/inspection_ws/src
cd ~/inspection_ws/src
git clone https://github.com/edgeimpulse/edgeimpulse-ros.git edgeimpulse_ros
pip install --user --break-system-packages edge_impulse_linux pyaudio

cd ~/inspection_ws
source /opt/ros/$ROS_DISTRO/setup.bash
rosdep install --from-paths src --ignore-src -r -y
colcon build --symlink-install
source install/setup.bash
```

> [!WARNING]
>
> Don't put the Edge Impulse Linux SDK source inside the `src/` folder of the workspace, because colcon can treat it as a ROS package. Install the SDK with `pip`, or add a `COLCON_IGNORE` file to any non-ROS folder in the workspace.

### Running the detector

Launch the detector:

```bash theme={"system"}
ros2 launch edgeimpulse_ros edgeimpulse_detector.launch.py \
  model_path:=/home/$USER/models/inspection-detector.eim \
  image_topic:=/wide_camera/image_raw \
  image_qos:=sensor_data
```

To override individual parameters, use `ros2 run`:

```bash theme={"system"}
ros2 run edgeimpulse_ros edgeimpulse_detector --ros-args \
  -p model_path:=/home/$USER/models/inspection-detector.eim \
  -p image_topic:=/wide_camera/image_raw \
  -p image_qos:=sensor_data \
  -p confidence_threshold:=0.5 \
  -p frame_id_override:=wide_camera
```

### Output topics

| Topic | Type | Content |
| - | - | - |
| `/edgeimpulse_detector/detections` | `vision_msgs/Detection2DArray` | Bounding boxes and class scores for accepted detections. |
| `/edgeimpulse_detector/anomaly` | `std_msgs/Float32` | Peak anomaly score for models with an anomaly output. |
| `/diagnostics` | `diagnostic_msgs/DiagnosticArray` | FPS, latency, dropped frames, and Edge Impulse DSP/inference timings. |

### Configuration

| Parameter | Use |
| - | - |
| `confidence_threshold` | Drops detections below this score before the mission logic sees them. A negative value uses the model's own threshold. |
| `image_topic` | The wide-view camera's `sensor_msgs/Image` topic. |
| `image_qos` | QoS that matches the camera driver: `sensor_data`, `reliable`, or `default`. |
| `publish_debug_image` | Publishes an annotated image for the operator dashboard. |
| `publish_diagnostics` | Publishes the `DiagnosticArray` for dashboards and watchdogs. |

## Building your inspection system

### 1. Define mission requirements

Before you collect data, decide on the inspection target and operating limits:

* Which points on the asset are critical?
* What does an abnormal result look like?
* Which inspection distances, angles, lighting, and speeds are acceptable?
* What should happen when confidence is low or the robot can't inspect a component?

### 2. Design data collection

Collect data for each camera under the conditions the robot will see.

| View | What to capture |
| - | - |
| Search (wide camera) | Several angles along the inspection path, with realistic lighting, motion blur, and partial occlusion. Label the target components. |
| Inspection (close-up camera) | Detailed images with consistent framing after the gimbal aims at a target. Start with normal examples, then add known defects and borderline cases. |

### 3. Train two models in Edge Impulse

Create a separate Edge Impulse project for each model.

| Model | Input | Output | Optimize for |
| - | - | - | - |
| Detection | Wide-view images | Bounding boxes for critical points | Speed and recall |
| Inspection | Close-up images | Anomaly score, or a binary or multiclass result | Precision and repeatability |

Export the detection model as a Linux `.eim` deployment.

### 4. Integrate with ROS 2

Run the detector with `edgeimpulse_ros` as shown above, and check that `/edgeimpulse_detector/detections` publishes. Then:

* Use standard message types where you can.
* Let the dashboard read topics, but keep mission state in the mission manager.
* Keep safety separate from the models and the mission logic.
* Version the model paths and thresholds with the mission configuration.

### 5. Run the mission with actions

Implement each long-running task as a ROS 2 action, so it supports timeouts, cancellation, and recovery:

* **Detect and aggregate:** run the detector and collect candidates.
* **Point gimbal:** move the pan/tilt mount to a target.
* **Capture and inspect:** take a close-up and run the inspection model.
* **Move:** advance the rover to the next position.

### 6. Add an independent safety layer

Treat model output as advice. Safety must not depend on the model being correct.

* Use a `/cmd_vel` watchdog with a short timeout.
* Keep the emergency stop and collision avoidance outside the mission manager.
* Gate motor commands independently of detection output.
* Use low speed limits during inspection.
* Test failures such as a model crash, lost communication, a stalled gimbal, and blocked motion.

### 7. Validate progressively

1. Test both models on representative offline images.
2. Run static camera tests with the robot powered but stationary.
3. Run controlled low-speed tests in a safe environment.
4. Review logs for repeatability, false positives, false negatives, and motion timing.
5. Expand field tests gradually.

## Visualization and control

[Vizanti](https://github.com/MoffKalast/vizanti) is a browser-based ROS 2 interface that connects directly to the ROS 2 graph. Use it, or a similar web UI, for demos, field tests, and debugging.

![Vizanti dashboard showing live detection overlays, rover state, and per-point inspection results](https://mintcdn.com/edgeimpulse/b5-ul7shu7FpiXoC/.assets/images/robotics/Vizanti_Screenshot.png?fit=max\&auto=format\&n=b5-ul7shu7FpiXoC\&q=85\&s=4334be8f2abf03aac8d191779234615b)

Vizanti operator dashboard

Show the operator:

* Detection boxes on the wide-view feed and anomaly scores on the close-up feed
* A timeline of the STEP, GIM, and ANOM actions
* The inspection log, with the location, detection confidence, anomaly score, model version, and timestamp of each point

## Improve the system over time

![Inspection robot operating in an industrial refinery environment](https://mintcdn.com/edgeimpulse/b5-ul7shu7FpiXoC/.assets/images/robotics/robot_in_refinery.png?fit=max\&auto=format\&n=b5-ul7shu7FpiXoC\&q=85\&s=4d0d4547139c4c5d8a8f22b14de1444d)

Inspection robot in a refinery

Log model scores, operator overrides, manual inspection results, camera pose, and model version. Use the logs to improve your datasets, thresholds, and mission policy.

## Resources

* [A look inside my edge AI inspection robot (ROS 2-native)](https://www.edgeimpulse.com/blog/edge-ai-inspection-robot-ros-2-native/): Moe Sani's build story and architecture walkthrough
* [edgeimpulse-ros on GitHub](https://github.com/edgeimpulse/edgeimpulse-ros)
* [Edge Impulse Linux SDK for Python](https://github.com/edgeimpulse/linux-sdk-python)
* [ROS 2 documentation](https://docs.ros.org/)
* [Vizanti web-based ROS 2 UI](https://github.com/MoffKalast/vizanti)
