Welcome to MicroROS V2 Robot Car repository
4. Visual Line Tracking
Visual Line Tracking (PTZ)
1. Experiment Objective
The camera detects the line on the ground (HSV color segmentation), PID controls the chassis angular velocity to drive along the line, and the LiDAR stops the car when an obstacle is detected ahead. The PTZ tilts down (pitch=-90°) toward the ground.
2. Experiment Principle
1. Key Terms
| Term | Description |
|---|---|
| Line Tracking | The camera detects the ground line and computes the yaw error relative to the image center; a PID closed loop controls the angular velocity |
| HSV Color Segmentation | Binarizes the HSV color space with cv.inRange to extract lines of a specific color, with hue wraparound support |
| Tracking ROI | Processes only the lower half of the image (line_roi_top_ratio=0.55), excluding the distant background |
| Discrete PID Controller | P+I+D independent gains with integral limiting; the pixel error is scaled by line_error_divisor |
| LiDAR Obstacle Stop | Obstacle points ahead in /scan exceed the threshold → stop immediately + optional buzzer |
| Line-Lost Auto Exit | After the line-lost timeout, automatically calls rclpy.shutdown() to exit the node |
| PTZ Tilt-Down | The pan-tilt sets pitch=-90° toward the ground, more flexible than a fixed mount |
| QoS | best_effort (image + LiDAR + cmd_vel), sensor_data (LiDAR) |
| TF Coordinate Frame | odom → base_footprint → base_link |
2. Technical Architecture
Dual timers (image at 30 Hz + control at 20 Hz) + LiDAR callback + PTZ tilt-down:
/camera/image_raw (BEST_EFFORT)→ follow_line_ptz_node├── Image processing (30 Hz): image conversion → flip → ROI HSV binarization → morphology → largest contour → EMA filtering├── Chassis control (20 Hz): pixel error → scaling → discrete PID → angular velocity limiting → /cmd_vel (BEST_EFFORT)└── PTZ: tilts down to -90° at startup → /cmd_ptz_angle/scan (sensor_data) → obstacle avoidance callback: valid point count → stop + buzzer above threshold
Key PTZ vs. No-PTZ differences: The pan-tilt can tilt down -90° toward the ground, so there is no need to manually adjust the camera mounting angle. Line tracking performs better than the fixed-mount version.
3. Core Topics Quick Reference
Firmware Uplink
| Topic | Message Type | QoS | Description |
|---|---|---|---|
/camera/image_raw | sensor_msgs/Image | best_effort | Image from the ESP32 camera |
/scan | sensor_msgs/LaserScan | sensor_data | LiDAR scan data |
Firmware Downlink
| Topic | Message Type | QoS | Field | Range | Direction |
|---|---|---|---|---|---|
/cmd_vel | geometry_msgs/Twist | best_effort + keep_last(1) | linear.x | 0.15 m/s (constant) | += forward |
angular.z | -1.20~1.20 rad/s | += turn left, −= turn right | |||
/cmd_ptz_angle | microros_v2_msgs/PtzAngle | reliable | pitch_deg | -90° | Tilted down toward the ground |
3. Experiment Steps
1. Hardware Preparation
| Hardware | Requirement |
|---|---|
| PTZ version | ✅ |
| No-PTZ version | ❌ |
| Required peripherals | ESP32 camera (pan-tilt) + PTZ servo + T-mini Plus LiDAR |
2. Start Agent + Camera + Joint Model (Terminal 1)
xxxxxxxxxxros2 launch microros_v2_bringup car_base.launch.py
3. Start Visual Line Tracking (Terminal 2)
xxxxxxxxxxros2 launch microros_v2_ptz_visual_control_pkg follow_line_ptz.launch.pyAfter startup, the pan-tilt tilts down to -90° and line tracking starts automatically (
auto_start_tracking:=true).

