3rd Place, Indoor Challenge · the only team to complete the course fully autonomously.
This repository holds the indoor navigation code that Black Bee Drones flew at the International Micro Air Vehicle Conference and Competition (IMAV 2023), hosted by RWTH Aachen University, Germany.
Heads up — this is ROS 1 (Noetic) code. It depends on the team's first-generation SDK,
tadinisdk. The team's current ROS 2 SDK,nectar-sdk, is a separate, later stack and is not compatible with this package.
drone_line_following_video.mp4
Drone executing the autonomous line-following mission during IMAV 2023.
The indoor challenge was a payload-transport task. The arena had a takeoff zone, a set of pickup blocks identified by ArUco markers, several transit routes marked by colored ropes, and a drop zone. A run consisted of taking off, landing on a pickup marker to attach a cone, following a transit route to the drop zone, and releasing the cone.
Both the pickup block and the transit route came in variants of increasing difficulty:
| Pickup block | Transit route |
|---|---|
| Unobstructed — marker flat on the floor | Free — open rope, no obstacles |
| Obstructed — marker under a 1 m obstacle | Gate — 1×1 m gate to fly through |
| Rotating — marker on a turntable | Moving gate — 2×1 m gate with a moving obstacle |
| Cooperative — cone carried by 2–3 drones together |
This repository implements the baseline run: the unobstructed block and the free transit route. The other variants were out of scope for the code published here.
- Companion computer: Raspberry Pi (runs the camera and pose bridge onboard)
- Pose/odometry: Intel RealSense T265 visual-inertial tracking camera, feeding ArduPilot through
vision_to_mavros - Vision camera: Raspberry Pi Camera v2 (down-facing), used for line and marker detection
- Flight controller: ArduPilot-based controller, EKF3, commanded over MAVLink/MAVROS
- Payload: servo-actuated gripper to attach and release the cone
- ROS 1 Noetic — middleware
- MAVROS — ROS ↔ ArduPilot bridge
- tadinisdk — team SDK: drone control, color detection, ArUco detection, PID wrapper
- vision_to_mavros — T265 pose → MAVROS vision pose
- SMACH — state-machine framework for mission orchestration
- OpenCV — image processing and line approximation
- ros
pid— PID control loops (wrapped bytadinisdk)
The mission is built from three components that can each run on their own: line following, ArUco centering, and a state machine that sequences them. Detection and control are split into separate nodes that communicate over ROS topics, so control gains can be tuned live (via rqt_reconfigure) without restarting detection.
state_machine.py (launched by mangalarga.launch) runs the full baseline course:
stateDiagram-v2
[*] --> SEARCH_ARUCO_PICK_UP_ZONE
SEARCH_ARUCO_PICK_UP_ZONE --> FOLLOW_ARUCO_PICK_UP_ZONE: marker 200 found
FOLLOW_ARUCO_PICK_UP_ZONE --> LAND: centered
LAND --> SEARCH_ARUCO_TRANSIT_ZONE: cone attached
SEARCH_ARUCO_TRANSIT_ZONE --> FOLLOW_ARUCO_TRANSIT_ZONE: marker 600 found
FOLLOW_ARUCO_TRANSIT_ZONE --> FOLLOW_LINE: centered
FOLLOW_LINE --> DROP_ZONE: line ended
DROP_ZONE --> [*]: cone released
| State | Action |
|---|---|
SEARCH_ARUCO_PICK_UP_ZONE |
Arm, take off to 1.35 m, fly forward at 0.12 m/s until the pickup marker (ID 200) is seen in 15 frames. |
FOLLOW_ARUCO_PICK_UP_ZONE |
Center over the marker with the ArUco controller. |
LAND |
Land on the marker, drive the gripper servo to grab the cone, wait for operator confirmation. |
SEARCH_ARUCO_TRANSIT_ZONE |
Take off again and search for the transit-route marker (ID 600). |
FOLLOW_ARUCO_TRANSIT_ZONE |
Center over the transit marker. |
FOLLOW_LINE |
Descend to ~0.70 m, then follow the colored rope until the line ends (no detection for 5 s). |
DROP_ZONE |
Move forward briefly, land, release the gripper servo to drop the cone. |
Keeps the drone centered over a colored rope and aligned with its direction while flying forward. Detection and control are two nodes.
Detection (line_detection_node.py → LineDetector.py):
- Resize the camera frame to 640×480 and color-filter it for
line_color(tadinisdkcolor detector). - Restrict processing to a 600×130 px band centered in the frame.
- Fit a line to the mask and extract its horizontal center (px) and angle (deg). Default method is a probabilistic Hough transform +
cv2.fitLine;LineDetector.pyalso includes min-area-rectangle and ellipse-fit alternatives. - Publish
center_xandangleonline_state(LineInfo.msg).
Control (follow_line_node.py):
- Constant forward speed: 0.12 m/s.
- Lateral velocity from a PID on
center_x—Kp=-0.00042, Ki=-0.00005, Kd=0, setpoint = 320 (image center). - Yaw rate from a PID on
angle—Kp=0.0078, Ki=0, Kd=0, setpoint = 0. - Small efforts are ignored (dead-bands of 0.025 m/s lateral, 0.1 rad/s yaw) to avoid jitter.
- The line is considered finished after 5 s without a detection, which ends the state.
Run it:
# detection only
roslaunch indoor line_detect.launch line_color:=red image_source:=/raspicam_node/image/compressed
# detection + control
roslaunch indoor follow_line.launch line_color:=red drone:=mavros image_source:=/raspicam_node/image/compressed plot:=falseCenters the drone directly over an ArUco marker, used to locate the pickup block, the transit route, and the drop zone.
Detection: tadinisdk ArUco node publishes the marker pose relative to the camera.
Control (aruco_control_node.py): two PID loops, one per horizontal axis, drive the drone until the marker is centered under the camera (each loop tracks one component of the marker's relative position with setpoint 0):
vel_xPID —Kp=-0.34, Ki=-0.02, Kd=0vel_yPID —Kp=-0.42, Ki=-0.024, Kd=0- Efforts below 0.03 m/s are ignored; once 15 frames have both efforts below threshold, the drone is reported "centered".
Run it:
roslaunch indoor aruco_centralize.launch image_source:=/raspicam_node/image/compressed drone:=mavros marker_dict:=5 tag_size:=0.15state_machine.py wires the components above into the full run (see the diagram and table in System Architecture). The detection/control nodes run in the background and the state machine activates each one over its control_activation topic, waiting for the matching "done" signal before transitioning.
Run the full mission:
roslaunch indoor mangalarga.launch drone:=mavros line_color:=red image_source:=/raspicam_node/image/compressed marker_dict:=5 tag_size:=0.15 plot:=falseThese are shared by the launch files above:
| Parameter | Default | Description |
|---|---|---|
image_source |
/raspicam_node/image/compressed |
webcam, or a ROS Image/CompressedImage topic. For the Pi camera the launch starts raspicam_node automatically. |
drone |
mavros |
Control backend: mavros (ArduPilot) or bebop. For mavros, the MAVROS apm.launch must already be running. |
line_color |
red |
Color name saved during color calibration (see below). |
marker_dict |
5 |
ArUco dictionary cell count. |
tag_size |
0.15 |
ArUco marker side length, in meters. |
plot |
false |
Open rqt_reconfigure to tune PID gains live. |
- Base Raspberry Pi + ROS Noetic setup.
- Intel RealSense SDK 2.0 (v2.53.1).
- realsense-ros (
ros1-legacy). raspicam_nodefor the Pi camera.- SMACH:
sudo apt-get install ros-noetic-smach-ros.
cd ~/catkin_ws/src
# Team SDK (provides control, color/ArUco detection, PID wrapper)
git clone https://github.com/Black-Bee-Drones/tadinisdk.git
# This package
git clone https://github.com/Black-Bee-Drones/imav2023-indoor.git
cd ~/catkin_ws
catkin_make
source devel/setup.bashTip: running the heavy nodes on a laptop while the Pi handles only the RealSense and camera noticeably improved flight time during testing.
# 1. T265 pose bridge to ArduPilot (set the EKF origin, then climb ~1 m to initialize height)
roslaunch vision_to_mavros t265_all_nodes.launch
# 2. Pi camera
roslaunch raspicam_node camerav2_410x308_30fps.launchBefore flying the line follower, calibrate the rope color:
roslaunch tadinisdk color_calibrate.launch image_source:=/raspicam_node/image/compressedPress s over the window and enter a color name to save it; use that name as line_color.
indoor/
├── launch/
│ ├── mangalarga.launch # Full mission: state machine + line + aruco
│ ├── follow_line.launch # Line detection + line controller
│ ├── line_detect.launch # Line detection only
│ └── aruco_centralize.launch # ArUco detection + aruco controller
├── msg/
│ └── LineInfo.msg # center_x (px), angle (deg)
├── src/
│ ├── state_machine.py # SMACH mission orchestration
│ ├── follow_line/
│ │ ├── LineDetector.py # Color filter + line approximation (OpenCV)
│ │ ├── line_detection_node.py # Publishes line_state (LineInfo)
│ │ └── follow_line_node.py # PID lateral + yaw, constant forward speed
│ └── aruco_control/
│ └── aruco_control_node.py # PID centering over an ArUco marker
├── CMakeLists.txt
└── package.xml
| Repository | Role |
|---|---|
tadinisdk |
ROS 1 SDK this package is built on (control, detection, PID). |
vision_to_mavros |
T265 pose → MAVROS vision pose bridge. |
pid-controller |
Standalone ROS 2 PID node (modern equivalent of the ROS 1 pid used here). |
nectar-sdk |
The team's current ROS 2 SDK (this code predates it). |
Black Bee Drones — Federal University of Itajubá (UNIFEI), Brazil. Latin America's first academic autonomous drone team.