4. Operation Instructions
| Key | Function |
|---|---|
| Space | tracking/paused |
| i / I | Recognition only (no control) |
| r / R | Reset |
| Mouse drag | HSV sampling (box-select the line to auto-update the threshold) |
| q / Q / ESC | Exit |
5. How to Stop
Press Ctrl+C to stop Terminal 2 → Terminal 1 in order.
4. Experiment Observations
- Automatic line tracking after startup: The pan-tilt tilts down -90° toward the ground, driving along the line at 0.15 m/s
- Line centered: Drives straight within the 40 px deadband, with the PID outputting zero angular velocity
- Line shifted left: Turns right to correct (positive angular velocity); shifted right → turns left
- Obstacle ahead: Stops + buzzer within 0.35 m, resumes after the obstacle is removed
- Line lost > 1.5 s: Stops and exits automatically
- Joystick override: Stops + resets the PID, resumes after release
5. Program Analysis
Source code paths:
- Launch:
~/microros_ws/src/microros_v2_ptz_visual_control_pkg/launch/follow_line_ptz.launch.py- Node:
~/microros_ws/src/microros_v2_ptz_visual_control_pkg/microros_v2_ptz_visual_control_pkg/follow_line_ptz_node.py
1. Core Node Analysis
follow_line_ptz_node integrates PTZ control on top of the No-PTZ version. Core features:
- Discrete PID controller (
DiscretePidController): Independent P/I/D terms + integral limiting, with thepid_scale(1000) normalized gain (actual P=0.022, D=0.005) - HSV hue wraparound: When
Hmin > Hmax(e.g., red H[160,10]), merges two masks in segments - EMA filtering:
target_smooth_alpha(0.55) exponential moving average to smooth the detected center - Mouse sampling: Box-select the ROI → 5th~95th percentile HSV → auto-update the threshold
- PTZ tilt-down: The pan-tilt tilts down to
initial_pitch_deg(-90°) at startup, facing the camera toward the ground
Chassis control timer — PID line tracking + obstacle avoidance priority:
x# Source file: ~/microros_ws/src/microros_v2_ptz_visual_control_pkg/microros_v2_ptz_visual_control_pkg/follow_line_ptz_node.pydef publish_chassis_control_command(self) -> None: if not self.is_node_running: return # Joystick override takes priority if self.joy_is_active: self.publish_zero_twist() return # Obstacle avoidance takes priority over the tracking PID if self.enable_obstacle_stop and self.obstacle_is_active: self.publish_zero_twist() self.publish_beep(True) return if self.tracking_state != TRACKING_STATE_TRACKING: return # Pixel error → PID angular velocity line_center_x = self.filtered_line_center[0] pixel_error_x = line_center_x - self.image_width * 0.5 twist_command = Twist() twist_command.linear.x = float(self.line_linear_speed) # constant 0.15 m/s if abs(pixel_error_x) <= self.line_center_deadband_pixel: angular_command = 0.0 # drive straight within the deadband else: pid_input = pixel_error_x / self.line_error_divisor # scaling pid_output = self.line_pid_controller.update(pid_input, 0.0) angular_command = -pid_output # reverse_angular_direction angular_command = np.clip(angular_command, -self.max_angular_speed, self.max_angular_speed) twist_command.angular.z = angular_command self.cmd_vel_publisher.publish(twist_command)2. Key Parameters
Parameter definition files:
- Launch:
~/microros_ws/src/microros_v2_ptz_visual_control_pkg/launch/follow_line_ptz.launch.py- Node:
~/microros_ws/src/microros_v2_ptz_visual_control_pkg/microros_v2_ptz_visual_control_pkg/follow_line_ptz_node.py
| Parameter | Type | Default Value | Description |
|---|---|---|---|
line_linear_speed | float | 0.15 m/s | Constant linear speed for line tracking |
max_angular_speed | float | 1.2 rad/s | Maximum angular velocity limit |
line_pid_kp / line_pid_kd | float | 22.0 / 5.0 | PID gains (scaled by pid_scale=1000) |
pid_scale | float | 1000.0 | PID gain normalization divisor |
line_error_divisor | float | 16.0 | Pixel error scaling divisor |
line_center_deadband_pixel | float | 40.0 px | Deadband |
line_roi_top_ratio | float | 0.55 | ROI top ratio |
target_smooth_alpha | float | 0.55 | EMA filtering coefficient |
line_lost_timeout_sec | float | 1.5 s | Line-lost timeout |
enable_obstacle_stop | bool | true | LiDAR obstacle avoidance switch |
obstacle_response_distance | float | 0.35 m | Obstacle response distance |
auto_start_tracking | bool | true | Start line tracking automatically |
lost_line_stop | bool | true | Stop after the line-lost timeout without exiting the node |
initial_pitch_deg | float | -90.0° | Pan-tilt tilt-down angle (facing the ground) |
3. Node Description
| Node | Package | Description |
|---|---|---|
follow_line_ptz_node | microros_v2_ptz_visual_control_pkg | PTZ visual line tracking + obstacle avoidance, dual timers + PID + pan-tilt tilt-down |
6. Frequently Asked Questions
| Problem | Cause | Solution |
|---|---|---|
| Line tracking sways left/right | PID gains too large | Reduce line_pid_kp / line_pid_kd |
| Line not detected | HSV threshold mismatch | Box-select the line with the mouse to re-sample |
| Obstacle avoidance too sensitive | obstacle_response_distance too large | Reduce the response distance |
| Stops suddenly during tracking | lost_line_stop=true or HSV/LiDAR input lost | Check the input; set to false as needed after confirmation |
| Abnormal pan-tilt angle | Servo not initialized correctly | Check the PTZ power supply and servo connection |
| Easily leaves the line while tracking | Unstable Wi-Fi causes frame stutter, so the control parameters cannot prevent deviation however they are tuned | Use a clean, stable 2.4G Wi-Fi network and re-tune the control parameters |