diff --git a/.gitignore b/.gitignore index ea4c7ea2d..4af7835f5 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,20 @@ A3_rs.launch test/ test.launch .vscode/ + +# Python bytecode +__pycache__/ +*.pyc +*.pyo +*.pyd + +# ROS 2 / colcon and runtime logs +/log/ +/logs/ +*.log +feature_tracker_crash.log + +# colcon build artifacts +build/ +install/ +log/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..8e8418b4d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,373 @@ +# CHANGELOG + +## [Unreleased] - 2026-01-04 + +### Added - Unified Feature Detection & Optical Flow Control +- **Single configuration flag (`use_advanced_flow`)** to switch between original VINS-Fusion and enhanced UAV implementation + - `use_advanced_flow: 0` — Original VINS-Fusion style: + - Shi-Tomasi corner detector (`goodFeaturesToTrack`) + - Basic Lucas-Kanade optical flow (default parameters) + - No forward-backward consistency check + - `use_advanced_flow: 1` — Enhanced UAV implementation (default): + - AGAST corner detector with grid-based spatial distribution + - Advanced LK with strict termination criteria (40 iterations, 0.001 eps) + - Optional forward-backward consistency check (if `use_bidirectional_flow: 1`) + - Subpixel refinement for top 50 features +- **Rationale**: Single toggle for complete feature tracking pipeline — useful for benchmarking and fallback to proven VINS-Fusion behavior +- **Configuration**: + ```yaml + use_advanced_flow: 1 # 1=enhanced (AGAST), 0=original (Shi-Tomasi) + use_bidirectional_flow: 0 # Only active when use_advanced_flow=1 + fast_threshold: 20 # AGAST response threshold (use_advanced_flow=1) + ``` + +## [Previous] - 2026-01-02 + +### Added - Velocity-Based Adaptive Feature Tracking +- **Dynamic feature count adjustment** based on estimated velocity for high-speed navigation stability + - Estimates velocity from optical flow magnitude between frames + - Automatically boosts feature count when velocity exceeds threshold + - Default: 150 features → 200 features (thermal) or 150 features (USB) at high speed + - Configurable velocity threshold (default: 2.5-3.0 m/s) + - Prevents odometry failure during fast UAV motion +- **Configuration parameters**: + ```yaml + enable_velocity_check: 1 # Enable/disable adaptive tracking + max_velocity_threshold: 2.5 # Velocity threshold (m/s) + velocity_boost_features: 50 # Extra features to add at high speed + min_parallax_threshold: 3.0 # Minimum parallax (pixels) + ``` + +### Modified - System Robustness & Error Handling +- **Negative timestamp handling**: System now gracefully handles timestamp synchronization issues after reboot + - Detects negative `dt_1` values and skips frame with warning instead of assertion failure + - Prevents crashes during system recovery after failure detection +- **Increased failure detection thresholds** for high-speed navigation: + - Horizontal position jump: 5m → 10m + - Vertical position jump: 1m → 3m + - Reduces false positive reboots during aggressive maneuvers + +### Modified - Performance Optimization +- **Bidirectional optical flow disabled by default** in both camera configs for ~40-50% speedup + - Trade-off: slightly reduced tracking accuracy for real-time performance + - Can be re-enabled via `use_bidirectional_flow: 1` if CPU allows +- **Reduced solver complexity**: + - `max_solver_time`: 0.035s → 0.03s + - `max_num_iterations`: 20-25 → 15 iterations + - `keyframe_parallax`: 5-8 → 10 pixels (fewer keyframes) +- **Optimized feature tracking**: + - Thermal camera: 200 → 150 features base count + - USB camera: maintained at 150 features + - `min_dist`: 10-12 → 15 pixels (thermal), 10 pixels (USB) + - `fast_threshold`: 15-20 → 20 (fewer but stronger corners) + +### Modified - Camera-Specific Tuning +- **Thermal camera (384×288) optimizations**: + - `F_threshold`: 2.0 (increased noise tolerance) + - `keyframe_parallax`: 10.0 pixels + - `min_parallax_threshold`: 3.0 pixels + - Stricter ZUPT: `vel_threshold=0.15`, `acc_threshold=0.25` +- **USB camera (640×480) optimizations**: + - `F_threshold`: 1.0 (higher resolution = tighter threshold) + - `min_parallax_threshold`: 5.0 pixels + - `max_velocity_threshold`: 3.0 m/s + +### Added - Velocity Logging +- **Real-time velocity output** in estimator logs + - Horizontal velocity: `sqrt(vx² + vy²)` in m/s + - Vertical velocity: `vz` in m/s + - Format: `Pose: x=... y=... z=... | Vel: horiz=... vert=... m/s` + - Helps diagnose IMU drift and odometry issues + +### Technical Details +- Velocity estimation formula: `velocity = (avg_optical_flow / (FOCAL_LENGTH * dt)) * depth_scale` +- Depth scale factor: 3.0 (assumes ~3m average scene depth) +- Flow outlier rejection: ignores features with >100px displacement +- Adaptive feature count updated at start of each `readImage()` call +- Feature boost only applied when sufficient tracked points available (>10 features) + +## [Previous] - 2026-01-01/02 + +### Added - IMU Data Filtering & Scale Correction + +### Added - Pose Graph TF Publishing (Loop-Closed Frame) +- **TF transform `world -> camera_pose_graph`** now published from `pose_graph` after loop-closure correction + - Uses loop-closed pose (`r_drift`, `t_drift`) exactly matching `/pose_graph/pose_graph_path` + - Frame id: `world`; Child frame: `camera_pose_graph` + - Broadcast created post-`ros::init` to avoid initialization crashes +- **Dependencies updated**: added `tf` to `pose_graph` package (CMake/package.xml) + +### Modified - Time Offset Estimation (td) +- **Periodic td optimization** to reduce runtime cost: td parameter is optimized in short bursts every `estimate_td_period` seconds (default 7s) +- When idle between bursts, td stays constant and projection factors fall back to fast version without td term +- Optional config key `estimate_td_period` added (seconds) +- **Logging improvements**: + - `td` value printed only when optimization occurs (not every frame) + - Optimized trajectory position (`x, y, z`) printed every frame for monitoring + - Format: `Optimized pose: x=... y=... z=...` and `td updated: ...` (when changed) + +#### Accelerometer Outlier Rejection +- **Spike detection and filtering** for noisy 9-DOF IMU data + - Detects sudden acceleration changes exceeding configurable threshold (default: 50 m/s²) + - Rejects outlier measurements and uses previous filtered value + - Prevents incorrect velocity/position estimates from bad IMU readings + - Applied in both `predict()` and main processing loop + +#### Exponential Smoothing Filter +- **Low-pass filtering** for accelerometer noise reduction + - Exponential moving average (EMA) with configurable alpha (default: 0.8) + - Reduces high-frequency noise while maintaining responsiveness + - Works in conjunction with outlier rejection + - Formula: `filtered = alpha * prev_filtered + (1-alpha) * current` + +#### Altitude-Based Scale Correction +- **MAVLink velocity integration** for scale drift prevention + - Subscribes to `/mavros/local_position/velocity_local` (geometry_msgs/TwistStamped) + - Uses **only Z-component** of normalized global velocity vector + - **Z-axis always points up** regardless of IMU orientation (global frame) + - X and Y velocities ignored (unreliable in local position estimate) + - Integrates vertical velocity to estimate altitude + - Corrects monocular VIO scale drift using altitude comparison + - Gentle correction with configurable strength (default: 10% per update) + - Safety bounds: only corrects scale ratio between 0.8-1.2 + - Automatically scales positions, velocities, and feature depths + - Only active after successful VIO initialization + +#### Configuration Parameters +```yaml +# IMU Filtering +enable_imu_acc_filter: 1 # Enable/disable accelerometer filtering +imu_acc_max_change: 50.0 # Outlier detection threshold (m/s²) +imu_acc_filter_alpha: 0.8 # Smoothing factor (0.0-1.0) + +# Altitude Scale Correction +enable_altitude_scale_correction: 1 # Enable/disable scale correction +altitude_correction_factor: 0.1 # Correction strength (0.0-1.0) +``` + +### Added - Feature Detection & Tracking + +#### AGAST Corner Detector +- **Replaced Shi-Tomasi with AGAST** (Adaptive Generic Accelerated Segment Test) + - Modern, faster corner detection algorithm + - Configurable threshold via `fast_threshold` parameter (default: 20) + - Adaptive feature detection for varying lighting conditions + +#### Grid-Based Feature Selection +- **Uniform spatial distribution** of features across image + - Divides image into grid cells of size `MIN_DIST` + - Selects strongest feature per grid cell + - Memory-efficient implementation using `std::unordered_map` + - Prevents feature clustering in high-texture areas + +#### Bidirectional Optical Flow +- **Forward-Backward consistency check** for robust tracking + - Forward flow: current → next frame + - Backward flow: next → current frame + - Outlier rejection when error > 0.7 pixels + - Configurable via `use_bidirectional_flow` parameter (0/1) + - Can be disabled for performance on resource-constrained systems + +#### Sub-Pixel Refinement Optimization +- **Smart refinement strategy** to reduce computational cost + - Applies sub-pixel refinement only to top-50 strongest features + - Reduces processing time while maintaining accuracy + - Uses OpenCV `cornerSubPix` with TermCriteria (40 iterations, 0.001 epsilon) + +#### CLAHE Optimization +- **Contrast Limited Adaptive Histogram Equalization** performance improvement + - CLAHE object created once in constructor, reused per frame + - Eliminates redundant object allocation overhead + - Critical for thermal imaging with low contrast + +### Added - Visual-Inertial Odometry + +#### Zero Velocity Update (ZUPT) +- **Drift prevention during stationary periods** + - Detects when camera/IMU is stationary + - Resets velocity to zero to prevent IMU integration drift + - Configurable parameters: + - `enable_zupt`: 0/1 to disable/enable + - `zupt_vel_threshold`: velocity threshold (m/s) for detection + - `zupt_acc_threshold`: acceleration deviation threshold (m/s²) + - Applied in `processIMU()` when velocity < threshold AND acceleration ≈ gravity + +### Added - Configuration & Launch Files + +#### Thermal Camera Configuration +- **Complete thermal camera support** (`config/termal_cam_config.yaml`) + - Resolution: 384×288 pixels + - Camera model: PINHOLE + - Intrinsics: fx=382.17, fy=381.92, cx=177.60, cy=137.18 + - Distortion coefficients: k1=-0.4137, k2=0.1618, p1=-0.0004, p2=-0.0022 + - IMU-Camera extrinsics (from Kalibr calibration) + - Rotation matrix: 90° CCW around Z-axis with axis corrections + - Feature tracker tuning for thermal imagery: + - max_cnt: 180 features + - min_dist: 30 pixels (reduced clustering) + - fast_threshold: 40 (stronger corners only) + - F_threshold: 2.0 pixels (RANSAC outlier rejection) + - equalize: 1 (CLAHE enabled) + - use_bidirectional_flow: 1 + - IMU parameters: + - acc_n: 0.1 (measurement noise) + - gyr_n: 0.05 (measurement noise) + - acc_w: 0.002 (bias random walk) + - gyr_w: 4.0e-5 (bias random walk) + - ZUPT configuration: + - enable_zupt: 1 + - zupt_vel_threshold: 0.2 m/s + - zupt_acc_threshold: 0.2 m/s² + +#### Launch File for Thermal Camera +- **Created** `vins_estimator/launch/termal_cam.launch` + - Configured for `/thermal_cam/image_raw` topic + - Uses thermal camera config file + - Ready for deployment + +### Modified - Feature Tracking Visualization + +#### Depth-Based Feature Size Rendering +- **Visual debugging enhancement** in `feature_tracker_node.cpp` + - Circle radius (1-4 pixels) based on optical flow velocity magnitude + - Velocity magnitude as proxy for depth (closer features move faster) + - Mapping: velocity / 10.0 → radius increment + - Helps diagnose: + - Scale/depth estimation issues + - Feature motion patterns + - Parallax distribution + - Color still indicates track length (blue=new, red=long-tracked) + +### Modified - Configuration Files + +#### USB Camera Configuration +- **Updated** `config/usb_cam_config.yaml` + - Added `use_bidirectional_flow` parameter + - Added ZUPT parameters (vel_threshold: 0.05, acc_threshold: 0.1) + - Changed `estimate_td` from 0 to 1 for time offset estimation + +#### RVIZ Configuration +- **Updated** `config/vins_rviz_config.rviz` + - Enabled `history_point` cloud visualization + - Adjusted point cloud size and rendering + - Modified camera view distance and angle + - Updated window geometry + +### Performance Improvements + +#### Memory Efficiency +- **Grid allocation**: Replaced full `vector` with `unordered_map` for sparse grid storage +- **CLAHE reuse**: Single object creation instead of per-frame allocation +- **Sub-pixel refinement**: Limited to top-50 features instead of all detected + +#### Processing Speed +- **Optional bidirectional flow**: Can be disabled for faster tracking +- **Grid-based selection**: Faster than sorting all features by response +- **AGAST detector**: Faster than Shi-Tomasi for equivalent quality + +### Debugging & Diagnostics + +#### Feature Visualization +- Circle size indicates estimated distance/depth +- Color indicates tracking age +- Helps identify: + - Scale drift + - Initialization problems + - Feature quality distribution + +#### Console Output +- ZUPT status messages when velocity reset occurs +- CLAHE processing time tracking +- Feature detection timing breakdown + +### Technical Details + +#### File Changes +- `vins_estimator/src/estimator_node.cpp`: IMU filtering, MAVLink odometry subscription +- `vins_estimator/src/estimator.h/cpp`: Scale correction method `correctScaleWithAltitude()` +- `vins_estimator/src/feature_manager.h/cpp`: Depth scaling method `scaleDepth()` +- `feature_tracker/src/feature_tracker.cpp`: Core detection and tracking logic +- `feature_tracker/src/feature_tracker.h`: Added CLAHE member variable +- `feature_tracker/src/feature_tracker_node.cpp`: Visualization modifications +- `feature_tracker/src/parameters.h/cpp`: Added FAST_THRESHOLD, USE_BIDIRECTIONAL_FLOW +- `vins_estimator/src/parameters.h/cpp`: Added ZUPT parameters +- `config/termal_cam_config.yaml`: Thermal camera complete configuration +- `config/usb_cam_config.yaml`: Parameter updates +- `vins_estimator/launch/termal_cam.launch`: New launch file + +#### Commit History +- `37418d2`: Bidirectional optical flow implementation +- `6710bdb`: FAST corner detector (replaced by AGAST later) +- `a51f8bd`: AGAST feature detection +- `a7fdcab`: AGAST + Grid selection + Lucas-Kanade optimization +- `9024f49`: Thermal camera configuration + +### Dependencies +- OpenCV 4.x (required for AGAST, CLAHE APIs) +- ROS (Kinetic/Melodic/Noetic) +- Eigen 3 +- Ceres Solver + +### Configuration Parameters Reference + +#### Feature Tracker Parameters +```yaml +max_cnt: 150-180 # Maximum number of tracked features +min_dist: 10-30 # Minimum distance between features (pixels) +fast_threshold: 20-40 # AGAST corner detection threshold +use_bidirectional_flow: 0/1 # Enable forward-backward tracking +F_threshold: 1.0-2.0 # RANSAC threshold for fundamental matrix +equalize: 0/1 # Enable CLAHE histogram equalization +``` + +#### ZUPT Parameters +```yaml +enable_zupt: 0/1 # Enable/disable zero velocity update +zupt_vel_threshold: 0.05-0.2 # Velocity threshold (m/s) +zupt_acc_threshold: 0.1-0.2 # Acceleration deviation threshold (m/s²) +``` + +### Known Issues & Future Work + +#### Addressing 9-DOF IMU Noise +- **Problem**: 9-DOF IMU occasionally produces incorrect velocity readings +- **Solution Implemented**: + - Accelerometer spike detection and rejection (threshold: 50 m/s²) + - Exponential smoothing filter (alpha: 0.8) + - Altitude-based scale correction using barometer-fused odometry +- **Result**: Robust to intermittent IMU errors while maintaining accurate orientation from gyroscope + +#### Pending Optimizations +- Multi-threading for AGAST detection (parallel processing) +- Memory management improvements (unique_ptr migration in estimator.cpp) + +#### System Stability +- Camera "flying away" issue under investigation +- Depth-based visualization added for diagnosis +- Potential causes: + - Initialization quality (requires sufficient motion) + - IMU calibration accuracy + - Scale observability in monocular VIO + +### IMU Requirements + +**Supported IMU Types:** +- **6-DOF IMU required** (3-axis accelerometer + 3-axis gyroscope) +- Magnetometer NOT used (9-DOF sensors work but mag data ignored) +- Standard ROS `sensor_msgs/Imu` message format +- Required calibration parameters: + - Measurement noise: `acc_n`, `gyr_n` + - Bias random walk: `acc_w`, `gyr_w` + +### References & Credits + +- Original VINS-Mono: HKUST Aerial Robotics Group +- AGAST detector: Elmar Mair et al. +- CLAHE: Karel Zuiderveld +- Kalibr calibration toolkit: ASL ETH Zurich +- Branch: `UAV_perfom` +- Author: Devitt Dmitry + +--- + +**Note**: This changelog tracks improvements made to the UAV performance optimization branch. For production deployment, thorough testing on target hardware is recommended. diff --git a/FEATURE_TRACKER_AUTO_CALIBRATION.md b/FEATURE_TRACKER_AUTO_CALIBRATION.md new file mode 100644 index 000000000..0fdac4005 --- /dev/null +++ b/FEATURE_TRACKER_AUTO_CALIBRATION.md @@ -0,0 +1,219 @@ +# Feature Tracker Auto-Calibration Guide + +## Overview + +The feature tracker auto-calibration system automatically optimizes detector parameters based on camera resolution and field-of-view (FOV). This ensures robust feature detection and tracking across different camera types without manual tuning. + +## How It Works + +### Calibration Logic + +The system calculates three key parameters at startup: + +#### 1. Maximum Feature Count (`max_cnt`) +``` +Formula: max_cnt = max(80, image_area / 1500) +Rationale: Target ~1500 pixels per feature for good tracking stability +``` + +**Examples:** +- Thermal (384×288 = 110,592 px): max_cnt = max(80, 74) = **80 features** +- VGA (640×480 = 307,200 px): max_cnt = max(80, 205) = **205 features** + +#### 2. Minimum Distance Between Features (`min_dist`) +``` +Formula: min_dist = max(15, sqrt(image_area / max_cnt * 0.8)) +Rationale: Ensure uniform grid distribution with 80% fill factor +``` + +**Examples:** +- Thermal: min_dist = max(15, sqrt(1380)) = max(15, **37**) = 37 px +- VGA: min_dist = max(15, sqrt(1199)) = max(15, **35**) = 35 px + +#### 3. AGAST Corner Detection Threshold (`fast_threshold`) +``` +Formula: fast_threshold = 25 + (image_area / 200000) +Rationale: Adapt sensitivity to resolution (lower = more sensitive) +``` + +**Examples:** +- Thermal: fast_threshold = 25 + 0.55 = **25.6 ≈ 26** +- VGA: fast_threshold = 25 + 1.54 = **26.5 ≈ 27** + +## Configuration + +### Enable/Disable Auto-Calibration + +```yaml +# In config file (thermal_cam_config.yaml or usb_cam_config.yaml) + +# Enable auto-calibration (overrides manual max_cnt, min_dist, fast_threshold) +auto_calibrate_tracker: 1 # 1 = enabled, 0 = disabled + +# Print statistics to terminal +calibrate_print_stats: 1 # 1 = print, 0 = silent +``` + +### Manual Configuration (When `auto_calibrate_tracker: 0`) + +```yaml +max_cnt: 150 # Keep manual values +min_dist: 25 +fast_threshold: 30 +``` + +## Console Output + +### Initialization Output + +When the feature tracker initializes with auto-calibration enabled: + +``` +[INFO] === Feature Tracker Auto-Calibration === +[INFO] Image resolution: 384x288 (110592 pixels) +[INFO] Manual config: max_cnt=180, min_dist=20, fast_threshold=30 +[INFO] Optimal config: max_cnt=96, min_dist=22, fast_threshold=26 +[WARN] Applied auto-calibrated feature tracker parameters! +``` + +### Real-time Statistics + +Every frame (if `calibrate_print_stats: 1`): + +``` +[INFO] [TRACKER STATS] Tracked: 82 | New: 18 | Total: 100/96 (104.2%) | Time: 12.3ms +``` + +**Fields:** +- `Tracked`: Features with age > 1 frame (good for tracking) +- `New`: Features detected in current frame (age = 1) +- `Total/Max`: Current features vs. maximum capacity +- `(%)`: Fill ratio - should stay 70-100% for optimal performance +- `Time`: Feature tracking processing time + +## Use Cases + +### Case 1: Thermal Camera (384×288) +``` +Application: UAV with thermal imaging +Config: auto_calibrate_tracker: 1 + +Results: +- Auto-calibrated: max_cnt=96, min_dist=22, fast_threshold=26 +- Benefit: Adapted for low-contrast thermal imagery +- Tracking quality: 80-100 stable features per frame +``` + +### Case 2: Standard USB Camera (640×480) +``` +Application: Desktop vision system +Config: auto_calibrate_tracker: 1 + +Results: +- Auto-calibrated: max_cnt=205, min_dist=35, fast_threshold=27 +- Benefit: More features for higher resolution +- Tracking quality: 150-200 stable features per frame +``` + +### Case 3: Override with Manual Tuning +```yaml +# Want specific parameters for your use case +auto_calibrate_tracker: 0 # Disable auto-calibration +max_cnt: 120 # Custom values +min_dist: 28 +fast_threshold: 35 +``` + +## Performance Monitoring + +### Healthy Feature Distribution + +``` +[TRACKER STATS] Tracked: 85 | New: 15 | Total: 100/100 (100.0%) | Time: 10.2ms +``` +✅ Good - 85% of features are tracked, 15% are new + +### Under-Featured + +``` +[TRACKER STATS] Tracked: 45 | New: 5 | Total: 50/100 (50.0%) | Time: 5.1ms +``` +⚠️ Warning - Less than 70% feature capacity being used + +**Solutions:** +- Increase `max_cnt` or decrease `min_dist` +- Lower `fast_threshold` to detect weaker corners +- Check if image is too dark (enable CLAHE with `equalize: 1`) + +### Over-Featured + +``` +[TRACKER STATS] Tracked: 120 | New: 30 | Total: 150/100 (150.0%) | Time: 25.3ms +``` +⚠️ Warning - More features than capacity (tracking will skip features) + +**Solutions:** +- Decrease `max_cnt` or increase `min_dist` +- Increase `fast_threshold` to select only strong corners +- Reduce `AGAST` iterations if processing time exceeds 20ms + +## Technical Details + +### Grid-Based Feature Selection + +Features are selected using a grid-based approach: +1. Image divided into `min_dist × min_dist` cells +2. Strongest corner in each cell is selected +3. Result: Uniform feature distribution (no clustering) + +### Inverse Depth Parameterization + +Features use inverse depth for triangulation: +- Closer features = larger inverse depth values +- Scale correction adjusts all inverse depths proportionally +- Prevents scale ambiguity in monocular VIO + +## Troubleshooting + +### Issue: "Feature detection too slow" + +``` +[TRACKER STATS] ... | Time: 45.2ms +``` + +**Solution:** +- Reduce `AGAST` iterations (in parameters.cpp) +- Disable `use_bidirectional_flow: 0` +- Lower `max_cnt` further + +### Issue: "Not enough features for tracking" + +``` +[TRACKER STATS] Tracked: 25 | New: 10 | Total: 35/100 (35.0%) +``` + +**Solution:** +- Enable histogram equalization: `equalize: 1` +- Lower `fast_threshold` by 5-10 +- Increase `max_cnt` + +### Issue: "Features jumping around" + +**Solution:** +- Enable `use_bidirectional_flow: 1` for consistency check +- Increase `F_threshold` for stricter outlier rejection +- Check camera calibration accuracy + +## References + +- **AGAST** - Accelerated Segment Test detector (Mair et al.) +- **Lucas-Kanade** Optical Flow - Feature tracking algorithm +- **Grid-based selection** - Uniform feature distribution +- **Inverse depth** - Monocular VIO parameterization + +## Related Parameters + +- **IMU Filtering**: `enable_imu_acc_filter`, `imu_acc_filter_alpha` +- **Scale Correction**: `enable_altitude_scale_correction` +- **ZUPT**: `enable_zupt`, `zupt_vel_threshold` +- **CLAHE**: `equalize` (histogram equalization) diff --git a/FEATURE_TRACKER_QUICK_REF.md b/FEATURE_TRACKER_QUICK_REF.md new file mode 100644 index 000000000..a4ebd8f9b --- /dev/null +++ b/FEATURE_TRACKER_QUICK_REF.md @@ -0,0 +1,89 @@ +# Feature Tracker Auto-Calibration - Quick Reference + +## Enable/Disable + +```yaml +# config/thermal_cam_config.yaml +auto_calibrate_tracker: 1 # Auto-calibrate (1) or manual (0) +calibrate_print_stats: 1 # Print stats to terminal +``` + +## What Gets Calibrated + +| Parameter | Manual | Auto (Thermal 384×288) | Auto (VGA 640×480) | +|-----------|--------|------------------------|-------------------| +| `max_cnt` | 180 | 96 | 205 | +| `min_dist` | 20 | 22 | 35 | +| `fast_threshold` | 30 | 26 | 27 | + +## Terminal Output + +### At Startup +``` +[INFO] === Feature Tracker Auto-Calibration === +[INFO] Image resolution: 384x288 (110592 pixels) +[INFO] Manual config: max_cnt=180, min_dist=20, fast_threshold=30 +[INFO] Optimal config: max_cnt=96, min_dist=22, fast_threshold=26 +[WARN] Applied auto-calibrated feature tracker parameters! +``` + +### Every Frame +``` +[INFO] [TRACKER STATS] Tracked: 82 | New: 18 | Total: 100/96 (104.2%) | Time: 12.3ms + └─ Tracked features └─ New features └─ Current/Max └─ Fill% └─ ms +``` + +## Target Statistics + +| Metric | Target | Range | +|--------|--------|-------| +| Fill Ratio | 80-100% | 70-120% | +| Tracked Ratio | 70-85% | 50-90% | +| Processing Time | < 15ms | < 20ms | + +## Auto-Calibration Formulas + +``` +max_cnt = max(80, image_pixels / 1500) +min_dist = max(15, sqrt(image_pixels / max_cnt * 0.8)) +fast_threshold = 25 + (image_pixels / 200000) +``` + +## Troubleshooting + +| Problem | Cause | Solution | +|---------|-------|----------| +| Too few features (< 50%) | Detector too strict | Lower `fast_threshold` | +| Too many features (> 120%) | Detector too loose | Increase `fast_threshold` | +| Processing slow (> 20ms) | Too many features | Increase `min_dist` | +| Dark image, no features | Low contrast | Enable `equalize: 1` | +| Features jumping | Outliers in tracking | Enable `use_bidirectional_flow: 1` | + +## Files Modified + +- `feature_tracker/src/parameters.h/cpp` - Auto-calibration logic +- `feature_tracker/src/feature_tracker_node.cpp` - Statistics output +- `config/thermal_cam_config.yaml` - Configuration +- `config/usb_cam_config.yaml` - Configuration +- `CHANGELOG.md` - Documentation + +## Quick Test + +```bash +# 1. Enable auto-calibration in config file +auto_calibrate_tracker: 1 +calibrate_print_stats: 1 + +# 2. Build +cd ~/catkin_ws && catkin build + +# 3. Run and check output +roslaunch vins_estimator vins_rviz.launch + +# 4. Look for [TRACKER STATS] lines in terminal +# Should show: Tracked: X | New: Y | Total: Z/MAX (%) +``` + +--- + +For details, see: `FEATURE_TRACKER_AUTO_CALIBRATION.md` diff --git a/README.md b/README.md index 179a5cf13..03dc66ec7 100644 --- a/README.md +++ b/README.md @@ -1,161 +1,546 @@ -# VINS-Mono -## A Robust and Versatile Monocular Visual-Inertial State Estimator +# VINS-Mono-Inno for ROS 2 -**11 Jan 2019**: An extension of **VINS**, which supports stereo cameras / stereo cameras + IMU / mono camera + IMU, is published at [VINS-Fusion](https://github.com/HKUST-Aerial-Robotics/VINS-Fusion) +VINS-Mono-Inno is a ROS 2 port and UAV-oriented fork of +[VINS-Mono](https://github.com/HKUST-Aerial-Robotics/VINS-Mono) by the HKUST +Aerial Robotics Group. It provides monocular visual-inertial odometry, feature +tracking, optional loop closure, and pose-graph optimization. -**29 Dec 2017**: New features: Add map merge, pose graph reuse, online temporal calibration function, and support rolling shutter camera. Map reuse videos: +This repository uses ROS 2 packages, `ament_cmake`, Python launch files, and +`colcon`. The old ROS 1 `catkin_make`, `roslaunch`, and `rosbag play` commands do +not apply to this version. - - +The current migration is developed and tested on Ubuntu 24.04 with ROS 2 Jazzy. -VINS-Mono is a real-time SLAM framework for **Monocular Visual-Inertial Systems**. It uses an optimization-based sliding window formulation for providing high-accuracy visual-inertial odometry. It features efficient IMU pre-integration with bias correction, automatic estimator initialization, online extrinsic calibration, failure detection and recovery, loop detection, and global pose graph optimization, map merge, pose graph reuse, online temporal calibration, rolling shutter support. VINS-Mono is primarily designed for state estimation and feedback control of autonomous drones, but it is also capable of providing accurate localization for AR applications. This code runs on **Linux**, and is fully integrated with **ROS**. For **iOS** mobile implementation, please go to [VINS-Mobile](https://github.com/HKUST-Aerial-Robotics/VINS-Mobile). +## Packages -**Authors:** [Tong Qin](http://www.qintonguav.com), [Peiliang Li](https://github.com/PeiliangLi), [Zhenfei Yang](https://github.com/dvorak0), and [Shaojie Shen](http://www.ece.ust.hk/ece.php/profile/facultydetail/eeshaojie) from the [HKUST Aerial Robotics Group](http://uav.ust.hk/) +- `camera_model` — pinhole and omnidirectional camera models. +- `feature_tracker` — image decoding, feature detection, optical flow, masks, + and debug image publication. +- `vins_estimator` — sliding-window visual-inertial estimator. +- `pose_graph` — loop detection and pose-graph optimization. +- `vins_bringup` — ROS 2 launch files and installed configuration files. +- `benchmark_publisher` — optional benchmark visualization support. -**Videos:** +The repository also contains UAV-specific additions such as adaptive feature +tracking, optional bidirectional optical flow, IMU filtering, ZUPT, temporal +offset estimation, and MAVROS integration. - - - +## Requirements -EuRoC dataset; Indoor and outdoor performance; AR application; +Install ROS 2 Jazzy and the required ROS packages: - - +```bash +sudo apt update +sudo apt install \ + ros-jazzy-cv-bridge \ + ros-jazzy-image-transport \ + ros-jazzy-compressed-image-transport \ + ros-jazzy-rviz2 \ + ros-jazzy-tf2-ros +``` - MAV application; Mobile implementation (Video link for mainland China friends: [Video1](http://www.bilibili.com/video/av10813254/) [Video2](http://www.bilibili.com/video/av10813205/) [Video3](http://www.bilibili.com/video/av10813089/) [Video4](http://www.bilibili.com/video/av10813325/) [Video5](http://www.bilibili.com/video/av10813030/)) +The project also requires OpenCV, Eigen, Boost, and Ceres Solver. On Ubuntu, +the development packages can normally be installed with: -**Related Papers** +```bash +sudo apt install libopencv-dev libeigen3-dev libboost-all-dev libceres-dev +``` -* **Online Temporal Calibration for Monocular Visual-Inertial Systems**, Tong Qin, Shaojie Shen, IEEE/RSJ International Conference on Intelligent Robots and Systems (IROS, 2018), **best student paper award** [pdf](https://ieeexplore.ieee.org/abstract/document/8593603) +## Build -* **VINS-Mono: A Robust and Versatile Monocular Visual-Inertial State Estimator**, Tong Qin, Peiliang Li, Zhenfei Yang, Shaojie Shen, IEEE Transactions on Robotics[pdf](https://ieeexplore.ieee.org/document/8421746/?arnumber=8421746&source=authoralert) +Place the repository inside a ROS 2 workspace, then build it with `colcon`: -*If you use VINS-Mono for your academic research, please cite at least one of our related papers.*[bib](https://github.com/HKUST-Aerial-Robotics/VINS-Mono/blob/master/support_files/paper_bib.txt) +```bash +mkdir -p ~/ros2_ws/src +cd ~/ros2_ws/src +git clone https://github.com/Innopolis-UAV-Team/VINS-Mono-inno.git -## 1. Prerequisites -1.1 **Ubuntu** and **ROS** -Ubuntu 16.04. -ROS Kinetic. [ROS Installation](http://wiki.ros.org/ROS/Installation) -additional ROS pacakge +cd ~/ros2_ws +source /opt/ros/jazzy/setup.bash +colcon build --symlink-install +source install/setup.bash ``` - sudo apt-get install ros-YOUR_DISTRO-cv-bridge ros-YOUR_DISTRO-tf ros-YOUR_DISTRO-message-filters ros-YOUR_DISTRO-image-transport + +If the repository is already located in `~/ros2_ws/src`, only the last four +commands are needed. Add the ROS and workspace setup files to `.bashrc` if you +want new terminals to source them automatically. + +## Run the Innospector fisheye bag configuration + +The launch file `innospector_cam.launch.py` starts: + +- `feature_tracker`; +- `vins_estimator`; +- `pose_graph`. + +Its parameters are stored in +`config/fisheye_bag/fisheye_bag_config.yaml`. The supplied configuration expects: + +- compressed camera images on `/image_raw/compressed`; +- IMU messages on `/mavros/imu/data`. + +The YAML uses the image-transport base topic `/image_raw` together with +`image_input_format: "compressed"`. Do not change the base topic to +`/image_raw/compressed` when compressed transport is enabled. + +Terminal 1 — start VINS: + +```bash +cd ~/ros2_ws +source /opt/ros/jazzy/setup.bash +source install/setup.bash +ros2 launch vins_bringup innospector_cam.launch.py ``` +Terminal 2 — play the ROS 2 bag: + +```bash +cd ~/ros2_ws +source /opt/ros/jazzy/setup.bash +source install/setup.bash +ros2 bag play /path/to/bag +``` -1.2. **Ceres Solver** -Follow [Ceres Installation](http://ceres-solver.org/installation.html), use **version 1.14.0** and remember to **sudo make install**. (There are compilation issues in Ceres versions 2.0.0 and above.) +Only the required bag topics may be played: -## 2. Build VINS-Mono on ROS -Clone the repository and catkin_make: +```bash +ros2 bag play /path/to/bag \ + --topics /image_raw/compressed /mavros/imu/data ``` - cd ~/catkin_ws/src - git clone https://github.com/HKUST-Aerial-Robotics/VINS-Mono.git - cd ../ - catkin_make - source ~/catkin_ws/devel/setup.bash + +Terminal 3 — start the supplied RViz configuration: + +```bash +source ~/ros2_ws/install/setup.bash +ros2 launch vins_bringup vins_rviz.launch.py ``` -## 3. Visual-Inertial Odometry and Pose Graph Reuse on Public datasets -Download [EuRoC MAV Dataset](http://projects.asl.ethz.ch/datasets/doku.php?id=kmavvisualinertialdatasets). Although it contains stereo cameras, we only use one camera. The system also works with [ETH-asl cla dataset](http://robotics.ethz.ch/~asl-datasets/maplab/multi_session_mapping_CLA/bags/). We take EuRoC as the example. +RViz is recommended for viewing the tracked-feature image. Some versions of +`rqt_image_view` perform poorly with large, high-rate images. -**3.1 visual-inertial odometry and loop closure** +## Other launch configurations -3.1.1 Open three terminals, launch the vins_estimator , rviz and play the bag file respectively. Take MH_01 for example +All supported ROS 2 launch files are owned by `vins_bringup`. The +`vins_estimator` package contains the estimator executable and algorithm; it is +not a duplicate bringup package and no longer contains legacy ROS 1 XML launch +files. Always launch a complete configuration with: + +```bash +ros2 launch vins_bringup .launch.py ``` - roslaunch vins_estimator euroc.launch - roslaunch vins_estimator vins_rviz.launch - rosbag play YOUR_PATH_TO_DATASET/MH_01_easy.bag + +For the thermal camera configuration, start the estimator pipeline with: + +```bash +cd ~/ros2_ws +source /opt/ros/jazzy/setup.bash +source install/setup.bash +ros2 launch vins_bringup termal_cam.launch.py ``` -(If you fail to open vins_rviz.launch, just open an empty rviz, then load the config file: file -> Open Config-> YOUR_VINS_FOLDER/config/vins_rviz_config.rviz) -3.1.2 (Optional) Visualize ground truth. We write a naive benchmark publisher to help you visualize the ground truth. It uses a naive strategy to align VINS with ground truth. Just for visualization. not for quantitative comparison on academic publications. +Then start RViz in another terminal: + +```bash +source ~/ros2_ws/install/setup.bash +ros2 launch vins_bringup vins_rviz.launch.py ``` - roslaunch benchmark_publisher publish.launch sequence_name:=MH_05_difficult + +The thermal launch reads `config/termal_cam_config.yaml` and starts +`feature_tracker`, `vins_estimator`, and `pose_graph` together. + +```bash +ros2 launch vins_bringup euroc.launch.py +ros2 launch vins_bringup euroc_no_extrinsic_param.launch.py +ros2 launch vins_bringup realsense_color.launch.py +ros2 launch vins_bringup realsense_fisheye.launch.py +ros2 launch vins_bringup usb_cam.launch.py +ros2 launch vins_bringup termal_cam.launch.py +ros2 launch vins_bringup 3dm.launch.py ``` - (Green line is VINS result, red line is ground truth). - -3.1.3 (Optional) You can even run EuRoC **without extrinsic parameters** between camera and IMU. We will calibrate them online. Replace the first command with: + +Review the corresponding file under `config/` before using a launch file. Topic +names, camera calibration, camera-to-IMU extrinsics, IMU noise parameters, and +time synchronization must match the actual sensor system. + +## Verify the pipeline + +Check that the source topics are active: + +```bash +ros2 topic hz /image_raw/compressed +ros2 topic hz /mavros/imu/data ``` - roslaunch vins_estimator euroc_no_extrinsic_param.launch + +Check each processing stage: + +```bash +ros2 topic hz /feature_tracker/feature +ros2 topic hz /feature_tracker/feature_img +ros2 topic hz /vins_estimator/odometry +ros2 topic hz /vins_estimator/path ``` -**No extrinsic parameters** in that config file. Waiting a few seconds for initial calibration. Sometimes you cannot feel any difference as the calibration is done quickly. -**3.2 map merge** +Inspect endpoint types and QoS when a publisher and subscriber do not connect: -After playing MH_01 bag, you can continue playing MH_02 bag, MH_03 bag ... The system will merge them according to the loop closure. +```bash +ros2 topic info -v /image_raw/compressed +ros2 topic info -v /feature_tracker/feature +``` -**3.3 map reuse** +Expected types include: -3.3.1 map save +- `/image_raw/compressed`: `sensor_msgs/msg/CompressedImage`; +- `/feature_tracker/feature`: `sensor_msgs/msg/PointCloud`; +- `/feature_tracker/feature_img`: `sensor_msgs/msg/Image`; +- `/vins_estimator/odometry`: `nav_msgs/msg/Odometry`. -Set the **pose_graph_save_path** in the config file (YOUR_VINS_FOLEDER/config/euroc/euroc_config.yaml). After playing MH_01 bag, input **s** in vins_estimator terminal, then **enter**. The current pose graph will be saved. +## Camera masks and image formats -3.3.2 map load +`feature_tracker` supports ROS 2 raw and compressed image transport. Select the +transport in the camera configuration: -Set the **load_previous_pose_graph** to 1 before doing 3.1.1. The system will load previous pose graph from **pose_graph_save_path**. Then you can play MH_02 bag. New sequence will be aligned to the previous pose graph. +```yaml +image_topic: "/image_raw" +image_input_format: "compressed" # use "raw" for sensor_msgs/msg/Image +``` -## 4. AR Demo -4.1 Download the [bag file](https://www.dropbox.com/s/s29oygyhwmllw9k/ar_box.bag?dl=0), which is collected from HKUST Robotic Institute. For friends in mainland China, download from [bag file](https://pan.baidu.com/s/1geEyHNl). +A custom feature mask can restrict tracking to valid image pixels: -4.2 Open three terminals, launch the ar_demo, rviz and play the bag file respectively. +```yaml +fisheye: 1 +fisheye_mask: "config/mask.jpg" ``` - roslaunch ar_demo 3dm_bag.launch - roslaunch ar_demo ar_rviz.launch - rosbag play YOUR_PATH_TO_DATASET/ar_box.bag + +The mask must have the same width and height as the configured camera. White +pixels enable feature detection and black pixels exclude an area. This mask +does not select or replace the camera projection model. + +## Feature tracker parameters + +These parameters are set in the camera YAML configuration file. They control +corner detection and optical-flow tracking. These are not BRIEF descriptor +parameters: BRIEF is used separately by the `pose_graph` module for loop +closure. + +The ranges below are practical starting ranges for a 1280×1024 image at +~30 FPS, not hard limits enforced by code. For other resolutions, `max_cnt` +should scale roughly with frame area and `min_dist` with linear size. The final +choice should be guided by CPU load and `[AUTO_TUNE]` output: aim for `fill` +above 90% and stable `retention` above 70–80% on segments without heavy motion +blur. + +| Parameter | Purpose and effect | Practical range / starting point | +|---|---|---| +| `max_cnt` | Target maximum number of tracked points per frame. Existing tracks are kept with priority and distributed evenly across the image; free slots are filled with new points. A higher value generally improves robustness but increases CPU load, RANSAC cost, and optimizer load. This is an upper bound, not a guarantee of the actual point count. | For 1280×1024: `150–800`; start at `250–400`. Above `500–600` only makes sense with sufficient CPU and texture. | +| `min_dist` | Minimum spatial distance between selected points, in pixels. A larger value gives sparser, more uniform coverage; too large prevents reaching `max_cnt`. A small value allows more points but increases the risk of clusters on a single textured region. | `8–40 px`; start at `15–25 px`. For `max_cnt: 250`, `10–25 px` is acceptable. | +| `freq` | Target rate for feature set publication and replenishment, in Hz. Incoming images are still used for optical flow, but RANSAC, new-point detection, and publication run only on selected frames. `0` in the config is replaced with `100`. | `10–30 Hz`; start at `20–25 Hz`. Generally do not set above the actual camera rate. | +| `F_threshold` | RANSAC error threshold for the fundamental-matrix check, in pixels on the virtual undistorted image. A lower value rejects outliers more aggressively but may drop good tracks under noise or blur. A higher value keeps more points, including potential outliers. | `0.5–2.0 px`; start at `1.0 px`. For sharp images try `0.7–1.0`; for noisy/blurry ones `1.0–1.5`. | +| `gftt_quality_level` | Relative quality threshold for Shi–Tomasi `goodFeaturesToTrack`; mathematically valid range `(0, 1]`. Used for new points only when `use_advanced_flow: 0`. Lower = more weak corners; higher = fewer but usually better. | Working range `0.001–0.03`; start at `0.005–0.01`. Values above `0.05` often yield too few points. | +| `fast_threshold` | AGAST threshold for new points. Used only when `use_advanced_flow: 1`. Lower = more candidates including weak and noisy ones; higher = fewer candidates with stronger contrast. | `7–40`; start at `15–20`. For low-contrast/thermal images often `7–15`; for noisy ones `20–30`. | +| `enable_feature_auto_tuning` | Enables (`1`) periodic adjustment of the active detector threshold. For AGAST it adjusts `fast_threshold`; for Shi–Tomasi it adjusts `gftt_quality_level`. `max_cnt`, `min_dist`, `F_threshold`, and optical-flow parameters are not changed automatically. | `0` or `1` only; start with `0` for manual baseline tuning, then try `1` on the full dataset. | +| `feature_auto_tune_interval` | Auto-tune interval in seconds; minimum allowed value is `0.5`. Fill and retention statistics are accumulated between updates. Active only when auto-tuning is enabled. | `2–30 s`; start at `5–10 s`. Below `2 s` may cause unnecessary threshold oscillation. | +| `feature_auto_tune_log` | When `1`, prints an `[AUTO_TUNE]` line to the ROS console and log after each interval. It reports fill, retention, candidate count, and current thresholds. Does not enable auto-tuning itself. | `0` or `1` only; recommended `1` during tuning. | +| `fast_threshold_min`, `fast_threshold_max` | Lower and upper bounds within which auto-tuning may change the AGAST threshold. When auto-tuning is off, the bounds do not modify `fast_threshold` except for initial clamping at config load. | General safe range `3–60`; starting pair `7` and `40–45`. The gap between bounds should be at least `10`. | +| `gftt_quality_min`, `gftt_quality_max` | Lower and upper bounds for automatic adjustment of `gftt_quality_level` (Shi–Tomasi). When `use_advanced_flow: 1`, this range is not used for new-point selection. | Minimum `0.0005–0.005`, maximum `0.02–0.10`; starting pair `0.001` and `0.03–0.05`. | +| `use_bidirectional_flow` | When `1`, runs a reverse LK pass after the forward flow and rejects points with poor forward-backward consistency. Improves robustness but nearly doubles optical-flow cost. Only implemented in the `use_advanced_flow: 1` branch. | `0` or `1` only; `1` for quality, `0` when CPU is limited. | +| `use_advanced_flow` | Selects the tracker mode. `1`: AGAST, spatial selection, subpixel refinement of a subset, and extended LK settings. `0`: Shi–Tomasi and basic LK. This parameter also determines which threshold auto-tuning adjusts. | `0` or `1` only; use `1` for the current uniform AGAST-based selection. | +| `show_track` | When `1`, publishes an image with track visualization on `/feature_tracker/feature_img`. Does not disable publication of the feature points themselves on `/feature_tracker/feature`. Visualization adds CPU and ROS bandwidth cost. | `0` or `1` only; `1` during tuning, `0` for minimal overhead. | +| `equalize` | When `1`, applies CLAHE to each grayscale frame before tracking and detection. Helps with low or uneven lighting but may amplify noise and adds computational cost. | `0` or `1` only; for dark/thermal cameras start with `1`; with good stable lighting compare against `0`. | +| `fisheye` | When `1`, enables applying `fisheye_mask` to point selection. This parameter does not select the camera model or enable fisheye distortion correction; the model is set separately via `model_type`. | `0` or `1` only; `1` if a mask is actually needed and matches the frame. | +| `fisheye_mask` | Path to a single-channel mask matching the image size. White pixels allow features, black pixels exclude regions. Relative paths are resolved against the `vins_bringup` package share directory; absolute paths are used as-is. | Non-numeric. Size must match `image_width × image_height`; the white usable area should be large enough to reach `max_cnt`. | + +Example manual AGAST configuration: + +```yaml +max_cnt: 250 +min_dist: 10 +fast_threshold: 20 +use_advanced_flow: 1 +enable_feature_auto_tuning: 0 ``` -We put one 0.8m x 0.8m x 0.8m virtual box in front of your view. -## 5. Run with your device +In this mode `gftt_quality_level`, `gftt_quality_min`, and `gftt_quality_max` +do not affect new-point detection. `feature_auto_tune_interval` and +`feature_auto_tune_log` take effect only after setting +`enable_feature_auto_tuning: 1`. + +### `use_advanced_flow: 1` pipeline (AGAST + LK) + +When `use_advanced_flow: 1` is set, the feature tracker uses an enhanced +processing pipeline instead of the basic Shi-Tomasi path. The full per-frame +pipeline is described below. + +```mermaid +flowchart TD + A["Input: new frame
+ timestamp"] --> B{"EQUALIZE?"} + B -->|Yes| C["CLAHE
clipLimit=3.0, grid=8x8"] + B -->|No| D["Use as-is"] + C --> E["forw_img = frame"] + D --> E + + E --> F{"Existing tracks
from prev frame?"} + F -->|No| G["Skip LK"] + F -->|Yes| H["LK optical flow
cur_img → forw_img
window 21x21, pyr=3
TermCriteria COUNT+EPS 40/0.001"] + + H --> I{"use_bidirectional_flow?"} + I -->|Yes| J["Reverse LK
forw_img → cur_img
reject if dist² > 0.5 px²"] + I -->|No| K["Skip"] + J --> L["Border check
reject edge points"] + K --> L + + L --> M["reduceVector
remove rejected"] + M --> N{"ENABLE_VELOCITY_CHECK?"} + N -->|Yes| O["Estimate velocity
from optical flow"] + O --> P{"vel > threshold?"} + P -->|Yes| Q["adaptive_max_cnt += boost"] + P -->|No| R["adaptive_max_cnt = MAX_CNT"] + N -->|No| R + + R --> S["track_cnt++ for all"] + S --> T{"PUB_THIS_FRAME?
freq throttle"} + T -->|No| Z["End of frame"] + T -->|Yes| U["rejectWithF()
RANSAC F-matrix
F_THRESHOLD, conf=0.99
on undistorted points"] + + U --> V["setMask()
fisheye_mask + MIN_DIST
spatial bucketing
round-robin selection"] + V --> W["Detect new points"] + W --> X["AGAST
threshold = fast_threshold"] + X --> Y["Grid filter
cells MIN_DIST x MIN_DIST
strongest response per cell"] + Y --> AA["Spatial bucketing
round-robin across buckets
until n_max_cnt filled"] + AA --> BB["cornerSubPix
top-50 points
window 5x5, EPS+COUNT 40/0.001"] + BB --> CC["addPoints()
new points → forw_pts"] + CC --> DD["updateAutoTuning()
fill/retention stats
adjust fast_threshold"] + DD --> EE["undistortedPoints()
liftProjective → normalized
compute pts_velocity"] + EE --> Z +``` -Suppose you are familiar with ROS and you can get a camera and an IMU with raw metric measurements in ROS topic, you can follow these steps to set up your device. For beginners, we highly recommend you to first try out [VINS-Mobile](https://github.com/HKUST-Aerial-Robotics/VINS-Mobile) if you have iOS devices since you don't need to set up anything. +#### Step-by-step description + +1. **Preprocessing** — optional CLAHE equalization (`equalize: 1`) for dark + or unevenly lit scenes. Applied before tracking and detection. + +2. **LK optical flow** — `cv::calcOpticalFlowPyrLK` tracks points from the + previous frame into the current one. Parameters: + - Window: 21×21 pixels + - Pyramid levels: 3 + - Termination criteria: 40 iterations or ε = 0.001 + +3. **Bidirectional check** (optional, `use_bidirectional_flow: 1`) — reverse + LK flow from the current frame back to the previous one. A point is rejected + if the squared distance between the original and reverse position exceeds + 0.5 px² (≈ 0.7 px). Doubles LK cost but sharply reduces drift on repetitive + textures. + +4. **Border check** — reject points that moved outside the image border + (1 px margin). + +5. **Velocity-adaptive boost** (optional, `enable_velocity_check: 1`) — + estimates velocity from average optical flow. When `vel > + max_velocity_threshold`, `velocity_boost_features` extra points are added. + +6. **RANSAC F-matrix rejection** (`rejectWithF`) — on frames selected for + publication (by `freq`), points are checked via the fundamental matrix. + Uses undistorted normalized coordinates (via the camera model's + `liftProjective`). Threshold `F_threshold`, confidence 0.99. + +7. **setMask** — builds a mask for new-point selection: + - Base: `fisheye_mask` (if `fisheye: 1`) or all-white + - Existing tracks are marked with circles of radius `MIN_DIST` (prevents + clustering) + - Spatial bucketing: round-robin across buckets for uniform coverage + +8. **AGAST detection** — `cv::AGAST` with threshold `fast_threshold`. Returns + keypoints with response values. + +9. **Grid filter** — the image is divided into cells of `MIN_DIST × MIN_DIST`. + Only the keypoint with the highest response is kept per cell. This provides + spatial distribution without a separate NMS step. + +10. **Spatial bucketing** — round-robin across coarse buckets (as in setMask) + until `n_max_cnt = adaptive_max_cnt - tracked_count` is filled. + +11. **Subpixel refinement** — `cv::cornerSubPix` for the top-50 points + (window 5×5, criteria EPS+COUNT 40/0.001). Improves accuracy to subpixel + level. + +12. **Auto-tuning** (`enable_feature_auto_tuning: 1`) — accumulates `fill` + (fill ratio) and `retention` (fraction of tracks kept) statistics over the + `feature_auto_tune_interval`. Adjusts `fast_threshold`: + - `fill < 0.90` → threshold −2 (more points) + - `fill > 0.98 && retention < 0.75` → threshold +2 (fewer but better) + +13. **Undistortion** — the camera model's `liftProjective` converts pixel + coordinates to normalized ones (x/z, y/z). Point velocities + (`pts_velocity`) are computed for the estimator. + +#### Parameters affecting the pipeline + +| Parameter | Stage | Effect | +|---|---|---| +| `equalize` | 1. Preprocessing | CLAHE for dark scenes | +| `use_bidirectional_flow` | 3. LK | Reject inconsistent tracks | +| `F_threshold` | 6. RANSAC | Geometric outlier rejection strictness | +| `max_cnt` | 7-10. Detection | Target point count | +| `min_dist` | 7-9. Mask/Grid | Spatial distribution | +| `fast_threshold` | 8. AGAST | Corner detector threshold | +| `enable_feature_auto_tuning` | 12. Auto-tune | Threshold adaptation | +| `fisheye` / `fisheye_mask` | 7. setMask | Restrict working area | +| `freq` | 6. RANSAC | Publication and replenishment rate | + +#### Comparison with `use_advanced_flow: 0` + +| Property | `use_advanced_flow: 1` | `use_advanced_flow: 0` | +|---|---|---| +| Detector | AGAST + grid + subpixel | Shi-Tomasi (`goodFeaturesToTrack`) | +| LK parameters | Stricter TermCriteria | Basic | +| Bidirectional | Optional | Not available | +| Subpixel refine | Yes (top-50) | No | +| Spatial bucketing | Round-robin across buckets | No | +| Auto-tune | Adjusts `fast_threshold` | Adjusts `gftt_quality_level` | +| CPU cost | Higher (AGAST + refine) | Lower | + +### Changing parameters at runtime + +All parameters in the table are declared as dynamic ROS 2 parameters. After +launching `feature_tracker`, open the GUI: + +```bash +source ~/ros2_ws/install/setup.bash +rqt_reconfigure +``` -5.1 Change to your topic name in the config file. The image should exceed 20Hz and IMU should exceed 100Hz. Both image and IMU should have the accurate time stamp. IMU should contain absolute acceleration values including gravity. +Select `/feature_tracker` in the tree. Numeric fields have valid bounds, and +changes are applied without restarting the node. The same can be done from the +terminal: + +```bash +ros2 param set /feature_tracker max_cnt 350 +ros2 param set /feature_tracker min_dist 15 +ros2 param set /feature_tracker fast_threshold 18 +ros2 param set /feature_tracker enable_feature_auto_tuning true +ros2 param get /feature_tracker fast_threshold +ros2 param dump /feature_tracker +``` -5.2 Camera calibration: +Change related bounds in a safe order, otherwise the intermediate combination +will be rejected. For example, when widening the range, increase the maximum +first, then decrease the minimum: -We support the [pinhole model](http://docs.opencv.org/2.4.8/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.html) and the [MEI model](http://www.robots.ox.ac.uk/~cmei/articles/single_viewpoint_calib_mei_07.pdf). You can calibrate your camera with any tools you like. Just write the parameters in the config file in the right format. If you use rolling shutter camera, please carefully calibrate your camera, making sure the reprojection error is less than 0.5 pixel. +```bash +ros2 param set /feature_tracker fast_threshold_max 45 +ros2 param set /feature_tracker fast_threshold_min 7 +``` -5.3 **Camera-Imu extrinsic parameters**: +The node validates `*_min <= *_max`, `fisheye_mask` size, and other valid +ranges. Accepted changes are logged with a `[RUNTIME_CONFIG]` line. Runtime +changes are not written back to the YAML config and will be lost on restart. +To persist tuned values, copy them into the YAML manually or save the output of +`ros2 param dump`. -If you have seen the config files for EuRoC and AR demos, you can find that we can estimate and refine them online. If you familiar with transformation, you can figure out the rotation and position by your eyes or via hand measurements. Then write these values into config as the initial guess. Our estimator will refine extrinsic parameters online. If you don't know anything about the camera-IMU transformation, just ignore the extrinsic parameters and set the **estimate_extrinsic** to **2**, and rotate your device set at the beginning for a few seconds. When the system works successfully, we will save the calibration result. you can use these result as initial values for next time. An example of how to set the extrinsic parameters is in[extrinsic_parameter_example](https://github.com/HKUST-Aerial-Robotics/VINS-Mono/blob/master/config/extrinsic_parameter_example.pdf) +## Calibration notes -5.4 **Temporal calibration**: -Most self-made visual-inertial sensor sets are unsynchronized. You can set **estimate_td** to 1 to online estimate the time offset between your camera and IMU. +Reliable VIO requires all of the following: -5.5 **Rolling shutter**: -For rolling shutter camera (carefully calibrated, reprojection error under 0.5 pixel), set **rolling_shutter** to 1. Also, you should set rolling shutter readout time **rolling_shutter_tr**, which is from sensor datasheet(usually 0-0.05s, not exposure time). Don't try web camera, the web camera is so awful. +1. Camera intrinsics and distortion parameters for the exact image resolution. +2. An accurate camera-to-IMU rotation and translation. +3. Correct IMU noise and bias random-walk parameters. +4. Monotonic timestamps and a known or estimated camera/IMU time offset. +5. Sufficient motion and parallax during estimator initialization. -5.6 Other parameter settings: Details are included in the config file. +Use `estimate_extrinsic: 0` only when the supplied extrinsic transform is known +to be accurate. Use `estimate_td: 1` for unsynchronized sensors; otherwise fix +the calibrated offset with `estimate_td: 0` and `td: `. -5.7 Performance on different devices: +For a physical fisheye or very wide-angle camera, calibrate an appropriate +camodocal model such as `KANNALA_BRANDT` or `MEI`. Setting `fisheye: 1` only +enables the feature mask; it does not change the projection model. -(global shutter camera + synchronized high-end IMU, e.g. VI-Sensor) > (global shutter camera + synchronized low-end IMU) > (global camera + unsync high frequency IMU) > (global camera + unsync low frequency IMU) > (rolling camera + unsync low frequency IMU). +## Loop closure and pose graph parameters -## 6. Docker Support +Loop closure is configured in the camera YAML file: -To further facilitate the building process, we add docker in our code. Docker environment is like a sandbox, thus makes our code environment-independent. To run with docker, first make sure [ros](http://wiki.ros.org/ROS/Installation) and [docker](https://docs.docker.com/install/linux/docker-ce/ubuntu/) are installed on your machine. Then add your account to `docker` group by `sudo usermod -aG docker $YOUR_USER_NAME`. **Relaunch the terminal or logout and re-login if you get `Permission denied` error**, type: +```yaml +loop_closure: 1 +load_previous_pose_graph: 0 +fast_relocalization: 0 +pose_graph_save_path: "/tmp/vins_thermal_output/pose_graph/" ``` -cd ~/catkin_ws/src/VINS-Mono/docker -make build -./run.sh LAUNCH_FILE_NAME # ./run.sh euroc.launch + +`loop_closure` enables image-based place recognition and pose graph +optimization: + +- `0` runs local visual-inertial odometry only. Accumulated drift is not + corrected by previously visited places. +- `1` loads the BRIEF vocabulary, searches for previously observed keyframes, + verifies candidates geometrically, and optimizes the pose graph after an + accepted loop. + +`load_previous_pose_graph` controls map reuse at startup: + +- `0` starts a new pose graph. +- `1` loads a previously saved graph from `pose_graph_save_path` and attempts + to align the current sequence with it. The saved graph must be compatible + with the current camera calibration and configuration. + +`fast_relocalization` sends geometrically verified loop matches back to the +VINS estimator: + +- `0` keeps loop correction primarily inside the pose graph. +- `1` publishes matched points to the estimator for faster correction of its + local state. Enable this only after ordinary loop closure has been verified + as stable. + +`pose_graph_save_path` is the directory used to save and load keyframes, +BRIEF descriptors, graph constraints, and trajectory data. Paths under `/tmp` +are normally removed after a reboot. Use a persistent location for map reuse, +for example: + +```yaml +pose_graph_save_path: "/home/jetson/vins_maps/thermal_pose_graph/" +``` + +For a first run, the recommended settings are: + +```yaml +loop_closure: 1 +load_previous_pose_graph: 0 +fast_relocalization: 0 +``` + +The console reports loop processing with `[LOOP]` messages. A real closure is +confirmed by `[LOOP] accepted`; a DBoW image candidate may still be rejected by +geometric verification. + +## Tests + +Build and run the package tests with: + +```bash +cd ~/ros2_ws +source install/setup.bash +colcon test --packages-select feature_tracker +colcon test-result --verbose ``` -Note that the docker building process may take a while depends on your network and machine. After VINS-Mono successfully started, open another terminal and play your bag file, then you should be able to see the result. If you need modify the code, simply run `./run.sh LAUNCH_FILE_NAME` after your changes. +Additional migration and runtime checks are documented in +[ROS2_FISHEYE_CHECKLIST.md](ROS2_FISHEYE_CHECKLIST.md), +[ROS2_MIGRATION_PLAN.md](ROS2_MIGRATION_PLAN.md), and +[ROS2_MIGRATION_HANDOFF.md](ROS2_MIGRATION_HANDOFF.md). + +## Original project and citation + +VINS-Mono is an optimization-based monocular visual-inertial SLAM framework by +Tong Qin, Peiliang Li, Zhenfei Yang, and Shaojie Shen. If this software is used +for academic research, cite the original publications: -## 7. Acknowledgements -We use [ceres solver](http://ceres-solver.org/) for non-linear optimization and [DBoW2](https://github.com/dorian3d/DBoW2) for loop detection, and a generic [camera model](https://github.com/hengli/camodocal). +- T. Qin, P. Li, Z. Yang, and S. Shen, “VINS-Mono: A Robust and Versatile + Monocular Visual-Inertial State Estimator,” IEEE Transactions on Robotics. +- T. Qin and S. Shen, “Online Temporal Calibration for Monocular + Visual-Inertial Systems,” IROS 2018. -## 8. Licence -The source code is released under [GPLv3](http://www.gnu.org/licenses/) license. +This fork uses Ceres Solver, DBoW2, OpenCV, Eigen, Boost, and camodocal. -We are still working on improving the code reliability. For any technical issues, please contact Tong QIN or Peiliang LI . +## License -For commercial inquiries, please contact Shaojie SHEN +The source code is released under the GPLv3 license. This ROS 2 fork is +maintained by the Innopolis UAV Team; the original VINS-Mono authors retain +credit for the underlying system. diff --git a/ROS2_FISHEYE_CHECKLIST.md b/ROS2_FISHEYE_CHECKLIST.md new file mode 100644 index 000000000..760122129 --- /dev/null +++ b/ROS2_FISHEYE_CHECKLIST.md @@ -0,0 +1,190 @@ +# ROS2 fisheye bag checklist + +Этот чек-лист подходит для запуска `vins_bringup` с bag, где входная картинка идет в формате `sensor_msgs/msg/CompressedImage`. + +## 1. Перед запуском + +Убедись, что bag уже запущен и публикует: + +- `/image_raw/compressed` +- `/mavros/imu/data` + +Проверка: + +```bash +source ~/.bashrc +ros2 topic list | grep -E 'image_raw/compressed|mavros/imu/data' +ros2 topic hz /image_raw/compressed +ros2 topic hz /mavros/imu/data +``` + +Ожидаемо: + +- `/image_raw/compressed` есть +- `/mavros/imu/data` есть +- частота у обоих топиков не нулевая + +## 2. Конфиг + +Для bag-режима в `config/fisheye_bag/fisheye_bag_config.yaml` должны быть: + +```yaml +image_topic: "/image_raw/compressed" +image_input_format: "compressed" +imu_topic: "/mavros/imu/data" +estimate_extrinsic: 0 +extrinsicTranslation: [0.0, 0.0, 0.0] +``` + +Если позже захочешь вернуть обычную raw-камеру: + +```yaml +image_topic: "/camera/fisheye/image_raw" +image_input_format: "raw" +``` + +## 3. Запуск + +В одном терминале: + +```bash +source ~/.bashrc +ros2 launch vins_bringup innospector_cam.launch.py +``` + +## 4. Что должно появиться в консоли + +### feature_tracker + +Должны быть строки вроде: + +```text +[START] image_input_format=compressed +[START] compressed subscription created for /image_raw/compressed +[INPUT] first compressed image received: ... +[INPUT] first image received: ... +[STATE] tracker primed on first frame +[OUTPUT] published feature packet #1 ... +``` + +### vins_estimator + +Должны быть строки вроде: + +```text +[START] vins_estimator is up and waiting for synchronized measurement pairs +[INPUT] imu #1 ... +[INPUT] first feature packet received: ... +``` + +### pose_graph + +Нормально увидеть: + +```text +no previous pose graph +``` + +## 5. Проверка топиков после старта + +В другом терминале: + +```bash +source ~/.bashrc +ros2 topic list | grep -E 'image_raw/compressed|feature_tracker|vins_estimator|pose_graph' +ros2 topic info -v /image_raw/compressed +ros2 topic hz /image_raw/compressed +ros2 topic hz /feature_tracker/feature +ros2 topic hz /feature_tracker/feature_img +ros2 topic hz /vins_estimator/odometry +``` + +Ожидаемо: + +- `/image_raw/compressed` публикуется rosbag с типом `sensor_msgs/msg/CompressedImage` +- `feature_tracker` подписан на него через transport `compressed` +- `/feature_tracker/feature` имеет ненулевую частоту +- `/feature_tracker/feature_img` имеет частоту около заданного `freq` и тип `sensor_msgs/msg/Image` +- `/vins_estimator/odometry` появляется после того, как наберется достаточно кадров + +## 6. Если что-то не работает + +### Сценарий A: есть IMU, но нет image callback + +Это значит, что `feature_tracker` не получает картинку. + +Проверь: + +```bash +ros2 topic echo --once /image_raw/compressed +ros2 topic info -v /image_raw/compressed +ros2 node list +``` + +### Сценарий B: `feature_tracker` получает картинку, но `vins_estimator` пишет `queue_feat=0` + +Это значит, что `feature_tracker` не публикует `feature`. + +Проверь логи `feature_tracker`: + +```text +[OUTPUT] published feature packet ... +``` + +### Сценарий C: `vins_estimator` пишет `imu message in disorder!` + +Это обычно проблема порядка временных меток. + +Проверь: + +```bash +ros2 topic hz /mavros/imu/data +ros2 topic echo --once /mavros/imu/data +``` + +### Сценарий D: launch стартует, но видны только старые предупреждения FastDDS + +Сообщения вида: + +```text +RTPS_TRANSPORT_SHM Error +``` + +часто не критичны для логики VINS, но могут мешать диагностике. +Главное — есть ли реальные сообщения на нужных топиках. + +## 7. Быстрая команда “все ли живо” + +```bash +source ~/.bashrc +ros2 topic hz /image_raw/compressed +ros2 topic hz /mavros/imu/data +ros2 topic hz /feature_tracker/feature +ros2 topic hz /vins_estimator/odometry +``` + +Если первые две частоты есть, а последние две нет — проблема между камерой и VINS. + +## 8. Диагностические сообщения новой цепочки + +При нормальной работе последовательно появляются: + +```text +[INPUT] first image received ... encoding=mono8 +[OUTPUT] published feature packet ... +[OUTPUT] published feature image ... /feature_tracker/feature_img +[INPUT] first feature packet received ... +[OUTPUT] accepted keyframe ... +``` + +Сообщения `[FLOW]`, `inconsistent feature vectors`, `non-increasing image timestamps`, +`invalid keyframe point layout` и `[SYNC] ... exhausted` означают конкретное место разрыва. + +Unit-тесты защитных проверок: + +```bash +cd ~/ros2_ws +source ~/.bashrc +colcon test --packages-select feature_tracker --event-handlers console_direct+ +colcon test-result --verbose +``` diff --git a/ROS2_MIGRATION_HANDOFF.md b/ROS2_MIGRATION_HANDOFF.md new file mode 100644 index 000000000..235742bd3 --- /dev/null +++ b/ROS2_MIGRATION_HANDOFF.md @@ -0,0 +1,521 @@ +# VINS-Mono ROS1 → ROS2 Migration Handoff Document + +## Для агента-преемника + +Этот документ содержит полную информацию о состоянии миграции VINS-Mono из ROS1 в ROS2 Jazzy на Jetson Orin NX. Передавайся от одного агента к другому для продолжения работы. + +--- + +## 1. Окружение + +| Параметр | Значение | +|-----------|----------| +| **ROS2** | Jazzy Jalisco (`/opt/ros/jazzy`) | +| **Hardware** | Jetson Orin NX, aarch64, 8 ядер, 9.5 GB RAM | +| **Workspace** | `~/ros2_ws` | +| **Source** | `~/ros2_ws/src/VINS-Mono-inno` | +| **Git branch** | `ros2` (ответвлён от `UAV_perfom`) | +| **Build command** | `cd ~/ros2_ws && source /opt/ros/jazzy/setup.bash && source install/setup.bash 2>/dev/null; colcon build --symlink-install --packages-select --base-paths src` | +| **C++ standard** | C++17 (обязательно для ROS2 Jazzy) | + +### Системные библиотеки (все установлены) +- Ceres 2.2.0 (`libceres-dev`) — **Важно: `ceres::LocalParameterization` удалён, используется `ceres::Manifold`** +- OpenCV 4.6 (`libopencv-dev`) +- Eigen 3.4 (`libeigen3-dev`) +- Boost 1.83 (`libboost-all-dev`) +- yaml-cpp 0.8 + +### ROS2 пакеты (все установлены) +- `rclcpp`, `std_msgs`, `sensor_msgs`, `nav_msgs`, `geometry_msgs`, `visualization_msgs` +- `tf2_ros`, `tf2_geometry_msgs`, `tf2_eigen` +- `cv_bridge` (header: `cv_bridge/cv_bridge.hpp`, не `.h`!) +- `image_transport`, `message_filters` +- `ament_cmake`, `ament_index_cpp` + +### Важно: `sensor_msgs::msg::PointCloud` + `ChannelFloat32` +В ROS2 Jazzy эти сообщения **доступны, но deprecated**. Решено использовать их как есть для минимизации изменений кода. + +--- + +## 2. Структура пакетов (6 пакетов) + +``` +~/ros2_ws/src/VINS-Mono-inno/ +├── camera_model/ # ✅ ГОТОВО — библиотека + Calibration tool +├── benchmark_publisher/ # ✅ ГОТОВО — простейший узел +├── feature_tracker/ # ✅ ГОТОВО — трекинг фич +├── vins_estimator/ # ✅ ГОТОВО — основной оценщик (самый сложный) +├── pose_graph/ # ✅ ГОТОВО — loop closure + pose graph +├── vins_bringup/ # ✅ ГОТОВО — launch-пакет +├── config/ # Общие YAML конфиги (не требуют изменений) +├── support_files/ # brief_k10L6.bin, brief_pattern.yml +├── ROS2_MIGRATION_PLAN.md # Общий план миграции +└── ROS2_MIGRATION_HANDOFF.md # Этот документ +``` + +### Статус сборки +| Пакет | Статус | Собран | +|-------|--------|--------| +| `camera_model` | ✅ Полностью мигрирован | Да | +| `benchmark_publisher` | ✅ Полностью мигрирован | Да | +| `feature_tracker` | ✅ Полностью мигрирован | Да | +| `vins_estimator` | ✅ Полностью мигрирован | Да | +| `pose_graph` | ✅ Полностью мигрирован | Да | +| `vins_bringup` | ✅ Создан и собран | Да | + +`ar_demo` и `data_generator` намеренно исключены из текущего scope и удалены из дерева репозитория. + +--- + +## 3. Ключевые архитектурные решения (уже реализованные) + +### 3.1 Ceres 2.2 Compatibility Shim +Файл: `camera_model/include/camodocal/gpl/ceres_local_parameterization_compat.h` + +Ceres 2.2 полностью удалил `ceres::LocalParameterization`. Создан shim-класс, который наследует `ceres::Manifold` и предоставляет старый API `LocalParameterization` (`Plus`, `ComputeJacobian`, `GlobalSize`, `LocalSize`). + +**Использование в коде:** +```cpp +#include "camodocal/gpl/ceres_local_parameterization_compat.h" + +// Старый код продолжает работать: +class PoseLocalParameterization : public ceres::LocalParameterization { ... }; + +// Но тип указателя меняется: +ceres::Manifold* local_parameterization = new PoseLocalParameterization(); // НЕ LocalParameterization* +problem.SetManifold(para_Pose[i], local_parameterization); // НЕ AddParameterBlock(..., local_parameterization) +``` + +**Также доступно:** +```cpp +// ceres::QuaternionManifold() — встроенный в Ceres 2.2 +ceres::Manifold* q_manifold = new ceres::QuaternionManifold(); + +// AutoDiffManifold заменяет AutoDiffLocalParameterization +static ceres::Manifold* Create() { + return new ceres::AutoDiffManifold(); +} +``` + +### 3.2 Helper функция для timestamp +Добавлена в `vins_estimator/src/utility/utility.h`: +```cpp +inline double stampToSec(const builtin_interfaces::msg::Time &stamp) { + return (double)stamp.sec + (double)stamp.nanosec * 1e-9; +} +``` +Используется вместо `msg->header.stamp.toSec()`. + +### 3.3 Паттерн миграции ROS1 → ROS2 (уже отработанный) + +#### Includes +```cpp +// ROS1 → ROS2 +#include → #include "rclcpp/rclcpp.hpp" +#include → #include "sensor_msgs/msg/image.hpp" +#include → #include "sensor_msgs/msg/point_cloud.hpp" +#include → #include "sensor_msgs/msg/imu.hpp" +#include → #include "sensor_msgs/image_encodings.hpp" +#include → #include "std_msgs/msg/bool.hpp" +#include → #include "std_msgs/msg/header.hpp" +#include → #include "nav_msgs/msg/odometry.hpp" +#include → #include "nav_msgs/msg/path.hpp" +#include → #include "geometry_msgs/msg/point_stamped.hpp" +#include → #include "geometry_msgs/msg/point32.hpp" +#include → #include "geometry_msgs/msg/point.hpp" +#include → #include "geometry_msgs/msg/twist_stamped.hpp" +#include → #include "visualization_msgs/msg/marker.hpp" +#include → #include "visualization_msgs/msg/marker_array.hpp" +#include → #include "std_msgs/msg/color_rgba.hpp" +#include → #include "tf2_ros/transform_broadcaster.h" +#include → #include "cv_bridge/cv_bridge.hpp" // ВАЖНО: .hpp, не .h! +``` + +#### API замены +```cpp +// ROS1 → ROS2 +ros::init(argc, argv, "node") → rclcpp::init(argc, argv) +ros::NodeHandle n("~") → auto node = std::make_shared("node") +ros::console::set_logger_level(...) → (не нужно, rclcpp по умолчанию Info) +readParameters(n) → readParameters(node) // signature: rclcpp::Node::SharedPtr + +// Parameters +n.getParam("key", var) → node->declare_parameter("key", default); node->get_parameter("key", var) +readParam(n, "key") → (см. parameters.cpp в feature_tracker/vins_estimator для шаблона) + +// Subscribers +ros::Subscriber sub = n.subscribe(topic, q, cb) + → auto sub = node->create_subscription(topic, q, cb) + +// Publishers +ros::Publisher pub = n.advertise(topic, q) + → rclcpp::Publisher::SharedPtr pub = node->create_publisher(topic, q) +pub.publish(msg) → pub->publish(msg) // для shared_ptr: pub->publish(*msg) + +// Spin +ros::spin() → rclcpp::spin(node) +ros::ok() → rclcpp::ok() + +// Time +ros::Time::now().toSec() → node->now().seconds() +msg->header.stamp.toSec() → stampToSec(msg->header.stamp) // или rclcpp::Time(msg->header.stamp).seconds() +ros::Time(double_t) → см. helper ниже + +// Logging +ROS_INFO(...) → RCLCPP_INFO(rclcpp::get_logger("node_name"), ...) +ROS_WARN(...) → RCLCPP_WARN(rclcpp::get_logger("node_name"), ...) +ROS_DEBUG(...) → RCLCPP_DEBUG(rclcpp::get_logger("node_name"), ...) +ROS_INFO_STREAM(...) → RCLCPP_INFO_STREAM(rclcpp::get_logger("node_name"), ...) +ROS_ASSERT(cond) → assert(cond) +ROS_BREAK() → std::abort() +ROS_WARN_THROTTLE(2.0, ...) → RCLCPP_WARN_SKIPFIRST_THROTTLE(logger, clock, 2000, ...) + +// Message types +sensor_msgs::ImageConstPtr → sensor_msgs::msg::Image::ConstSharedPtr +sensor_msgs::PointCloudConstPtr → sensor_msgs::msg::PointCloud::ConstSharedPtr +sensor_msgs::PointCloudPtr → sensor_msgs::msg::PointCloud::SharedPtr +nav_msgs::Odometry::ConstPtr → nav_msgs::msg::Odometry::ConstSharedPtr +std_msgs::BoolConstPtr → std_msgs::msg::Bool::ConstSharedPtr +geometry_msgs::TwistStamped::ConstPtr → geometry_msgs::msg::TwistStamped::ConstSharedPtr +sensor_msgs::ChannelFloat32 → sensor_msgs::msg::ChannelFloat32 +geometry_msgs::Point32 → geometry_msgs::msg::Point32 +geometry_msgs::Point → geometry_msgs::msg::Point +nav_msgs::Path → nav_msgs::msg::Path +nav_msgs::Odometry → nav_msgs::msg::Odometry +visualization_msgs::Marker → visualization_msgs::msg::Marker +visualization_msgs::MarkerArray → visualization_msgs::msg::MarkerArray +std_msgs::Header → std_msgs::msg::Header +std_msgs::ColorRGBA → std_msgs::msg::ColorRGBA +ros::Duration() → builtin_interfaces::msg::Duration() + +// TF +tf::TransformBroadcaster → tf2_ros::TransformBroadcaster +tf::Transform → geometry_msgs::msg::TransformStamped +tf::StampedTransform(...) → (заполнить TransformStamped полями) +br.sendTransform(StampedTransform(...)) → br->sendTransform(transform_stamped) + +// Package path +ros::package::getPath("pkg") → ament_index_cpp::get_package_share_directory("pkg") + +// cv_bridge +cv_bridge::toCvCopy(msg, encoding) → cv_bridge::toCvCopy(msg, encoding) // тот же API +cv_bridge::CvImageConstPtr → cv_bridge::CvImageConstPtr // тот же тип +ptr->toImageMsg() → ptr->toImageMsg() // возвращает SharedPtr, нужно *разыменовать +``` + +#### CMakeLists.txt паттерн +```cmake +cmake_minimum_required(VERSION 3.8) +project(PACKAGE_NAME) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release") +endif() +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) +endif() +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_FLAGS_RELEASE "-O3 -Wall") + +find_package(ament_cmake REQUIRED) +find_package(rclcpp REQUIRED) +find_package(std_msgs REQUIRED) +find_package(sensor_msgs REQUIRED) +find_package(cv_bridge REQUIRED) +find_package(camera_model REQUIRED) +find_package(Boost REQUIRED COMPONENTS filesystem program_options system) +find_package(OpenCV REQUIRED) +find_package(Eigen3 REQUIRED) + +add_executable(NODE_NAME src/file1.cpp src/file2.cpp ...) + +ament_target_dependencies(NODE_NAME rclcpp std_msgs sensor_msgs cv_bridge camera_model) +target_link_libraries(NODE_NAME camera_model::camera_model ${OpenCV_LIBS}) +target_include_directories(NODE_NAME PRIVATE ${EIGEN3_INCLUDE_DIR} ${camera_model_INCLUDE_DIRS}) + +install(TARGETS NODE_NAME DESTINATION lib/${PROJECT_NAME}) +ament_package() +``` + +#### package.xml паттерн +```xml + + + PACKAGE_NAME + 0.0.0 + ... + Name + TODO + ament_cmake + rclcpp + std_msgs + sensor_msgs + + ament_cmake + +``` + +--- + +## 4. Детали по каждому ГОТОВОМУ пакету + +### 4.1 camera_model ✅ +- **Тип**: Библиотека (без ROS зависимостей в коде) +- **Изменения**: CMakeLists.txt (catkin → ament), package.xml, `SetParameterization` → `SetManifold` в CameraCalibration.cc +- **Новый файл**: `include/camodocal/gpl/ceres_local_parameterization_compat.h` — shim для Ceres 2.2 +- **Изменён**: `include/camodocal/gpl/EigenQuaternionParameterization.h` — include shim вместо удалённого `ceres/local_parameterization.h` +- **Собирается**: Да + +### 4.2 benchmark_publisher ✅ +- **Тип**: Простой узел (1 subscriber, 2 publishers) +- **Изменения**: Полная переработка `benchmark_publisher_node.cpp`, CMakeLists.txt, package.xml +- **Особенности**: `ros::Time(double)` → ручная конверсия в `builtin_interfaces::msg::Time` +- **Собирается**: Да + +### 4.3 feature_tracker ✅ +- **Тип**: Узел (1 subscriber, 3 publishers) +- **Изменения**: `feature_tracker_node.cpp` (полная переработка), `parameters.cpp/h`, `feature_tracker.cpp` (ROS_DEBUG → RCLCPP_DEBUG) +- **Особенности**: + - `pub_img` и `pub_match` — разные типы (`PointCloud` и `Image`), разделены на отдельные объявления + - `pub->publish(*feature_points)` — разыменование SharedPtr при публикации + - `cv_bridge/cv_bridge.hpp` (не `.h`) + - `cv::COLOR_GRAY2RGB` вместо `CV_GRAY2RGB` + - `g_clock` — глобальный `rclcpp::Clock::SharedPtr` для `RCLCPP_WARN_THROTTLE` +- **Собирается**: Да + +### 4.4 vins_estimator ✅ +- **Тип**: Самый сложный узел (5 subscribers, 13 publishers, TF, потоки) +- **Изменения**: 19 файлов модифицированы +- **Ключевые решения**: + - `estimator.h`: `std_msgs::Header` → `std_msgs::msg::Header` (включая member `Headers[WINDOW_SIZE+1]`) + - `estimator.cpp`: `ros::Time::now().toSec()` → `std::chrono::system_clock` (wall clock, не зависит от ROS) + - `visualization.cpp/h`: Все 13 publishers → `rclcpp::Publisher<...>::SharedPtr`, TF → `tf2_ros::TransformBroadcaster` + - `CameraPoseVisualization.cpp/h`: `ros::Publisher&` → `rclcpp::Publisher<...>::SharedPtr` + - `feature_manager.h`: `#include ` + `` → `#include ` + `#include "rclcpp/rclcpp.hpp"` + - `initial/initial_alignment.h`, `initial_ex_rotation.h`, `solve_5pts.h`: ROS includes заменены + - `factor/imu_factor.h`, `marginalization_factor.h`: добавлен `#include "rclcpp/rclcpp.hpp"` для ROS_ASSERT + - `pose_local_parameterization.h`: include compat shim + - `estimator.cpp`: `ceres::LocalParameterization*` → `ceres::Manifold*`, `AddParameterBlock(..., local_param)` → `SetManifold(...)` + - `initial_sfm.cpp`: `ceres::LocalParameterization*` → `ceres::Manifold*` + - `utility.h`: добавлена `stampToSec()` helper функция + - QoS для IMU: `rclcpp::QoS(rclcpp::KeepLast(2000)).best_effort()` (замена `tcpNoDelay`) +- **Собирается**: Да + +--- + +## 5. Детали по НЕЗАВЕРШЁННЫМ пакетам + +### 5.1 pose_graph ✅ (ГОТОВО) + +> Обновлено 2026-07-04: перечисленный ниже исторический чек-лист выполнен. Пакет +> переведён на ROS2 Jazzy и успешно собран через `colcon`. Мигрированы node API, +> сообщения и publishers, TF2, `ament_index_cpp`, Ceres 2.2 `Manifold`; BRIEF-файлы +> теперь устанавливаются в `share/pose_graph/support_files`. + +**Уже мигрированы (3 файла):** +- `src/parameters.h` — includes заменены, `ros::Publisher` → `rclcpp::Publisher<...>::SharedPtr` +- `src/pose_graph.h` — includes заменены, `nav_msgs::Path` → `nav_msgs::msg::Path`, `tf::TransformBroadcaster` → `tf2_ros::TransformBroadcaster`, `AngleLocalParameterization::Create()` → `ceres::AutoDiffManifold`, `registerPub(ros::NodeHandle&)` → `registerPub(rclcpp::Node::SharedPtr)`, publishers → `rclcpp::Publisher<...>::SharedPtr` +- `src/utility/CameraPoseVisualization.h` — includes заменены, типы обновлены, `publish_by` и `publish_image_by` сигнатуры обновлены + +**НЕ мигрированы (нужно сделать):** + +#### `src/utility/CameraPoseVisualization.cpp` +Заменить: +- `geometry_msgs::Point` → `geometry_msgs::msg::Point` +- `visualization_msgs::Marker` → `visualization_msgs::msg::Marker` +- `visualization_msgs::MarkerArray` → `visualization_msgs::msg::MarkerArray` +- `visualization_msgs::Marker::LINE_LIST` → `visualization_msgs::msg::Marker::LINE_LIST` (и т.д.) +- `ros::Duration()` → `builtin_interfaces::msg::Duration()` +- `ros::Publisher &pub` → `rclcpp::Publisher::SharedPtr pub` (в `publish_by`) +- `ros::Publisher &pub` → `rclcpp::Publisher::SharedPtr pub` (в `publish_image_by`) +- `const std_msgs::Header &header` → `const std_msgs::msg::Header &header` +- `pub.publish(...)` → `pub->publish(...)` + +#### `src/pose_graph.cpp` +Заменить: +- `#include ` → удалить (уже в pose_graph.h) +- `registerPub(ros::NodeHandle &n)` → `registerPub(rclcpp::Node::SharedPtr n)` +- `n.advertise(topic, q)` → `n->create_publisher(topic, q)` +- `tf_broadcaster.reset(new tf::TransformBroadcaster())` → `tf_broadcaster.reset(new tf2_ros::TransformBroadcaster(n))` +- `ros::Time(cur_kf->time_stamp)` → helper: заполнить `builtin_interfaces::msg::Time` из double +- TF broadcasting: `tf::Transform`, `tf::Vector3`, `tf::Quaternion`, `tf::StampedTransform` → `geometry_msgs::msg::TransformStamped` +- `pub.publish(...)` → `pub->publish(...)` +- `ceres::LocalParameterization*` → `ceres::Manifold*` +- `problem.AddParameterBlock(euler_array[i], 1, angle_local_parameterization)` → `problem.AddParameterBlock(euler_array[i], 1); problem.SetManifold(euler_array[i], angle_local_parameterization)` + +#### `src/pose_graph_node.cpp` +Заменить: +- Все ROS1 includes → ROS2 (см. паттерн выше) +- `ros::init` → `rclcpp::init` +- `ros::NodeHandle n("~")` → `auto node = std::make_shared("pose_graph")` +- `n.getParam(...)` → `node->declare_parameter(...)` + `node->get_parameter(...)` +- `ros::package::getPath("pose_graph")` → `ament_index_cpp::get_package_share_directory("pose_graph")` +- Все `ros::Subscriber` → `rclcpp::Subscription<...>::SharedPtr` через `node->create_subscription<>()` +- Все `ros::Publisher` → `rclcpp::Publisher<...>::SharedPtr` через `node->create_publisher<>()` +- `sensor_msgs::ImageConstPtr` → `sensor_msgs::msg::Image::ConstSharedPtr` +- `sensor_msgs::PointCloudConstPtr` → `sensor_msgs::msg::PointCloud::ConstSharedPtr` +- `nav_msgs::Odometry::ConstPtr` → `nav_msgs::msg::Odometry::ConstSharedPtr` +- `cv_bridge::toCvCopy` — тот же API +- `ROS_WARN(...)` → `RCLCPP_WARN(node->get_logger(), ...)` +- `ROS_BREAK()` → `std::abort()` +- `ros::Duration()` → `builtin_interfaces::msg::Duration()` +- `visualization_msgs::Marker::SPHERE_LIST` → `visualization_msgs::msg::Marker::SPHERE_LIST` +- `pub.publish(...)` → `pub->publish(...)` +- `ros::spin()` → `rclcpp::spin(node)` +- `std::thread process` и `std::thread command` — остаются без изменений + +**Подписчики (7 штук):** +| Topic | Message Type | Callback | +|-------|-------------|----------| +| `/vins_estimator/imu_propagate` | `nav_msgs::msg::Odometry` | `imu_forward_callback` | +| `/vins_estimator/odometry` | `nav_msgs::msg::Odometry` | `vio_callback` | +| `IMAGE_TOPIC` (из конфига) | `sensor_msgs::msg::Image` | `image_callback` | +| `/vins_estimator/keyframe_pose` | `nav_msgs::msg::Odometry` | `pose_callback` | +| `/vins_estimator/extrinsic` | `nav_msgs::msg::Odometry` | `extrinsic_callback` | +| `/vins_estimator/keyframe_point` | `sensor_msgs::msg::PointCloud` | `point_callback` | +| `/vins_estimator/relo_relative_pose` | `nav_msgs::msg::Odometry` | `relo_relative_pose_callback` | + +**Издатели (5 в node + 9 в PoseGraph):** +| Variable | Topic | Message Type | +|----------|-------|-------------| +| `pub_match_img` | `match_image` | `sensor_msgs::msg::Image` | +| `pub_camera_pose_visual` | `camera_pose_visual` | `visualization_msgs::msg::MarkerArray` | +| `pub_key_odometrys` | `key_odometrys` | `visualization_msgs::msg::Marker` | +| `pub_vio_path` | `no_loop_path` | `nav_msgs::msg::Path` | +| `pub_match_points` | `match_points` | `sensor_msgs::msg::PointCloud` | +| `pub_pg_path` | `pose_graph_path` | `nav_msgs::msg::Path` | +| `pub_base_path` | `base_path` | `nav_msgs::msg::Path` | +| `pub_pose_graph` | `pose_graph` | `visualization_msgs::msg::MarkerArray` | +| `pub_path[1-9]` | `path_1`...`path_9` | `nav_msgs::msg::Path` | + +#### `src/keyframe.cpp` +Заменить: +- `sensor_msgs::ImagePtr` → `sensor_msgs::msg::Image::SharedPtr` +- `cv_bridge::CvImage(std_msgs::Header(), "bgr8", thumbimage).toImageMsg()` → `cv_bridge::CvImage(std_msgs::msg::Header(), "bgr8", thumbimage).toImageMsg()` +- `msg->header.stamp = ros::Time(time_stamp)` → ручная конверсия double → `builtin_interfaces::msg::Time` +- `sensor_msgs::PointCloud` → `sensor_msgs::msg::PointCloud` +- `geometry_msgs::Point32` → `geometry_msgs::msg::Point32` +- `sensor_msgs::ChannelFloat32` → `sensor_msgs::msg::ChannelFloat32` +- `pub_match_img.publish(msg)` → `pub_match_img->publish(*msg)` +- `pub_match_points.publish(msg_match_points)` → `pub_match_points->publish(msg_match_points)` + +#### `src/keyframe.h` +- Не имеет прямых ROS includes, но включает `parameters.h` (уже мигрирован) +- Проверить, что нет косвенных ROS зависимостей + +#### CMakeLists.txt +Заменить по паттерну. Дополнительно: +- `find_package(ament_index_cpp REQUIRED)` для `get_package_share_directory` +- Установить support_files: `install(DIRECTORY ../../support_files/ DESTINATION share/${PROJECT_NAME}/support_files FILES_MATCHING PATTERN "*.bin" PATTERN "*.yml")` +- ThirdParty source files включаются в `add_executable` (DBoW, DUtils, DVision, VocabularyBinary) + +#### package.xml +Заменить по паттерну. Зависимости: `rclcpp`, `std_msgs`, `nav_msgs`, `sensor_msgs`, `visualization_msgs`, `geometry_msgs`, `cv_bridge`, `camera_model`, `tf2_ros`, `ament_index_cpp` + +### 5.2 vins_bringup ✅ + +**Файлы:** `CMakeLists.txt`, `package.xml`, `launch/*.launch.py`, `launch/common.py` + +**Что делает пакет:** +- Поднимает `feature_tracker`, `vins_estimator` и `pose_graph` в нужной конфигурации. +- Поддерживает сценарии `euroc`, `3dm`, `usb_cam`, `realsense`, `cla`, `black_box`, `termal_cam`. +- Отдельно запускает `rviz2` с готовым конфигом. + +**Что уже сделано:** +- Создан `vins_bringup/CMakeLists.txt`. +- Создан `vins_bringup/package.xml`. +- Добавлены launch-файлы для основных сценариев. +- Добавлен bag-specific launch `innospector_cam.launch.py` с `image_transport republish` для входа `/image_raw/compressed`. +- Добавлен конфиг `config/fisheye_bag/fisheye_bag_config.yaml` под камеру `1280x1024` и topic `"/mavros/imu/data"`. +- Конфиги из `config/` устанавливаются в `share/vins_bringup/config`. + +**Что осталось:** +- Пакет собран. +- Launch-файлы синтаксически проверены. + +--- + +## 6. Создание vins_bringup (launch package) — ГОТОВО + +Пакет создан и собран; далее остаётся только финальная проверка всего workspace при необходимости. + +--- + +## 7. Порядок оставшихся работ + +1. **Финальная сборка** — `colcon build --symlink-install --base-paths src` (все пакеты) +2. **Тестирование** — запуск с реальными данными + +--- + +## 8. Известные проблемы и решения + +| Проблема | Решение | +|----------|---------| +| `ceres::LocalParameterization` удалён в Ceres 2.2 | Использовать `ceres_local_parameterization_compat.h` shim или `ceres::Manifold` напрямую | +| `cv_bridge/cv_bridge.h` не найден | В ROS2 Jazzy: `cv_bridge/cv_bridge.hpp` (не `.h`) | +| `pub->publish(shared_ptr)` не компилируется | Разыменовать: `pub->publish(*msg)` | +| `ros::Time(double)` нет прямого аналога | Ручная конверсия: `sec = (int32_t)t; nanosec = (uint32_t)((t - sec) * 1e9)` | +| `ros::Duration()` для marker lifetime | `builtin_interfaces::msg::Duration()` | +| `CV_GRAY2RGB` удалён в OpenCV 4.x | `cv::COLOR_GRAY2RGB` | +| `AddParameterBlock(..., local_parameterization)` | `AddParameterBlock(...); SetManifold(..., local_parameterization)` | +| `camera_model` не линкуется | Использовать imported target: `camera_model::camera_model` (не просто `camera_model`) | +| Boost targets не найдены при линковке | Добавить `find_package(Boost REQUIRED COMPONENTS filesystem program_options system)` в downstream пакеты | +| colcon не видит пакеты | Использовать `--base-paths src` (пакеты в подпапках `src/VINS-Mono-inno/`) | +| `ROS_WARN_THROTTLE(rate, ...)` | `RCLCPP_WARN_SKIPFIRST_THROTTLE(logger, clock, ms, ...)` — нужен `rclcpp::Clock` | + +--- + +## 9. Ссылки на эталонные файлы + +Для миграции оставшихся пакетов используй уже мигрированные файлы как образцы: + +| Что посмотреть | Эталонный файл | +|---------------|---------------| +| CMakeLists.txt (библиотека) | `camera_model/CMakeLists.txt` | +| CMakeLists.txt (узел) | `feature_tracker/CMakeLists.txt` или `vins_estimator/CMakeLists.txt` | +| package.xml | `feature_tracker/package.xml` | +| Node main() | `feature_tracker/src/feature_tracker_node.cpp` | +| parameters.cpp/h | `feature_tracker/src/parameters.cpp` или `vins_estimator/src/parameters.cpp` | +| visualization с TF | `vins_estimator/src/utility/visualization.cpp` | +| CameraPoseVisualization | `vins_estimator/src/utility/CameraPoseVisualization.cpp` | +| Ceres Manifold usage | `vins_estimator/src/estimator.cpp` (строки ~710-718) | +| stampToSec helper | `vins_estimator/src/utility/utility.h` | + +--- + +## 10. Инновационные дополнения этого форка (сохранить при миграции) + +Эти функции добавлены в `UAV_perfom` branch и должны быть сохранены: + +1. **AGAST corner detector** + grid-based feature selection (`use_advanced_flow`) +2. **Bidirectional optical flow** (`use_bidirectional_flow`) +3. **Velocity-based adaptive feature tracking** (`enable_velocity_check`, `max_velocity_threshold`, `velocity_boost_features`) +4. **IMU accelerometer filtering** (outlier rejection + exponential smoothing) +5. **Zero Velocity Update (ZUPT)** (`enable_zupt`, `zupt_vel_threshold`, `zupt_acc_threshold`) +6. **Altitude-based scale correction** via MAVLink velocity +7. **Periodic td estimation** (`estimate_td_period`) +8. **Thermal camera support** (`config/termal_cam_config.yaml`) +9. **TF broadcasting** of loop-closed trajectory (`world → camera_pose_graph`) +10. **Failure detection thresholds** tuned for high-speed UAV + +Все эти функции реализованы в коде и не требуют отдельных изменений — они используют те же ROS API, что и остальной код, и мигрируются автоматически. + +--- + +## 11. Команды для проверки + +```bash +# Сборка одного пакета +cd ~/ros2_ws && source /opt/ros/jazzy/setup.bash && source install/setup.bash 2>/dev/null +colcon build --symlink-install --packages-select --base-paths src + +# Сборка всех пакетов VINS-Mono +colcon build --symlink-install --packages-select camera_model benchmark_publisher feature_tracker vins_estimator pose_graph vins_bringup --base-paths src + +# Проверка ошибок компиляции (без сборки) +colcon build --packages-select --base-paths src --cmake-args -DCMAKE_BUILD_TYPE=Release 2>&1 | grep "error:" + +# Запуск узла +ros2 run feature_tracker feature_tracker --ros-args -p config_file:=/path/to/config.yaml -p vins_folder:=/path/to/ +``` diff --git a/ROS2_MIGRATION_PLAN.md b/ROS2_MIGRATION_PLAN.md new file mode 100644 index 000000000..93e3e32f0 --- /dev/null +++ b/ROS2_MIGRATION_PLAN.md @@ -0,0 +1,386 @@ +# VINS-Mono → ROS2 Migration Plan + +## Target Environment +- **ROS2 Distro**: Jazzy Jalisco (installed at `/opt/ros/jazzy`) +- **Hardware**: Jetson Orin NX (aarch64, 8 cores, 9.5 GB RAM) +- **Build System**: `colcon build --symlink-install` in `~/ros2_ws` +- **C++ Standard**: C++17 (required by ROS2 Jazzy / rclcpp) + +--- + +## 1. Key Findings from Analysis + +### 1.1 Environment Status +| Component | Status | +|-----------|--------| +| ROS2 Jazzy | ✅ Installed | +| rclcpp, sensor_msgs, nav_msgs, geometry_msgs, std_msgs, visualization_msgs | ✅ Available | +| tf2_ros, tf2_geometry_msgs, tf2_eigen | ✅ Available | +| cv_bridge, image_transport, message_filters | ✅ Available | +| ament_cmake, ament_index_cpp | ✅ Available | +| Ceres 2.2.0, OpenCV 4.6, Eigen 3.4, Boost 1.83, yaml-cpp 0.8 | ✅ Available | +| `sensor_msgs::msg::PointCloud` + `ChannelFloat32` | ✅ Available (deprecated but functional) | + +### 1.2 Critical Decision: PointCloud Message +`sensor_msgs::msg::PointCloud` is **deprecated but still available** in ROS2 Jazzy. +**Decision**: Use `sensor_msgs::msg::PointCloud` as-is for initial migration to minimize code changes. This avoids rewriting all the ChannelFloat32 encoding/decoding logic. Can migrate to PointCloud2 or custom messages later if needed. + +### 1.3 Package Summary (6 packages) +| Package | ROS API Density | Migration Effort | +|---------|----------------|-----------------| +| `camera_model` | Minimal (library) | Low — mostly CMake changes | +| `feature_tracker` | High | Medium | +| `vins_estimator` | Very High | High — most complex | +| `pose_graph` | Very High | High — includes ThirdParty libs | +| `benchmark_publisher` | Medium | Low — simplest node | +| `vins_bringup` | Low | Low — launch-only package | + +--- + +## 2. Migration Architecture + +### 2.1 New Package Structure +``` +~/ros2_ws/src/VINS-Mono-inno/ +├── vins_msgs/ # NEW: shared custom messages (optional, future) +├── camera_model/ # Library + calibration tool +├── feature_tracker/ # Node +├── vins_estimator/ # Node (main estimator) +├── pose_graph/ # Node (loop closure) +├── benchmark_publisher/ # Node (benchmark) +├── vins_bringup/ # Launch package +├── config/ # Shared config (installed to share dir) +├── support_files/ # Shared support files (installed to share dir) +└── ... # Remaining repo files +``` + +### 2.2 Dependency Graph (build order) +``` +camera_model (library, no ROS deps) + ↓ +feature_tracker, vins_estimator, pose_graph (depend on camera_model) + ↓ +benchmark_publisher (independent) + ↓ +vins_bringup (launch files, depends on all nodes) +``` + +--- + +## 3. Per-Package Migration Steps + +### 3.1 `camera_model` (Library Package) + +**Changes needed:** +1. **package.xml**: format 3, `ament_cmake` +2. **CMakeLists.txt**: + - Remove `catkin` macros + - Use `add_library(camera_model ...)` + `ament_export_targets(export_camera_model HAS_LIBRARY_TARGET)` + - Install headers to `include/camodocal/` + - Install library +3. **Source code**: No changes needed (pure C++ with Ceres/OpenCV/Boost) +4. **Dependencies**: Remove `roscpp`, `std_msgs` (unused in library code) + +### 3.2 `feature_tracker` + +**package.xml changes:** +```xml +ament_cmake +rclcpp +std_msgs +sensor_msgs +cv_bridge +camera_model +``` + +**CMakeLists.txt changes:** +- `find_package(ament_cmake REQUIRED)` +- `find_package(rclcpp REQUIRED)` +- `find_package(sensor_msgs REQUIRED)` +- `find_package(cv_bridge REQUIRED)` +- `find_package(camera_model REQUIRED)` +- `add_executable(feature_tracker_node src/feature_tracker_node.cpp ...)` +- `ament_target_dependencies(feature_tracker_node rclcpp sensor_msgs cv_bridge camera_model)` +- `install(TARGETS feature_tracker_node DESTINATION lib/${PROJECT_NAME})` + +**Source code changes:** + +`feature_tracker_node.cpp`: +```cpp +// ROS1 → ROS2 +#include "rclcpp/rclcpp.hpp" +#include "sensor_msgs/msg/image.hpp" +#include "sensor_msgs/msg/point_cloud.hpp" +#include "std_msgs/msg/bool.hpp" +#include "cv_bridge/cv_bridge.h" + +// Node class instead of global callbacks +class FeatureTrackerNode : public rclcpp::Node { + // subscriptions, publishers as members + // img_callback as member function +}; + +int main(int argc, char** argv) { + rclcpp::init(argc, argv); + auto node = std::make_shared(); + rclcpp::spin(node); + rclcpp::shutdown(); +} +``` + +`parameters.cpp`: +```cpp +// readParam(n, "key") → node->declare_parameter("key", default) +// cv::FileStorage usage stays unchanged +``` + +**Key API mappings:** +| ROS1 | ROS2 | +|------|------| +| `ros::init(argc, argv, "name")` | `rclcpp::init(argc, argv)` | +| `ros::NodeHandle n("~")` | `rclcpp::Node` (use `~` via remapping or NodeOptions) | +| `n.subscribe(topic, q, cb)` | `node->create_subscription(topic, qos, cb)` | +| `n.advertise(topic, q)` | `node->create_publisher(topic, qos)` | +| `pub.publish(msg)` | `pub->publish(msg)` | +| `ros::spin()` | `rclcpp::spin(node)` | +| `ros::Time::now().toSec()` | `node->now().seconds()` | +| `ROS_INFO(...)` | `RCLCPP_INFO(node->get_logger(), ...)` | +| `ROS_WARN_THROTTLE(2.0, ...)` | `RCLCPP_WARN_THROTTLE(node->get_logger(), *node->get_clock(), 2000ms, ...)` | +| `sensor_msgs::ImageConstPtr` | `sensor_msgs::msg::Image::ConstSharedPtr` | +| `sensor_msgs::PointCloud` | `sensor_msgs::msg::PointCloud` | +| `cv_bridge::toCvCopy` | `cv_bridge::toCvCopy` (same API) | + +### 3.3 `vins_estimator` (Most Complex) + +**Major changes:** +1. **estimator_node.cpp**: Convert to `rclcpp::Node` class + - 5 subscribers, 12+ publishers + - `ros::TransportHints().tcpNoDelay()` → `rclcpp::QoS` with `BestEffort` reliability + - `std::thread measurement_process` stays the same + - `std::mutex` / `std::condition_variable` stays the same +2. **estimator.h**: `std_msgs::Header` → `std_msgs::msg::Header` +3. **estimator.cpp**: `ros::Time::now().toSec()` → pass clock or use `rclcpp::Clock` +4. **visualization.cpp**: + - `tf::TransformBroadcaster` → `tf2_ros::TransformBroadcaster` + - `tf::Transform` → `geometry_msgs::msg::TransformStamped` + - All publishers → `rclcpp::Publisher<...>::SharedPtr` +5. **CameraPoseVisualization.cpp**: `ros::Publisher` → `rclcpp::Publisher` +6. **parameters.cpp**: Same pattern as feature_tracker + +**QoS for IMU subscriber:** +```cpp +rclcpp::QoS imu_qos(2000); +imu_qos.best_effort(); // replaces tcpNoDelay +auto sub_imu = node->create_subscription( + IMU_TOPIC, imu_qos, imu_callback); +``` + +**TF broadcasting:** +```cpp +#include "tf2_ros/transform_broadcaster.h" +#include "geometry_msgs/msg/transform_stamped.hpp" + +// In visualization: +static tf2_ros::TransformBroadcaster br(node); +geometry_msgs::msg::TransformStamped transform; +transform.header.stamp = node->now(); +transform.header.frame_id = "world"; +transform.child_frame_id = "body"; +// ... set translation/rotation +br.sendTransform(transform); +``` + +### 3.4 `pose_graph` + +**Major changes:** +1. **pose_graph_node.cpp**: Convert to `rclcpp::Node` class + - 7 subscribers, 5+ publishers + - `ros::package::getPath("pose_graph")` → `ament_index_cpp::get_package_share_directory("pose_graph")` + - `std::thread process()` and `std::thread command()` stay the same +2. **pose_graph.cpp**: + - TF broadcaster → `tf2_ros::TransformBroadcaster` + - All publishers → `rclcpp::Publisher` + - `ros::Time(double)` → `rclcpp::Time(double * 1e9)` or manual conversion +3. **ThirdParty/** (DBoW, DUtils, DVision, VocabularyBinary): **No changes** — pure C++ +4. **Support files**: Install `brief_k10L6.bin` and `brief_pattern.yml` to share directory +5. **parameters.h**: Remove `ros/ros.h`, update includes + +**Support files installation:** +```cmake +install(DIRECTORY ../support_files/ + DESTINATION share/${PROJECT_NAME}/support_files + FILES_MATCHING PATTERN "*.bin" PATTERN "*.yml") +``` + +### 3.5 `ar_demo` (removed from current scope) + +This package is intentionally out of scope for the current migration and has been removed from the repository tree. + +### 3.6 `benchmark_publisher` + +**Changes:** Minimal — convert to `rclcpp::Node`, remove unused `tf` dependency. + +### 3.7 `data_generator` (removed from current scope) + +This package is intentionally out of scope for the current migration and has been removed from the repository tree. + +--- + +## 4. Launch File Migration + +All VINS launch XML files → `.launch.py` (Python) in new `vins_bringup` package. + +**Example ROS2 launch:** +```python +# vins_bringup/launch/euroc.launch.py +from launch import LaunchDescription +from launch.actions import DeclareLaunchArgument +from launch.substitutions import LaunchConfiguration +from launch_ros.actions import Node +import os +from ament_index_python.packages import get_package_share_directory + +def generate_launch_description(): + config_path = os.path.join( + get_package_share_directory('vins_bringup'), 'config', 'euroc', 'euroc_config.yaml') + + return LaunchDescription([ + Node( + package='feature_tracker', + executable='feature_tracker_node', + name='feature_tracker', + output='screen', + parameters=[{ + 'config_file': config_path, + 'vins_folder': os.path.dirname(config_path), + }], + remappings=[], + ), + Node( + package='vins_estimator', + executable='vins_estimator_node', + name='vins_estimator', + output='screen', + parameters=[{ + 'config_file': config_path, + 'vins_folder': os.path.dirname(config_path), + }], + ), + Node( + package='pose_graph', + executable='pose_graph_node', + name='pose_graph', + output='screen', + parameters=[{ + 'config_file': config_path, + 'visualization_shift_x': 0, + 'visualization_shift_y': 0, + 'skip_cnt': 0, + 'skip_dis': 0.0, + }], + ), + ]) +``` + +--- + +## 5. Config & Support Files Installation + +**New `vins_bringup` package** will install shared resources: +```cmake +install(DIRECTORY ../config/ + DESTINATION share/${PROJECT_NAME}/config) +install(DIRECTORY ../support_files/ + DESTINATION share/${PROJECT_NAME}/support_files) +install(DIRECTORY launch/ + DESTINATION share/${PROJECT_NAME}/launch) +``` + +--- + +## 6. Jetson Orin NX Optimization Notes + +### 6.1 Build Optimization +- Use `-O2` or `-O3` optimization (already in original CMakeLists) +- Enable NEON SIMD: `-mfpu=neon` (arm64 has NEON by default) +- Use `-j$(nproc)` for parallel build (8 cores available) +- Consider `-flto` for link-time optimization + +### 6.2 Runtime Optimization +- **QoS profiles**: Use `BestEffort` for sensor data (IMU, images) to reduce overhead +- **Thread affinity**: Original `taskset -c N` in black_box.launch can be preserved +- **Memory**: 9.5 GB RAM — sufficient for VINS-Mono +- **Feature count**: Already optimized in this fork (150-200 features) + +### 6.3 CMake Optimization Flags +```cmake +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -DNDEBUG -fopenmp") +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +``` + +--- + +## 7. Migration Order (Recommended) + +1. **camera_model** — library, no ROS deps, foundation for others +2. **benchmark_publisher** — simplest node, good test of build system +3. **feature_tracker** — medium complexity, core pipeline +4. **vins_estimator** — most complex, depends on camera_model +5. **pose_graph** — complex, depends on camera_model + ThirdParty +6. **vins_bringup** — launch files, depends on all nodes + +--- + +## 8. Common ROS1 → ROS2 API Mapping (Quick Reference) + +| ROS1 | ROS2 | +|------|------| +| `#include ` | `#include "rclcpp/rclcpp.hpp"` | +| `#include ` | `#include "sensor_msgs/msg/image.hpp"` | +| `#include ` | `#include "nav_msgs/msg/odometry.hpp"` | +| `#include ` | `#include "geometry_msgs/msg/point.hpp"` | +| `#include ` | `#include "std_msgs/msg/bool.hpp"` | +| `#include ` | `#include "visualization_msgs/msg/marker.hpp"` | +| `#include ` | `#include "tf2_ros/transform_broadcaster.h"` | +| `#include ` | `#include "cv_bridge/cv_bridge.h"` (same) | +| `ros::init(argc, argv, "node")` | `rclcpp::init(argc, argv)` | +| `ros::NodeHandle n("~")` | `rclcpp::Node::make_shared("node")` | +| `n.getParam("key", var)` | `node->declare_parameter("key", default); node->get_parameter("key", var)` | +| `n.subscribe(topic, q, cb)` | `node->create_subscription(topic, QoS, cb)` | +| `n.advertise(topic, q)` | `node->create_publisher(topic, QoS)` | +| `ros::spin()` | `rclcpp::spin(node)` | +| `ros::ok()` | `rclcpp::ok()` | +| `ros::Rate r(hz)` | `rclcpp::Rate r(hz)` | +| `ros::Time::now().toSec()` | `node->now().seconds()` | +| `ros::Time(t).toSec()` | `rclcpp::Time(t).seconds()` | +| `ros::Duration(d).sleep()` | `rclcpp::sleep_for(std::chrono::duration(d))` | +| `ROS_INFO(...)` | `RCLCPP_INFO(rclcpp::get_logger("name"), ...)` | +| `ROS_WARN(...)` | `RCLCPP_WARN(rclcpp::get_logger("name"), ...)` | +| `ROS_DEBUG(...)` | `RCLCPP_DEBUG(rclcpp::get_logger("name"), ...)` | +| `ROS_ASSERT(cond)` | `assert(cond)` | +| `ROS_BREAK()` | `std::abort()` | +| `sensor_msgs::ImageConstPtr` | `sensor_msgs::msg::Image::ConstSharedPtr` | +| `sensor_msgs::PointCloudPtr` | `sensor_msgs::msg::PointCloud::SharedPtr` | +| `msg->header.stamp.toSec()` | `rclcpp::Time(msg->header.stamp).seconds()` | +| `msg->header.stamp = ros::Time(t)` | `msg->header.stamp = rclcpp::Time(t).to_msg()` or manual | +| `ros::package::getPath("pkg")` | `ament_index_cpp::get_package_share_directory("pkg")` | +| `catkin_package(...)` | `ament_export_*` macros | +| `add_dependencies(target catkin_package)` | Not needed in ament | + +--- + +## 9. Potential Issues & Mitigations + +| Issue | Mitigation | +|-------|------------| +| `sensor_msgs::msg::PointCloud` deprecated | Works in Jazzy; plan future migration to PointCloud2 | +| `ros::Time::now()` in library code (estimator.cpp) | Pass `rclcpp::Clock` or node pointer | +| `std_msgs::Header` in estimator.h | Change to `std_msgs::msg::Header` | +| TF static broadcaster in visualization.cpp | Make it a class member or shared pointer | +| `ros::TransportHints().tcpNoDelay()` | Use `rclcpp::QoS` with `BestEffort` | +| Global callback functions with global state | Convert to class member functions | +| `ROS_WARN_THROTTLE(rate, ...)` | `RCLCPP_WARN_THROTTLE(logger, clock, ms, ...)` | +| Support files path (`pkg_path + "/../support_files/"`) | Install to share dir, use `get_package_share_directory` | +| `cv::FileStorage` YAML with `!!opencv-matrix` | Works unchanged (ROS-independent) | +| Multi-threaded spin + processing thread | Keep `std::thread` pattern, use `rclcpp::spin` in main | diff --git a/USB_CAM_SETUP.md b/USB_CAM_SETUP.md new file mode 100644 index 000000000..97e721619 --- /dev/null +++ b/USB_CAM_SETUP.md @@ -0,0 +1,248 @@ +# Адаптация VINS-Mono для USB камеры и MAVROS IMU + +## Обзор конфигурации + +Созданы два новых файла для вашей системы: +- **`config/usb_cam_config.yaml`** - конфигурация параметров +- **`vins_estimator/launch/usb_cam.launch`** - файл запуска + +## Параметры камеры + +### Разрешение и калибровка +``` +Разрешение: 640x480 +Матрица камеры K: + fx = 473.41 пикселей + fy = 473.57 пикселей + cx = 311.40 пикселей (центр по X) + cy = 256.94 пикселей (центр по Y) + +Дистortion (модель plumb_bob): + k1 = 0.0516 (радиальное искажение) + k2 = -0.0484 (радиальное искажение) + p1 = 0.0047 (тангенциальное искажение) + p2 = -0.0033 (тангенциальное искажение) +``` + +## Экстринсики камеры-IMU (Extrinsics) + +### Геометрия +Ваша система имеет следующее расположение: +``` +IMU координаты: Камера повернута на 90° вправо +X - forward относительно IMU +Y - left +Z - up +``` + +### Матрица вращения (Rotation Matrix) +Поворот камеры на -90° вокруг оси Z (вправо по курсу): +``` +imu^R_cam = [0 1 0] + [-1 0 0] + [0 0 1] + +Преобразование: imu_point = R_cam * camera_point +``` + +### Вектор трансляции (Translation) +``` +imu^T_cam = [0, 0, 0]^T (камера находится в центре IMU) +``` + +Если камера смещена относительно IMU, отредактируйте значения в строках: +```yaml +extrinsicTranslation: !!opencv-matrix + rows: 3 + cols: 1 + dt: d + data: [dx, dy, dz] # Смещения в метрах +``` + +## ROS Топики + +### Входящие сигналы: +- **Изображения:** `/usb_cam/image_raw` +- **Данные IMU:** `/mavros/imu/data` + +### Исходящие сигналы: +- `/vins_estimator/odometry` - визуально-инерциальная одометрия +- `/vins_estimator/path` - траектория движения +- `/vins_estimator/relo_relative_pose` - релокализация +- `/feature_tracker/feature` - отслеживаемые признаки (для отладки) +- `/pose_graph/base_path` - оптимизированная траектория + +## Запуск системы + +### 1. Сборка проекта +```bash +cd ~/catkin_ws +catkin_make +source devel/setup.bash +``` + +### 2. Запуск USB камеры (если еще не запущена) +```bash +roslaunch usb_cam usb_cam-test.launch +# или +rosrun usb_cam usb_cam_node +``` + +### 3. Запуск MAVROS +```bash +roslaunch mavros apm.launch fcu_url:=/dev/ttyUSB0:921600 +# или другой адрес вашего автопилота +``` + +### 4. Запуск VINS-Mono с USB камерой +```bash +roslaunch vins_estimator usb_cam.launch +``` + +В другом терминале для визуализации: +```bash +roslaunch vins_estimator vins_rviz.launch +``` + +## Параметры, которые можно настроить + +### Отслеживание признаков +Файл: `config/usb_cam_config.yaml` +```yaml +max_cnt: 150 # Максимум отслеживаемых точек (↑ = точнее, но медленнее) +min_dist: 30 # Минимальное расстояние между точками (в пиксе) +freq: 10 # Частота обновления (Hz) +F_threshold: 1.0 # Порог RANSAC (пиксели) +show_track: 1 # Показывать отслеживаемые точки +equalize: 1 # Выравнивание гистограммы (для темных/светлых изображений) +``` + +### Оптимизация +```yaml +max_solver_time: 0.04 # Макс время решения (мс) для реалтайма +max_num_iterations: 8 # Макс итераций оптимизатора +keyframe_parallax: 10.0 # Порог параллакса для выбора ключевого кадра +``` + +### IMU параметры +```yaml +acc_n: 0.08 # Шум акселерометра +gyr_n: 0.004 # Шум гироскопа +acc_w: 0.00004 # Дрейф акселерометра +gyr_w: 2.0e-6 # Дрейф гироскопа +g_norm: 9.81007 # Ускорение свободного падения +``` + +Эти значения подобраны для типичного 9-DOF IMU. Если результаты неудовлетворительны, их можно調整. + +### Замыкание цикла +```yaml +loop_closure: 1 # Включить обнаружение замыкания цикла +fast_relocalization: 0 # Быстрая релокализация (для больших карт) +load_previous_pose_graph: 0 # Переиспользовать старый граф +``` + +## Отладка и тестирование + +### Проверить топики +```bash +rostopic list # Список всех топиков +rostopic echo /usb_cam/image_raw +rostopic echo /mavros/imu/data +rostopic echo /vins_estimator/odometry +``` + +### Проверить частоту обновления +```bash +rostopic hz /usb_cam/image_raw +rostopic hz /mavros/imu/data +rostopic hz /vins_estimator/odometry +``` + +### Визуализация в RVIZ +```bash +# Откроется RVIZ с предконфигурированными трансформациями +roslaunch vins_estimator vins_rviz.launch +``` + +Добавьте подписки на топики: +- `Image` → `/usb_cam/image_raw` +- `Odometry` → `/vins_estimator/odometry` +- `Path` → `/vins_estimator/path` +- `Point Cloud` → `/feature_tracker/feature` (для отладки) + +## Синхронизация камеры-IMU + +Если камера и IMU работают с разными временными метками: + +1. **Включите автоматическую временную калибровку:** +```yaml +estimate_td: 1 # вместо 0 +td: 0.0 # начальное смещение времени +``` + +2. **Система будет автоматически калибровать временное смещение** между камерой и IMU + +## Проблемы и решения + +### Проблема: "Waiting for features..." +**Решение:** Убедитесь, что: +- Камера получает достаточно света +- `max_cnt` не слишком мал +- `equalize: 1` для темных изображений + +### Проблема: "Cannot find corresponding features" +**Решение:** +- Уменьшите `min_dist` (минимальное расстояние между точками) +- Увеличьте `max_cnt` (максимум точек) +- Проверьте калибровку камеры + +### Проблема: "IMU data drift" +**Решение:** +- Отредактируйте параметры `acc_w` и `gyr_w` в конфиге +- Используйте более точный IMU + +### Проблема: Неправильная ориентация камеры +**Решение:** Отредактируйте матрицу вращения в конфиге: +```yaml +extrinsicRotation: !!opencv-matrix + rows: 3 + cols: 3 + dt: d + data: [...] # Измените эту матрицу +``` + +## Сохранение и загрузка карты + +### Сохранить текущую карту: +В терминале `vins_estimator` нажмите **`s` + Enter** + +### Загрузить сохраненную карту: +Установите в конфиге: +```yaml +load_previous_pose_graph: 1 +pose_graph_save_path: "/path/to/saved/pose_graph/" +``` + +## Контрольный список перед запуском + +- [ ] Откалибрована USB камера (параметры K, D обновлены) +- [ ] MAVROS работает и публикует `/mavros/imu/data` +- [ ] USB камера работает и публикует `/usb_cam/image_raw` +- [ ] Проверены топики: `rostopic list | grep -E "usb_cam|mavros|imu"` +- [ ] Созданы директории вывода: `mkdir -p /home/op/output/pose_graph/` +- [ ] Выполнена сборка: `cd ~/catkin_ws && catkin_make` + +## Справка по файлам + +- **Основной конфиг:** `config/usb_cam_config.yaml` +- **Launch файл:** `vins_estimator/launch/usb_cam.launch` +- **Код оценивателя:** `vins_estimator/src/estimator_node.cpp` +- **Отслеживание признаков:** `feature_tracker/src/feature_tracker_node.cpp` +- **Граф позиций:** `pose_graph/src/pose_graph_node.cpp` + +## Дополнительные ресурсы + +- [VINS-Mono GitHub](https://github.com/HKUST-Aerial-Robotics/VINS-Mono) +- [VINS-Mono Paper (IEEE)](https://ieeexplore.ieee.org/document/8421746) +- [ROS Navigation](http://wiki.ros.org/ROS/Tutorials) diff --git a/ar_demo/CMakeLists.txt b/ar_demo/CMakeLists.txt deleted file mode 100644 index fc4e682b6..000000000 --- a/ar_demo/CMakeLists.txt +++ /dev/null @@ -1,43 +0,0 @@ -cmake_minimum_required(VERSION 2.8.3) -project(ar_demo) - -set(CMAKE_BUILD_TYPE "Release") -set(CMAKE_CXX_FLAGS "-std=c++11 -DEIGEN_DONT_PARALLELIZE") -#-DEIGEN_USE_MKL_ALL") -set(CMAKE_CXX_FLAGS_RELEASE "-O3 -Wall -g") - -find_package(catkin REQUIRED COMPONENTS - roscpp - rospy - std_msgs - image_transport - sensor_msgs - cv_bridge - message_filters - camera_model -) -find_package(OpenCV REQUIRED) - -catkin_package( - -) - - -include_directories( - ${catkin_INCLUDE_DIRS} -) - -set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) -find_package(Eigen3) -include_directories( - ${catkin_INCLUDE_DIRS} - ${EIGEN3_INCLUDE_DIR} -) - -add_executable(ar_demo_node src/ar_demo_node.cpp) - - target_link_libraries(ar_demo_node - ${catkin_LIBRARIES} ${OpenCV_LIBS} - ) - - diff --git a/ar_demo/cmake/FindEigen.cmake b/ar_demo/cmake/FindEigen.cmake deleted file mode 100644 index cdeb30557..000000000 --- a/ar_demo/cmake/FindEigen.cmake +++ /dev/null @@ -1,172 +0,0 @@ -# Ceres Solver - A fast non-linear least squares minimizer -# Copyright 2015 Google Inc. All rights reserved. -# http://ceres-solver.org/ -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# * Neither the name of Google Inc. nor the names of its contributors may be -# used to endorse or promote products derived from this software without -# specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. -# -# Author: alexs.mac@gmail.com (Alex Stewart) -# - -# FindEigen.cmake - Find Eigen library, version >= 3. -# -# This module defines the following variables: -# -# EIGEN_FOUND: TRUE iff Eigen is found. -# EIGEN_INCLUDE_DIRS: Include directories for Eigen. -# -# EIGEN_VERSION: Extracted from Eigen/src/Core/util/Macros.h -# EIGEN_WORLD_VERSION: Equal to 3 if EIGEN_VERSION = 3.2.0 -# EIGEN_MAJOR_VERSION: Equal to 2 if EIGEN_VERSION = 3.2.0 -# EIGEN_MINOR_VERSION: Equal to 0 if EIGEN_VERSION = 3.2.0 -# -# The following variables control the behaviour of this module: -# -# EIGEN_INCLUDE_DIR_HINTS: List of additional directories in which to -# search for eigen includes, e.g: /timbuktu/eigen3. -# -# The following variables are also defined by this module, but in line with -# CMake recommended FindPackage() module style should NOT be referenced directly -# by callers (use the plural variables detailed above instead). These variables -# do however affect the behaviour of the module via FIND_[PATH/LIBRARY]() which -# are NOT re-called (i.e. search for library is not repeated) if these variables -# are set with valid values _in the CMake cache_. This means that if these -# variables are set directly in the cache, either by the user in the CMake GUI, -# or by the user passing -DVAR=VALUE directives to CMake when called (which -# explicitly defines a cache variable), then they will be used verbatim, -# bypassing the HINTS variables and other hard-coded search locations. -# -# EIGEN_INCLUDE_DIR: Include directory for CXSparse, not including the -# include directory of any dependencies. - -# Called if we failed to find Eigen or any of it's required dependencies, -# unsets all public (designed to be used externally) variables and reports -# error message at priority depending upon [REQUIRED/QUIET/] argument. -macro(EIGEN_REPORT_NOT_FOUND REASON_MSG) - unset(EIGEN_FOUND) - unset(EIGEN_INCLUDE_DIRS) - # Make results of search visible in the CMake GUI if Eigen has not - # been found so that user does not have to toggle to advanced view. - mark_as_advanced(CLEAR EIGEN_INCLUDE_DIR) - # Note _FIND_[REQUIRED/QUIETLY] variables defined by FindPackage() - # use the camelcase library name, not uppercase. - if (Eigen_FIND_QUIETLY) - message(STATUS "Failed to find Eigen - " ${REASON_MSG} ${ARGN}) - elseif (Eigen_FIND_REQUIRED) - message(FATAL_ERROR "Failed to find Eigen - " ${REASON_MSG} ${ARGN}) - else() - # Neither QUIETLY nor REQUIRED, use no priority which emits a message - # but continues configuration and allows generation. - message("-- Failed to find Eigen - " ${REASON_MSG} ${ARGN}) - endif () - return() -endmacro(EIGEN_REPORT_NOT_FOUND) - -# Protect against any alternative find_package scripts for this library having -# been called previously (in a client project) which set EIGEN_FOUND, but not -# the other variables we require / set here which could cause the search logic -# here to fail. -unset(EIGEN_FOUND) - -# Search user-installed locations first, so that we prefer user installs -# to system installs where both exist. -list(APPEND EIGEN_CHECK_INCLUDE_DIRS - /usr/local/include - /usr/local/homebrew/include # Mac OS X - /opt/local/var/macports/software # Mac OS X. - /opt/local/include - /usr/include) -# Additional suffixes to try appending to each search path. -list(APPEND EIGEN_CHECK_PATH_SUFFIXES - eigen3 # Default root directory for Eigen. - Eigen/include/eigen3 # Windows (for C:/Program Files prefix) < 3.3 - Eigen3/include/eigen3 ) # Windows (for C:/Program Files prefix) >= 3.3 - -# Search supplied hint directories first if supplied. -find_path(EIGEN_INCLUDE_DIR - NAMES Eigen/Core - PATHS ${EIGEN_INCLUDE_DIR_HINTS} - ${EIGEN_CHECK_INCLUDE_DIRS} - PATH_SUFFIXES ${EIGEN_CHECK_PATH_SUFFIXES}) - -if (NOT EIGEN_INCLUDE_DIR OR - NOT EXISTS ${EIGEN_INCLUDE_DIR}) - eigen_report_not_found( - "Could not find eigen3 include directory, set EIGEN_INCLUDE_DIR to " - "path to eigen3 include directory, e.g. /usr/local/include/eigen3.") -endif (NOT EIGEN_INCLUDE_DIR OR - NOT EXISTS ${EIGEN_INCLUDE_DIR}) - -# Mark internally as found, then verify. EIGEN_REPORT_NOT_FOUND() unsets -# if called. -set(EIGEN_FOUND TRUE) - -# Extract Eigen version from Eigen/src/Core/util/Macros.h -if (EIGEN_INCLUDE_DIR) - set(EIGEN_VERSION_FILE ${EIGEN_INCLUDE_DIR}/Eigen/src/Core/util/Macros.h) - if (NOT EXISTS ${EIGEN_VERSION_FILE}) - eigen_report_not_found( - "Could not find file: ${EIGEN_VERSION_FILE} " - "containing version information in Eigen install located at: " - "${EIGEN_INCLUDE_DIR}.") - else (NOT EXISTS ${EIGEN_VERSION_FILE}) - file(READ ${EIGEN_VERSION_FILE} EIGEN_VERSION_FILE_CONTENTS) - - string(REGEX MATCH "#define EIGEN_WORLD_VERSION [0-9]+" - EIGEN_WORLD_VERSION "${EIGEN_VERSION_FILE_CONTENTS}") - string(REGEX REPLACE "#define EIGEN_WORLD_VERSION ([0-9]+)" "\\1" - EIGEN_WORLD_VERSION "${EIGEN_WORLD_VERSION}") - - string(REGEX MATCH "#define EIGEN_MAJOR_VERSION [0-9]+" - EIGEN_MAJOR_VERSION "${EIGEN_VERSION_FILE_CONTENTS}") - string(REGEX REPLACE "#define EIGEN_MAJOR_VERSION ([0-9]+)" "\\1" - EIGEN_MAJOR_VERSION "${EIGEN_MAJOR_VERSION}") - - string(REGEX MATCH "#define EIGEN_MINOR_VERSION [0-9]+" - EIGEN_MINOR_VERSION "${EIGEN_VERSION_FILE_CONTENTS}") - string(REGEX REPLACE "#define EIGEN_MINOR_VERSION ([0-9]+)" "\\1" - EIGEN_MINOR_VERSION "${EIGEN_MINOR_VERSION}") - - # This is on a single line s/t CMake does not interpret it as a list of - # elements and insert ';' separators which would result in 3.;2.;0 nonsense. - set(EIGEN_VERSION "${EIGEN_WORLD_VERSION}.${EIGEN_MAJOR_VERSION}.${EIGEN_MINOR_VERSION}") - endif (NOT EXISTS ${EIGEN_VERSION_FILE}) -endif (EIGEN_INCLUDE_DIR) - -# Set standard CMake FindPackage variables if found. -if (EIGEN_FOUND) - set(EIGEN_INCLUDE_DIRS ${EIGEN_INCLUDE_DIR}) -endif (EIGEN_FOUND) - -# Handle REQUIRED / QUIET optional arguments and version. -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Eigen - REQUIRED_VARS EIGEN_INCLUDE_DIRS - VERSION_VAR EIGEN_VERSION) - -# Only mark internal variables as advanced if we found Eigen, otherwise -# leave it visible in the standard GUI for the user to set manually. -if (EIGEN_FOUND) - mark_as_advanced(FORCE EIGEN_INCLUDE_DIR) -endif (EIGEN_FOUND) diff --git a/ar_demo/launch/3dm_bag.launch b/ar_demo/launch/3dm_bag.launch deleted file mode 100644 index 8e1b300cd..000000000 --- a/ar_demo/launch/3dm_bag.launch +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/ar_demo/launch/ar_rviz.launch b/ar_demo/launch/ar_rviz.launch deleted file mode 100644 index 105063b46..000000000 --- a/ar_demo/launch/ar_rviz.launch +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/ar_demo/launch/realsense_ar.launch b/ar_demo/launch/realsense_ar.launch deleted file mode 100755 index 68c63ee77..000000000 --- a/ar_demo/launch/realsense_ar.launch +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/ar_demo/package.xml b/ar_demo/package.xml deleted file mode 100644 index 74270241d..000000000 --- a/ar_demo/package.xml +++ /dev/null @@ -1,58 +0,0 @@ - - - ar_demo - 0.0.0 - The ar_demo package - - - - - tony-ws - - - - - - TODO - - - - - - - - - - - - - - - - - - - - - - - - - - catkin - roscpp - rospy - std_msgs - camera_model - roscpp - rospy - std_msgs - camera_model - - - - - - - - \ No newline at end of file diff --git a/ar_demo/src/ar_demo_node.cpp b/ar_demo/src/ar_demo_node.cpp deleted file mode 100644 index 95c3a220a..000000000 --- a/ar_demo/src/ar_demo_node.cpp +++ /dev/null @@ -1,557 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "camodocal/camera_models/CameraFactory.h" -#include "camodocal/camera_models/CataCamera.h" -#include "camodocal/camera_models/PinholeCamera.h" -#include -#include -#include -#include -#include - -using namespace std; -using namespace Eigen; -using namespace sensor_msgs; -using namespace message_filters; -using namespace camodocal; - -int ROW; -int COL; -double FOCAL_LENGTH; -const int axis_num = 0; -const int cube_num = 1; -const double box_length = 0.8; -bool USE_UNDISTORED_IMG; -bool pose_init = false; -int img_cnt = 0; - -ros::Publisher object_pub; -image_transport::Publisher pub_ARimage; -Vector3d Axis[6]; -Vector3d Cube_center[3]; -vector Cube_corner[3]; -vector output_Axis[6]; -vector output_Cube[3]; -vector output_corner_dis[3]; -double Cube_center_depth[3]; -queue img_buf; -camodocal::CameraPtr m_camera; -bool look_ground = 0; -std_msgs::ColorRGBA line_color_r; -std_msgs::ColorRGBA line_color_g; -std_msgs::ColorRGBA line_color_b; - -void axis_generate(visualization_msgs::Marker &line_list, Vector3d &origin, int id) -{ - - line_list.id = id; - line_list.header.frame_id = "world"; - line_list.header.stamp = ros::Time::now(); - line_list.action = visualization_msgs::Marker::ADD; - line_list.type = visualization_msgs::Marker::LINE_LIST; - line_list.scale.x = 0.1; - line_list.color.a = 1.0; - line_list.lifetime = ros::Duration(); - - line_list.pose.orientation.w = 1.0; - line_list.color.b = 1.0; - geometry_msgs::Point p; - p.x = origin.x(); - p.y = origin.y(); - p.z = origin.z(); - line_list.points.push_back(p); - line_list.colors.push_back(line_color_r); - p.x += 1.0; - line_list.points.push_back(p); - line_list.colors.push_back(line_color_r); - p.x -= 1.0; - line_list.points.push_back(p); - line_list.colors.push_back(line_color_g); - p.y += 1.0; - line_list.points.push_back(p); - line_list.colors.push_back(line_color_g); - p.y -= 1.0; - line_list.points.push_back(p); - line_list.colors.push_back(line_color_b); - p.z += 1.0; - line_list.points.push_back(p); - line_list.colors.push_back(line_color_b); -} - -void cube_generate(visualization_msgs::Marker &marker, Vector3d &origin, int id) -{ - - //uint32_t shape = visualization_msgs::Marker::CUBE; - marker.header.frame_id = "world"; - marker.header.stamp = ros::Time::now(); - marker.ns = "basic_shapes"; - marker.id = 0; - //marker.type = shape; - marker.action = visualization_msgs::Marker::ADD; - marker.type = visualization_msgs::Marker::CUBE_LIST; - /* - marker.pose.position.x = origin.x(); - marker.pose.position.y = origin.y(); - marker.pose.position.z = origin.z(); - marker.pose.orientation.x = 0.0; - marker.pose.orientation.y = 0.0; - marker.pose.orientation.z = 0.0; - marker.pose.orientation.w = 1.0; - */ - marker.scale.x = box_length; - marker.scale.y = box_length; - marker.scale.z = box_length; - - marker.color.r = 0.0f; - marker.color.g = 1.0f; - marker.color.b = 0.0f; - marker.color.a = 1.0; - - marker.lifetime = ros::Duration(); - geometry_msgs::Point p; - p.x = origin.x(); - p.y = origin.y(); - p.z = origin.z(); - marker.points.push_back(p); - marker.colors.push_back(line_color_r); - Cube_corner[id].clear(); - Cube_corner[id].push_back(Vector3d(origin.x() - box_length / 2, origin.y() - box_length / 2, origin.z() - box_length / 2)); - Cube_corner[id].push_back(Vector3d(origin.x() + box_length / 2, origin.y() - box_length / 2, origin.z() - box_length / 2)); - Cube_corner[id].push_back(Vector3d(origin.x() - box_length / 2, origin.y() + box_length / 2, origin.z() - box_length / 2)); - Cube_corner[id].push_back(Vector3d(origin.x() + box_length / 2, origin.y() + box_length / 2, origin.z() - box_length / 2)); - Cube_corner[id].push_back(Vector3d(origin.x() - box_length / 2, origin.y() - box_length / 2, origin.z() + box_length / 2)); - Cube_corner[id].push_back(Vector3d(origin.x() + box_length / 2, origin.y() - box_length / 2, origin.z() + box_length / 2)); - Cube_corner[id].push_back(Vector3d(origin.x() - box_length / 2, origin.y() + box_length / 2, origin.z() + box_length / 2)); - Cube_corner[id].push_back(Vector3d(origin.x() + box_length / 2, origin.y() + box_length / 2, origin.z() + box_length / 2)); -} - -void add_object() -{ - visualization_msgs::MarkerArray markerArray_msg; - - visualization_msgs::Marker line_list; - visualization_msgs::Marker cube_list; - - for (int i = 0; i < axis_num; i++) - { - axis_generate(line_list, Axis[i], i); - markerArray_msg.markers.push_back(line_list); - } - - for (int i = 0; i spaceToPlane(local_point, local_uv); - - if (local_point.z() > 0) - //&& 0 <= local_uv.x() && local_uv.x() <= COL - 1 && 0 <= local_uv.y() && local_uv.y() <= ROW -1) - { - output_Axis[i].push_back(Vector3d(local_uv.x(), local_uv.y(), 1)); - - local_point = camera_q.inverse() * (Axis[i] + Vector3d(1, 0, 0) - camera_p); - m_camera->spaceToPlane(local_point, local_uv); - output_Axis[i].push_back(Vector3d(local_uv.x(), local_uv.y(), 1)); - - local_point = camera_q.inverse() * (Axis[i] + Vector3d(0, 1, 0) - camera_p); - m_camera->spaceToPlane(local_point, local_uv); - output_Axis[i].push_back(Vector3d(local_uv.x(), local_uv.y(), 1)); - - local_point = camera_q.inverse() * (Axis[i] + Vector3d(0, 0, 1) - camera_p); - m_camera->spaceToPlane(local_point, local_uv); - output_Axis[i].push_back(Vector3d(local_uv.x(), local_uv.y(), 1)); - - } - } - - for (int i = 0; i < cube_num; i++) - { - output_Cube[i].clear(); - output_corner_dis[i].clear(); - Vector3d local_point; - Vector2d local_uv; - local_point = camera_q.inverse() * (Cube_center[i] - camera_p); - if (USE_UNDISTORED_IMG) - { - local_uv.x() = local_point(0) / local_point(2) * FOCAL_LENGTH + COL / 2; - local_uv.y() = local_point(1) / local_point(2) * FOCAL_LENGTH + ROW / 2; - } - else - m_camera->spaceToPlane(local_point, local_uv); - if (local_point.z() > box_length / 2) - //&& 0 <= local_uv.x() && local_uv.x() <= COL - 1 && 0 <= local_uv.y() && local_uv.y() <= ROW -1) - { - Cube_center_depth[i] = local_point.z(); - for (int j = 0; j < 8; j++) - { - local_point = camera_q.inverse() * (Cube_corner[i][j] - camera_p); - output_corner_dis[i].push_back(local_point.norm()); - if (USE_UNDISTORED_IMG) - { - //ROS_INFO("directly project!"); - local_uv.x() = local_point(0) / local_point(2) * FOCAL_LENGTH + COL / 2; - local_uv.y() = local_point(1) / local_point(2) * FOCAL_LENGTH + ROW / 2; - } - else - { - //ROS_INFO("camera model project!"); - m_camera->spaceToPlane(local_point, local_uv); - local_uv.x() = std::min(std::max(-5000.0, local_uv.x()),5000.0); - local_uv.y() = std::min(std::max(-5000.0, local_uv.y()),5000.0); - } - output_Cube[i].push_back(Vector3d(local_uv.x(), local_uv.y(), 1)); - } - } - else - { - Cube_center_depth[i] = -1; - } - - } -} - -void draw_object(cv::Mat &AR_image) -{ - for (int i = 0; i < axis_num; i++) - { - if(output_Axis[i].empty()) - continue; - cv::Point2d origin(output_Axis[i][0].x(), output_Axis[i][0].y()); - cv::Point2d axis_x(output_Axis[i][1].x(), output_Axis[i][1].y()); - cv::Point2d axis_y(output_Axis[i][2].x(), output_Axis[i][2].y()); - cv::Point2d axis_z(output_Axis[i][3].x(), output_Axis[i][3].y()); - cv::line(AR_image, origin, axis_x, cv::Scalar(0, 0, 255), 2, 8, 0); - cv::line(AR_image, origin, axis_y, cv::Scalar(0, 255, 0), 2, 8, 0); - cv::line(AR_image, origin, axis_z, cv::Scalar(255, 0, 0), 2, 8, 0); - } - - //depth sort big---->small - int index[cube_num]; - for (int i = 0; i < cube_num; i++) - { - index[i] = i; - //cout << "i " << i << " init depth" << Cube_center_depth[i] << endl; - } - for (int i = 0; i < cube_num; i++) - for (int j = 0; j < cube_num - i - 1; j++) - { - if (Cube_center_depth[j] < Cube_center_depth[j + 1]) - { - double tmp = Cube_center_depth[j]; - Cube_center_depth[j] = Cube_center_depth[j + 1]; - Cube_center_depth[j + 1] = tmp; - int tmp_index = index[j]; - index[j] = index[j + 1]; - index[j + 1] = tmp_index; - } - } - - for (int k = 0; k < cube_num; k++) - { - int i = index[k]; - //cout << "draw " << i << "depth " << Cube_center_depth[i] << endl; - if (output_Cube[i].empty()) - continue; - //draw color - cv::Point* p = new cv::Point[8]; - p[0] = cv::Point(output_Cube[i][0].x(), output_Cube[i][0].y()); - p[1] = cv::Point(output_Cube[i][1].x(), output_Cube[i][1].y()); - p[2] = cv::Point(output_Cube[i][2].x(), output_Cube[i][2].y()); - p[3] = cv::Point(output_Cube[i][3].x(), output_Cube[i][3].y()); - p[4] = cv::Point(output_Cube[i][4].x(), output_Cube[i][4].y()); - p[5] = cv::Point(output_Cube[i][5].x(), output_Cube[i][5].y()); - p[6] = cv::Point(output_Cube[i][6].x(), output_Cube[i][6].y()); - p[7] = cv::Point(output_Cube[i][7].x(), output_Cube[i][7].y()); - - int npts[1] = {4}; - float min_depth = 100000; - int min_index = 5; - for(int j= 0; j < (int)output_corner_dis[i].size(); j++) - { - if(output_corner_dis[i][j] < min_depth) - { - min_depth = output_corner_dis[i][j]; - min_index = j; - } - } - - cv::Point plain[1][4]; - const cv::Point* ppt[1] = {plain[0]}; - //first draw large depth plane - int point_group[8][12] = {{0,1,5,4, 0,4,6,2, 0,1,3,2}, - {0,1,5,4, 1,5,7,3, 0,1,3,2}, - {2,3,7,6, 0,4,6,2, 0,1,3,2}, - {2,3,7,6, 1,5,7,3, 0,1,3,2}, - {0,1,5,4, 0,4,6,2, 4,5,7,6}, - {0,1,5,4, 1,5,7,3, 4,5,7,6}, - {2,3,7,6, 0,4,6,2, 4,5,7,6}, - {2,3,7,6, 1,5,7,3, 4,5,7,6}}; - - plain[0][0] = p[point_group[min_index][4]]; - plain[0][1] = p[point_group[min_index][5]]; - plain[0][2] = p[point_group[min_index][6]]; - plain[0][3] = p[point_group[min_index][7]]; - cv::fillPoly(AR_image, ppt, npts, 1, cv::Scalar(0, 200, 0)); - - plain[0][0] = p[point_group[min_index][0]]; - plain[0][1] = p[point_group[min_index][1]]; - plain[0][2] = p[point_group[min_index][2]]; - plain[0][3] = p[point_group[min_index][3]]; - cv::fillPoly(AR_image, ppt, npts, 1, cv::Scalar(200, 0, 0)); - - if(output_corner_dis[i][point_group[min_index][2]] + output_corner_dis[i][point_group[min_index][3]] > - output_corner_dis[i][point_group[min_index][5]] + output_corner_dis[i][point_group[min_index][6]]) - { - plain[0][0] = p[point_group[min_index][4]]; - plain[0][1] = p[point_group[min_index][5]]; - plain[0][2] = p[point_group[min_index][6]]; - plain[0][3] = p[point_group[min_index][7]]; - cv::fillPoly(AR_image, ppt, npts, 1, cv::Scalar(0, 200, 0)); - - } - plain[0][0] = p[point_group[min_index][8]]; - plain[0][1] = p[point_group[min_index][9]]; - plain[0][2] = p[point_group[min_index][10]]; - plain[0][3] = p[point_group[min_index][11]]; - cv::fillPoly(AR_image, ppt, npts, 1, cv::Scalar(0, 0, 200)); - delete p; - } -} - -void callback(const ImageConstPtr& img_msg, const nav_msgs::Odometry::ConstPtr pose_msg) -{ - //throw the first few unstable pose - if(img_cnt < 50) - { - img_cnt ++; - return; - } - //ROS_INFO("sync callback!"); - Vector3d camera_p(pose_msg->pose.pose.position.x, - pose_msg->pose.pose.position.y, - pose_msg->pose.pose.position.z); - Quaterniond camera_q(pose_msg->pose.pose.orientation.w, - pose_msg->pose.pose.orientation.x, - pose_msg->pose.pose.orientation.y, - pose_msg->pose.pose.orientation.z); - - //test plane - Vector3d cam_z(0, 0, -1); - Vector3d w_cam_z = camera_q * cam_z; - //cout << "angle " << acos(w_cam_z.dot(Vector3d(0, 0, 1))) * 180.0 / M_PI << endl; - if (acos(w_cam_z.dot(Vector3d(0, 0, 1))) * 180.0 / M_PI < 90) - { - //ROS_WARN(" look down"); - look_ground = 1; - } - else - look_ground = 0; - - project_object(camera_p, camera_q); - - cv_bridge::CvImageConstPtr ptr; - if (img_msg->encoding == "8UC1") - { - sensor_msgs::Image img; - img.header = img_msg->header; - img.height = img_msg->height; - img.width = img_msg->width; - img.is_bigendian = img_msg->is_bigendian; - img.step = img_msg->step; - img.data = img_msg->data; - img.encoding = "mono8"; - ptr = cv_bridge::toCvCopy(img, sensor_msgs::image_encodings::MONO8); - } - else - ptr = cv_bridge::toCvCopy(img_msg, sensor_msgs::image_encodings::MONO8); - - //cv_bridge::CvImagePtr bridge_ptr = cv_bridge::toCvCopy(ptr, sensor_msgs::image_encodings::MONO8); - cv::Mat AR_image; - AR_image = ptr->image.clone(); - cv::cvtColor(AR_image, AR_image, cv::COLOR_GRAY2RGB); - draw_object(AR_image); - - sensor_msgs::ImagePtr AR_msg = cv_bridge::CvImage(img_msg->header, "bgr8", AR_image).toImageMsg(); - pub_ARimage.publish(AR_msg); - -} -void point_callback(const sensor_msgs::PointCloudConstPtr &point_msg) -{ - if (!look_ground) - return; - int height_range[30]; - double height_sum[30]; - for (int i = 0; i < 30; i++) - { - height_range[i] = 0; - height_sum[i] = 0; - } - for (unsigned int i = 0; i < point_msg->points.size(); i++) - { - //double x = point_msg->points[i].x; - //double y = point_msg->points[i].y; - double z = point_msg->points[i].z; - int index = (z + 2.0) / 0.1; - if (0 <= index && index < 30) - { - height_range[index]++; - height_sum[index] += z; - } - //cout << "point " << " z " << z << endl; - } - int max_num = 0; - int max_index = -1; - for (int i = 1; i < 29; i++) - { - if (max_num < height_range[i]) - { - max_num = height_range[i]; - max_index = i; - } - } - if (max_index == -1) - return; - int tmp_num = height_range[max_index - 1] + height_range[max_index] + height_range[max_index + 1]; - double new_height = (height_sum[max_index - 1] + height_sum[max_index] + height_sum[max_index + 1]) / tmp_num; - //ROS_WARN("detect ground plain, height %f", new_height); - if (tmp_num < (int)point_msg->points.size() / 2) - { - //ROS_INFO("points not enough"); - return; - } - //update height - for (int i = 0; i < cube_num; i++) - { - Cube_center[i].z() = new_height + box_length / 2.0; - } - add_object(); - -} -void img_callback(const ImageConstPtr& img_msg) -{ - if(pose_init) - { - img_buf.push(img_msg); - } - else - return; -} -void pose_callback(const nav_msgs::Odometry::ConstPtr &pose_msg) -{ - if(!pose_init) - { - pose_init = true; - return; - } - - if (img_buf.empty()) - { - //ROS_WARN("image coming late"); - return; - } - - while (img_buf.front()->header.stamp < pose_msg->header.stamp && !img_buf.empty()) - { - img_buf.pop(); - } - - if (!img_buf.empty()) - { - callback(img_buf.front(), pose_msg); - img_buf.pop(); - } - //else - // ROS_WARN("image coming late"); -} -int main( int argc, char** argv ) -{ - ros::init(argc, argv, "points_and_lines"); - ros::NodeHandle n("~"); - object_pub = n.advertise("AR_object", 10); - n.getParam("use_undistored_img", USE_UNDISTORED_IMG); - ros::Subscriber sub_img; - if (USE_UNDISTORED_IMG) - { - // the same as image crop - ROW = 600; - COL = 480; - FOCAL_LENGTH = 320.0; - sub_img = n.subscribe("image_undistored", 100, img_callback); - } - else - { - ROW = 752; - COL = 480; - FOCAL_LENGTH = 460.0; - sub_img = n.subscribe("image_raw", 100, img_callback); - } - - Axis[0] = Vector3d(0, 1.5, -1.2); - Axis[1]= Vector3d(-10, 5, 0); - Axis[2] = Vector3d(3, 3, 3); - Axis[3] = Vector3d(-2, 2, 0); - Axis[4]= Vector3d(5, 10, -5); - Axis[5] = Vector3d(0, 10, -1); - - Cube_center[0] = Vector3d(0, 1.5, -1.2 + box_length / 2.0); - //Cube_center[0] = Vector3d(0, 3, -1.2 + box_length / 2.0); - Cube_center[1] = Vector3d(4, -2, -1.2 + box_length / 2.0); - Cube_center[2] = Vector3d(0, -2, -1.2 + box_length / 2.0); - - ros::Subscriber pose_img = n.subscribe("camera_pose", 100, pose_callback); - ros::Subscriber sub_point = n.subscribe("pointcloud", 2000, point_callback); - image_transport::ImageTransport it(n); - pub_ARimage = it.advertise("AR_image", 1000); - - line_color_r.r = 1.0; - line_color_r.a = 1.0; - line_color_g.g = 1.0; - line_color_g.a = 1.0; - line_color_b.b = 1.0; - line_color_b.a = 1.0; - - string calib_file; - n.getParam("calib_file", calib_file); - ROS_INFO("reading paramerter of camera %s", calib_file.c_str()); - m_camera = CameraFactory::instance()->generateCameraFromYamlFile(calib_file); - - ros::Rate r(100); - ros::Duration(1).sleep(); - add_object(); - add_object(); - ros::spin(); -} - diff --git a/benchmark_publisher/CMakeLists.txt b/benchmark_publisher/CMakeLists.txt index fabccc1cb..969931bda 100644 --- a/benchmark_publisher/CMakeLists.txt +++ b/benchmark_publisher/CMakeLists.txt @@ -1,28 +1,37 @@ -cmake_minimum_required(VERSION 2.8.3) +cmake_minimum_required(VERSION 3.8) project(benchmark_publisher) -set(CMAKE_BUILD_TYPE "Release") -set(CMAKE_CXX_FLAGS "-std=c++11 -DEIGEN_DONT_PARALLELIZE") -set(CMAKE_CXX_FLAGS_RELEASE "-O3 -Wall -g -rdynamic") +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release") +endif() +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) +endif() -find_package(catkin REQUIRED COMPONENTS - roscpp - tf - ) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_FLAGS_RELEASE "-O3 -Wall -DEIGEN_DONT_PARALLELIZE") -catkin_package() -include_directories(${catkin_INCLUDE_DIRS}) +find_package(ament_cmake REQUIRED) +find_package(rclcpp REQUIRED) +find_package(nav_msgs REQUIRED) +find_package(geometry_msgs REQUIRED) -set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) -find_package(Eigen3) -include_directories( - ${catkin_INCLUDE_DIRS} - ${EIGEN3_INCLUDE_DIR} -) +find_package(Eigen3 REQUIRED) add_executable(benchmark_publisher src/benchmark_publisher_node.cpp ) -target_link_libraries(benchmark_publisher ${catkin_LIBRARIES}) +ament_target_dependencies(benchmark_publisher + rclcpp + nav_msgs + geometry_msgs +) + +target_include_directories(benchmark_publisher PRIVATE ${EIGEN3_INCLUDE_DIR}) + +install(TARGETS benchmark_publisher + DESTINATION lib/${PROJECT_NAME}) + +ament_package() diff --git a/benchmark_publisher/launch/publish.launch b/benchmark_publisher/launch/publish.launch deleted file mode 100644 index 5e066fa61..000000000 --- a/benchmark_publisher/launch/publish.launch +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - diff --git a/benchmark_publisher/package.xml b/benchmark_publisher/package.xml index 6a33c48f0..2a3866ae0 100644 --- a/benchmark_publisher/package.xml +++ b/benchmark_publisher/package.xml @@ -1,52 +1,19 @@ - + benchmark_publisher 0.0.0 The benchmark_publisher package - - - dvorak - - - - - TODO + ament_cmake - - - - - - - - - - + rclcpp + nav_msgs + geometry_msgs - - - - - - - - - - - - - catkin - roscpp - roscpp - - - - - + ament_cmake \ No newline at end of file diff --git a/benchmark_publisher/src/benchmark_publisher_node.cpp b/benchmark_publisher/src/benchmark_publisher_node.cpp index c405c87fd..566a7d7bb 100644 --- a/benchmark_publisher/src/benchmark_publisher_node.cpp +++ b/benchmark_publisher/src/benchmark_publisher_node.cpp @@ -1,10 +1,11 @@ #include #include -#include -#include -#include -#include -#include +#include +#include +#include "rclcpp/rclcpp.hpp" +#include "nav_msgs/msg/odometry.hpp" +#include "nav_msgs/msg/path.hpp" +#include "geometry_msgs/msg/pose_stamped.hpp" #include #include @@ -14,21 +15,6 @@ using namespace Eigen; const int SKIP = 50; string benchmark_output_path; string estimate_output_path; -template -T readParam(ros::NodeHandle &n, std::string name) -{ - T ans; - if (n.getParam(name, ans)) - { - ROS_INFO_STREAM("Loaded " << name << ": " << ans); - } - else - { - ROS_ERROR_STREAM("Failed to load " << name); - n.shutdown(); - } - return ans; -} struct Data { @@ -54,22 +40,21 @@ struct Data int idx = 1; vector benchmark; -ros::Publisher pub_odom; -ros::Publisher pub_path; -nav_msgs::Path path; +rclcpp::Publisher::SharedPtr pub_odom; +rclcpp::Publisher::SharedPtr pub_path; +nav_msgs::msg::Path path; int init = 0; Quaterniond baseRgt; Vector3d baseTgt; -tf::Transform trans; -void odom_callback(const nav_msgs::OdometryConstPtr &odom_msg) +void odom_callback(const nav_msgs::msg::Odometry::ConstSharedPtr &odom_msg) { - //ROS_INFO("odom callback!"); - if (odom_msg->header.stamp.toSec() > benchmark.back().t) + double stamp = rclcpp::Time(odom_msg->header.stamp).seconds(); + if (stamp > benchmark.back().t) return; - - for (; idx < static_cast(benchmark.size()) && benchmark[idx].t <= odom_msg->header.stamp.toSec(); idx++) + + for (; idx < static_cast(benchmark.size()) && benchmark[idx].t <= stamp; idx++) ; @@ -90,8 +75,12 @@ void odom_callback(const nav_msgs::OdometryConstPtr &odom_msg) return; } - nav_msgs::Odometry odometry; - odometry.header.stamp = ros::Time(benchmark[idx - 1].t); + nav_msgs::msg::Odometry odometry; + builtin_interfaces::msg::Time stamp_msg; + int64_t ns = static_cast(benchmark[idx - 1].t * 1e9); + stamp_msg.sec = static_cast(ns / 1000000000LL); + stamp_msg.nanosec = static_cast(ns % 1000000000LL); + odometry.header.stamp = stamp_msg; odometry.header.frame_id = "world"; odometry.child_frame_id = "world"; @@ -115,46 +104,50 @@ void odom_callback(const nav_msgs::OdometryConstPtr &odom_msg) odometry.twist.twist.linear.x = tmp_V.x(); odometry.twist.twist.linear.y = tmp_V.y(); odometry.twist.twist.linear.z = tmp_V.z(); - pub_odom.publish(odometry); + pub_odom->publish(odometry); - geometry_msgs::PoseStamped pose_stamped; + geometry_msgs::msg::PoseStamped pose_stamped; pose_stamped.header = odometry.header; pose_stamped.pose = odometry.pose.pose; path.header = odometry.header; path.poses.push_back(pose_stamped); - pub_path.publish(path); + pub_path->publish(path); } int main(int argc, char **argv) { - ros::init(argc, argv, "benchmark_publisher"); - ros::NodeHandle n("~"); + rclcpp::init(argc, argv); + auto node = std::make_shared("benchmark_publisher"); + + std::string csv_file; + node->declare_parameter("data_name", ""); + node->get_parameter("data_name", csv_file); - string csv_file = readParam(n, "data_name"); std::cout << "load ground truth " << csv_file << std::endl; FILE *f = fopen(csv_file.c_str(), "r"); - if (f==NULL) + if (f == NULL) { - ROS_WARN("can't load ground truth; wrong path"); - //std::cerr << "can't load ground truth; wrong path " << csv_file << std::endl; - return 0; + RCLCPP_WARN(node->get_logger(), "can't load ground truth; wrong path"); + rclcpp::shutdown(); + return 0; } char tmp[10000]; if (fgets(tmp, 10000, f) == NULL) { - ROS_WARN("can't load ground truth; no data available"); + RCLCPP_WARN(node->get_logger(), "can't load ground truth; no data available"); } while (!feof(f)) benchmark.emplace_back(f); fclose(f); benchmark.pop_back(); - ROS_INFO("Data loaded: %d", (int)benchmark.size()); + RCLCPP_INFO(node->get_logger(), "Data loaded: %d", (int)benchmark.size()); + + pub_odom = node->create_publisher("odometry", 1000); + pub_path = node->create_publisher("path", 1000); - pub_odom = n.advertise("odometry", 1000); - pub_path = n.advertise("path", 1000); + auto sub_odom = node->create_subscription( + "estimated_odometry", 1000, odom_callback); - ros::Subscriber sub_odom = n.subscribe("estimated_odometry", 1000, odom_callback); - - ros::Rate r(20); - ros::spin(); + rclcpp::spin(node); + rclcpp::shutdown(); } diff --git a/camera_imu_calibration.py b/camera_imu_calibration.py new file mode 100644 index 000000000..a552df3c0 --- /dev/null +++ b/camera_imu_calibration.py @@ -0,0 +1,241 @@ +#!/usr/bin/env python3 +""" +Интерактивный инструмент для определения матрицы вращения камеры-IMU. + +Использование: +1. Запустите этот скрипт +2. Следуйте инструкциям и отвечайте на вопросы о расположении осей +3. Получите матрицу вращения для конфига +""" + +import numpy as np +from scipy.spatial.transform import Rotation +import sys + +def print_section(title): + print("\n" + "="*60) + print(f" {title}") + print("="*60) + +def get_axis_direction(sensor_name, axis_name): + """Получить направление оси от пользователя""" + directions = { + 'x': 'X', + 'y': 'Y', + 'z': 'Z', + '+x': '+X (forward)', + '-x': '-X (backward)', + '+y': '+Y (right)', + '-y': '-Y (left)', + '+z': '+Z (up)', + '-z': '-Z (down)' + } + + print(f"\n{sensor_name} - Ось {axis_name}:") + print(" Введите направление этой оси:") + print(" +x = forward, -x = backward") + print(" +y = right, -y = left") + print(" +z = up, -z = down") + print(" (пример: +z, -y, +x)") + + while True: + response = input(f" {sensor_name} {axis_name} направлена в: ").strip().lower() + if response in directions: + return response + print(" ❌ Некорректный ввод. Попробуйте снова.") + +def direction_to_vector(direction): + """Преобразовать направление в вектор""" + mapping = { + '+x': np.array([1, 0, 0]), + '-x': np.array([-1, 0, 0]), + '+y': np.array([0, 1, 0]), + '-y': np.array([0, -1, 0]), + '+z': np.array([0, 0, 1]), + '-z': np.array([0, 0, -1]), + } + return mapping[direction] + +def get_imu_axes(): + """Получить оси IMU""" + print_section("КОНФИГУРАЦИЯ IMU") + print("\nОпределите направление осей IMU в пространстве.") + print("(Как правило X-forward, Y-left/right, Z-up)") + + imu_x = get_axis_direction("IMU", "X") + imu_y = get_axis_direction("IMU", "Y") + imu_z = get_axis_direction("IMU", "Z") + + return imu_x, imu_y, imu_z + +def get_camera_axes(): + """Получить оси камеры""" + print_section("КОНФИГУРАЦИЯ КАМЕРЫ") + print("\nОпределите направление осей камеры в пространстве.") + print("(Стандартная ориентация камеры: Z-forward, X-right, Y-down)") + + cam_x = get_axis_direction("КАМЕРА", "X") + cam_y = get_axis_direction("КАМЕРА", "Y") + cam_z = get_axis_direction("КАМЕРА", "Z") + + return cam_x, cam_y, cam_z + +def verify_orthogonal(v1, v2, v3, name): + """Проверить ортогональность векторов""" + # Проверить что все векторы разные + if np.allclose(np.abs(v1), np.abs(v2)) or np.allclose(np.abs(v1), np.abs(v3)) or np.allclose(np.abs(v2), np.abs(v3)): + print(f"\n❌ ОШИБКА в {name}: Две оси указывают на одно направление!") + return False + + # Проверить ортогональность (скалярное произведение должно быть 0) + dot12 = np.dot(v1, v2) + dot13 = np.dot(v1, v3) + dot23 = np.dot(v2, v3) + + tol = 1e-6 + if abs(dot12) > tol or abs(dot13) > tol or abs(dot23) > tol: + print(f"\n❌ ОШИБКА в {name}: Оси не ортогональны!") + return False + + return True + +def vectors_to_rotation_matrix(cam_x_dir, cam_y_dir, cam_z_dir, imu_x_dir, imu_y_dir, imu_z_dir): + """ + Вычислить матрицу вращения от камеры к IMU. + + Входные данные: + - cam_x_dir, cam_y_dir, cam_z_dir: направления осей камеры в мировых координатах + - imu_x_dir, imu_y_dir, imu_z_dir: направления осей IMU в мировых координатах + + Выход: + - R: матрица вращения такая что imu_point = R * camera_point + """ + + # Матрица, где столбцы - это базис камеры в мировых координатах + camera_basis = np.column_stack([cam_x_dir, cam_y_dir, cam_z_dir]) + + # Матрица, где столбцы - это базис IMU в мировых координатах + imu_basis = np.column_stack([imu_x_dir, imu_y_dir, imu_z_dir]) + + # R преобразует вектор из камеры в IMU + # мир = camera_basis @ cam_coords + # мир = imu_basis @ imu_coords + # Поэтому: imu_basis @ imu_coords = camera_basis @ cam_coords + # imu_coords = (imu_basis^-1 @ camera_basis) @ cam_coords + # R = imu_basis^-1 @ camera_basis + + R = np.linalg.inv(imu_basis) @ camera_basis + + return R + +def print_matrix_in_yaml(R): + """Вывести матрицу в формате YAML для конфига""" + print_section("РЕЗУЛЬТАТ: МАТРИЦА ВРАЩЕНИЯ") + print("\nКопируйте эту матрицу в config/usb_cam_config.yaml:") + print("\nextrinsicRotation: !!opencv-matrix") + print(" rows: 3") + print(" cols: 3") + print(" dt: d") + print(" data: [", end="") + + # Вывести в одну строку для копирования + flat = R.flatten() + for i, val in enumerate(flat): + # Округлить до 6 знаков после запятой + print(f"{val:.15f}", end="") + if i < len(flat) - 1: + print(", ", end="") + print("]") + + print("\nИли в развёрнутом виде:") + print("extrinsicRotation: !!opencv-matrix") + print(" rows: 3") + print(" cols: 3") + print(" dt: d") + print(" data: [" + f"{R[0,0]:.6f}, {R[0,1]:.6f}, {R[0,2]:.6f},") + print(f" {R[1,0]:.6f}, {R[1,1]:.6f}, {R[1,2]:.6f},") + print(f" {R[2,0]:.6f}, {R[2,1]:.6f}, {R[2,2]:.6f}]") + +def print_verification(cam_x, cam_y, cam_z, imu_x, imu_y, imu_z, R): + """Напечатать проверку преобразований""" + print_section("ПРОВЕРКА ПРЕОБРАЗОВАНИЙ") + + print("\nОси КАМЕРЫ в МИРОВЫХ координатах:") + print(f" X: {cam_x} → {direction_to_vector(cam_x)}") + print(f" Y: {cam_y} → {direction_to_vector(cam_y)}") + print(f" Z: {cam_z} → {direction_to_vector(cam_z)}") + + print("\nОси IMU в МИРОВЫХ координатах:") + print(f" X: {imu_x} → {direction_to_vector(imu_x)}") + print(f" Y: {imu_y} → {direction_to_vector(imu_y)}") + print(f" Z: {imu_z} → {direction_to_vector(imu_z)}") + + print("\nМатрица вращения imu^R_cam:") + print(R) + + print("\nПроверка: преобразование осей камеры через R:") + cam_x_vec = direction_to_vector(cam_x) + cam_y_vec = direction_to_vector(cam_y) + cam_z_vec = direction_to_vector(cam_z) + + result_x = R @ cam_x_vec + result_y = R @ cam_y_vec + result_z = R @ cam_z_vec + + imu_x_vec = direction_to_vector(imu_x) + imu_y_vec = direction_to_vector(imu_y) + imu_z_vec = direction_to_vector(imu_z) + + print(f"\n R @ cam_X = {result_x} (должно быть {imu_x_vec} - IMU X)") + print(f" R @ cam_Y = {result_y} (должно быть {imu_y_vec} - IMU Y)") + print(f" R @ cam_Z = {result_z} (должно быть {imu_z_vec} - IMU Z)") + + # Проверить корректность + if (np.allclose(result_x, imu_x_vec) and + np.allclose(result_y, imu_y_vec) and + np.allclose(result_z, imu_z_vec)): + print("\n✅ Матрица ВЕРНА! Оси правильно преобразуются.") + else: + print("\n❌ Что-то не так. Проверьте введённые данные.") + +def main(): + print_section("КАЛИБРОВКА МАТРИЦЫ ВРАЩЕНИЯ КАМЕРЫ-IMU") + print("\nЭтот инструмент поможет вам определить правильную матрицу вращения") + print("между камерой и IMU на основе фактического расположения осей.") + + # Получить оси IMU + imu_x, imu_y, imu_z = get_imu_axes() + + # Получить оси камеры + cam_x, cam_y, cam_z = get_camera_axes() + + # Преобразовать в векторы + cam_x_vec = direction_to_vector(cam_x) + cam_y_vec = direction_to_vector(cam_y) + cam_z_vec = direction_to_vector(cam_z) + + imu_x_vec = direction_to_vector(imu_x) + imu_y_vec = direction_to_vector(imu_y) + imu_z_vec = direction_to_vector(imu_z) + + # Проверить ортогональность + if not verify_orthogonal(cam_x_vec, cam_y_vec, cam_z_vec, "КАМЕРА"): + sys.exit(1) + if not verify_orthogonal(imu_x_vec, imu_y_vec, imu_z_vec, "IMU"): + sys.exit(1) + + # Вычислить матрицу вращения + R = vectors_to_rotation_matrix(cam_x_vec, cam_y_vec, cam_z_vec, + imu_x_vec, imu_y_vec, imu_z_vec) + + # Вывести результаты + print_verification(cam_x, cam_y, cam_z, imu_x, imu_y, imu_z, R) + print_matrix_in_yaml(R) + + print_section("ДАЛЕЕ") + print("\n1. Скопируйте матрицу выше в файл config/usb_cam_config.yaml") + print("2. Пересоберите: cd ~/catkin_ws && catkin_make") + print("3. Перезапустите VINS: roslaunch vins_estimator usb_cam.launch") + +if __name__ == "__main__": + main() diff --git a/camera_model/CMakeLists.txt b/camera_model/CMakeLists.txt index 95a0c5b3d..c1d633790 100644 --- a/camera_model/CMakeLists.txt +++ b/camera_model/CMakeLists.txt @@ -1,41 +1,27 @@ -cmake_minimum_required(VERSION 2.8.3) +cmake_minimum_required(VERSION 3.8) project(camera_model) -set(CMAKE_BUILD_TYPE "Release") -set(CMAKE_CXX_FLAGS "-std=c++11") -set(CMAKE_CXX_FLAGS_RELEASE "-O3 -fPIC") +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release") +endif() -find_package(catkin REQUIRED COMPONENTS - roscpp - std_msgs - ) +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) +endif() -find_package(Boost REQUIRED COMPONENTS filesystem program_options system) -include_directories(${Boost_INCLUDE_DIRS}) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_FLAGS_RELEASE "-O3 -fPIC -DNDEBUG") +find_package(ament_cmake REQUIRED) +find_package(Boost REQUIRED COMPONENTS filesystem program_options system) find_package(OpenCV REQUIRED) - -# set(EIGEN_INCLUDE_DIR "/usr/local/include/eigen3") find_package(Ceres REQUIRED) -include_directories(${CERES_INCLUDE_DIRS}) - - -catkin_package( - INCLUDE_DIRS include - LIBRARIES camera_model - CATKIN_DEPENDS roscpp std_msgs -# DEPENDS system_lib - ) - -include_directories( - ${catkin_INCLUDE_DIRS} - ) +include_directories(${Boost_INCLUDE_DIRS}) +include_directories(${CERES_INCLUDE_DIRS}) include_directories("include") - -add_executable(Calibration - src/intrinsic_calib.cc +add_library(camera_model SHARED src/chessboard/Chessboard.cc src/calib/CameraCalibration.cc src/camera_models/Camera.cc @@ -49,7 +35,10 @@ add_executable(Calibration src/gpl/gpl.cc src/gpl/EigenQuaternionParameterization.cc) -add_library(camera_model +target_link_libraries(camera_model ${Boost_LIBRARIES} ${OpenCV_LIBS} ${CERES_LIBRARIES}) + +add_executable(Calibration + src/intrinsic_calib.cc src/chessboard/Chessboard.cc src/calib/CameraCalibration.cc src/camera_models/Camera.cc @@ -63,5 +52,27 @@ add_library(camera_model src/gpl/gpl.cc src/gpl/EigenQuaternionParameterization.cc) -target_link_libraries(Calibration ${Boost_LIBRARIES} ${OpenCV_LIBS} ${CERES_LIBRARIES}) -target_link_libraries(camera_model ${Boost_LIBRARIES} ${OpenCV_LIBS} ${CERES_LIBRARIES}) +target_link_libraries(Calibration ${Boost_LIBRARIES} ${OpenCV_LIBS} ${CERES_LIBRARIES} camera_model) + +# Export library for downstream packages +ament_export_targets(export_camera_model HAS_LIBRARY_TARGET) +ament_export_dependencies(OpenCV Ceres) +ament_export_include_directories(include) + +# Ensure Boost targets are available for downstream packages +find_package(Boost REQUIRED COMPONENTS filesystem program_options system) +ament_export_dependencies(Boost) + +install(TARGETS camera_model + EXPORT export_camera_model + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin) + +install(DIRECTORY include/ + DESTINATION include) + +install(TARGETS Calibration + DESTINATION lib/${PROJECT_NAME}) + +ament_package() diff --git a/camera_model/include/camodocal/gpl/EigenQuaternionParameterization.h b/camera_model/include/camodocal/gpl/EigenQuaternionParameterization.h index 3d08f0ea0..22e75abc8 100644 --- a/camera_model/include/camodocal/gpl/EigenQuaternionParameterization.h +++ b/camera_model/include/camodocal/gpl/EigenQuaternionParameterization.h @@ -1,7 +1,7 @@ #ifndef EIGENQUATERNIONPARAMETERIZATION_H #define EIGENQUATERNIONPARAMETERIZATION_H -#include "ceres/local_parameterization.h" +#include "camodocal/gpl/ceres_local_parameterization_compat.h" namespace camodocal { diff --git a/camera_model/include/camodocal/gpl/ceres_local_parameterization_compat.h b/camera_model/include/camodocal/gpl/ceres_local_parameterization_compat.h new file mode 100644 index 000000000..45f195fb1 --- /dev/null +++ b/camera_model/include/camodocal/gpl/ceres_local_parameterization_compat.h @@ -0,0 +1,70 @@ +#ifndef CERES_LOCAL_PARAMETERIZATION_COMPAT_H +#define CERES_LOCAL_PARAMETERIZATION_COMPAT_H + +#include +#include + +namespace ceres { + +// Compatibility wrapper: Ceres 2.2 removed LocalParameterization in favor of Manifold. +// This shim provides the old LocalParameterization API on top of Manifold, +// so existing code can be compiled with minimal changes. +// +// Usage: inherit from LocalParameterization and implement: +// bool Plus(const double* x, const double* delta, double* x_plus_delta) const +// bool ComputeJacobian(const double* x, double* jacobian) const +// int GlobalSize() const +// int LocalSize() const +class LocalParameterization : public Manifold { +public: + virtual ~LocalParameterization() {} + + // Old API — user implements these + virtual bool Plus(const double* x, const double* delta, + double* x_plus_delta) const = 0; + virtual bool ComputeJacobian(const double* x, double* jacobian) const = 0; + virtual int GlobalSize() const = 0; + virtual int LocalSize() const = 0; + + // Manifold interface — maps old names to new + int AmbientSize() const final { return GlobalSize(); } + int TangentSize() const final { return LocalSize(); } + + // Manifold::Plus is pure virtual; we provide it via the old Plus + // (same signature, so the derived class's Plus satisfies both) + + bool PlusJacobian(const double* x, double* jacobian) const final { + return ComputeJacobian(x, jacobian); + } + + bool Minus(const double* y, const double* x, double* y_minus_x) const final { + int n = LocalSize(); + for (int i = 0; i < n; ++i) { + y_minus_x[i] = y[i] - x[i]; + } + return true; + } + + bool MinusJacobian(const double* x, double* jacobian) const final { + int n = LocalSize(); + int m = GlobalSize(); + for (int i = 0; i < n * m; ++i) jacobian[i] = 0.0; + for (int i = 0; i < n && i < m; ++i) { + jacobian[i * m + i] = 1.0; + } + return true; + } +}; + +// Compatibility for AutoDiffLocalParameterization +template +class AutoDiffLocalParameterization { +public: + static LocalParameterization* Create() { + return new AutoDiffManifold(); + } +}; + +} // namespace ceres + +#endif // CERES_LOCAL_PARAMETERIZATION_COMPAT_H \ No newline at end of file diff --git a/camera_model/package.xml b/camera_model/package.xml index 511c997f6..72cb40f88 100644 --- a/camera_model/package.xml +++ b/camera_model/package.xml @@ -1,54 +1,23 @@ - + camera_model 0.0.0 The camera_model package - - - dvorak - - - - - TODO + ament_cmake - - - - - - - - - - + boost + libopencv-dev + libceres-dev + boost + libopencv-dev + libceres-dev - - - - - - - - - - - - catkin - roscpp - std_msgs - roscpp - std_msgs - - - - - + ament_cmake \ No newline at end of file diff --git a/camera_model/src/calib/CameraCalibration.cc b/camera_model/src/calib/CameraCalibration.cc index 682fe07a1..aa717834c 100644 --- a/camera_model/src/calib/CameraCalibration.cc +++ b/camera_model/src/calib/CameraCalibration.cc @@ -507,8 +507,8 @@ CameraCalibration::optimize(CameraPtr& camera, ceres::LocalParameterization* quaternionParameterization = new EigenQuaternionParameterization; - problem.SetParameterization(transformVec.at(i).rotationData(), - quaternionParameterization); + problem.SetManifold(transformVec.at(i).rotationData(), + quaternionParameterization); } std::cout << "begin ceres" << std::endl; diff --git a/config/3dm/3dm_config.yaml b/config/3dm/3dm_config.yaml deleted file mode 100644 index 8677bb261..000000000 --- a/config/3dm/3dm_config.yaml +++ /dev/null @@ -1,86 +0,0 @@ -%YAML:1.0 - -#common parameters -imu_topic: "/imu_3dm_gx4/imu" -image_topic: "/mv_25001498/image_raw" -output_path: "/home/tony-ws1/output/" - -# MEI is better than PINHOLE for large FOV camera -model_type: MEI -camera_name: camera -image_width: 752 -image_height: 480 -mirror_parameters: - xi: 2.057e+00 -distortion_parameters: - k1: 7.145e-02 - k2: 5.059e-01 - p1: 4.727e-05 - p2: -5.492e-04 -projection_parameters: - gamma1: 1.115e+03 - gamma2: 1.114e+03 - u0: 3.672e+02 - v0: 2.385e+02 - -# Extrinsic parameter between IMU and Camera. -estimate_extrinsic: 0 # 0 Have an accurate extrinsic parameters. We will trust the following imu^R_cam, imu^T_cam, don't change it. - # 1 Have an initial guess about extrinsic parameters. We will optimize around your initial guess. - # 2 Don't know anything about extrinsic parameters. You don't need to give R,T. We will try to calibrate it. Do some rotation movement at beginning. -#If you choose 0 or 1, you should write down the following matrix. -#Rotation from camera frame to imu frame, imu^R_cam -extrinsicRotation: !!opencv-matrix - rows: 3 - cols: 3 - dt: d - data: [ 0, 0, -1, - -1, 0, 0, - 0, 1, 0] - -#Translation from camera frame to imu frame, imu^T_cam -extrinsicTranslation: !!opencv-matrix - rows: 3 - cols: 1 - dt: d - data: [ 0, 0, 0.02] - -#feature traker paprameters - -max_cnt: 150 # max feature number in feature tracking -min_dist: 20 # min distance between two features -freq: 20 # frequence (Hz) of publish tracking result. At least 10Hz for good estimation. If set 0, the frequence will be same as raw image -F_threshold: 1.0 # ransac threshold (pixel) -show_track: 1 # publish tracking image as topic -equalize: 0 # if image is too dark or light, trun on equalize to find enough features -fisheye: 0 # if using fisheye, trun on it. A circle mask will be loaded to remove edge noisy points -#optimization parameters - -max_solver_time: 0.035 # max solver itration time (ms), to guarantee real time -max_num_iterations: 10 # max solver itrations, to guarantee real time -keyframe_parallax: 10.0 # keyframe selection threshold (pixel) - -#imu parameters The more accurate parameters you provide, the better performance -acc_n: 0.2 # accelerometer measurement noise standard deviation. -gyr_n: 0.05 # gyroscope measurement noise standard deviation. -acc_w: 0.002 # accelerometer bias random work noise standard deviation. -gyr_w: 4.0e-5 # gyroscope bias random work noise standard deviation. -g_norm: 9.805 # - -#loop closure parameters -loop_closure: 0 # start loop closure -load_previous_pose_graph: 0 # load and reuse previous pose graph; load from 'pose_graph_save_path' -fast_relocalization: 0 # useful in real-time and large project -pose_graph_save_path: "/home/tony-ws1/output/pose_graph/" # save and load path - -#unsynchronization parameters -estimate_td: 0 # online estimate time offset between camera and imu -td: 0.000 # initial value of time offset. unit: s. readed image clock + td = real image clock (IMU clock) - -#rolling shutter parameters -rolling_shutter: 0 # 0: global shutter camera, 1: rolling shutter camera -rolling_shutter_tr: 0 # unit: s. rolling shutter read out time per frame (from data sheet). - -#visualization parameters -save_image: 1 # save image in pose graph for visualization prupose; you can close this function by setting 0 -visualize_imu_forward: 0 # output imu forward propogation to achieve low latency and high frequence results -visualize_camera_size: 0.4 # size of camera marker in RVIZ \ No newline at end of file diff --git a/config/AR_demo.rviz b/config/AR_demo.rviz deleted file mode 100644 index 12c630e12..000000000 --- a/config/AR_demo.rviz +++ /dev/null @@ -1,235 +0,0 @@ -Panels: - - Class: rviz/Displays - Help Height: 0 - Name: Displays - Property Tree Widget: - Expanded: - - /Global Options1 - - /Status1 - - /AR_image1/Status1 - - /raw image1/Status1 - - /Path2/Status1 - Splitter Ratio: 0.465116 - Tree Height: 729 - - Class: rviz/Selection - Name: Selection - - Class: rviz/Tool Properties - Expanded: - - /2D Pose Estimate1 - - /2D Nav Goal1 - - /Publish Point1 - Name: Tool Properties - Splitter Ratio: 0.588679 - - Class: rviz/Views - Expanded: - - /Current View1 - Name: Views - Splitter Ratio: 0.5 - - Class: rviz/Time - Experimental: false - Name: Time - SyncMode: 0 - SyncSource: PointCloud -Visualization Manager: - Class: "" - Displays: - - Alpha: 0.5 - Cell Size: 1 - Class: rviz/Grid - Color: 130; 130; 130 - Enabled: true - Line Style: - Line Width: 0.03 - Value: Lines - Name: Grid - Normal Cell Count: 0 - Offset: - X: 0 - Y: 0 - Z: -1.2 - Plane: XY - Plane Cell Count: 10 - Reference Frame: - Value: true - - Alpha: 1 - Buffer Length: 1 - Class: rviz/Path - Color: 0; 170; 0 - Enabled: true - Head Diameter: 0.3 - Head Length: 0.2 - Length: 0.3 - Line Style: Lines - Line Width: 0.01 - Name: Path - Offset: - X: 0 - Y: 0 - Z: 0 - Pose Style: None - Radius: 0.03 - Shaft Diameter: 0.1 - Shaft Length: 0.1 - Topic: /vins_estimator/path - Unreliable: false - Value: true - - Alpha: 1 - Autocompute Intensity Bounds: false - Autocompute Value Bounds: - Max Value: 10 - Min Value: -10 - Value: true - Axis: Z - Channel Name: intensity - Class: rviz/PointCloud - Color: 255; 255; 255 - Color Transformer: FlatColor - Decay Time: 0 - Enabled: true - Invert Rainbow: true - Max Color: 0; 0; 0 - Max Intensity: 4096 - Min Color: 0; 0; 0 - Min Intensity: 0 - Name: PointCloud - Position Transformer: XYZ - Queue Size: 10 - Selectable: true - Size (Pixels): 1 - Size (m): 0.5 - Style: Points - Topic: /vins_estimator/point_cloud - Unreliable: false - Use Fixed Frame: true - Use rainbow: false - Value: true - - Class: rviz/Image - Enabled: true - Image Topic: /ar_demo_node/AR_image - Max Value: 1 - Median window: 5 - Min Value: 0 - Name: AR_image - Normalize Range: true - Queue Size: 2 - Transport Hint: raw - Unreliable: false - Value: true - - Class: rviz/Image - Enabled: true - Image Topic: /mv_25001498/image_raw - Max Value: 1 - Median window: 5 - Min Value: 0 - Name: raw image - Normalize Range: true - Queue Size: 2 - Transport Hint: raw - Unreliable: false - Value: true - - Class: rviz/MarkerArray - Enabled: true - Marker Topic: /vins_estimator/camera_pose_visual - Name: MarkerArray - Namespaces: - CameraPoseVisualization: true - Queue Size: 100 - Value: true - - Class: rviz/MarkerArray - Enabled: true - Marker Topic: /ar_demo_node/AR_object - Name: MarkerArray - Namespaces: - basic_shapes: true - Queue Size: 100 - Value: true - - Class: rviz/Axes - Enabled: true - Length: 1 - Name: Axes - Radius: 0.1 - Reference Frame: - Value: true - - Alpha: 1 - Buffer Length: 1 - Class: rviz/Path - Color: 255; 85; 255 - Enabled: true - Head Diameter: 0.3 - Head Length: 0.2 - Length: 0.3 - Line Style: Billboards - Line Width: 0.3 - Name: Path - Offset: - X: 0 - Y: 0 - Z: 0 - Pose Style: None - Radius: 0.03 - Shaft Diameter: 0.1 - Shaft Length: 0.1 - Topic: /odom_translation/vins - Unreliable: false - Value: true - Enabled: true - Global Options: - Background Color: 0; 0; 0 - Fixed Frame: world - Frame Rate: 30 - Name: root - Tools: - - Class: rviz/MoveCamera - - Class: rviz/Select - - Class: rviz/FocusCamera - - Class: rviz/Measure - - Class: rviz/SetInitialPose - Topic: /initialpose - - Class: rviz/SetGoal - Topic: /move_base_simple/goal - - Class: rviz/PublishPoint - Single click: true - Topic: /clicked_point - Value: true - Views: - Current: - Class: rviz/Orbit - Distance: 33.293 - Enable Stereo Rendering: - Stereo Eye Separation: 0.06 - Stereo Focal Distance: 1 - Swap Stereo Eyes: false - Value: false - Focal Point: - X: -3.42082 - Y: -4.08465 - Z: -8.071 - Name: Current View - Near Clip Distance: 0.01 - Pitch: 1.5698 - Target Frame: - Value: Orbit (rviz) - Yaw: 0.0571614 - Saved: ~ -Window Geometry: - AR_image: - collapsed: false - Displays: - collapsed: true - Height: 959 - Hide Left Dock: false - Hide Right Dock: true - QMainWindow State: 000000ff00000000fd00000004000000000000024900000338fc020000000ffb0000000a0049006d0061006700650000000028000001590000000000000000fb0000001200530065006c0065006300740069006f006e00000001e10000009b0000006400fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb0000001200720061007700200049006d0061006700650100000028000000160000000000000000fb0000001a0074007200610063006b0065006400200069006d0061006700650100000044000000160000000000000000fb0000001200720061007700200069006d0061006700650100000028000001a30000001600fffffffb0000001000410052005f0069006d00610067006501000001d10000018f0000001600fffffffb0000000a0049006d0061006700650100000235000000a20000000000000000fb0000001200720061007700200069006d0061006700650100000028000000e80000000000000000fb0000001000410052005f0069006d0061006700650100000116000000e70000000000000000000000010000016a00000338fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fc00000028000003380000000000fffffffaffffffff0100000002fb000000100044006900730070006c0061007900730000000000ffffffff0000016a00fffffffb0000000a00560069006500770073000000023f0000016a0000010f00fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000006240000003bfc0100000002fb0000000800540069006d0065010000000000000624000002f600fffffffb0000000800540069006d00650100000000000004500000000000000000000003d50000033800000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 - Selection: - collapsed: false - Time: - collapsed: false - Tool Properties: - collapsed: false - Views: - collapsed: true - Width: 1572 - X: 286 - Y: 43 - raw image: - collapsed: false diff --git a/config/black_box/black_box_config.yaml b/config/black_box/black_box_config.yaml deleted file mode 100644 index f834011b5..000000000 --- a/config/black_box/black_box_config.yaml +++ /dev/null @@ -1,89 +0,0 @@ -%YAML:1.0 - -#common parameters -imu_topic: "/djiros/imu" -image_topic: "/djiros/image" -output_path: "/home/urop/output/" # vins outputs will be wrttento vins_folder_path + output_path - -#camera calibration -model_type: MEI -camera_name: camera -image_width: 752 -image_height: 480 -mirror_parameters: - xi: 2.2134257311108083e+00 -distortion_parameters: - k1: 1.4213768437132895e-01 - k2: 9.1226950620748259e-01 - p1: 1.2056297779277966e-03 - p2: 2.0300076091651340e-03 -projection_parameters: - gamma1: 1.1659242643040975e+03 - gamma2: 1.1656143723709608e+03 - u0: 3.9238492754088008e+02 - v0: 2.4392485271819217e+02 - - -# Extrinsic parameter between IMU and Camera. -estimate_extrinsic: 1 # 0 Have an accurate extrinsic parameters. We will trust the following imu^R_cam, imu^T_cam, don't change it. - # 1 Have an initial guess about extrinsic parameters. We will optimize around your initial guess. - # 2 Don't know anything about extrinsic parameters. You don't need to give R,T. We will try to calibrate it. Do some rotation movement at beginning. -#If you choose 0 or 1, you should write down the following matrix. -#Rotation from camera frame to imu frame, imu^R_cam -extrinsicRotation: !!opencv-matrix - rows: 3 - cols: 3 - dt: d - data: [ 3.5547377966504534e-02, -9.9819308302994181e-01, - -4.8445360055281682e-02, 9.9855985185796758e-01, - 3.3527772724371241e-02, 4.1882104932016398e-02, - -4.0182162424389177e-02, -4.9864390574059170e-02, - 9.9794736152543517e-01 ] -extrinsicTranslation: !!opencv-matrix - rows: 3 - cols: 1 - dt: d - data: [ 6.5272135116678912e-02, -6.8544177447541876e-02, - 3.7304589197375740e-02 ] - - -#feature traker paprameters - -max_cnt: 120 # max feature number in feature tracking -min_dist: 30 # min distance between two features -freq: 10 # frequence (Hz) of publish tracking result. At least 10Hz for good estimation. If set 0, the frequence will be same as raw image -F_threshold: 1.0 # ransac threshold (pixel) -show_track: 1 # publish tracking image as topic -equalize: 1 # if image is too dark or light, trun on equalize to find enough features -fisheye: 0 # if using fisheye, trun on it. A circle mask will be loaded to remove edge noisy points - -#optimization parameters -max_solver_time: 0.04 # max solver itration time (ms), to guarantee real time -max_num_iterations: 8 # max solver itrations, to guarantee real time -keyframe_parallax: 10.0 # keyframe selection threshold (pixel) - -#imu parameters The more accurate parameters you provide, the better performance -acc_n: 0.2 # accelerometer measurement noise standard deviation. -gyr_n: 0.05 # gyroscope measurement noise standard deviation. -acc_w: 0.002 # accelerometer bias random work noise standard deviation. -gyr_w: 4.0e-5 # gyroscope bias random work noise standard deviation. -g_norm: 9.805 # - -#loop closure parameters -loop_closure: 1 # start loop closure -load_previous_pose_graph: 0 # load and reuse previous pose graph; load from 'pose_graph_save_path' -fast_relocalization: 1 # useful in real-time and large project -pose_graph_save_path: "/home/tony-ws1/output/pose_graph/" # save and load path - -#unsynchronization parameters -estimate_td: 1 # online estimate time offset between camera and imu -td: 0.0 # initial value of time offset. unit: s. readed image clock + td = real image clock (IMU clock) - -#rolling shutter parameters -rolling_shutter: 0 # 0: global shutter camera, 1: rolling shutter camera -rolling_shutter_tr: 0 # unit: s. rolling shutter read out time per frame (from data sheet). - -#visualization parameters -save_image: 1 # save image in pose graph for visualization prupose; you can close this function by setting 0 -visualize_imu_forward: 1 # output imu forward propogation to achieve low latency and high frequence results -visualize_camera_size: 0.4 # size of camera marker in RVIZ diff --git a/config/cla/cla_config.yaml b/config/cla/cla_config.yaml deleted file mode 100644 index 7c0818da6..000000000 --- a/config/cla/cla_config.yaml +++ /dev/null @@ -1,81 +0,0 @@ -%YAML:1.0 - -#common parameters -imu_topic: "/imu0" -image_topic: "/cam0/image_raw" -output_path: "/home/tony-ws1/output/" - -#camera calibration -model_type: KANNALA_BRANDT -camera_name: camera -image_width: 752 -image_height: 480 -projection_parameters: - k2: -0.005740195474458931 - k3: 0.02878252863739417 - k4: -0.04010621197185408 - k5: 0.02008469575876223 - mu: 472.2863830700696 - mv: 470.83759684346785 - u0: 368.8316828103749 - v0: 232.23688706965652 - -# Extrinsic parameter between IMU and Camera. -estimate_extrinsic: 0 # 0 Have an accurate extrinsic parameters. We will trust the following imu^R_cam, imu^T_cam, don't change it. - # 1 Have an initial guess about extrinsic parameters. We will optimize around your initial guess. - # 2 Don't know anything about extrinsic parameters. You don't need to give R,T. We will try to calibrate it. Do some rotation movement at beginning. -#If you choose 0 or 1, you should write down the following matrix. -#Rotation from camera frame to imu frame, imu^R_cam -extrinsicRotation: !!opencv-matrix - rows: 3 - cols: 3 - dt: d - data: [ 0.99992185, -0.01241689, -0.00145542, - 0.01242827, 0.99989004, 0.00809021, - 0.0013548, -0.00810766, 0.99996621 ] -extrinsicTranslation: !!opencv-matrix - rows: 3 - cols: 1 - dt: d - data: [ 0.03661314, -0.01027102, -0.00654466 ] - -#feature traker paprameters -max_cnt: 150 # max feature number in feature tracking -min_dist: 30 # min distance between two features -freq: 10 # frequence (Hz) of publish tracking result. At least 10Hz for good estimation. If set 0, the frequence will be same as raw image -F_threshold: 1.0 # ransac threshold (pixel) -show_track: 1 # publish tracking image as topic -equalize: 1 # if image is too dark or light, trun on equalize to find enough features -fisheye: 0 # if using fisheye, trun on it. A circle mask will be loaded to remove edge noisy points - -#optimization parameters -max_solver_time: 0.04 # max solver itration time (ms), to guarantee real time -max_num_iterations: 8 # max solver itrations, to guarantee real time -keyframe_parallax: 10.0 # keyframe selection threshold (pixel) - -#imu parameters The more accurate parameters you provide, the better performance -acc_n: 4e-2 # accelerometer measurement noise standard deviation. #0.2 4e-2 -gyr_n: 1e-3 # gyroscope measurement noise standard deviation. #0.05 1e-3 -acc_w: 1e-3 # accelerometer bias random work noise standard deviation. #0.02 4e-2 4e-3 1e-2 -gyr_w: 1e-4 # gyroscope bias random work noise standard deviation. #4.0e-5 1e-3 1e-4 1e-4 -g_norm: 9.81 # gravity magnitude - - -#loop closure parameters -loop_closure: 1 # start loop closure -load_previous_pose_graph: 0 # load and reuse previous pose graph; load from 'pose_graph_save_path' -fast_relocalization: 0 # useful in real-time and large project -pose_graph_save_path: "/home/tony-ws1/output/pose_graph/" # save and load path - -#unsynchronization parameters -estimate_td: 0 # online estimate time offset between camera and imu -td: 0.0 # initial value of time offset. unit: s. readed image clock + td = real image clock (IMU clock) - -#rolling shutter parameters -rolling_shutter: 0 # 0: global shutter camera, 1: rolling shutter camera -rolling_shutter_tr: 0 # unit: s. rolling shutter read out time per frame (from data sheet). - -#visualization parameters -save_image: 1 # save image in pose graph for visualization prupose; you can close this function by setting 0 -visualize_imu_forward: 0 # output imu forward propogation to achieve low latency and high frequence results -visualize_camera_size: 0.4 # size of camera marker in RVIZ \ No newline at end of file diff --git a/config/euroc/euroc_config.yaml b/config/euroc/euroc_config.yaml deleted file mode 100644 index 97cd8d160..000000000 --- a/config/euroc/euroc_config.yaml +++ /dev/null @@ -1,82 +0,0 @@ -%YAML:1.0 - -#common parameters -imu_topic: "/imu0" -image_topic: "/cam0/image_raw" -output_path: "/home/shaozu/output/" - -#camera calibration -model_type: PINHOLE -camera_name: camera -image_width: 752 -image_height: 480 -distortion_parameters: - k1: -2.917e-01 - k2: 8.228e-02 - p1: 5.333e-05 - p2: -1.578e-04 -projection_parameters: - fx: 4.616e+02 - fy: 4.603e+02 - cx: 3.630e+02 - cy: 2.481e+02 - -# Extrinsic parameter between IMU and Camera. -estimate_extrinsic: 0 # 0 Have an accurate extrinsic parameters. We will trust the following imu^R_cam, imu^T_cam, don't change it. - # 1 Have an initial guess about extrinsic parameters. We will optimize around your initial guess. - # 2 Don't know anything about extrinsic parameters. You don't need to give R,T. We will try to calibrate it. Do some rotation movement at beginning. -#If you choose 0 or 1, you should write down the following matrix. -#Rotation from camera frame to imu frame, imu^R_cam -extrinsicRotation: !!opencv-matrix - rows: 3 - cols: 3 - dt: d - data: [0.0148655429818, -0.999880929698, 0.00414029679422, - 0.999557249008, 0.0149672133247, 0.025715529948, - -0.0257744366974, 0.00375618835797, 0.999660727178] -#Translation from camera frame to imu frame, imu^T_cam -extrinsicTranslation: !!opencv-matrix - rows: 3 - cols: 1 - dt: d - data: [-0.0216401454975,-0.064676986768, 0.00981073058949] - -#feature traker paprameters -max_cnt: 150 # max feature number in feature tracking -min_dist: 30 # min distance between two features -freq: 10 # frequence (Hz) of publish tracking result. At least 10Hz for good estimation. If set 0, the frequence will be same as raw image -F_threshold: 1.0 # ransac threshold (pixel) -show_track: 1 # publish tracking image as topic -equalize: 1 # if image is too dark or light, trun on equalize to find enough features -fisheye: 0 # if using fisheye, trun on it. A circle mask will be loaded to remove edge noisy points - -#optimization parameters -max_solver_time: 0.04 # max solver itration time (ms), to guarantee real time -max_num_iterations: 8 # max solver itrations, to guarantee real time -keyframe_parallax: 10.0 # keyframe selection threshold (pixel) - -#imu parameters The more accurate parameters you provide, the better performance -acc_n: 0.08 # accelerometer measurement noise standard deviation. #0.2 0.04 -gyr_n: 0.004 # gyroscope measurement noise standard deviation. #0.05 0.004 -acc_w: 0.00004 # accelerometer bias random work noise standard deviation. #0.02 -gyr_w: 2.0e-6 # gyroscope bias random work noise standard deviation. #4.0e-5 -g_norm: 9.81007 # gravity magnitude - -#loop closure parameters -loop_closure: 1 # start loop closure -load_previous_pose_graph: 0 # load and reuse previous pose graph; load from 'pose_graph_save_path' -fast_relocalization: 0 # useful in real-time and large project -pose_graph_save_path: "/home/shaozu/output/pose_graph/" # save and load path - -#unsynchronization parameters -estimate_td: 0 # online estimate time offset between camera and imu -td: 0.0 # initial value of time offset. unit: s. readed image clock + td = real image clock (IMU clock) - -#rolling shutter parameters -rolling_shutter: 0 # 0: global shutter camera, 1: rolling shutter camera -rolling_shutter_tr: 0 # unit: s. rolling shutter read out time per frame (from data sheet). - -#visualization parameters -save_image: 1 # save image in pose graph for visualization prupose; you can close this function by setting 0 -visualize_imu_forward: 0 # output imu forward propogation to achieve low latency and high frequence results -visualize_camera_size: 0.4 # size of camera marker in RVIZ diff --git a/config/euroc/euroc_config_no_extrinsic.yaml b/config/euroc/euroc_config_no_extrinsic.yaml deleted file mode 100644 index 14887eee0..000000000 --- a/config/euroc/euroc_config_no_extrinsic.yaml +++ /dev/null @@ -1,68 +0,0 @@ -%YAML:1.0 - -#common parameters -imu_topic: "/imu0" -image_topic: "/cam0/image_raw" -output_path: "/home/tony-ws1/output/" - -#camera calibration -model_type: PINHOLE -camera_name: camera -image_width: 752 -image_height: 480 -distortion_parameters: - k1: -2.917e-01 - k2: 8.228e-02 - p1: 5.333e-05 - p2: -1.578e-04 -projection_parameters: - fx: 4.616e+02 - fy: 4.603e+02 - cx: 3.630e+02 - cy: 2.481e+02 - -# Extrinsic parameter between IMU and Camera. -estimate_extrinsic: 2 # 0 Have an accurate extrinsic parameters. We will trust the following imu^R_cam, imu^T_cam, don't change it. - # 1 Have an initial guess about extrinsic parameters. We will optimize around your initial guess. - # 2 Don't know anything about extrinsic parameters. You don't need to give R,T. We will try to calibrate it. Do some rotation movement at beginning. -#If you choose 0 or 1, you should write down the following matrix. - -#feature traker paprameters -max_cnt: 150 # max feature number in feature tracking -min_dist: 30 # min distance between two features -freq: 10 # frequence (Hz) of publish tracking result. At least 10Hz for good estimation. If set 0, the frequence will be same as raw image -F_threshold: 1.0 # ransac threshold (pixel) -show_track: 1 # publish tracking image as topic -equalize: 1 # if image is too dark or light, trun on equalize to find enough features -fisheye: 0 # if using fisheye, trun on it. A circle mask will be loaded to remove edge noisy points - -#optimization parameters -max_solver_time: 0.04 # max solver itration time (ms), to guarantee real time -max_num_iterations: 8 # max solver itrations, to guarantee real time -keyframe_parallax: 10.0 # keyframe selection threshold (pixel) - -#imu parameters The more accurate parameters you provide, the better performance -acc_n: 0.2 # accelerometer measurement noise standard deviation. #0.2 -gyr_n: 0.02 # gyroscope measurement noise standard deviation. #0.05 -acc_w: 0.0002 # accelerometer bias random work noise standard deviation. #0.02 -gyr_w: 2.0e-5 # gyroscope bias random work noise standard deviation. #4.0e-5 -g_norm: 9.81007 # gravity magnitude - -#loop closure parameters -loop_closure: 1 # start loop closure -load_previous_pose_graph: 0 # load and reuse previous pose graph; load from 'pose_graph_save_path' -fast_relocalization: 0 # useful in real-time and large project -pose_graph_save_path: "/home/tony-ws1/output/pose_graph/" # save and load path - -#unsynchronization parameters -estimate_td: 0 # online estimate time offset between camera and imu -td: 0.0 # initial value of time offset. unit: s. readed image clock + td = real image clock (IMU clock) - -#rolling shutter parameters -rolling_shutter: 0 # 0: global shutter camera, 1: rolling shutter camera -rolling_shutter_tr: 0 # unit: s. rolling shutter read out time per frame (from data sheet). - -#visualization parameters -save_image: 1 # save image in pose graph for visualization prupose; you can close this function by setting 0 -visualize_imu_forward: 0 # output imu forward propogation to achieve low latency and high frequence results -visualize_camera_size: 0.4 # size of camera marker in RVIZ \ No newline at end of file diff --git a/config/fisheye_bag/fisheye_bag_config.yaml b/config/fisheye_bag/fisheye_bag_config.yaml new file mode 100644 index 000000000..396a9a3f3 --- /dev/null +++ b/config/fisheye_bag/fisheye_bag_config.yaml @@ -0,0 +1,136 @@ +%YAML:1.0 + +#common parameters +imu_topic: "/mavros/imu/data" +image_topic: "/image_raw" +image_input_format: "compressed" +output_path: "/tmp/vins_fisheye_output/" +world_frame_id: "map" # frame_id for published odometry, path, TF, and visualization + +#camera calibration +# Recalibrated with OpenCV fisheye model (Kannala-Brandt) +# RMS reprojection error: 0.676, mean view error: 0.389, frames: 130 +model_type: KANNALA_BRANDT +camera_name: camera +image_width: 1280 +image_height: 1024 +projection_parameters: + k2: -0.024036232663034018 # OpenCV dist_coeffs[0] (k1) + k3: -0.002794445348728834 # OpenCV dist_coeffs[1] (k2) + k4: 0.0011798140355741464 # OpenCV dist_coeffs[2] (k3) + k5: -0.00057002774959145452 # OpenCV dist_coeffs[3] (k4) + mu: 472.88439456353871 # fx + mv: 472.20758450689351 # fy + u0: 615.40978596261721 # cx + v0: 518.71597814251959 # cy + +# Extrinsic parameter between IMU and Camera. +estimate_extrinsic: 1 +extrinsicRotation: !!opencv-matrix + rows: 3 + cols: 3 + dt: d + # cam_z -> imu_z, cam_x -> -imu_y, cam_y -> imu_x + data: [0., 1., 0., + -1., 0., 0., + 0., 0., 1.] +extrinsicTranslation: !!opencv-matrix + rows: 3 + cols: 1 + dt: d + data: [0.0, 0.0, 0.0] + + +#feature traker paprameters +max_cnt: 250 # target number of tracked features for a 1280x1024 frame +min_dist: 20 # preserve spatial distribution; lower values cluster features +freq: 25 # leaves CPU headroom while using most of the 30 Hz camera stream +F_threshold: 2.5 # stricter geometric outlier rejection for a sharp image + +use_advanced_flow: 1 # stable LK + Shi-Tomasi path; AGAST path is experimental +#andvanced flow parameters +fast_threshold: 20 # used only when use_advanced_flow=1 (AGAST) +use_bidirectional_flow: 0 # used only by advanced flow; disabled in this stable profile + +#basic tracking parameters +gftt_quality_level: 0.01 # Shi-Tomasi sensitivity; lower detects weaker usable corners + + +show_track: 1 # publish tracking image as topic +equalize: 1 # if image is too dark or light, trun on equalize to find enough features + +fisheye: 1 # apply a custom valid-pixel mask during feature selection +fisheye_mask: "config/mask.jpg" # relative to the vins_bringup share directory + +#autotune filter +enable_feature_auto_tuning: 0 # adapt active detector to texture and motion blur +feature_auto_tune_interval: 5.0 # seconds between adaptation/diagnostic reports +feature_auto_tune_log: 0 # print [AUTO_TUNE] values to ROS console and log +fast_threshold_min: 7 # safe AGAST tuning range +fast_threshold_max: 45 +gftt_quality_min: 0.001 # used only when use_advanced_flow=0 +gftt_quality_max: 0.05 + + +#adaptive tracking for high-speed navigation +enable_velocity_check: 0 # enable velocity-based adaptive feature tracking +max_velocity_threshold: 5.0 # m/s - velocity threshold to trigger feature boost +velocity_boost_features: 75 # extra features to add when high velocity detected +min_parallax_threshold: 5.0 # pixels - minimum parallax for quality tracking + +#optimization parameters +max_solver_time: 0.05 # max solver time (s) - reduced for real-time performance +max_num_iterations: 15 # max solver itrations, to guarantee real time +keyframe_parallax: 8.0 # keyframe selection threshold (pixel) + + +#imu parameters The more accurate parameters you provide, the better performance +acc_n: 0.15 # accelerometer measurement noise standard deviation. +gyr_n: 0.015 # gyroscope measurement noise standard deviation. +acc_w: 0.003 # accelerometer bias random work noise standard deviation. +gyr_w: 0.001 # gyroscope bias random work noise standard deviation. +g_norm: 9.805 # + +#loop closure parameters +loop_closure: 0 # start loop closure (ОТКЛЮЧЕНО - может вызывать скачки при движении) +load_previous_pose_graph: 0 # load and reuse previous pose graph +fast_relocalization: 0 # useful in real-time and large project +pose_graph_save_path: "/tmp/vins_fisheye_output/pose_graph/" # save and load path + +#unsynchronization parameters +estimate_td: 1 # online estimate time offset between camera and imu (ОТКЛЮЧЕНО для стабильности) +estimate_td_period: 10.0 # seconds between td optimization bursts +td: 0.0 # initial value of time offset. unit: s. readed image clock + td = real image clock (IMU clock) + +#rolling shutter parameters +rolling_shutter: 0 # 0: global shutter camera, 1: rolling shutter camera (отключено - улучшает результаты) +rolling_shutter_tr: 0 # unit: s. rolling shutter read out time per frame (from data sheet) + +#visualization parameters +save_image: 0 # save image in pose graph for visualization prupose; you can close this function by setting 0 +visualize_imu_forward: 0 # output imu forward propogation to achieve low latency and high frequence results +visualize_camera_size: 0.4 # size of camera marker in RVIZ + +#zero velocity update (ZUPT) parameters +enable_zupt: 0 # enable zero velocity update to prevent drift when stationary +zupt_vel_threshold: 0.15 # velocity threshold (m/s) for detecting stationary state +zupt_acc_threshold: 0.25 # acceleration deviation threshold (m/s^2) from gravity + +#imu filtering parameters +enable_imu_acc_filter: 0 # enable accelerometer outlier rejection and smoothing +imu_acc_max_change: 3.0 # maximum allowed acceleration change (m/s^2) for outlier detection +imu_acc_filter_alpha: 0.75 # exponential filter coefficient (0.0-1.0, higher = more smoothing) + +#altitude scale correction parameters +enable_altitude_scale_correction: 0 # enable scale correction using MAVLink altitude +altitude_correction_factor: 0.1 # correction strength (0.0-1.0, lower = gentler correction) + +#lidar odometry initialization & scale correction parameters +# Uses an external lidar odometry (e.g. LIO) to initialize the VINS scale and +# starting coordinate so that both estimators share the same reference frame. +enable_lidar_init: 1 # enable lidar-assisted deep init + ongoing scale correction +lidar_odom_topic: "/lio/odom" # nav_msgs/msg/Odometry source for init/scale reference +lidar_init_timeout: 10.0 # seconds to wait for lidar odom before giving up +lidar_init_pos_threshold: 1.0 # min displacement (m) before using lidar for scale init +lidar_scale_correction_factor: 0.05 # ongoing gentle scale correction strength (0.0-1.0) +lidar_sync_max_dt: 0.1 # max time diff (s) for lidar-vins sample synchronization diff --git a/config/fisheye_mask_752x480.jpg b/config/fisheye_mask_752x480.jpg deleted file mode 100644 index 5caf51f55..000000000 Binary files a/config/fisheye_mask_752x480.jpg and /dev/null differ diff --git a/config/mask.jpg b/config/mask.jpg new file mode 100644 index 000000000..b1aafa7a1 Binary files /dev/null and b/config/mask.jpg differ diff --git a/config/realsense/realsense_color_config.yaml b/config/realsense/realsense_color_config.yaml deleted file mode 100644 index c1eacb1e2..000000000 --- a/config/realsense/realsense_color_config.yaml +++ /dev/null @@ -1,82 +0,0 @@ -%YAML:1.0 - -#common parameters -imu_topic: "/camera/imu/data_raw" -image_topic: "/camera/color/image_raw" -output_path: "/home/tony-ws1/output/" - -#camera calibration -model_type: PINHOLE -camera_name: camera -image_width: 640 -image_height: 480 -distortion_parameters: - k1: 9.2615504465028850e-02 - k2: -1.8082438825995681e-01 - p1: -6.5484100374765971e-04 - p2: -3.5829351558557421e-04 -projection_parameters: - fx: 6.0970550296798035e+02 - fy: 6.0909579671294716e+02 - cx: 3.1916667152289227e+02 - cy: 2.3558360480225772e+02 - -# Extrinsic parameter between IMU and Camera. -estimate_extrinsic: 0 # 0 Have an accurate extrinsic parameters. We will trust the following imu^R_cam, imu^T_cam, don't change it. - # 1 Have an initial guess about extrinsic parameters. We will optimize around your initial guess. - # 2 Don't know anything about extrinsic parameters. You don't need to give R,T. We will try to calibrate it. Do some rotation movement at beginning. -#If you choose 0 or 1, you should write down the following matrix. -#Rotation from camera frame to imu frame, imu^R_cam -extrinsicRotation: !!opencv-matrix - rows: 3 - cols: 3 - dt: d - data: [ 0.99964621, 0.01105994, 0.02418954, - -0.01088975, 0.9999151, -0.00715601, - -0.02426663, 0.00689006, 0.99968178] -#Translation from camera frame to imu frame, imu^T_cam -extrinsicTranslation: !!opencv-matrix - rows: 3 - cols: 1 - dt: d - data: [0.07494282, -0.01077138, -0.00641822] - -#feature traker paprameters -max_cnt: 150 # max feature number in feature tracking -min_dist: 25 # min distance between two features -freq: 10 # frequence (Hz) of publish tracking result. At least 10Hz for good estimation. If set 0, the frequence will be same as raw image -F_threshold: 1.0 # ransac threshold (pixel) -show_track: 1 # publish tracking image as topic -equalize: 0 # if image is too dark or light, trun on equalize to find enough features -fisheye: 0 # if using fisheye, trun on it. A circle mask will be loaded to remove edge noisy points - -#optimization parameters -max_solver_time: 0.04 # max solver itration time (ms), to guarantee real time -max_num_iterations: 8 # max solver itrations, to guarantee real time -keyframe_parallax: 10.0 # keyframe selection threshold (pixel) - -#imu parameters The more accurate parameters you provide, the better performance -acc_n: 0.1 # accelerometer measurement noise standard deviation. #0.2 -gyr_n: 0.01 # gyroscope measurement noise standard deviation. #0.05 -acc_w: 0.0002 # accelerometer bias random work noise standard deviation. #0.02 -gyr_w: 2.0e-5 # gyroscope bias random work noise standard deviation. #4.0e-5 -g_norm: 9.805 # gravity magnitude - -#loop closure parameters -loop_closure: 1 # start loop closure -fast_relocalization: 1 # useful in real-time and large project -load_previous_pose_graph: 0 # load and reuse previous pose graph; load from 'pose_graph_save_path' -pose_graph_save_path: "/home/tony-ws1/output/pose_graph/" # save and load path - -#unsynchronization parameters -estimate_td: 1 # online estimate time offset between camera and imu -td: 0.000 # initial value of time offset. unit: s. readed image clock + td = real image clock (IMU clock) - -#rolling shutter parameters -rolling_shutter: 1 # 0: global shutter camera, 1: rolling shutter camera -rolling_shutter_tr: 0.033 # unit: s. rolling shutter read out time per frame (from data sheet). - -#visualization parameters -save_image: 1 # save image in pose graph for visualization prupose; you can close this function by setting 0 -visualize_imu_forward: 0 # output imu forward propogation to achieve low latency and high frequence results -visualize_camera_size: 0.4 # size of camera marker in RVIZ \ No newline at end of file diff --git a/config/realsense/realsense_fisheye_config.yaml b/config/realsense/realsense_fisheye_config.yaml deleted file mode 100644 index f3e1fb584..000000000 --- a/config/realsense/realsense_fisheye_config.yaml +++ /dev/null @@ -1,81 +0,0 @@ -%YAML:1.0 - -#common parameters -imu_topic: "/camera/imu/data_raw" -image_topic: "/camera/fisheye/image_raw" -output_path: "/home/tony-ws1/output/" - -#camera calibration -model_type: KANNALA_BRANDT -camera_name: camera -image_width: 640 -image_height: 480 -projection_parameters: - k2: 1.7280355035195181e-02 - k3: -2.5505200860040985e-02 - k4: 2.2621441637715487e-02 - k5: -7.3355871719731113e-03 - mu: 2.7723712054408202e+02 - mv: 2.7699784668734617e+02 - u0: 3.3625356873985868e+02 - v0: 2.3603924727453901e+02 - -# Extrinsic parameter between IMU and Camera. -estimate_extrinsic: 1 # 0 Have an accurate extrinsic parameters. We will trust the following imu^R_cam, imu^T_cam, don't change it. - # 1 Have an initial guess about extrinsic parameters. We will optimize around your initial guess. - # 2 Don't know anything about extrinsic parameters. You don't need to give R,T. We will try to calibrate it. Do some rotation movement at beginning. -#If you choose 0 or 1, you should write down the following matrix. -#Rotation from camera frame to imu frame, imu^R_cam -extrinsicRotation: !!opencv-matrix - rows: 3 - cols: 3 - dt: d - data: [0.99992917, 0.00878151, 0.00803387, - -0.00870674, 0.9999189, -0.0092943, - -0.00811483, 0.00922369, 0.99992453] -#Translation from camera frame to imu frame, imu^T_cam -extrinsicTranslation: !!opencv-matrix - rows: 3 - cols: 1 - dt: d - data: [0.00188568, 0.00123801, 0.01044055] - -#feature traker paprameters -max_cnt: 120 # max feature number in feature tracking -min_dist: 30 # min distance between two features -freq: 10 # frequence (Hz) of publish tracking result. At least 10Hz for good estimation. If set 0, the frequence will be same as raw image -F_threshold: 1.0 # ransac threshold (pixel) -show_track: 1 # publish tracking image as topic -equalize: 0 # if image is too dark or light, trun on equalize to find enough features -fisheye: 0 # if using fisheye, trun on it. A circle mask will be loaded to remove edge noisy points - -#optimization parameters -max_solver_time: 0.04 # max solver itration time (ms), to guarantee real time -max_num_iterations: 8 # max solver itrations, to guarantee real time -keyframe_parallax: 10.0 # keyframe selection threshold (pixel) - -#imu parameters The more accurate parameters you provide, the better performance -acc_n: 0.08 # accelerometer measurement noise standard deviation. #0.2 0.04 -gyr_n: 0.004 # gyroscope measurement noise standard deviation. #0.05 0.004 -acc_w: 0.00004 # accelerometer bias random work noise standard deviation. #0.02 -gyr_w: 2.0e-6 # gyroscope bias random work noise standard deviation. #4.0e-5 -g_norm: 9.805 # gravity magnitude - -#loop closure parameters -loop_closure: 1 # start loop closure -fast_relocalization: 1 # useful in real-time and large project -load_previous_pose_graph: 0 # load and reuse previous pose graph; load from 'pose_graph_save_path' -pose_graph_save_path: "/home/tony-ws1/output/pose_graph/" # save and load path - -#unsynchronization parameters -estimate_td: 1 # online estimate time offset between camera and imu -td: 0.010 # initial value of time offset. unit: s. readed image clock + td = real image clock (IMU clock) - -#rolling shutter parameters -rolling_shutter: 0 # 0: global shutter camera, 1: rolling shutter camera -rolling_shutter_tr: 0 # unit: s. rolling shutter read out time per frame (from data sheet). - -#visualization parameters -save_image: 1 # save image in pose graph for visualization prupose; you can close this function by setting 0 -visualize_imu_forward: 0 # output imu forward propogation to achieve low latency and high frequence results -visualize_camera_size: 0.4 # size of camera marker in RVIZ diff --git a/config/simulation/simulation_config.yaml b/config/simulation/simulation_config.yaml deleted file mode 100644 index 2fce55899..000000000 --- a/config/simulation/simulation_config.yaml +++ /dev/null @@ -1,52 +0,0 @@ -%YAML:1.0 - -#common parameters -imu_topic: "/data_generator/imu" -image_topic: "/data_generator/image" -output_path: "/home/shaozu/output/" - -# Extrinsic parameter between IMU and Camera. -estimate_extrinsic: 0 # 0 Have an accurate extrinsic parameters. We will trust the following imu^R_cam, imu^T_cam, don't change it. - # 1 Have an initial guess about extrinsic parameters. We will optimize around your initial guess. -# 2 Don't know anything about extrinsic parameters. You don't need to give R,T. We will try to calibrate it. Do some rotation movement at beginning. -#If you choose 0 or 1, you should write down the following matrix. -#Rotation from camera frame to imu frame, imu^R_cam -extrinsicRotation: !!opencv-matrix - rows: 3 - cols: 3 - dt: d - data: [0, 0, -1, - -1, 0, 0, - 0, 1, 0] -#Translation from camera frame to imu frame, imu^T_cam -extrinsicTranslation: !!opencv-matrix - rows: 3 - cols: 1 - dt: d - data: [0,0.2, 0.6] - -#optimization parameters -max_solver_time: 0.04 # max solver itration time (ms), to guarantee real time -max_num_iterations: 8 # max solver itrations, to guarantee real time -keyframe_parallax: 10.0 # keyframe selection threshold (pixel) - -#imu parameters The more accurate parameters you provide, the better performance -acc_n: 0.1 # accelerometer measurement noise standard deviation. #0.2 0.04 -gyr_n: 0.05 # gyroscope measurement noise standard deviation. #0.05 0.004 -acc_w: 0.002 # accelerometer bias random work noise standard deviation. #0.02 -gyr_w: 4.0e-5 # gyroscope bias random work noise standard deviation. #4.0e-5 -g_norm: 9.81007 # gravity magnitude - -#unsynchronization parameters -estimate_td: 0 # online estimate time offset between camera and imu -td: 0.0 # initial value of time offset. unit: s. readed image clock + td = real image clock (IMU clock) - -#rolling shutter parameters -rolling_shutter: 0 # 0: global shutter camera, 1: rolling shutter camera -rolling_shutter_tr: 0 # unit: s. rolling shutter read out time per frame (from data sheet). - -#visualization parameters -save_image: 0 # save image in pose graph for visualization prupose; you can close this function by setting 0 -visualize_imu_forward: 0 # output imu forward propogation to achieve low latency and high frequence results -visualize_camera_size: 0.4 # size of camera marker in RVIZ - diff --git a/config/termal_cam_config.yaml b/config/termal_cam_config.yaml new file mode 100644 index 000000000..1ae6bc943 --- /dev/null +++ b/config/termal_cam_config.yaml @@ -0,0 +1,122 @@ +%YAML:1.0 + +#common parameters +imu_topic: "/mavros/imu/data" +image_topic: "/thermal_cam/image_raw" +image_input_format: "raw" +output_path: "/tmp/vins_thermal_output/" + +#camera calibration +model_type: PINHOLE +camera_name: termal_camera +image_width: 384 +image_height: 288 +distortion_parameters: + k1: -0.41373247158445464 + k2: 0.16183802909949693 + p1: -0.0004056243478585802 + p2: -0.0022150821156080806 +projection_parameters: + fx: 382.1655349116403 + fy: 381.916045874893 + cx: 177.59980485848902 + cy: 137.17607935580716 + +# Extrinsic parameter between IMU and Camera. +# Полученно из калибровки Kalibr +# +# Kalibr выдал T_ci (imu to cam): +# [[-0.99853052 -0.04402128 -0.03160572] +# [ 0.03392303 -0.05292855 -0.99802194] +# [ 0.04226135 -0.99762753 0.0543441 ]] +# +# Нам нужна T_ic (cam to imu) = T_ci^T +estimate_extrinsic: 0 # 0 Have an accurate extrinsic parameters. We will trust the following imu^R_cam, imu^T_cam, don't change it. + # 1 Have an initial guess about extrinsic parameters. We will optimize around your initial guess. + # 2 Don't know anything about extrinsic parameters. You don't need to give R,T. We will try to calibrate it. Do some rotation movement at beginning. +#If you choose 0 or 1, you should write down the following matrix. +#Rotation from camera frame to imu frame, imu^R_cam (T_ic) +extrinsicRotation: !!opencv-matrix + rows: 3 + cols: 3 + dt: d + # data: [-0.99853052, 0.03392303, 0.04226135, + # -0.04402128, -0.05292855, -0.99762753, + # -0.03160572, -0.99802194, 0.0543441] + + data: [0., 0., 1., + -1., 0., 0., + 0., -1., 0.] +#Translation from camera frame to imu frame, imu^T_cam +# Из калибровки Kalibr +extrinsicTranslation: !!opencv-matrix + rows: 3 + cols: 1 + dt: d + data: [0.00, 0.000, 0.00] + +#feature traker paprameters +max_cnt: 150 # max feature number (reduced for faster processing) +min_dist: 15 # min distance between features (larger spacing = fewer features) +freq: 20 # frequence (Hz) of publish tracking result. At least 10Hz for good estimation. If set 0, the frequence will be same as raw image +F_threshold: 2.0 # ransac threshold (thermal: more tolerance for noise) +fast_threshold: 20 # threshold for AGAST corner detector (higher = fewer but stronger features) +gftt_quality_level: 0.01 # Shi-Tomasi sensitivity used by the stable tracker +use_bidirectional_flow: 0 # used only by advanced flow +use_advanced_flow: 1 # stable LK + Shi-Tomasi; AGAST mode is experimental +show_track: 1 # publish tracking image as topic +equalize: 1 # if image is too dark or light, trun on equalize to find enough features +fisheye: 0 + +#adaptive tracking for high-speed navigation +enable_velocity_check: 0 # enable velocity-based adaptive feature tracking +max_velocity_threshold: 2.5 # m/s - higher threshold to avoid unnecessary boosting +velocity_boost_features: 50 # reduced boost for speed +min_parallax_threshold: 3.0 # pixels - minimum parallax (thermal: higher due to lower resolution) + +#optimization parameters +max_solver_time: 0.03 # max solver time (s) - reduced for real-time performance +max_num_iterations: 15 # reduced iterations for speed +keyframe_parallax: 10.0 # higher threshold = fewer keyframes = less computation + + +#imu parameters The more accurate parameters you provide, the better performance +acc_n: 0.15 # accelerometer measurement noise standard deviation. +gyr_n: 0.015 # gyroscope measurement noise standard deviation. +acc_w: 0.003 # accelerometer bias random work noise standard deviation. +gyr_w: 0.001 # gyroscope bias random work noise standard deviation. +g_norm: 9.805 # + +#loop closure parameters +loop_closure: 1 # enable loop closure and pose graph +load_previous_pose_graph: 0 # load and reuse previous pose graph +fast_relocalization: 0 # useful in real-time and large project +pose_graph_save_path: "/tmp/vins_thermal_output/pose_graph/" # save and load path + +#unsynchronization parameters +estimate_td: 1 # online estimate time offset between camera and IMU +estimate_td_period: 7.0 # seconds between td optimization bursts +td: -0.1 # initial value of time offset. unit: s. readed image clock + td = real image clock (IMU clock) + +#rolling shutter parameters +rolling_shutter: 0 # 0: global shutter camera, 1: rolling shutter camera (отключено - улучшает результаты) +rolling_shutter_tr: 0 # unit: s. rolling shutter read out time per frame (from data sheet) + +#visualization parameters +save_image: 1 # save image in pose graph for visualization prupose; you can close this function by setting 0 +visualize_imu_forward: 0 # output imu forward propogation to achieve low latency and high frequence results +visualize_camera_size: 0.4 # size of camera marker in RVIZ + +#zero velocity update (ZUPT) parameters +enable_zupt: 0 # enable zero velocity update to prevent drift when stationary +zupt_vel_threshold: 0.15 # velocity threshold (thermal: more sensitive to catch stationary state) +zupt_acc_threshold: 0.25 # acceleration deviation threshold (thermal: tighter threshold) + +#imu filtering parameters +enable_imu_acc_filter: 0 # enable accelerometer outlier rejection and smoothing +imu_acc_max_change: 3.0 # maximum allowed acceleration change (thermal: slightly more tolerance) +imu_acc_filter_alpha: 0.75 # exponential filter coefficient (thermal: slightly less smoothing for responsiveness) + +#altitude scale correction parameters +enable_altitude_scale_correction: 0 # bag has no /mavros/local_position/velocity_local topic +altitude_correction_factor: 0.1 # correction strength (0.0-1.0, lower = gentler correction) diff --git a/config/tum/tum_config.yaml b/config/tum/tum_config.yaml deleted file mode 100644 index 3916c5a37..000000000 --- a/config/tum/tum_config.yaml +++ /dev/null @@ -1,83 +0,0 @@ -%YAML:1.0 - -#common parameters -imu_topic: "/imu0" -image_topic: "/cam0/image_raw" -output_path: "/home/tony-ws1/output/" - -#camera calibration -model_type: KANNALA_BRANDT -camera_name: camera -image_width: 512 -image_height: 512 -projection_parameters: - k2: 0.0034823894022493434 - k3: 0.0007150348452162257 - k4: -0.0020532361418706202 - k5: 0.00020293673591811182 - mu: 190.97847715128717 - mv: 190.9733070521226 - u0: 254.93170605935475 - v0: 256.8974428996504 - -# Extrinsic parameter between IMU and Camera. -estimate_extrinsic: 0 # 0 Have an accurate extrinsic parameters. We will trust the following imu^R_cam, imu^T_cam, don't change it. - # 1 Have an initial guess about extrinsic parameters. We will optimize around your initial guess. - # 2 Don't know anything about extrinsic parameters. You don't need to give R,T. We will try to calibrate it. Do some rotation movement at beginning. -#If you choose 0 or 1, you should write down the following matrix. -#Rotation from camera frame to imu frame, imu^R_cam -extrinsicRotation: !!opencv-matrix - rows: 3 - cols: 3 - dt: d - data: [ -9.9951465899298464e-01, 7.5842033363785165e-03, - -3.0214670573904204e-02, 2.9940114644659861e-02, - -3.4023430206013172e-02, -9.9897246995704592e-01, - -8.6044170750674241e-03, -9.9939225835343004e-01, - 3.3779845322755464e-02 ] -extrinsicTranslation: !!opencv-matrix - rows: 3 - cols: 1 - dt: d - data: [ 4.4511917113940799e-02, -7.3197096234105752e-02, - -4.7972907300764499e-02 ] - -#feature traker paprameters -max_cnt: 150 # max feature number in feature tracking -min_dist: 25 # min distance between two features -freq: 10 # frequence (Hz) of publish tracking result. At least 10Hz for good estimation. If set 0, the frequence will be same as raw image -F_threshold: 1.0 # ransac threshold (pixel) -show_track: 1 # publish tracking image as topic -equalize: 1 # if image is too dark or light, trun on equalize to find enough features -fisheye: 1 # if using fisheye, trun on it. A circle mask will be loaded to remove edge noisy points - -#optimization parameters -max_solver_time: 0.04 # max solver itration time (ms), to guarantee real time -max_num_iterations: 8 # max solver itrations, to guarantee real time -keyframe_parallax: 10.0 # keyframe selection threshold (pixel) - -#imu parameters The more accurate parameters you provide, the better performance -acc_n: 0.04 # accelerometer measurement noise standard deviation. #0.2 0.04 -gyr_n: 0.004 # gyroscope measurement noise standard deviation. #0.05 0.004 -acc_w: 0.0004 # accelerometer bias random work noise standard deviation. #0.02 -gyr_w: 2.0e-5 # gyroscope bias random work noise standard deviation. #4.0e-5 -g_norm: 9.80766 # gravity magnitude - -#loop closure parameters -loop_closure: 0 # start loop closure -load_previous_pose_graph: 0 # load and reuse previous pose graph; load from 'pose_graph_save_path' -fast_relocalization: 0 # useful in real-time and large project -pose_graph_save_path: "/home/tony-ws1/output/pose_graph/" # save and load path - -#unsynchronization parameters -estimate_td: 0 # online estimate time offset between camera and imu -td: 0.0 # initial value of time offset. unit: s. readed image clock + td = real image clock (IMU clock) - -#rolling shutter parameters -rolling_shutter: 0 # 0: global shutter camera, 1: rolling shutter camera -rolling_shutter_tr: 0 # unit: s. rolling shutter read out time per frame (from data sheet). - -#visualization parameters -save_image: 1 # save image in pose graph for visualization prupose; you can close this function by setting 0 -visualize_imu_forward: 0 # output imu forward propogation to achieve low latency and high frequence results -visualize_camera_size: 0.4 # size of camera marker in RVIZ diff --git a/config/usb_cam_config.yaml b/config/usb_cam_config.yaml new file mode 100644 index 000000000..0c2bf9bed --- /dev/null +++ b/config/usb_cam_config.yaml @@ -0,0 +1,121 @@ +%YAML:1.0 + +#common parameters +imu_topic: "/mavros/imu/data" +image_topic: "/usb_cam/image_raw" +output_path: "/home/op/output/" + +#camera calibration +model_type: PINHOLE +camera_name: usb_camera +image_width: 640 +image_height: 480 +distortion_parameters: + k1: 0.05164855018573922 + k2: -0.04835348092937038 + p1: 0.00473987181761685 + p2: -0.00328630818706201 +projection_parameters: + fx: 473.4113628724277 + fy: 473.5713828088313 + cx: 311.4014706693625 + cy: 256.9394802970527 + +# Extrinsic parameter between IMU and Camera. +# Полученно из калибровки Kalibr +# +# Kalibr выдал T_ci (imu to cam): +# [[-0.99853052 -0.04402128 -0.03160572] +# [ 0.03392303 -0.05292855 -0.99802194] +# [ 0.04226135 -0.99762753 0.0543441 ]] +# +# Нам нужна T_ic (cam to imu) = T_ci^T +estimate_extrinsic: 0 # 0 Have an accurate extrinsic parameters. We will trust the following imu^R_cam, imu^T_cam, don't change it. + # 1 Have an initial guess about extrinsic parameters. We will optimize around your initial guess. + # 2 Don't know anything about extrinsic parameters. You don't need to give R,T. We will try to calibrate it. Do some rotation movement at beginning. +#If you choose 0 or 1, you should write down the following matrix. +#Rotation from camera frame to imu frame, imu^R_cam (T_ic) +# Матрица вращения из калибровки Kalibr (транспонирована) +extrinsicRotation: !!opencv-matrix + rows: 3 + cols: 3 + dt: d + # data: [-0.99853052, 0.03392303, 0.04226135, + # -0.04402128, -0.05292855, -0.99762753, + # -0.03160572, -0.99802194, 0.0543441] + + data: [-1., 0., 0., + 0., 0., -1., + 0., -1., 0.0] +#Translation from camera frame to imu frame, imu^T_cam +# Из калибровки Kalibr +extrinsicTranslation: !!opencv-matrix + rows: 3 + cols: 1 + dt: d + data: [0.00, 0.000, 0.00] + +#feature traker paprameters +max_cnt: 150 # max feature number in feature tracking +min_dist: 10 # min distance between two features +freq: 20 # frequence (Hz) of publish tracking result. At least 10Hz for good estimation. If set 0, the frequence will be same as raw image +F_threshold: 1.0 # ransac threshold (pixel) +fast_threshold: 20 # threshold for AGAST corner detector (default: 20) +use_bidirectional_flow: 0 # DISABLED for speed (forward-only tracking) +use_advanced_flow: 1 # 1 = advanced LK (term crit + optional backward check), 0 = basic LK +show_track: 1 # publish tracking image as topic +equalize: 0 # if image is too dark or light, trun on equalize to find enough features +fisheye: 0 # if using fisheye, trun on it. A circle mask will be loaded to remove edge noisy points + +#adaptive tracking for high-speed navigation +enable_velocity_check: 1 # enable velocity-based adaptive feature tracking +max_velocity_threshold: 3.0 # m/s - velocity threshold to trigger feature boost +velocity_boost_features: 50 # extra features to add when high velocity detected +min_parallax_threshold: 5.0 # pixels - minimum parallax for quality tracking + +#optimization parameters +max_solver_time: 0.03 # max solver time (s) - reduced for real-time performance +max_num_iterations: 15 # max solver itrations, to guarantee real time +keyframe_parallax: 10.0 # keyframe selection threshold (pixel) + + +#imu parameters The more accurate parameters you provide, the better performance +acc_n: 0.2 # accelerometer measurement noise standard deviation. +gyr_n: 0.05 # gyroscope measurement noise standard deviation. +acc_w: 0.002 # accelerometer bias random work noise standard deviation. +gyr_w: 4.0e-5 # gyroscope bias random work noise standard deviation. +g_norm: 9.805 # + +#loop closure parameters +loop_closure: 1 # start loop closure (ОТКЛЮЧЕНО - может вызывать скачки при движении) +load_previous_pose_graph: 0 # load and reuse previous pose graph +fast_relocalization: 0 # useful in real-time and large project +pose_graph_save_path: "/home/op/output/pose_graph/" # save and load path + +#unsynchronization parameters +estimate_td: 1 # online estimate time offset between camera and imu (ОТКЛЮЧЕНО для стабильности) +estimate_td_period: 7.0 # seconds between td optimization bursts +td: -0.1 # initial value of time offset. unit: s. readed image clock + td = real image clock (IMU clock) + +#rolling shutter parameters +rolling_shutter: 0 # 0: global shutter camera, 1: rolling shutter camera (отключено - улучшает результаты) +rolling_shutter_tr: 0 # unit: s. rolling shutter read out time per frame (from data sheet) + +#visualization parameters +save_image: 1 # save image in pose graph for visualization prupose; you can close this function by setting 0 +visualize_imu_forward: 0 # output imu forward propogation to achieve low latency and high frequence results +visualize_camera_size: 0.4 # size of camera marker in RVIZ + +#zero velocity update (ZUPT) parameters +enable_zupt: 1 # enable zero velocity update to prevent drift when stationary +zupt_vel_threshold: 0.15 # velocity threshold (m/s) for detecting stationary state +zupt_acc_threshold: 0.25 # acceleration deviation threshold (m/s^2) from gravity + +#imu filtering parameters +enable_imu_acc_filter: 1 # enable accelerometer outlier rejection and smoothing +imu_acc_max_change: 3.0 # maximum allowed acceleration change (m/s^2) for outlier detection +imu_acc_filter_alpha: 0.75 # exponential filter coefficient (0.0-1.0, higher = more smoothing) + +#altitude scale correction parameters +enable_altitude_scale_correction: 1 # enable scale correction using MAVLink altitude +altitude_correction_factor: 0.1 # correction strength (0.0-1.0, lower = gentler correction) diff --git a/config/vins_rviz_config.rviz b/config/vins_rviz_config.rviz index 6ed63a93d..c0c7fde84 100644 --- a/config/vins_rviz_config.rviz +++ b/config/vins_rviz_config.rviz @@ -1,47 +1,41 @@ Panels: - - Class: rviz/Displays + - Class: rviz_common/Displays Help Height: 0 Name: Displays Property Tree Widget: - Expanded: ~ - Splitter Ratio: 0.465115994 - Tree Height: 221 - - Class: rviz/Selection + Expanded: + - /Global Options1 + - /Status1 + - /feature points1 + - /pose graph points1 + Splitter Ratio: 0.5 + Tree Height: 576 + - Class: rviz_common/Selection Name: Selection - - Class: rviz/Tool Properties - Expanded: - - /2D Pose Estimate1 - - /2D Nav Goal1 - - /Publish Point1 + - Class: rviz_common/Tool Properties + Expanded: ~ Name: Tool Properties - Splitter Ratio: 0.588679016 - - Class: rviz/Views + Splitter Ratio: 0.5 + - Class: rviz_common/Views Expanded: - /Current View1 Name: Views Splitter Ratio: 0.5 - - Class: rviz/Time + - Class: rviz_common/Time Experimental: false Name: Time SyncMode: 0 - SyncSource: tracked image - - Class: rviz/Displays - Help Height: 78 - Name: Displays - Property Tree Widget: - Expanded: ~ - Splitter Ratio: 0.5 - Tree Height: 359 + SyncSource: "" Visualization Manager: Class: "" Displays: - Alpha: 0.5 Cell Size: 1 - Class: rviz/Grid - Color: 130; 130; 130 + Class: rviz_default_plugins/Grid + Color: 160; 160; 164 Enabled: true Line Style: - Line Width: 0.0299999993 + Line Width: 0.029999999329447746 Value: Lines Name: Grid Normal Cell Count: 0 @@ -53,442 +47,325 @@ Visualization Manager: Plane Cell Count: 10 Reference Frame: Value: true - - Class: rviz/Axes + - Class: rviz_default_plugins/TF Enabled: true - Length: 1 - Name: Axes - Radius: 0.100000001 - Reference Frame: + Filter (blacklist): "" + Filter (whitelist): "" + Frame Timeout: 15 + Frames: + All Enabled: true + Marker Scale: 1 + Name: TF + Show Arrows: true + Show Axes: true + Show Names: false + Tree: + {} + Update Interval: 0 + Value: true + - Angle Tolerance: 0.10000000149011612 + Class: rviz_default_plugins/Odometry + Covariance: + Orientation: + Alpha: 0.5 + Color: 255; 255; 127 + Color Style: Unique + Frame: Local + Offset: 1 + Scale: 1 + Value: true + Position: + Alpha: 0.30000001192092896 + Color: 204; 51; 204 + Scale: 1 + Value: true + Value: true + Enabled: false + Keep: 100 + Name: vins odometry + Position Tolerance: 0.10000000149011612 + Shape: + Alpha: 1 + Axes Length: 1 + Axes Radius: 0.10000000149011612 + Color: 255; 25; 0 + Head Length: 0.30000001192092896 + Head Radius: 0.10000000149011612 + Shaft Length: 1 + Shaft Radius: 0.05000000074505806 + Value: Arrow + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /vins_estimator/odometry + Value: false + - Alpha: 1 + Buffer Length: 1 + Class: rviz_default_plugins/Path + Color: 0; 255; 0 + Enabled: true + Head Diameter: 0.30000001192092896 + Head Length: 0.20000000298023224 + Length: 0.30000001192092896 + Line Style: Lines + Line Width: 0.029999999329447746 + Name: vins path + Offset: + X: 0 + Y: 0 + Z: 0 + Pose Color: 255; 85; 255 + Pose Style: None + Radius: 0.029999999329447746 + Shaft Diameter: 0.10000000149011612 + Shaft Length: 0.10000000149011612 + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /vins_estimator/path Value: true - Alpha: 1 Buffer Length: 1 - Class: rviz/Path + Class: rviz_default_plugins/Path Color: 255; 0; 0 Enabled: true - Head Diameter: 0.300000012 - Head Length: 0.200000003 - Length: 0.300000012 + Head Diameter: 0.30000001192092896 + Head Length: 0.20000000298023224 + Length: 0.30000001192092896 Line Style: Lines - Line Width: 0.5 - Name: ground_truth_path + Line Width: 0.029999999329447746 + Name: pose graph path Offset: X: 0 Y: 0 Z: 0 Pose Color: 255; 85; 255 Pose Style: None - Radius: 0.0299999993 - Shaft Diameter: 0.100000001 - Shaft Length: 0.100000001 - Topic: /benchmark_publisher/path - Unreliable: false + Radius: 0.029999999329447746 + Shaft Diameter: 0.10000000149011612 + Shaft Length: 0.10000000149011612 + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /pose_graph/path_1 Value: true - - Class: rviz/Image + - Class: rviz_default_plugins/MarkerArray Enabled: true - Image Topic: /feature_tracker/feature_img - Max Value: 1 - Median window: 5 - Min Value: 0 - Name: tracked image - Normalize Range: true - Queue Size: 2 - Transport Hint: raw - Unreliable: false + Name: camera pose visual + Namespaces: + {} + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /vins_estimator/camera_pose_visual Value: true - - Class: rviz/Image + - Alpha: 1 + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 10 + Min Value: -10 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz_default_plugins/PointCloud + Color: 255; 255; 255 + Color Transformer: Intensity + Decay Time: 0 + Enabled: true + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 4096 + Min Color: 0; 0; 0 + Min Intensity: 0 + Name: feature points + Position Transformer: XYZ + Selectable: true + Size (Pixels): 3 + Size (m): 0.009999999776482582 + Style: Points + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /vins_estimator/keyframe_point + Use Fixed Frame: true + Use rainbow: true + Value: true + - Alpha: 1 + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 10 + Min Value: -10 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz_default_plugins/PointCloud + Color: 246; 211; 45 + Color Transformer: FlatColor + Decay Time: 0 + Enabled: true + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 4096 + Min Color: 0; 0; 0 + Min Intensity: 0 + Name: pose graph points + Position Transformer: XYZ + Selectable: true + Size (Pixels): 3 + Size (m): 0.009999999776482582 + Style: Points + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /vins_estimator/point_cloud + Use Fixed Frame: true + Use rainbow: true + Value: true + - Class: rviz_default_plugins/Image Enabled: false - Image Topic: /cam0/image_raw Max Value: 1 Median window: 5 Min Value: 0 - Name: raw_image + Name: Image Normalize Range: true - Queue Size: 2 - Transport Hint: raw - Unreliable: false + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /feature_tracker/feature_img Value: false - - Class: rviz/Group - Displays: - - Alpha: 1 - Buffer Length: 1 - Class: rviz/Path - Color: 0; 255; 0 - Enabled: true - Head Diameter: 0.300000012 - Head Length: 0.200000003 - Length: 0.300000012 - Line Style: Lines - Line Width: 0.0299999993 - Name: Path - Offset: - X: 0 - Y: 0 - Z: 0 - Pose Color: 255; 85; 255 - Pose Style: None - Radius: 0.0299999993 - Shaft Diameter: 0.100000001 - Shaft Length: 0.100000001 - Topic: /vins_estimator/path - Unreliable: false - Value: true - - Class: rviz/MarkerArray - Enabled: true - Marker Topic: /vins_estimator/camera_pose_visual - Name: camera_visual - Namespaces: - {} - Queue Size: 100 - Value: true - - Alpha: 1 - Autocompute Intensity Bounds: true - Autocompute Value Bounds: - Max Value: 10 - Min Value: -10 - Value: true - Axis: Z - Channel Name: intensity - Class: rviz/PointCloud - Color: 0; 255; 0 - Color Transformer: FlatColor - Decay Time: 0 - Enabled: true - Invert Rainbow: false - Max Color: 255; 255; 255 - Max Intensity: 4096 - Min Color: 0; 0; 0 - Min Intensity: 0 - Name: current_point - Position Transformer: XYZ - Queue Size: 10 - Selectable: true - Size (Pixels): 1 - Size (m): 0.00999999978 - Style: Points - Topic: /vins_estimator/point_cloud - Unreliable: false - Use Fixed Frame: true - Use rainbow: true - Value: true - - Alpha: 1 - Autocompute Intensity Bounds: false - Autocompute Value Bounds: - Max Value: 10 - Min Value: -10 - Value: true - Axis: Z - Channel Name: intensity - Class: rviz/PointCloud - Color: 255; 255; 255 - Color Transformer: FlatColor - Decay Time: 3000 - Enabled: false - Invert Rainbow: true - Max Color: 0; 0; 0 - Max Intensity: 4096 - Min Color: 0; 0; 0 - Min Intensity: 0 - Name: history_point - Position Transformer: XYZ - Queue Size: 10 - Selectable: true - Size (Pixels): 1 - Size (m): 0.5 - Style: Points - Topic: /vins_estimator/history_cloud - Unreliable: false - Use Fixed Frame: true - Use rainbow: false - Value: false - - Class: rviz/TF - Enabled: true - Frame Timeout: 15 - Frames: - All Enabled: true - Marker Scale: 1 - Name: TF - Show Arrows: true - Show Axes: true - Show Names: true - Tree: - {} - Update Interval: 0 - Value: true - Enabled: false - Name: VIO - - Class: rviz/Group - Displays: - - Alpha: 1 - Buffer Length: 1 - Class: rviz/Path - Color: 0; 255; 0 - Enabled: true - Head Diameter: 0.300000012 - Head Length: 0.200000003 - Length: 0.300000012 - Line Style: Lines - Line Width: 0.0299999993 - Name: pose_graph_path - Offset: - X: 0 - Y: 0 - Z: 0 - Pose Color: 255; 85; 255 - Pose Style: None - Radius: 0.0299999993 - Shaft Diameter: 0.100000001 - Shaft Length: 0.100000001 - Topic: /pose_graph/pose_graph_path - Unreliable: false - Value: true - - Alpha: 1 - Buffer Length: 1 - Class: rviz/Path - Color: 255; 170; 0 - Enabled: true - Head Diameter: 0.300000012 - Head Length: 0.200000003 - Length: 0.300000012 - Line Style: Lines - Line Width: 0.0299999993 - Name: base_path - Offset: - X: 0 - Y: 0 - Z: 0 - Pose Color: 255; 85; 255 - Pose Style: None - Radius: 0.0299999993 - Shaft Diameter: 0.100000001 - Shaft Length: 0.100000001 - Topic: /pose_graph/base_path - Unreliable: false - Value: true - - Class: rviz/MarkerArray - Enabled: true - Marker Topic: /pose_graph/pose_graph - Name: loop_visual - Namespaces: - {} - Queue Size: 100 - Value: true - - Class: rviz/MarkerArray - Enabled: true - Marker Topic: /pose_graph/camera_pose_visual - Name: camera_visual - Namespaces: - {} - Queue Size: 100 - Value: true - - Class: rviz/Image - Enabled: true - Image Topic: /pose_graph/match_image - Max Value: 1 - Median window: 5 - Min Value: 0 - Name: loop_match_image - Normalize Range: true - Queue Size: 2 - Transport Hint: raw - Unreliable: false - Value: true - - Class: rviz/Marker - Enabled: true - Marker Topic: /pose_graph/key_odometrys - Name: Marker - Namespaces: - {} - Queue Size: 100 + - Angle Tolerance: 0.10000000149011612 + Class: rviz_default_plugins/Odometry + Covariance: + Orientation: + Alpha: 0.5 + Color: 255; 255; 127 + Color Style: Unique + Frame: Local + Offset: 1 + Scale: 1 Value: true - - Alpha: 1 - Buffer Length: 1 - Class: rviz/Path - Color: 25; 255; 0 - Enabled: true - Head Diameter: 0.300000012 - Head Length: 0.200000003 - Length: 0.300000012 - Line Style: Lines - Line Width: 0.300000012 - Name: Sequence1 - Offset: - X: 0 - Y: 0 - Z: 0 - Pose Color: 255; 85; 255 - Pose Style: None - Radius: 0.0299999993 - Shaft Diameter: 0.100000001 - Shaft Length: 0.100000001 - Topic: /pose_graph/path_1 - Unreliable: false - Value: true - - Alpha: 1 - Buffer Length: 1 - Class: rviz/Path - Color: 255; 0; 0 - Enabled: true - Head Diameter: 0.300000012 - Head Length: 0.200000003 - Length: 0.300000012 - Line Style: Lines - Line Width: 0.300000012 - Name: Sequence2 - Offset: - X: 0 - Y: 0 - Z: 0 - Pose Color: 255; 85; 255 - Pose Style: None - Radius: 0.0299999993 - Shaft Diameter: 0.100000001 - Shaft Length: 0.100000001 - Topic: /pose_graph/path_2 - Unreliable: false - Value: true - - Alpha: 1 - Buffer Length: 1 - Class: rviz/Path - Color: 0; 85; 255 - Enabled: true - Head Diameter: 0.300000012 - Head Length: 0.200000003 - Length: 0.300000012 - Line Style: Lines - Line Width: 0.300000012 - Name: Sequence3 - Offset: - X: 0 - Y: 0 - Z: 0 - Pose Color: 255; 85; 255 - Pose Style: None - Radius: 0.0299999993 - Shaft Diameter: 0.100000001 - Shaft Length: 0.100000001 - Topic: /pose_graph/path_3 - Unreliable: false - Value: true - - Alpha: 1 - Buffer Length: 1 - Class: rviz/Path - Color: 255; 170; 0 - Enabled: true - Head Diameter: 0.300000012 - Head Length: 0.200000003 - Length: 0.300000012 - Line Style: Lines - Line Width: 0.300000012 - Name: Sequence4 - Offset: - X: 0 - Y: 0 - Z: 0 - Pose Color: 255; 85; 255 - Pose Style: None - Radius: 0.0299999993 - Shaft Diameter: 0.100000001 - Shaft Length: 0.100000001 - Topic: /pose_graph/path_4 - Unreliable: false - Value: true - - Alpha: 1 - Buffer Length: 1 - Class: rviz/Path - Color: 255; 170; 255 - Enabled: true - Head Diameter: 0.300000012 - Head Length: 0.200000003 - Length: 0.300000012 - Line Style: Lines - Line Width: 0.0299999993 - Name: Sequence5 - Offset: - X: 0 - Y: 0 - Z: 0 - Pose Color: 255; 85; 255 - Pose Style: None - Radius: 0.0299999993 - Shaft Diameter: 0.100000001 - Shaft Length: 0.100000001 - Topic: /pose_graph/path_5 - Unreliable: false - Value: true - - Alpha: 1 - Buffer Length: 1 - Class: rviz/Path - Color: 25; 255; 0 - Enabled: true - Head Diameter: 0.300000012 - Head Length: 0.200000003 - Length: 0.300000012 - Line Style: Lines - Line Width: 0.0299999993 - Name: no_loop_path - Offset: - X: 0 - Y: 0 - Z: 0 - Pose Color: 255; 85; 255 - Pose Style: None - Radius: 0.0299999993 - Shaft Diameter: 0.100000001 - Shaft Length: 0.100000001 - Topic: /pose_graph/no_loop_path - Unreliable: false + Position: + Alpha: 0.30000001192092896 + Color: 204; 51; 204 + Scale: 1 Value: true + Value: true Enabled: true - Name: pose_graph + Keep: 100 + Name: Odometry + Position Tolerance: 0.10000000149011612 + Shape: + Alpha: 1 + Axes Length: 1 + Axes Radius: 0.10000000149011612 + Color: 255; 25; 0 + Head Length: 0.30000001192092896 + Head Radius: 0.10000000149011612 + Shaft Length: 1 + Shaft Radius: 0.05000000074505806 + Value: Arrow + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /lio/odom + Value: true Enabled: true Global Options: - Background Color: 0; 0; 0 - Fixed Frame: world + Background Color: 48; 48; 48 + Fixed Frame: map Frame Rate: 30 Name: root Tools: - - Class: rviz/MoveCamera - - Class: rviz/Select - - Class: rviz/FocusCamera - - Class: rviz/Measure - - Class: rviz/SetInitialPose - Topic: /initialpose - - Class: rviz/SetGoal - Topic: /move_base_simple/goal - - Class: rviz/PublishPoint + - Class: rviz_default_plugins/Interact + Hide Inactive Objects: true + - Class: rviz_default_plugins/MoveCamera + - Class: rviz_default_plugins/Select + - Class: rviz_default_plugins/FocusCamera + - Class: rviz_default_plugins/Measure + Line color: 128; 128; 0 + - Class: rviz_default_plugins/SetInitialPose + Covariance x: 0.25 + Covariance y: 0.25 + Covariance yaw: 0.06853891909122467 + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /initialpose + - Class: rviz_default_plugins/SetGoal + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /goal_pose + - Class: rviz_default_plugins/PublishPoint Single click: true - Topic: /clicked_point + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /clicked_point + Transformation: + Current: + Class: rviz_default_plugins/TF Value: true Views: Current: - Class: rviz/XYOrbit - Distance: 20.9684067 + Class: rviz_default_plugins/Orbit + Distance: 5.277319431304932 Enable Stereo Rendering: - Stereo Eye Separation: 0.0599999987 + Stereo Eye Separation: 0.05999999865889549 Stereo Focal Distance: 1 Swap Stereo Eyes: false Value: false Focal Point: - X: -2.7069304 - Y: -1.26974416 - Z: 2.1410624e-05 - Focal Shape Fixed Size: true - Focal Shape Size: 0.0500000007 + X: 0 + Y: 0 + Z: 0 + Focal Shape Fixed Size: false + Focal Shape Size: 0.05000000074505806 Invert Z Axis: false Name: Current View - Near Clip Distance: 0.00999999978 - Pitch: 1.0797962 - Target Frame: - Value: XYOrbit (rviz) - Yaw: 3.08722663 + Near Clip Distance: 0.009999999776482582 + Pitch: 0.3303983509540558 + Target Frame: camera + Value: Orbit (rviz_default_plugins) + Yaw: 5.818586826324463 Saved: ~ Window Geometry: Displays: collapsed: false - Height: 912 + Height: 831 Hide Left Dock: false - Hide Right Dock: true - QMainWindow State: 000000ff00000000fd0000000400000000000001df00000309fc0200000010fb0000000a0049006d00610067006501000000280000013d0000000000000000fb0000001200530065006c0065006300740069006f006e00000001e10000009b0000006400fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb0000001200720061007700200049006d0061006700650000000028000000300000000000000000fb00000012007200610077005f0069006d0061006700650000000028000000f90000001600fffffffb0000001a0074007200610063006b0065006400200069006d00610067006501000000280000012c0000001600fffffffb00000020006c006f006f0070005f006d0061007400630068005f0069006d006100670065010000015a000000b30000001600fffffffb000000100044006900730070006c00610079007301000002130000011e000000dd00fffffffc000000280000011e0000000000fffffffa000000000100000002fb0000001200720061007700200049006d0061006700650000000000ffffffff0000000000000000fb0000001a0074007200610063006b0065006400200069006d0061006700650100000000000002370000000000000000fb0000001000410052005f0069006d0061006700650100000373000000160000000000000000fb0000001200720061007700200069006d006100670065010000038f000000160000000000000000000000010000020800000399fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fc00000028000003990000000000fffffffaffffffff0100000002fb000000100044006900730070006c0061007900730000000000ffffffff0000016a00fffffffb0000000a00560069006500770073000000023f0000016a0000010f00fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e100000197000000030000056e0000003bfc0100000002fb0000000800540069006d006501000000000000056e0000030000fffffffb0000000800540069006d00650100000000000004500000000000000000000003890000030900000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 + Hide Right Dock: false + Image: + collapsed: false + QMainWindow State: 000000ff00000000fd0000000300000000000001560000029ffc0200000004fc0000003f0000029f000000eb0100001efa000000030100000004fb0000000a0049006d0061006700650100000000ffffffff0000005e00fffffffb0000000a0049006d0061006700650100000000ffffffff0000000000000000fb00000016006d006100740063006800200069006d0061006700650000000000ffffffff0000000000000000fb000000100044006900730070006c0061007900730100000000000001690000015600fffffffb00000016006d0061007400630068005f0069006d00610067006500000001e00000007a0000000000000000fb0000000a0049006d0061006700650100000111000001490000000000000000fb0000000a0049006d00610067006501000001b5000000a5000000000000000000000001000001000000029ffc0200000003fb0000000a0049006d006100670065010000003f000000b40000000000000000fb0000000a0049006d006100670065010000003f000000a50000000000000000fc0000003f0000029f000000c80100001efa000000040100000005fb0000000a0049006d0061006700650000000000ffffffff0000000000000000fb0000001a006600650061007400750072006500200069006d0061006700650000000000ffffffff0000000000000000fb0000001200530065006c0065006300740069006f006e0000000000ffffffff0000007300fffffffb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000000ffffffff0000009b00fffffffb0000000a00560069006500770073010000045e000001000000010000ffffff000000030000055e0000003cfc0100000001fb0000000800540069006d006501000000000000055e0000026f00ffffff000002fc0000029f00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 Selection: collapsed: false Time: @@ -496,13 +373,7 @@ Window Geometry: Tool Properties: collapsed: false Views: - collapsed: true - Width: 1390 - X: 2127 - Y: 109 - loop_match_image: - collapsed: false - raw_image: - collapsed: false - tracked image: collapsed: false + Width: 1374 + X: 66 + Y: 32 diff --git a/data_generator/CMakeLists.txt b/data_generator/CMakeLists.txt deleted file mode 100644 index dfd0a44e5..000000000 --- a/data_generator/CMakeLists.txt +++ /dev/null @@ -1,23 +0,0 @@ -cmake_minimum_required(VERSION 2.8.3) -project(data_generator) - -set(CMAKE_BUILD_TYPE "Release") -set(CMAKE_CXX_FLAGS "-std=c++11 -march=native") -set(CMAKE_CXX_FLAGS_RELEASE "-O3 -Wall") - -find_package(catkin REQUIRED COMPONENTS roscpp std_msgs geometry_msgs nav_msgs cv_bridge image_transport) - -find_package(OpenCV REQUIRED) - -catkin_package() - -include_directories( - ${catkin_INCLUDE_DIRS} -) - -add_executable(data_generator - src/data_generator_node.cpp - src/data_generator.cpp - ) - -target_link_libraries(data_generator ${catkin_LIBRARIES} ${OpenCV_LIBS}) diff --git a/data_generator/package.xml b/data_generator/package.xml deleted file mode 100644 index 8bc1e2b68..000000000 --- a/data_generator/package.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - data_generator - 0.0.0 - The data_generator package - - - - - dvorak - - - - - - TODO - - - - - - - - - - - - - - - - - - - - - - - - - - catkin - roscpp - roscpp - - - - - - - - - - - diff --git a/data_generator/src/data_generator.cpp b/data_generator/src/data_generator.cpp deleted file mode 100644 index 2876777a6..000000000 --- a/data_generator/src/data_generator.cpp +++ /dev/null @@ -1,344 +0,0 @@ -#include "data_generator.h" - -#define SEED 1 -#define Y_COS 2 -#define Z_COS 2 * 2 -#define IMU_NOISE 0 -#define IMG_NOISE 0 -#define BIAS_ACC 0 -#define BIAS_GYR 1 - -DataGenerator::DataGenerator() -{ - srand(SEED); - t = 0; - current_id = 0; - - for (int i = 0; i < NUM_POINTS; i++) - { - pts[i * 3 + 0] = rand() % (6 * MAX_BOX) - 3 * MAX_BOX; - pts[i * 3 + 1] = rand() % (6 * MAX_BOX) - 3 * MAX_BOX; - pts[i * 3 + 2] = rand() % (6 * MAX_BOX) - 3 * MAX_BOX; - cout << "pts i " << i << " " << pts[i * 3 + 0] << " " << pts[i * 3 + 1] << " " << pts[i * 3 + 2] << endl; - } - - ap[0] = Vector3d(MAX_BOX, -MAX_BOX, MAX_BOX); - ap[1] = Vector3d(-MAX_BOX, MAX_BOX, MAX_BOX); - ap[2] = Vector3d(-MAX_BOX, -MAX_BOX, -MAX_BOX); - ap[3] = Vector3d(MAX_BOX, MAX_BOX, -MAX_BOX); - - Ric[0] << 0, 0, -1, - -1, 0, 0, - 0, 1, 0; - Tic[0] << 0.0, 0.2, 0.6; - if (NUMBER_OF_CAMERA >= 2) - { - Ric[1] = Ric[0]; - Tic[1] << 0.00, 0.00, 0.30; - } - if (NUMBER_OF_CAMERA >= 3) - { - Ric[2] << 0, 1, 0, - -1, 0, 0, - 0, 0, 1; - Tic[2] << 0.00, 0.00, 0.15; - } - - acc_cov = 0.01 * 0.01 * Matrix3d::Identity(); - gyr_cov = 0.001 * 0.001 * Matrix3d::Identity(); - pts_cov = (0.3 / 460) * (0.3 / 460) * Matrix2d::Identity(); - - generator = default_random_engine(SEED); - distribution = normal_distribution(0.0, 1); - Axis[0] = Vector3d(10, 0, 0); - Axis[1]= Vector3d(0, 10, 0); - Axis[2] = Vector3d(0, 0, 10); - Axis[3] = Vector3d(-10, 0, 0); - Axis[4]= Vector3d(0, -10, 0); - Axis[5] = Vector3d(0, 0, -10); -} - -void DataGenerator::update() -{ - t += 1.0 / FREQ; -} - -double DataGenerator::getTime() -{ - return t; -} - -Vector3d DataGenerator::getPoint(int i) -{ - return Vector3d(pts[3 * i], pts[3 * i + 1], pts[3 * i + 2]); -} - -Vector3d DataGenerator::getAP(int i) -{ - return ap[i]; -} - -Vector3d DataGenerator::getPosition() -{ - double x, y, z; - if (t < MAX_TIME) - { - x = MAX_BOX / 2.0 + MAX_BOX / 2.0 * cos(t / MAX_TIME * M_PI); - y = MAX_BOX / 2.0 + MAX_BOX / 2.0 * cos(t / MAX_TIME * M_PI * Y_COS); - z = MAX_BOX / 2.0 + MAX_BOX / 2.0 * cos(t / MAX_TIME * M_PI * Z_COS); - } - else if (t >= MAX_TIME && t < 2 * MAX_TIME) - { - x = MAX_BOX / 2.0 - MAX_BOX / 2.0; - y = MAX_BOX / 2.0 + MAX_BOX / 2.0; - z = MAX_BOX / 2.0 + MAX_BOX / 2.0; - } - else - { - double tt = t - 2 * MAX_TIME; - x = -MAX_BOX / 2.0 + MAX_BOX / 2.0 * cos(tt / MAX_TIME * M_PI); - y = MAX_BOX / 2.0 + MAX_BOX / 2.0 * cos(tt / MAX_TIME * M_PI * Y_COS); - z = MAX_BOX / 2.0 + MAX_BOX / 2.0 * cos(tt / MAX_TIME * M_PI * Z_COS); - } - - return Vector3d(x, y, z); -} - -Matrix3d DataGenerator::getRotation() -{ - //return Matrix3d::Identity(); - return (AngleAxisd(30.0 / 180 * M_PI * sin(t / MAX_TIME * M_PI * 2), Vector3d::UnitX()) * AngleAxisd(40.0 / 180 * M_PI * sin(t / MAX_TIME * M_PI * 2), Vector3d::UnitY()) * AngleAxisd(0, Vector3d::UnitZ())).toRotationMatrix(); -} - -Vector3d DataGenerator::getAngularVelocity() -{ - const double delta_t = 0.00001; - Matrix3d rot = getRotation(); - t += delta_t; - Matrix3d drot = (getRotation() - rot) / delta_t; - t -= delta_t; - Matrix3d skew = rot.inverse() * drot; - if(IMU_NOISE) - { - Vector3d disturb = Vector3d(distribution(generator) * sqrt(gyr_cov(0, 0)), - distribution(generator) * sqrt(gyr_cov(1, 1)), - distribution(generator) * sqrt(gyr_cov(2, 2))); - return disturb + Vector3d(skew(2, 1), -skew(2, 0), skew(1, 0)); - } - else - { -#if BIAS_GYR - return Vector3d(skew(2, 1) + 0.02, -skew(2, 0) + 0.03, skew(1, 0) + 0.04); -#endif - return Vector3d(skew(2, 1), -skew(2, 0), skew(1, 0)); - } - -} - -Vector3d DataGenerator::getVelocity() -{ - double dx, dy, dz; - if (t < MAX_TIME) - { - dx = MAX_BOX / 2.0 * -sin(t / MAX_TIME * M_PI) * (1.0 / MAX_TIME * M_PI); - dy = MAX_BOX / 2.0 * -sin(t / MAX_TIME * M_PI * Y_COS) * (1.0 / MAX_TIME * M_PI * Y_COS); - dz = MAX_BOX / 2.0 * -sin(t / MAX_TIME * M_PI * Z_COS) * (1.0 / MAX_TIME * M_PI * Z_COS); - } - else if (t >= MAX_TIME && t < 2 * MAX_TIME) - { - dx = 0.0; - dy = 0.0; - dz = 0.0; - } - else - { - double tt = t - 2 * MAX_TIME; - dx = MAX_BOX / 2.0 * -sin(tt / MAX_TIME * M_PI) * (1.0 / MAX_TIME * M_PI); - dy = MAX_BOX / 2.0 * -sin(tt / MAX_TIME * M_PI * Y_COS) * (1.0 / MAX_TIME * M_PI * Y_COS); - dz = MAX_BOX / 2.0 * -sin(tt / MAX_TIME * M_PI * Z_COS) * (1.0 / MAX_TIME * M_PI * Z_COS); - } - - return getRotation().inverse() * Vector3d(dx, dy, dz); -} - -Vector3d DataGenerator::getLinearAcceleration() -{ - double ddx, ddy, ddz; - if (t < MAX_TIME) - { - ddx = MAX_BOX / 2.0 * -cos(t / MAX_TIME * M_PI) * (1.0 / MAX_TIME * M_PI) * (1.0 / MAX_TIME * M_PI); - ddy = MAX_BOX / 2.0 * -cos(t / MAX_TIME * M_PI * Y_COS) * (1.0 / MAX_TIME * M_PI * Y_COS) * (1.0 / MAX_TIME * M_PI * Y_COS); - ddz = MAX_BOX / 2.0 * -cos(t / MAX_TIME * M_PI * Z_COS) * (1.0 / MAX_TIME * M_PI * Z_COS) * (1.0 / MAX_TIME * M_PI * Z_COS); - } - else if (t >= MAX_TIME && t < 2 * MAX_TIME) - { - ddx = 0.0; - ddy = 0.0; - ddz = 0.0; - } - else - { - double tt = t - 2 * MAX_TIME; - ddx = MAX_BOX / 2.0 * -cos(tt / MAX_TIME * M_PI) * (1.0 / MAX_TIME * M_PI) * (1.0 / MAX_TIME * M_PI); - ddy = MAX_BOX / 2.0 * -cos(tt / MAX_TIME * M_PI * Y_COS) * (1.0 / MAX_TIME * M_PI * Y_COS) * (1.0 / MAX_TIME * M_PI * Y_COS); - ddz = MAX_BOX / 2.0 * -cos(tt / MAX_TIME * M_PI * Z_COS) * (1.0 / MAX_TIME * M_PI * Z_COS) * (1.0 / MAX_TIME * M_PI * Z_COS); - } - if(IMU_NOISE) - { - Vector3d disturb = Vector3d(distribution(generator) * sqrt(acc_cov(0, 0)), - distribution(generator) * sqrt(acc_cov(1, 1)), - distribution(generator) * sqrt(acc_cov(2, 2))); - return getRotation().inverse() * (disturb + Vector3d(ddx, ddy, ddz + 9.805)); - } - else - { -#if BIAS_ACC - return getRotation().inverse() * Vector3d(ddx, ddy, ddz + 9.805) + Vector3d(0.01, 0.02, 0.03); -#endif - return getRotation().inverse() * Vector3d(ddx, ddy, ddz + 9.805); - - } - -} - -vector> DataGenerator::getImage() -{ - vector> image; - Vector3d position = getPosition(); - Matrix3d quat = getRotation(); - printf("max: %d\n", current_id); - - vector ids[NUMBER_OF_CAMERA], gr_ids[NUMBER_OF_CAMERA]; - vector cur_pts[NUMBER_OF_CAMERA]; - for (int k = 0; k < NUMBER_OF_CAMERA; k++) - { - for (int i = 0; i < NUM_POINTS; i++) - { - double xx = pts[i * 3 + 0] - position(0); - double yy = pts[i * 3 + 1] - position(1); - double zz = pts[i * 3 + 2] - position(2); - Vector3d local_point = Ric[k].inverse() * (quat.inverse() * Vector3d(xx, yy, zz) - Tic[k]); - xx = local_point(0); - yy = local_point(1); - zz = local_point(2); - - if (zz > 0.0 && std::fabs(atan2(xx, zz)) <= M_PI * FOV / 2 / 180 && std::fabs(atan2(yy, zz)) <= M_PI * FOV / 2 / 180) - { - xx = xx / zz; - yy = yy / zz; - zz = zz / zz; -#if IMG_NOISE - xx += distribution(generator) * sqrt(pts_cov(0, 0)); - yy += distribution(generator) * sqrt(pts_cov(1, 1)); -#endif - - int n_id = before_feature_id[k].find(i) == before_feature_id[k].end() ? -1 /*current_id++*/ : before_feature_id[k][i]; - ids[k].push_back(n_id); - gr_ids[k].push_back(i); - cur_pts[k].push_back(Vector3d(xx, yy, zz)); - } - } - - for (int i = 0; i < 6; i++) - { - output_Axis[i].clear(); - Vector3d local_point; - local_point = Ric[k].inverse() * (quat.inverse() * (Axis[i] - position) - Tic[k]); - double xx = local_point(0); - double yy = local_point(1); - double zz = local_point(2); - if(zz > 0.0 && std::fabs(atan2(xx, zz)) <= M_PI * FOV / 2 / 180 && std::fabs(atan2(yy, zz)) <= M_PI * FOV / 2 / 180) - { - xx = xx / zz; - yy = yy / zz; - zz = zz / zz; - output_Axis[i].push_back(Vector3d(xx, yy, zz)); - local_point = Ric[k].inverse() * (quat.inverse() * (Axis[i] + Vector3d(1, 0, 0) - position) - Tic[k]); - xx = local_point(0); - yy = local_point(1); - zz = local_point(2); - xx = xx / zz; - yy = yy / zz; - zz = zz / zz; - output_Axis[i].push_back(Vector3d(xx, yy, zz)); - local_point = Ric[k].inverse() * (quat.inverse() * (Axis[i] + Vector3d(0, 1, 0) - position) - Tic[k]); - xx = local_point(0); - yy = local_point(1); - zz = local_point(2); - xx = xx / zz; - yy = yy / zz; - zz = zz / zz; - output_Axis[i].push_back(Vector3d(xx, yy, zz)); - local_point = Ric[k].inverse() * (quat.inverse() * (Axis[i] + Vector3d(0, 0, 1) - position) - Tic[k]); - xx = local_point(0); - yy = local_point(1); - zz = local_point(2); - xx = xx / zz; - yy = yy / zz; - zz = zz / zz; - output_Axis[i].push_back(Vector3d(xx, yy, zz)); - } - } - } - - output_gr_pts.clear(); - for (auto i : gr_ids[0]) - { - output_gr_pts.emplace_back(pts[i * 3 + 0], pts[i * 3 + 1], pts[i * 3 + 2]); - } - - //for (int k = 0; k < 1/* NUMBER_OF_CAMERA - 1 */; k++) - //{ - // for (int i = 0; i < int(ids[k].size()); i++) - // { - // if (ids[k][i] != -1) - // continue; - // for (int j = 0; j < int(ids[k + 1].size()); j++) - // if (ids[k + 1][j] == -1 && gr_ids[k][i] == gr_ids[k + 1][j]) - // ids[k][i] = ids[k + 1][j] = current_id++; - // } - //} - - for (int k = 0; k < NUMBER_OF_CAMERA; k++) - { - for (int i = 0; i < int(ids[k].size()); i++) - { - if (ids[k][i] == -1) - ids[k][i] = current_id++; - current_feature_id[k][gr_ids[k][i]] = ids[k][i]; - } - std::swap(before_feature_id[k], current_feature_id[k]); - current_feature_id[k].clear(); - } - - for (int k = 0; k < NUMBER_OF_CAMERA; k++) - { - if (k != 1) - { - for (unsigned int i = 0; i < ids[k].size(); i++) - image.push_back(make_pair(ids[k][i] * NUMBER_OF_CAMERA + k, cur_pts[k][i])); - } - else if (k == 1) - { - for (unsigned int i = 0; i < ids[k].size(); i++) - { - if (before_feature_id[0].find(gr_ids[k][i]) != before_feature_id[0].end()) - image.push_back(make_pair(before_feature_id[0][gr_ids[k][i]] * NUMBER_OF_CAMERA + k, cur_pts[k][i])); - } - } - } - return image; -} - -vector DataGenerator::getCloud() -{ - vector cloud; - for (int i = 0; i < NUM_POINTS; i++) - { - double xx = pts[i * 3 + 0]; - double yy = pts[i * 3 + 1]; - double zz = pts[i * 3 + 2]; - cloud.push_back(Vector3d(xx, yy, zz)); - } - return cloud; -} diff --git a/data_generator/src/data_generator.h b/data_generator/src/data_generator.h deleted file mode 100644 index 390fc551d..000000000 --- a/data_generator/src/data_generator.h +++ /dev/null @@ -1,66 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace std; -using namespace Eigen; - -class DataGenerator -{ - public: - DataGenerator(); - void update(); - - double getTime(); - - Vector3d getPoint(int i); - Vector3d getAP(int i); - vector getCloud(); - Vector3d getPosition(); - Matrix3d getRotation(); - Vector3d getVelocity(); - - Vector3d getAngularVelocity(); - Vector3d getLinearAcceleration(); - - vector> getImage(); - - static int const FREQ = 500; - //static int const MAX_TIME = 10; - static int const MAX_TIME = 40; - static int const FOV = 90; - - static int const NUMBER_OF_CAMERA = 1; - static int const NUMBER_OF_AP = 1; - static int const NUM_POINTS = 500; - static int const MAX_BOX = 10; - static int const IMU_PER_IMG = 50; - static int const IMU_PER_WIFI = 5; - - vector output_gr_pts; - vector output_Axis[6]; - - private: - int pts[NUM_POINTS * 3]; - double t; - map before_feature_id[NUMBER_OF_CAMERA]; - map current_feature_id[NUMBER_OF_CAMERA]; - int current_id; - - Matrix3d Ric[NUMBER_OF_CAMERA]; - Vector3d Tic[NUMBER_OF_CAMERA]; - Vector3d ap[NUMBER_OF_AP]; - Matrix3d acc_cov, gyr_cov; - Matrix2d pts_cov; - default_random_engine generator; - normal_distribution distribution; - - Vector3d Axis[6]; -}; diff --git a/data_generator/src/data_generator_node.cpp b/data_generator/src/data_generator_node.cpp deleted file mode 100644 index 4608c6540..000000000 --- a/data_generator/src/data_generator_node.cpp +++ /dev/null @@ -1,272 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include "data_generator.h" -#include -#include -#include - -#include -#include -#include -#include - -using namespace std; -using namespace Eigen; - -#define ROW 480 -#define COL 752 - -int main(int argc, char **argv) -{ - ros::init(argc, argv, "data_generator"); - ros::NodeHandle n("~"); - - ros::Publisher pub_imu = n.advertise("imu", 1000); - ros::Publisher pub_feature = n.advertise("/feature_tracker/feature", 1000); - ros::Publisher pub_wifi = n.advertise("wifi", 1000); - ros::Publisher pub_flow = n.advertise("flow", 1000); - - - ros::Publisher pub_path = n.advertise("path", 1000); - ros::Publisher pub_odometry = n.advertise("odometry", 1000); - - ros::Publisher pub_pose = n.advertise("pose", 1000); - ros::Publisher pub_cloud = n.advertise("cloud", 1000); - ros::Publisher pub_ap = n.advertise("ap", 1000); - ros::Publisher pub_line = n.advertise("sar", 1000); - - image_transport::ImageTransport it(n); - image_transport::Publisher pub_image; - pub_image = it.advertise("tracked_image", 1000); - - ros::Duration(1).sleep(); - - DataGenerator generator; - ros::Rate loop_rate(generator.FREQ); - - //if (argc == 1) - // while (pub_imu.getNumSubscribers() == 0) - // loop_rate.sleep(); - - sensor_msgs::PointCloud point_cloud; - point_cloud.header.frame_id = "world"; - point_cloud.header.stamp = ros::Time::now(); - for (auto &it : generator.getCloud()) - { - geometry_msgs::Point32 p; - p.x = it(0); - p.y = it(1); - p.z = it(2); - point_cloud.points.push_back(p); - } - pub_cloud.publish(point_cloud); - - point_cloud.points.clear(); - - visualization_msgs::Marker line_ap[DataGenerator::NUMBER_OF_AP]; - for (int i = 0; i < DataGenerator::NUMBER_OF_AP; i++) - { - geometry_msgs::Point32 p; - Vector3d p_ap = generator.getAP(i); - p.x = p_ap(0); - p.y = p_ap(1); - p.z = p_ap(2); - point_cloud.points.push_back(p); - - line_ap[i].id = i; - line_ap[i].header.frame_id = "world"; - line_ap[i].ns = "line"; - line_ap[i].action = visualization_msgs::Marker::ADD; - line_ap[i].pose.orientation.w = 1.0; - line_ap[i].type = visualization_msgs::Marker::LINE_STRIP; - line_ap[i].scale.x = 0.1; - line_ap[i].color.r = 1.0; - line_ap[i].color.a = 1.0; - - geometry_msgs::Point p2; - p2.x = p.x; - p2.y = p.y; - p2.z = p.z; - line_ap[i].points.push_back(p2); - line_ap[i].points.push_back(p2); - } - pub_ap.publish(point_cloud); - - int publish_count = 0; - - nav_msgs::Path path; - path.header.frame_id = "world"; - - while (ros::ok()) - { - double current_time = generator.getTime(); - ROS_INFO("time: %lf", current_time); - - Vector3d position = generator.getPosition(); - Vector3d velocity = generator.getVelocity(); - Matrix3d rotation = generator.getRotation(); - Quaterniond q(rotation); - - Vector3d linear_acceleration = generator.getLinearAcceleration(); - Vector3d angular_velocity = generator.getAngularVelocity(); - - nav_msgs::Odometry odometry; - odometry.header.frame_id = "world"; - odometry.header.stamp = ros::Time(current_time); - odometry.pose.pose.position.x = position(0); - odometry.pose.pose.position.y = position(1); - odometry.pose.pose.position.z = position(2); - odometry.pose.pose.orientation.x = q.x(); - odometry.pose.pose.orientation.y = q.y(); - odometry.pose.pose.orientation.z = q.z(); - odometry.pose.pose.orientation.w = q.w(); - odometry.twist.twist.linear.x = velocity(0); - odometry.twist.twist.linear.y = velocity(1); - odometry.twist.twist.linear.z = velocity(2); - pub_odometry.publish(odometry); - - geometry_msgs::PoseStamped pose_stamped; - pose_stamped.header.frame_id = "world"; - pose_stamped.header.stamp = ros::Time(current_time); - pose_stamped.pose = odometry.pose.pose; - path.poses.push_back(pose_stamped); - pub_path.publish(path); - pub_pose.publish(pose_stamped); - - for (int i = 0; i < DataGenerator::NUMBER_OF_AP; i++) - { - line_ap[i].header.stamp = ros::Time(current_time); - line_ap[i].points.back().x = position(0); - line_ap[i].points.back().y = position(1); - line_ap[i].points.back().z = position(2); - pub_line.publish(line_ap[i]); - } - - sensor_msgs::Imu imu; - imu.header.frame_id = "world"; - imu.header.stamp = ros::Time(current_time); - imu.linear_acceleration.x = linear_acceleration(0); - imu.linear_acceleration.y = linear_acceleration(1); - imu.linear_acceleration.z = linear_acceleration(2); - imu.angular_velocity.x = angular_velocity(0); - imu.angular_velocity.y = angular_velocity(1); - imu.angular_velocity.z = angular_velocity(2); - imu.orientation.x = q.x(); - imu.orientation.y = q.y(); - imu.orientation.z = q.z(); - imu.orientation.w = q.w(); - - pub_imu.publish(imu); - //ROS_INFO("publish imu data with stamp %lf", imu.header.stamp.toSec()); - - //publish wifi data - if (publish_count % generator.IMU_PER_WIFI == 0) - { - sensor_msgs::PointCloud wifi; - sensor_msgs::ChannelFloat32 id_ap; - wifi.header.stamp = ros::Time(current_time); - - for (int i = 0; i < DataGenerator::NUMBER_OF_AP; i++) - { - Vector3d sar; - sar(0) = line_ap[i].points[0].x - line_ap[i].points[1].x; - sar(1) = line_ap[i].points[0].y - line_ap[i].points[1].y; - sar(2) = line_ap[i].points[0].z - line_ap[i].points[1].z; - sar.normalize(); - geometry_msgs::Point32 p; - p.x = sar.dot(rotation.col(0)); - p.y = 0.0; - p.z = 0.0; - wifi.points.push_back(p); - id_ap.values.push_back(i); - } - wifi.channels.push_back(id_ap); - pub_wifi.publish(wifi); - } - - //publish image data - if (publish_count % generator.IMU_PER_IMG == 0) - { - ROS_INFO("feature count: %lu", generator.getImage().size()); - sensor_msgs::PointCloud feature; - sensor_msgs::ChannelFloat32 ids; - sensor_msgs::ChannelFloat32 pixel; - sensor_msgs::ChannelFloat32 p_x, p_y, p_z; - for (int i = 0; i < ROW; i++) - for (int j = 0; j < COL; j++) - pixel.values.push_back(255); - feature.header.stamp = ros::Time(current_time); - feature.header.frame_id = "world"; - - cv::Mat simu_img[DataGenerator::NUMBER_OF_CAMERA]; - for (int i = 0; i < DataGenerator::NUMBER_OF_CAMERA; i++) - simu_img[i] = cv::Mat(600, 600, CV_8UC3, cv::Scalar(0, 0, 0)); - - int tmp_idx = 0; - for (auto &id_pts : generator.getImage()) - { - int id = id_pts.first; - geometry_msgs::Point32 p; - p.x = id_pts.second(0); - p.y = id_pts.second(1); - p.z = id_pts.second(2); - - feature.points.push_back(p); - ids.values.push_back(id); - p_x.values.push_back(generator.output_gr_pts[tmp_idx].x()); - p_y.values.push_back(generator.output_gr_pts[tmp_idx].y()); - p_z.values.push_back(generator.output_gr_pts[tmp_idx].z()); - tmp_idx++; - - char label[10]; - sprintf(label, "%d", id / DataGenerator::NUMBER_OF_CAMERA); - cv::putText(simu_img[id % DataGenerator::NUMBER_OF_CAMERA], label, cv::Point2d(p.x + 1, p.y + 1) * 0.5 * 600, cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(255, 255, 255)); - } - - for (int i = 0; i < 6; i++) - { - if(generator.output_Axis[i].empty()) - continue; - cv::Point2d origin((generator.output_Axis[i][0].x() + 1) * 300, (generator.output_Axis[i][0].y() + 1) * 300); - cv::Point2d axis_x((generator.output_Axis[i][1].x() + 1) * 300, (generator.output_Axis[i][1].y() + 1) * 300); - cv::Point2d axis_y((generator.output_Axis[i][2].x() + 1) * 300, (generator.output_Axis[i][2].y() + 1) * 300); - cv::Point2d axis_z((generator.output_Axis[i][3].x() + 1) * 300, (generator.output_Axis[i][3].y() + 1) * 300); - //cv::line(simu_img[0], origin, axis_x, cv::Scalar(0, 255, 0), 2, 8, 0); - //cv::line(simu_img[0], origin, axis_y, cv::Scalar(0, 0, 255), 2, 8, 0); - //cv::line(simu_img[0], origin, axis_z, cv::Scalar(255, 0, 0), 2, 8, 0); - } - feature.channels.push_back(ids); - feature.channels.push_back(pixel); - feature.channels.push_back(p_x); - feature.channels.push_back(p_y); - feature.channels.push_back(p_z); - pub_feature.publish(feature); - ROS_INFO("publish image data with stamp %lf", feature.header.stamp.toSec()); - for (int k = 0; k < DataGenerator::NUMBER_OF_CAMERA; k++) - { - char name[] = "camera 1"; - name[7] += k; - cv::imshow(name, simu_img[k]); - cv::Mat gray_image; - cv::cvtColor(simu_img[k], gray_image, CV_BGR2GRAY); - sensor_msgs::ImagePtr img_msg = cv_bridge::CvImage(feature.header, "mono8", gray_image).toImageMsg(); - pub_image.publish(img_msg); - } - cv::waitKey(1); - if (generator.getTime() > 3 * DataGenerator::MAX_TIME) - break; - } - - //update work - generator.update(); - publish_count++; - ros::spinOnce(); - loop_rate.sleep(); - } - return 0; -} diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index c46cfe86a..000000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,46 +0,0 @@ -FROM ros:kinetic-perception - -ENV CERES_VERSION="1.12.0" -ENV CATKIN_WS=/root/catkin_ws - - # set up thread number for building -RUN if [ "x$(nproc)" = "x1" ] ; then export USE_PROC=1 ; \ - else export USE_PROC=$(($(nproc)/2)) ; fi && \ - apt-get update && apt-get install -y \ - cmake \ - libatlas-base-dev \ - libeigen3-dev \ - libgoogle-glog-dev \ - libsuitesparse-dev \ - python-catkin-tools \ - ros-${ROS_DISTRO}-cv-bridge \ - ros-${ROS_DISTRO}-image-transport \ - ros-${ROS_DISTRO}-message-filters \ - ros-${ROS_DISTRO}-tf && \ - rm -rf /var/lib/apt/lists/* && \ - # Build and install Ceres - git clone https://ceres-solver.googlesource.com/ceres-solver && \ - cd ceres-solver && \ - git checkout tags/${CERES_VERSION} && \ - mkdir build && cd build && \ - cmake .. && \ - make -j$(USE_PROC) install && \ - rm -rf ../../ceres-solver && \ - mkdir -p $CATKIN_WS/src/VINS-Mono/ - -# Copy VINS-Mono -COPY ./ $CATKIN_WS/src/VINS-Mono/ -# use the following line if you only have this dockerfile -# RUN git clone https://github.com/HKUST-Aerial-Robotics/VINS-Mono.git - -# Build VINS-Mono -WORKDIR $CATKIN_WS -ENV TERM xterm -ENV PYTHONIOENCODING UTF-8 -RUN catkin config \ - --extend /opt/ros/$ROS_DISTRO \ - --cmake-args \ - -DCMAKE_BUILD_TYPE=Release && \ - catkin build && \ - sed -i '/exec "$@"/i \ - source "/root/catkin_ws/devel/setup.bash"' /ros_entrypoint.sh diff --git a/docker/Makefile b/docker/Makefile deleted file mode 100644 index 95bc50e68..000000000 --- a/docker/Makefile +++ /dev/null @@ -1,16 +0,0 @@ -all: help - -help: - @echo "" - @echo "-- Help Menu" - @echo "" - @echo " 1. make build - build all images" - # @echo " 1. make pull - pull all images" - @echo " 1. make clean - remove all images" - @echo "" - -build: - @docker build --tag ros:vins-mono -f ./Dockerfile .. - -clean: - @docker rmi -f ros:vins-mono diff --git a/docker/run.sh b/docker/run.sh deleted file mode 100755 index 815030445..000000000 --- a/docker/run.sh +++ /dev/null @@ -1,61 +0,0 @@ -#!/bin/bash -trap : SIGTERM SIGINT - -function abspath() { - # generate absolute path from relative path - # $1 : relative filename - # return : absolute path - if [ -d "$1" ]; then - # dir - (cd "$1"; pwd) - elif [ -f "$1" ]; then - # file - if [[ $1 = /* ]]; then - echo "$1" - elif [[ $1 == */* ]]; then - echo "$(cd "${1%/*}"; pwd)/${1##*/}" - else - echo "$(pwd)/$1" - fi - fi -} - -if [ "$#" -ne 1 ]; then - echo "Usage: $0 LAUNCH_FILE" >&2 - exit 1 -fi - -roscore & -ROSCORE_PID=$! -sleep 1 - -rviz -d ../config/vins_rviz_config.rviz & -RVIZ_PID=$! - -VINS_MONO_DIR=$(abspath "..") - -docker run \ - -it \ - --rm \ - --net=host \ - -v ${VINS_MONO_DIR}:/root/catkin_ws/src/VINS-Mono/ \ - ros:vins-mono \ - /bin/bash -c \ - "cd /root/catkin_ws/; \ - catkin config \ - --env-cache \ - --extend /opt/ros/$ROS_DISTRO \ - --cmake-args \ - -DCMAKE_BUILD_TYPE=Release; \ - catkin build; \ - source devel/setup.bash; \ - roslaunch vins_estimator ${1}" - -wait $ROSCORE_PID -wait $RVIZ_PID - -if [[ $? -gt 128 ]] -then - kill $ROSCORE_PID - kill $RVIZ_PID -fi diff --git a/feature_tracker/CMakeLists.txt b/feature_tracker/CMakeLists.txt index bc3fb44c9..c904a716d 100644 --- a/feature_tracker/CMakeLists.txt +++ b/feature_tracker/CMakeLists.txt @@ -1,37 +1,59 @@ -cmake_minimum_required(VERSION 2.8.3) +cmake_minimum_required(VERSION 3.8) project(feature_tracker) -set(CMAKE_BUILD_TYPE "Release") -set(CMAKE_CXX_FLAGS "-std=c++11") -set(CMAKE_CXX_FLAGS_RELEASE "-O3 -Wall -g") +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release") +endif() + +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) +endif() + +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_FLAGS_RELEASE "-O3 -Wall") + +find_package(ament_cmake REQUIRED) +find_package(rclcpp REQUIRED) +find_package(rcl_interfaces REQUIRED) +find_package(std_msgs REQUIRED) +find_package(sensor_msgs REQUIRED) +find_package(cv_bridge REQUIRED) +find_package(image_transport REQUIRED) +find_package(camera_model REQUIRED) +find_package(Boost REQUIRED COMPONENTS filesystem program_options system) +find_package(OpenCV REQUIRED) +find_package(Eigen3 REQUIRED) -find_package(catkin REQUIRED COMPONENTS - roscpp +add_executable(feature_tracker + src/feature_tracker_node.cpp + src/parameters.cpp + src/feature_tracker.cpp + ) + +ament_target_dependencies(feature_tracker + rclcpp + rcl_interfaces std_msgs sensor_msgs cv_bridge + image_transport camera_model - ) - -find_package(OpenCV REQUIRED) +) -catkin_package() +target_link_libraries(feature_tracker camera_model::camera_model ${OpenCV_LIBS} -rdynamic) -include_directories( - ${catkin_INCLUDE_DIRS} - ) +target_include_directories(feature_tracker PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src + ${EIGEN3_INCLUDE_DIR} + ${camera_model_INCLUDE_DIRS}) -set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) -find_package(Eigen3) -include_directories( - ${catkin_INCLUDE_DIRS} - ${EIGEN3_INCLUDE_DIR} -) +install(TARGETS feature_tracker + DESTINATION lib/${PROJECT_NAME}) -add_executable(feature_tracker - src/feature_tracker_node.cpp - src/parameters.cpp - src/feature_tracker.cpp - ) +if(BUILD_TESTING) + find_package(ament_cmake_gtest REQUIRED) + ament_add_gtest(test_tracking_safety test/test_tracking_safety.cpp) + target_include_directories(test_tracking_safety PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) +endif() -target_link_libraries(feature_tracker ${catkin_LIBRARIES} ${OpenCV_LIBS}) +ament_package() diff --git a/feature_tracker/package.xml b/feature_tracker/package.xml index 2317a8068..f783975bb 100644 --- a/feature_tracker/package.xml +++ b/feature_tracker/package.xml @@ -1,59 +1,25 @@ - + feature_tracker 0.0.0 The feature_tracker package - - - dvorak - - - - - TODO + ament_cmake - - - - - - - - - - + rclcpp + rcl_interfaces + std_msgs + sensor_msgs + cv_bridge + image_transport + camera_model + ament_cmake_gtest - - - - - - - - - - - - catkin - roscpp - camera_model - message_generation - roscpp - camera_model - message_runtime - - - - - - - - + ament_cmake diff --git a/feature_tracker/src/feature_tracker.cpp b/feature_tracker/src/feature_tracker.cpp index d33709cce..f69260e74 100644 --- a/feature_tracker/src/feature_tracker.cpp +++ b/feature_tracker/src/feature_tracker.cpp @@ -1,6 +1,12 @@ #include "feature_tracker.h" +#include "rclcpp/rclcpp.hpp" +#include "tracking_safety.h" + +#include +#include int FeatureTracker::n_id = 0; +static rclcpp::Clock tracker_log_clock(RCL_STEADY_TIME); bool inBorder(const cv::Point2f &pt) { @@ -13,7 +19,8 @@ bool inBorder(const cv::Point2f &pt) void reduceVector(vector &v, vector status) { int j = 0; - for (int i = 0; i < int(v.size()); i++) + const int safe_size = std::min(v.size(), status.size()); + for (int i = 0; i < safe_size; i++) if (status[i]) v[j++] = v[i]; v.resize(j); @@ -22,7 +29,8 @@ void reduceVector(vector &v, vector status) void reduceVector(vector &v, vector status) { int j = 0; - for (int i = 0; i < int(v.size()); i++) + const int safe_size = std::min(v.size(), status.size()); + for (int i = 0; i < safe_size; i++) if (status[i]) v[j++] = v[i]; v.resize(j); @@ -31,40 +39,105 @@ void reduceVector(vector &v, vector status) FeatureTracker::FeatureTracker() { + clahe = cv::createCLAHE(3.0, cv::Size(8, 8)); + current_velocity = 0.0; + adaptive_max_cnt = MAX_CNT; + auto_tune_last_time = -1.0; + auto_tune_retention_sum = 0.0; + auto_tune_fill_sum = 0.0; + auto_tune_candidate_sum = 0.0; + auto_tune_samples = 0; +} + +namespace +{ +int spatialBucket(const cv::Point2f &pt, int target, int width, int height, + int &bucket_cols, int &bucket_rows) +{ + const double aspect = height > 0 ? static_cast(width) / height : 1.0; + // Keep the number of buckets <= target. Therefore the complete first + // spatial pass always runs before any bucket receives a second point. + bucket_cols = std::max(1, static_cast(std::floor(std::sqrt(std::max(1, target) * aspect)))); + bucket_rows = std::max(1, static_cast(std::floor(static_cast(std::max(1, target)) / bucket_cols))); + const int col = std::clamp(static_cast(pt.x * bucket_cols / std::max(1, width)), 0, bucket_cols - 1); + const int row = std::clamp(static_cast(pt.y * bucket_rows / std::max(1, height)), 0, bucket_rows - 1); + return row * bucket_cols + col; +} } void FeatureTracker::setMask() { - if(FISHEYE) + if (FISHEYE && !fisheye_mask.empty()) mask = fisheye_mask.clone(); else mask = cv::Mat(ROW, COL, CV_8UC1, cv::Scalar(255)); - // prefer to keep features that are tracked for long time vector>> cnt_pts_id; - for (unsigned int i = 0; i < forw_pts.size(); i++) + const size_t aligned_count = tracking_safety::commonSize( + {forw_pts.size(), track_cnt.size(), ids.size()}); + if (aligned_count != forw_pts.size() || aligned_count != track_cnt.size() || + aligned_count != ids.size()) + { + RCLCPP_WARN(rclcpp::get_logger("feature_tracker"), + "[MASK] vector mismatch: points=%zu tracks=%zu ids=%zu; using %zu", + forw_pts.size(), track_cnt.size(), ids.size(), aligned_count); + } + for (size_t i = 0; i < aligned_count; i++) cnt_pts_id.push_back(make_pair(track_cnt[i], make_pair(forw_pts[i], ids[i]))); - sort(cnt_pts_id.begin(), cnt_pts_id.end(), [](const pair> &a, const pair> &b) - { - return a.first > b.first; - }); + // Keep old IDs, but choose them round-robin across the full image. This + // makes a reduced max_cnt spatially balanced and temporally stable. + int bucket_cols = 1, bucket_rows = 1; + vector>>> buckets; + for (const auto &item : cnt_pts_id) + { + const int bucket = spatialBucket(item.second.first, adaptive_max_cnt, mask.cols, mask.rows, + bucket_cols, bucket_rows); + if (buckets.empty()) buckets.resize(bucket_cols * bucket_rows); + buckets[bucket].push_back(item); + } + for (auto &bucket : buckets) + sort(bucket.begin(), bucket.end(), [](const auto &a, const auto &b) { + if (a.first != b.first) return a.first > b.first; + return a.second.second < b.second.second; + }); forw_pts.clear(); ids.clear(); track_cnt.clear(); - for (auto &it : cnt_pts_id) + size_t depth = 0; + bool have_candidates = true; + while (have_candidates && static_cast(forw_pts.size()) < adaptive_max_cnt) { - if (mask.at(it.second.first) == 255) + have_candidates = false; + for (const auto &bucket : buckets) + { + if (depth >= bucket.size()) continue; + have_candidates = true; + const auto &it = bucket[depth]; + const int x = cvRound(it.second.first.x); + const int y = cvRound(it.second.first.y); + if (!std::isfinite(it.second.first.x) || !std::isfinite(it.second.first.y) || + !tracking_safety::pixelIsInside(x, y, mask.cols, mask.rows)) + { + RCLCPP_WARN(rclcpp::get_logger("feature_tracker"), + "[MASK] rejected invalid feature coordinate x=%.3f y=%.3f", + it.second.first.x, it.second.first.y); + continue; + } + if (mask.at(y, x) == 255) { forw_pts.push_back(it.second.first); ids.push_back(it.second.second); track_cnt.push_back(it.first); cv::circle(mask, it.second.first, MIN_DIST, 0, -1); } + if (static_cast(forw_pts.size()) >= adaptive_max_cnt) break; + } + ++depth; } } @@ -83,13 +156,15 @@ void FeatureTracker::readImage(const cv::Mat &_img, double _cur_time) cv::Mat img; TicToc t_r; cur_time = _cur_time; + + // Always initialize with default feature count + adaptive_max_cnt = MAX_CNT; if (EQUALIZE) { - cv::Ptr clahe = cv::createCLAHE(3.0, cv::Size(8, 8)); TicToc t_c; clahe->apply(_img, img); - ROS_DEBUG("CLAHE costs: %fms", t_c.toc()); + RCLCPP_DEBUG(rclcpp::get_logger("feature_tracker"), "CLAHE costs: %fms", t_c.toc()); } else img = _img; @@ -105,12 +180,59 @@ void FeatureTracker::readImage(const cv::Mat &_img, double _cur_time) forw_pts.clear(); + const size_t tracked_before = cur_pts.size(); if (cur_pts.size() > 0) { TicToc t_o; vector status; vector err; - cv::calcOpticalFlowPyrLK(cur_img, forw_img, cur_pts, forw_pts, status, err, cv::Size(21, 21), 3); + + if (USE_ADVANCED_FLOW) + { + // Advanced LK: stricter termination and optional forward-backward consistency + cv::calcOpticalFlowPyrLK(cur_img, forw_img, cur_pts, forw_pts, status, err, cv::Size(21, 21), 3, + cv::TermCriteria(cv::TermCriteria::COUNT + cv::TermCriteria::EPS, 40, 0.001)); + + if (USE_BIDIRECTIONAL_FLOW) + { + vector reverse_status; + vector reverse_pts = cur_pts; + cv::calcOpticalFlowPyrLK(forw_img, cur_img, forw_pts, reverse_pts, reverse_status, err, cv::Size(21, 21), 3, + cv::TermCriteria(cv::TermCriteria::COUNT + cv::TermCriteria::EPS, 40, 0.001)); + + const size_t flow_count = tracking_safety::commonSize( + {status.size(), reverse_status.size(), cur_pts.size(), reverse_pts.size()}); + if (flow_count != status.size()) + { + RCLCPP_WARN( + rclcpp::get_logger("feature_tracker"), + "[FLOW] vector mismatch: forward=%zu reverse=%zu cur=%zu reverse_pts=%zu; rejecting tail", + status.size(), reverse_status.size(), cur_pts.size(), reverse_pts.size()); + std::fill(status.begin() + flow_count, status.end(), 0); + } + for (size_t i = 0; i < flow_count; i++) + { + if (status[i] && reverse_status[i]) + { + cv::Point2f dist_pt = cur_pts[i] - reverse_pts[i]; + float dist = (dist_pt.x * dist_pt.x + dist_pt.y * dist_pt.y); + if (dist > 0.5) // 0.5 pixel squared error threshold (approx 0.7 px distance) + { + status[i] = 0; + } + } + else + { + status[i] = 0; + } + } + } + } + else + { + // Basic LK flow (original behavior) without advanced termination or backward check + cv::calcOpticalFlowPyrLK(cur_img, forw_img, cur_pts, forw_pts, status, err, cv::Size(21, 21), 3); + } for (int i = 0; i < int(forw_pts.size()); i++) if (status[i] && !inBorder(forw_pts[i])) @@ -121,7 +243,42 @@ void FeatureTracker::readImage(const cv::Mat &_img, double _cur_time) reduceVector(ids, status); reduceVector(cur_un_pts, status); reduceVector(track_cnt, status); - ROS_DEBUG("temporal optical flow costs: %fms", t_o.toc()); + + // Estimate velocity based on optical flow magnitude (adaptive_max_cnt already initialized above) + if (ENABLE_VELOCITY_CHECK && cur_pts.size() > 10) + { + double total_flow = 0.0; + int valid_count = 0; + for (size_t i = 0; i < cur_pts.size(); i++) + { + cv::Point2f diff = forw_pts[i] - cur_pts[i]; + double flow_mag = sqrt(diff.x * diff.x + diff.y * diff.y); + if (flow_mag < 100.0) // sanity check: ignore outliers + { + total_flow += flow_mag; + valid_count++; + } + } + + if (valid_count > 0) + { + double avg_flow = total_flow / valid_count; + // Rough estimate: flow (pixels/frame) * focal_length_scale → velocity + // Assuming ~30fps and depth ~3m: pixel_flow * 0.01 ≈ velocity + double dt = cur_time - prev_time; + current_velocity = (dt > 0.001) ? (avg_flow / (FOCAL_LENGTH * dt)) * 3.0 : 0.0; + + // Adaptive feature count based on velocity + if (current_velocity > MAX_VELOCITY_THRESHOLD) + { + adaptive_max_cnt = MAX_CNT + VELOCITY_BOOST_FEATURES; + RCLCPP_WARN_SKIPFIRST_THROTTLE(rclcpp::get_logger("feature_tracker"), tracker_log_clock, 2000, "High velocity detected: %.2f m/s, boosting features to %d", + current_velocity, adaptive_max_cnt); + } + } + } + + RCLCPP_DEBUG(rclcpp::get_logger("feature_tracker"), "temporal optical flow costs: %fms", t_o.toc()); } for (auto &n : track_cnt) @@ -130,32 +287,115 @@ void FeatureTracker::readImage(const cv::Mat &_img, double _cur_time) if (PUB_THIS_FRAME) { rejectWithF(); - ROS_DEBUG("set mask begins"); + RCLCPP_DEBUG(rclcpp::get_logger("feature_tracker"), "set mask begins"); TicToc t_m; setMask(); - ROS_DEBUG("set mask costs %fms", t_m.toc()); + RCLCPP_DEBUG(rclcpp::get_logger("feature_tracker"), "set mask costs %fms", t_m.toc()); - ROS_DEBUG("detect feature begins"); + RCLCPP_DEBUG(rclcpp::get_logger("feature_tracker"), "detect feature begins"); TicToc t_t; - int n_max_cnt = MAX_CNT - static_cast(forw_pts.size()); + int n_max_cnt = adaptive_max_cnt - static_cast(forw_pts.size()); + size_t detector_candidates = 0; if (n_max_cnt > 0) { if(mask.empty()) cout << "mask is empty " << endl; - if (mask.type() != CV_8UC1) + if(mask.type() != CV_8UC1) cout << "mask type wrong " << endl; if (mask.size() != forw_img.size()) cout << "wrong size " << endl; - cv::goodFeaturesToTrack(forw_img, n_pts, MAX_CNT - forw_pts.size(), 0.01, MIN_DIST, mask); + + n_pts.clear(); + + if (USE_ADVANCED_FLOW) + { + // AGAST + grid selection (current enhanced method) + vector keypoints; + cv::AGAST(forw_img, keypoints, FAST_THRESHOLD, true); + detector_candidates = keypoints.size(); + + int grid_size = MIN_DIST; + int grid_cols = COL / grid_size + 1; + std::unordered_map grid_best_features; + + for (const auto& kp : keypoints) { + const int x = cvRound(kp.pt.x); + const int y = cvRound(kp.pt.y); + if (!tracking_safety::pixelIsInside(x, y, mask.cols, mask.rows)) + continue; + if (mask.at(y, x) == 0) continue; + int r = y / grid_size; + int c = x / grid_size; + int idx = r * grid_cols + c; + auto it = grid_best_features.find(idx); + if (it == grid_best_features.end() || kp.response > it->second.response) { + grid_best_features[idx] = kp; + } + } + + vector> spatial_buckets; + int coarse_cols = 1, coarse_rows = 1; + for (const auto &grid_cell : grid_best_features) + { + const auto &kp = grid_cell.second; + const int bucket = spatialBucket(kp.pt, n_max_cnt, mask.cols, mask.rows, + coarse_cols, coarse_rows); + if (spatial_buckets.empty()) spatial_buckets.resize(coarse_cols * coarse_rows); + spatial_buckets[bucket].push_back(kp); + } + for (auto &bucket : spatial_buckets) + sort(bucket.begin(), bucket.end(), [](const auto &a, const auto &b) { + if (a.response != b.response) return a.response > b.response; + if (a.pt.y != b.pt.y) return a.pt.y < b.pt.y; + return a.pt.x < b.pt.x; + }); + size_t bucket_depth = 0; + bool have_spatial_candidates = true; + while (have_spatial_candidates && static_cast(n_pts.size()) < n_max_cnt) + { + have_spatial_candidates = false; + for (const auto &bucket : spatial_buckets) + { + if (bucket_depth >= bucket.size()) continue; + have_spatial_candidates = true; + n_pts.push_back(bucket[bucket_depth].pt); + if (static_cast(n_pts.size()) >= n_max_cnt) break; + } + ++bucket_depth; + } + + if (!n_pts.empty()) + { + // Subpixel refine strongest points only + int refine_count = std::min(50, (int)n_pts.size()); + if (refine_count > 0) { + vector pts_to_refine(n_pts.begin(), n_pts.begin() + refine_count); + cv::TermCriteria criteria = cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 40, 0.001); + cv::cornerSubPix(forw_img, pts_to_refine, cv::Size(5, 5), cv::Size(-1, -1), criteria); + std::copy(pts_to_refine.begin(), pts_to_refine.end(), n_pts.begin()); + } + } + } + else + { + // Original VINS-Fusion style: Shi-Tomasi (goodFeaturesToTrack) + cv::goodFeaturesToTrack( + forw_img, n_pts, n_max_cnt, GFTT_QUALITY_LEVEL, MIN_DIST, mask); + detector_candidates = n_pts.size(); + } } else + { n_pts.clear(); - ROS_DEBUG("detect feature costs: %fms", t_t.toc()); + } + + RCLCPP_DEBUG(rclcpp::get_logger("feature_tracker"), "detect feature costs: %fms", t_t.toc()); - ROS_DEBUG("add feature begins"); + RCLCPP_DEBUG(rclcpp::get_logger("feature_tracker"), "add feature begins"); TicToc t_a; addPoints(); - ROS_DEBUG("selectFeature costs: %fms", t_a.toc()); + updateAutoTuning(tracked_before, forw_pts.size() - n_pts.size(), detector_candidates); + RCLCPP_DEBUG(rclcpp::get_logger("feature_tracker"), "selectFeature costs: %fms", t_a.toc()); } prev_img = cur_img; prev_pts = cur_pts; @@ -166,11 +406,56 @@ void FeatureTracker::readImage(const cv::Mat &_img, double _cur_time) prev_time = cur_time; } +void FeatureTracker::updateAutoTuning(size_t tracked_before, size_t tracked_after, + size_t detector_candidates) +{ + if (!ENABLE_FEATURE_AUTO_TUNING) return; + if (auto_tune_last_time < 0.0) auto_tune_last_time = cur_time; + + const double retention = tracked_before > 0 + ? static_cast(tracked_after) / tracked_before : 1.0; + const double fill = static_cast(forw_pts.size()) / std::max(1, adaptive_max_cnt); + auto_tune_retention_sum += std::clamp(retention, 0.0, 1.0); + auto_tune_fill_sum += std::clamp(fill, 0.0, 2.0); + auto_tune_candidate_sum += detector_candidates; + ++auto_tune_samples; + + if (cur_time - auto_tune_last_time < FEATURE_AUTO_TUNE_INTERVAL) return; + const double mean_retention = auto_tune_retention_sum / std::max(1, auto_tune_samples); + const double mean_fill = auto_tune_fill_sum / std::max(1, auto_tune_samples); + const double mean_candidates = auto_tune_candidate_sum / std::max(1, auto_tune_samples); + + if (USE_ADVANCED_FLOW) + { + if (mean_fill < 0.90) + FAST_THRESHOLD = std::max(FAST_THRESHOLD_MIN, FAST_THRESHOLD - 2); + else if (mean_fill > 0.98 && mean_candidates > adaptive_max_cnt * 2.0 && mean_retention < 0.75) + FAST_THRESHOLD = std::min(FAST_THRESHOLD_MAX, FAST_THRESHOLD + 2); + } + else + { + if (mean_fill < 0.90) + GFTT_QUALITY_LEVEL = std::max(GFTT_QUALITY_MIN, GFTT_QUALITY_LEVEL * 0.8); + else if (mean_fill > 0.98 && mean_retention < 0.75) + GFTT_QUALITY_LEVEL = std::min(GFTT_QUALITY_MAX, GFTT_QUALITY_LEVEL * 1.2); + } + + if (FEATURE_AUTO_TUNE_LOG) + RCLCPP_INFO(rclcpp::get_logger("feature_tracker"), + "[AUTO_TUNE] fill=%.1f%% retention=%.1f%% candidates=%.0f; fast_threshold=%d gftt_quality=%.5f", + mean_fill * 100.0, mean_retention * 100.0, mean_candidates, + FAST_THRESHOLD, GFTT_QUALITY_LEVEL); + + auto_tune_last_time = cur_time; + auto_tune_retention_sum = auto_tune_fill_sum = auto_tune_candidate_sum = 0.0; + auto_tune_samples = 0; +} + void FeatureTracker::rejectWithF() { if (forw_pts.size() >= 8) { - ROS_DEBUG("FM ransac begins"); + RCLCPP_DEBUG(rclcpp::get_logger("feature_tracker"), "FM ransac begins"); TicToc t_f; vector un_cur_pts(cur_pts.size()), un_forw_pts(forw_pts.size()); for (unsigned int i = 0; i < cur_pts.size(); i++) @@ -189,6 +474,13 @@ void FeatureTracker::rejectWithF() vector status; cv::findFundamentalMat(un_cur_pts, un_forw_pts, cv::FM_RANSAC, F_THRESHOLD, 0.99, status); + if (status.size() != forw_pts.size()) + { + RCLCPP_WARN(rclcpp::get_logger("feature_tracker"), + "[RANSAC] invalid status size=%zu for points=%zu; keeping current tracks", + status.size(), forw_pts.size()); + return; + } int size_a = cur_pts.size(); reduceVector(prev_pts, status); reduceVector(cur_pts, status); @@ -196,8 +488,8 @@ void FeatureTracker::rejectWithF() reduceVector(cur_un_pts, status); reduceVector(ids, status); reduceVector(track_cnt, status); - ROS_DEBUG("FM ransac: %d -> %lu: %f", size_a, forw_pts.size(), 1.0 * forw_pts.size() / size_a); - ROS_DEBUG("FM ransac costs: %fms", t_f.toc()); + RCLCPP_DEBUG(rclcpp::get_logger("feature_tracker"), "FM ransac: %d -> %lu: %f", size_a, forw_pts.size(), 1.0 * forw_pts.size() / size_a); + RCLCPP_DEBUG(rclcpp::get_logger("feature_tracker"), "FM ransac costs: %fms", t_f.toc()); } } @@ -215,7 +507,7 @@ bool FeatureTracker::updateID(unsigned int i) void FeatureTracker::readIntrinsicParameter(const string &calib_file) { - ROS_INFO("reading paramerter of camera %s", calib_file.c_str()); + RCLCPP_INFO(rclcpp::get_logger("feature_tracker"), "reading paramerter of camera %s", calib_file.c_str()); m_camera = CameraFactory::instance()->generateCameraFromYamlFile(calib_file); } @@ -273,6 +565,14 @@ void FeatureTracker::undistortedPoints() if (!prev_un_pts_map.empty()) { double dt = cur_time - prev_time; + if (!tracking_safety::timestampStepIsValid(prev_time, cur_time)) + { + RCLCPP_WARN( + rclcpp::get_logger("feature_tracker"), + "[TRACKER] non-increasing image timestamps: previous=%.9f current=%.9f; velocities forced to zero", + prev_time, cur_time); + dt = 0.0; + } pts_velocity.clear(); for (unsigned int i = 0; i < cur_un_pts.size(); i++) { @@ -282,8 +582,8 @@ void FeatureTracker::undistortedPoints() it = prev_un_pts_map.find(ids[i]); if (it != prev_un_pts_map.end()) { - double v_x = (cur_un_pts[i].x - it->second.x) / dt; - double v_y = (cur_un_pts[i].y - it->second.y) / dt; + double v_x = dt > 0.0 ? (cur_un_pts[i].x - it->second.x) / dt : 0.0; + double v_y = dt > 0.0 ? (cur_un_pts[i].y - it->second.y) / dt : 0.0; pts_velocity.push_back(cv::Point2f(v_x, v_y)); } else diff --git a/feature_tracker/src/feature_tracker.h b/feature_tracker/src/feature_tracker.h index 9862ec191..7e30e4d81 100644 --- a/feature_tracker/src/feature_tracker.h +++ b/feature_tracker/src/feature_tracker.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -45,6 +46,8 @@ class FeatureTracker void rejectWithF(); void undistortedPoints(); + void updateAutoTuning(size_t tracked_before, size_t tracked_after, + size_t detector_candidates); cv::Mat mask; cv::Mat fisheye_mask; @@ -60,6 +63,16 @@ class FeatureTracker camodocal::CameraPtr m_camera; double cur_time; double prev_time; + cv::Ptr clahe; // Cached CLAHE object for performance + + // Adaptive tracking state + double current_velocity; // estimated velocity magnitude (m/s) + int adaptive_max_cnt; // dynamically adjusted feature count + double auto_tune_last_time; + double auto_tune_retention_sum; + double auto_tune_fill_sum; + double auto_tune_candidate_sum; + int auto_tune_samples; static int n_id; }; diff --git a/feature_tracker/src/feature_tracker_node.cpp b/feature_tracker/src/feature_tracker_node.cpp index 5a5261c9c..bf9430802 100644 --- a/feature_tracker/src/feature_tracker_node.cpp +++ b/feature_tracker/src/feature_tracker_node.cpp @@ -1,22 +1,38 @@ -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "rclcpp/rclcpp.hpp" +#include "rcl_interfaces/msg/floating_point_range.hpp" +#include "rcl_interfaces/msg/integer_range.hpp" +#include "rcl_interfaces/msg/parameter_descriptor.hpp" +#include "rcl_interfaces/msg/set_parameters_result.hpp" +#include "image_transport/image_transport.hpp" +#include "sensor_msgs/msg/image.hpp" +#include "sensor_msgs/image_encodings.hpp" +#include "sensor_msgs/msg/point_cloud.hpp" +#include "sensor_msgs/msg/channel_float32.hpp" +#include "std_msgs/msg/bool.hpp" +#include "cv_bridge/cv_bridge.hpp" #include "feature_tracker.h" +#include "tracking_safety.h" #define SHOW_UNDISTORTION 0 vector r_status; vector r_err; -queue img_buf; +queue img_buf; -ros::Publisher pub_img,pub_match; -ros::Publisher pub_restart; +rclcpp::Publisher::SharedPtr pub_img; +rclcpp::Publisher::SharedPtr pub_match; +rclcpp::Publisher::SharedPtr pub_restart; FeatureTracker trackerData[NUM_OF_CAM]; double first_image_time; @@ -24,47 +40,313 @@ int pub_count = 1; bool first_image_flag = true; double last_image_time = 0; bool init_pub = 0; +rclcpp::Clock::SharedPtr g_clock; +static bool first_image_logged = false; +static int image_count = 0; +static int published_feature_count = 0; +static int published_debug_image_count = 0; +static std::atomic current_stage{"startup"}; +static int crash_log_fd = -1; -void img_callback(const sensor_msgs::ImageConstPtr &img_msg) +static rcl_interfaces::msg::ParameterDescriptor integerDescriptor( + const std::string &description, int64_t from, int64_t to) { + rcl_interfaces::msg::ParameterDescriptor descriptor; + descriptor.description = description; + rcl_interfaces::msg::IntegerRange range; + range.from_value = from; + range.to_value = to; + range.step = 1; + descriptor.integer_range.push_back(range); + return descriptor; +} + +static rcl_interfaces::msg::ParameterDescriptor floatingDescriptor( + const std::string &description, double from, double to, double step = 0.0) +{ + rcl_interfaces::msg::ParameterDescriptor descriptor; + descriptor.description = description; + rcl_interfaces::msg::FloatingPointRange range; + range.from_value = from; + range.to_value = to; + range.step = step; + descriptor.floating_point_range.push_back(range); + return descriptor; +} + +static rcl_interfaces::msg::ParameterDescriptor plainDescriptor(const std::string &description) +{ + rcl_interfaces::msg::ParameterDescriptor descriptor; + descriptor.description = description; + return descriptor; +} + +static std::string resolveMaskPath(const std::string &path) +{ + if (path.empty() || path.front() == '/') return path; + return VINS_FOLDER_PATH + path; +} + +static bool loadFeatureMask(const std::string &path, cv::Mat &loaded, std::string &reason) +{ + const std::string resolved = resolveMaskPath(path); + loaded = cv::imread(resolved, cv::IMREAD_GRAYSCALE); + if (loaded.empty()) + { + reason = "cannot read fisheye_mask: " + resolved; + return false; + } + if (loaded.cols != COL || loaded.rows != ROW) + { + reason = "fisheye_mask size " + std::to_string(loaded.cols) + "x" + + std::to_string(loaded.rows) + " does not match image " + + std::to_string(COL) + "x" + std::to_string(ROW); + return false; + } + cv::threshold(loaded, loaded, 127, 255, cv::THRESH_BINARY); + return true; +} + +static void declareRuntimeParameters(const rclcpp::Node::SharedPtr &node) +{ + MAX_CNT = node->declare_parameter("max_cnt", MAX_CNT, + integerDescriptor("Maximum tracked feature count", 8, 5000)); + MIN_DIST = node->declare_parameter("min_dist", MIN_DIST, + integerDescriptor("Minimum feature spacing in pixels", 1, 500)); + FREQ = node->declare_parameter("freq", FREQ, + integerDescriptor("Feature publication and replenishment rate in Hz", 1, 240)); + F_THRESHOLD = node->declare_parameter("F_threshold", F_THRESHOLD, + floatingDescriptor("Fundamental-matrix RANSAC threshold in pixels", 0.1, 10.0, 0.1)); + GFTT_QUALITY_LEVEL = node->declare_parameter("gftt_quality_level", GFTT_QUALITY_LEVEL, + floatingDescriptor("Shi-Tomasi relative quality threshold", 0.0001, 1.0)); + FAST_THRESHOLD = node->declare_parameter("fast_threshold", FAST_THRESHOLD, + integerDescriptor("AGAST response threshold", 1, 255)); + ENABLE_FEATURE_AUTO_TUNING = node->declare_parameter( + "enable_feature_auto_tuning", ENABLE_FEATURE_AUTO_TUNING != 0, + plainDescriptor("Periodically tune the active detector threshold")); + FEATURE_AUTO_TUNE_INTERVAL = node->declare_parameter( + "feature_auto_tune_interval", FEATURE_AUTO_TUNE_INTERVAL, + floatingDescriptor("Auto-tuning interval in seconds", 0.5, 3600.0, 0.5)); + FEATURE_AUTO_TUNE_LOG = node->declare_parameter( + "feature_auto_tune_log", FEATURE_AUTO_TUNE_LOG != 0, + plainDescriptor("Log periodic auto-tuning diagnostics")); + FAST_THRESHOLD_MIN = node->declare_parameter("fast_threshold_min", FAST_THRESHOLD_MIN, + integerDescriptor("Minimum AGAST auto-tuning threshold", 1, 255)); + FAST_THRESHOLD_MAX = node->declare_parameter("fast_threshold_max", FAST_THRESHOLD_MAX, + integerDescriptor("Maximum AGAST auto-tuning threshold", 1, 255)); + GFTT_QUALITY_MIN = node->declare_parameter("gftt_quality_min", GFTT_QUALITY_MIN, + floatingDescriptor("Minimum Shi-Tomasi auto-tuning threshold", 0.0001, 1.0)); + GFTT_QUALITY_MAX = node->declare_parameter("gftt_quality_max", GFTT_QUALITY_MAX, + floatingDescriptor("Maximum Shi-Tomasi auto-tuning threshold", 0.0001, 1.0)); + USE_BIDIRECTIONAL_FLOW = node->declare_parameter( + "use_bidirectional_flow", USE_BIDIRECTIONAL_FLOW != 0, + plainDescriptor("Enable forward-backward optical-flow validation")); + USE_ADVANCED_FLOW = node->declare_parameter( + "use_advanced_flow", USE_ADVANCED_FLOW != 0, + plainDescriptor("Use AGAST and advanced LK instead of Shi-Tomasi/basic LK")); + SHOW_TRACK = node->declare_parameter("show_track", SHOW_TRACK != 0, + plainDescriptor("Publish the feature visualization image")); + EQUALIZE = node->declare_parameter("equalize", EQUALIZE != 0, + plainDescriptor("Apply CLAHE before tracking")); + FISHEYE = node->declare_parameter("fisheye", FISHEYE != 0, + plainDescriptor("Apply the feature validity mask")); + FISHEYE_MASK = resolveMaskPath(node->declare_parameter( + "fisheye_mask", FISHEYE_MASK, plainDescriptor("Feature validity mask path"))); +} + +static rclcpp::node_interfaces::OnSetParametersCallbackHandle::SharedPtr +registerRuntimeParameterCallback(const rclcpp::Node::SharedPtr &node) +{ + return node->add_on_set_parameters_callback( + [node](const std::vector ¶meters) { + static const std::unordered_set runtime_names = { + "max_cnt", "min_dist", "freq", "F_threshold", "gftt_quality_level", + "fast_threshold", "enable_feature_auto_tuning", "feature_auto_tune_interval", + "feature_auto_tune_log", "fast_threshold_min", "fast_threshold_max", + "gftt_quality_min", "gftt_quality_max", "use_bidirectional_flow", + "use_advanced_flow", "show_track", "equalize", "fisheye", "fisheye_mask"}; + int max_cnt = MAX_CNT, min_dist = MIN_DIST, freq = FREQ; + int fast_threshold = FAST_THRESHOLD, fast_min = FAST_THRESHOLD_MIN, fast_max = FAST_THRESHOLD_MAX; + double f_threshold = F_THRESHOLD, gftt = GFTT_QUALITY_LEVEL; + double tune_interval = FEATURE_AUTO_TUNE_INTERVAL; + double gftt_min = GFTT_QUALITY_MIN, gftt_max = GFTT_QUALITY_MAX; + int auto_tune = ENABLE_FEATURE_AUTO_TUNING, tune_log = FEATURE_AUTO_TUNE_LOG; + int bidirectional = USE_BIDIRECTIONAL_FLOW, advanced = USE_ADVANCED_FLOW; + int show_track = SHOW_TRACK, equalize = EQUALIZE, fisheye = FISHEYE; + std::string mask_path = FISHEYE_MASK; + bool freq_changed = false; + bool mask_changed = false; + bool has_runtime_parameter = false; + + for (const auto ¶meter : parameters) + { + const auto &name = parameter.get_name(); + if (runtime_names.count(name) == 0) continue; + has_runtime_parameter = true; + if (name == "max_cnt") max_cnt = parameter.as_int(); + else if (name == "min_dist") min_dist = parameter.as_int(); + else if (name == "freq") { freq = parameter.as_int(); freq_changed = true; } + else if (name == "F_threshold") f_threshold = parameter.as_double(); + else if (name == "gftt_quality_level") gftt = parameter.as_double(); + else if (name == "fast_threshold") fast_threshold = parameter.as_int(); + else if (name == "enable_feature_auto_tuning") auto_tune = parameter.as_bool(); + else if (name == "feature_auto_tune_interval") tune_interval = parameter.as_double(); + else if (name == "feature_auto_tune_log") tune_log = parameter.as_bool(); + else if (name == "fast_threshold_min") fast_min = parameter.as_int(); + else if (name == "fast_threshold_max") fast_max = parameter.as_int(); + else if (name == "gftt_quality_min") gftt_min = parameter.as_double(); + else if (name == "gftt_quality_max") gftt_max = parameter.as_double(); + else if (name == "use_bidirectional_flow") bidirectional = parameter.as_bool(); + else if (name == "use_advanced_flow") advanced = parameter.as_bool(); + else if (name == "show_track") show_track = parameter.as_bool(); + else if (name == "equalize") equalize = parameter.as_bool(); + else if (name == "fisheye") fisheye = parameter.as_bool(); + else if (name == "fisheye_mask") { mask_path = parameter.as_string(); mask_changed = true; } + } + + rcl_interfaces::msg::SetParametersResult result; + if (!has_runtime_parameter) + { + result.successful = true; + return result; + } + result.successful = false; + if (fast_min > fast_max) { result.reason = "fast_threshold_min must be <= fast_threshold_max"; return result; } + if (gftt_min > gftt_max) { result.reason = "gftt_quality_min must be <= gftt_quality_max"; return result; } + if (min_dist >= std::max(ROW, COL)) { result.reason = "min_dist must be smaller than the image size"; return result; } + + cv::Mat loaded_mask; + if (mask_changed || (fisheye && trackerData[0].fisheye_mask.empty())) + { + if (!loadFeatureMask(mask_path, loaded_mask, result.reason)) return result; + } + + MAX_CNT = max_cnt; MIN_DIST = min_dist; FREQ = freq; F_THRESHOLD = f_threshold; + FAST_THRESHOLD_MIN = fast_min; FAST_THRESHOLD_MAX = fast_max; + GFTT_QUALITY_MIN = gftt_min; GFTT_QUALITY_MAX = gftt_max; + FAST_THRESHOLD = std::clamp(fast_threshold, fast_min, fast_max); + GFTT_QUALITY_LEVEL = std::clamp(gftt, gftt_min, gftt_max); + ENABLE_FEATURE_AUTO_TUNING = auto_tune; FEATURE_AUTO_TUNE_INTERVAL = tune_interval; + FEATURE_AUTO_TUNE_LOG = tune_log; USE_BIDIRECTIONAL_FLOW = bidirectional; + USE_ADVANCED_FLOW = advanced; SHOW_TRACK = show_track; EQUALIZE = equalize; + FISHEYE = fisheye; + if (mask_changed) + { + FISHEYE_MASK = resolveMaskPath(mask_path); + for (auto &tracker : trackerData) tracker.fisheye_mask = loaded_mask.clone(); + } + else if (!loaded_mask.empty()) + { + for (auto &tracker : trackerData) tracker.fisheye_mask = loaded_mask.clone(); + } + if (freq_changed) + { + first_image_time = last_image_time; + pub_count = 0; + } + result.successful = true; + result.reason = "applied"; + RCLCPP_INFO(node->get_logger(), + "[RUNTIME_CONFIG] max_cnt=%d min_dist=%d freq=%d detector=%s fast=%d gftt=%.5f auto_tune=%d", + MAX_CNT, MIN_DIST, FREQ, USE_ADVANCED_FLOW ? "AGAST" : "Shi-Tomasi", + FAST_THRESHOLD, GFTT_QUALITY_LEVEL, ENABLE_FEATURE_AUTO_TUNING); + return result; + }); +} + +static void writeCrashReport(int fd, const char *stage, void *const *frames, int frame_count) +{ + if (fd < 0) + return; + static constexpr char prefix[] = "\n[CRASH] feature_tracker stage="; + static constexpr char suffix[] = "\n[CRASH] native stack:\n"; + (void)::write(fd, prefix, sizeof(prefix) - 1); + (void)::write(fd, stage, std::strlen(stage)); + (void)::write(fd, suffix, sizeof(suffix) - 1); + ::backtrace_symbols_fd(frames, frame_count, fd); +} + +static void crashSignalHandler(int signal_number) +{ + const char *stage = current_stage.load(std::memory_order_relaxed); + void *frames[64]; + const int frame_count = ::backtrace(frames, 64); + writeCrashReport(STDERR_FILENO, stage, frames, frame_count); + writeCrashReport(crash_log_fd, stage, frames, frame_count); + std::_Exit(128 + signal_number); +} + +void img_callback(const sensor_msgs::msg::Image::ConstSharedPtr &img_msg) +{ + current_stage.store("callback/input", std::memory_order_relaxed); + image_count++; + double stamp = rclcpp::Time(img_msg->header.stamp).seconds(); + if (!first_image_logged) + { + first_image_logged = true; + RCLCPP_INFO( + rclcpp::get_logger("feature_tracker"), + "[INPUT] first image received: topic=%s stamp=%.6f encoding=%s size=%ux%u", + IMAGE_TOPIC.c_str(), stamp, img_msg->encoding.c_str(), img_msg->width, img_msg->height); + } + else if (image_count % 50 == 0) + { + RCLCPP_INFO( + rclcpp::get_logger("feature_tracker"), + "[INPUT] image #%d stamp=%.6f queue=%zu", + image_count, stamp, img_buf.size()); + } + + if (!tracking_safety::imageSizeIsValid( + img_msg->width, img_msg->height, COL, ROW, NUM_OF_CAM)) + { + RCLCPP_ERROR( + rclcpp::get_logger("feature_tracker"), + "[INPUT] image size %ux%u is smaller than configured %dx%d; frame dropped", + img_msg->width, img_msg->height, COL, ROW * NUM_OF_CAM); + return; + } + if(first_image_flag) { first_image_flag = false; - first_image_time = img_msg->header.stamp.toSec(); - last_image_time = img_msg->header.stamp.toSec(); + first_image_time = stamp; + last_image_time = stamp; + RCLCPP_INFO(rclcpp::get_logger("feature_tracker"), "[STATE] tracker primed on first frame"); return; } // detect unstable camera stream - if (img_msg->header.stamp.toSec() - last_image_time > 1.0 || img_msg->header.stamp.toSec() < last_image_time) + if (stamp - last_image_time > 1.0 || stamp < last_image_time) { - ROS_WARN("image discontinue! reset the feature tracker!"); + RCLCPP_WARN(rclcpp::get_logger("feature_tracker"), "image discontinue! reset the feature tracker!"); first_image_flag = true; last_image_time = 0; pub_count = 1; - std_msgs::Bool restart_flag; + std_msgs::msg::Bool restart_flag; restart_flag.data = true; - pub_restart.publish(restart_flag); + pub_restart->publish(restart_flag); return; } - last_image_time = img_msg->header.stamp.toSec(); + last_image_time = stamp; // frequency control - if (round(1.0 * pub_count / (img_msg->header.stamp.toSec() - first_image_time)) <= FREQ) + if (round(1.0 * pub_count / (stamp - first_image_time)) <= FREQ) { PUB_THIS_FRAME = true; // reset the frequency control - if (abs(1.0 * pub_count / (img_msg->header.stamp.toSec() - first_image_time) - FREQ) < 0.01 * FREQ) + if (abs(1.0 * pub_count / (stamp - first_image_time) - FREQ) < 0.01 * FREQ) { - first_image_time = img_msg->header.stamp.toSec(); + first_image_time = stamp; pub_count = 0; } } else PUB_THIS_FRAME = false; + current_stage.store("cv_bridge/to_mono8", std::memory_order_relaxed); cv_bridge::CvImageConstPtr ptr; if (img_msg->encoding == "8UC1") { - sensor_msgs::Image img; + sensor_msgs::msg::Image img; img.header = img_msg->header; img.height = img_msg->height; img.width = img_msg->width; @@ -81,9 +363,10 @@ void img_callback(const sensor_msgs::ImageConstPtr &img_msg) TicToc t_r; for (int i = 0; i < NUM_OF_CAM; i++) { - ROS_DEBUG("processing camera %d", i); + current_stage.store("tracking/readImage", std::memory_order_relaxed); + RCLCPP_DEBUG(rclcpp::get_logger("feature_tracker"), "processing camera %d", i); if (i != 1 || !STEREO_TRACK) - trackerData[i].readImage(ptr->image.rowRange(ROW * i, ROW * (i + 1)), img_msg->header.stamp.toSec()); + trackerData[i].readImage(ptr->image.rowRange(ROW * i, ROW * (i + 1)), stamp); else { if (EQUALIZE) @@ -102,6 +385,7 @@ void img_callback(const sensor_msgs::ImageConstPtr &img_msg) for (unsigned int i = 0;; i++) { + current_stage.store("tracking/updateID", std::memory_order_relaxed); bool completed = false; for (int j = 0; j < NUM_OF_CAM; j++) if (j != 1 || !STEREO_TRACK) @@ -112,13 +396,14 @@ void img_callback(const sensor_msgs::ImageConstPtr &img_msg) if (PUB_THIS_FRAME) { + current_stage.store("output/build_feature_packet", std::memory_order_relaxed); pub_count++; - sensor_msgs::PointCloudPtr feature_points(new sensor_msgs::PointCloud); - sensor_msgs::ChannelFloat32 id_of_point; - sensor_msgs::ChannelFloat32 u_of_point; - sensor_msgs::ChannelFloat32 v_of_point; - sensor_msgs::ChannelFloat32 velocity_x_of_point; - sensor_msgs::ChannelFloat32 velocity_y_of_point; + sensor_msgs::msg::PointCloud::SharedPtr feature_points(new sensor_msgs::msg::PointCloud); + sensor_msgs::msg::ChannelFloat32 id_of_point; + sensor_msgs::msg::ChannelFloat32 u_of_point; + sensor_msgs::msg::ChannelFloat32 v_of_point; + sensor_msgs::msg::ChannelFloat32 velocity_x_of_point; + sensor_msgs::msg::ChannelFloat32 velocity_y_of_point; feature_points->header = img_msg->header; feature_points->header.frame_id = "world"; @@ -130,13 +415,26 @@ void img_callback(const sensor_msgs::ImageConstPtr &img_msg) auto &cur_pts = trackerData[i].cur_pts; auto &ids = trackerData[i].ids; auto &pts_velocity = trackerData[i].pts_velocity; - for (unsigned int j = 0; j < ids.size(); j++) + const size_t point_count = tracking_safety::commonSize( + {un_pts.size(), cur_pts.size(), ids.size(), pts_velocity.size(), + trackerData[i].track_cnt.size()}); + if (point_count != ids.size() || point_count != cur_pts.size() || + point_count != un_pts.size() || point_count != pts_velocity.size() || + point_count != trackerData[i].track_cnt.size()) + { + RCLCPP_WARN_THROTTLE( + rclcpp::get_logger("feature_tracker"), *g_clock, 2000, + "[TRACKER] inconsistent feature vectors: ids=%zu cur=%zu un=%zu vel=%zu track=%zu; using %zu", + ids.size(), cur_pts.size(), un_pts.size(), pts_velocity.size(), + trackerData[i].track_cnt.size(), point_count); + } + for (size_t j = 0; j < point_count; j++) { if (trackerData[i].track_cnt[j] > 1) { int p_id = ids[j]; hash_ids[i].insert(p_id); - geometry_msgs::Point32 p; + geometry_msgs::msg::Point32 p; p.x = un_pts[j].x; p.y = un_pts[j].y; p.z = 1; @@ -155,30 +453,55 @@ void img_callback(const sensor_msgs::ImageConstPtr &img_msg) feature_points->channels.push_back(v_of_point); feature_points->channels.push_back(velocity_x_of_point); feature_points->channels.push_back(velocity_y_of_point); - ROS_DEBUG("publish %f, at %f", feature_points->header.stamp.toSec(), ros::Time::now().toSec()); + RCLCPP_DEBUG(rclcpp::get_logger("feature_tracker"), "publish %f, at %f", rclcpp::Time(feature_points->header.stamp).seconds(), g_clock->now().seconds()); // skip the first image; since no optical speed on frist image if (!init_pub) { init_pub = 1; + RCLCPP_INFO(rclcpp::get_logger("feature_tracker"), "[STATE] first feature packet is ready for publication"); } else - pub_img.publish(feature_points); + { + pub_img->publish(*feature_points); + current_stage.store("output/feature_published", std::memory_order_relaxed); + published_feature_count++; + if (published_feature_count == 1 || published_feature_count % 20 == 0) + { + RCLCPP_INFO( + rclcpp::get_logger("feature_tracker"), + "[OUTPUT] published feature packet #%d with %zu points", + published_feature_count, + feature_points->points.size()); + } + } if (SHOW_TRACK) { - ptr = cv_bridge::cvtColor(ptr, sensor_msgs::image_encodings::BGR8); - //cv::Mat stereo_img(ROW * NUM_OF_CAM, COL, CV_8UC3); - cv::Mat stereo_img = ptr->image; + current_stage.store("output/draw_feature_image", std::memory_order_relaxed); + cv::Mat stereo_img; + cv::cvtColor(show_img, stereo_img, cv::COLOR_GRAY2BGR); for (int i = 0; i < NUM_OF_CAM; i++) { cv::Mat tmp_img = stereo_img.rowRange(i * ROW, (i + 1) * ROW); - cv::cvtColor(show_img, tmp_img, CV_GRAY2RGB); + const size_t draw_count = tracking_safety::commonSize( + {trackerData[i].cur_pts.size(), trackerData[i].track_cnt.size(), + trackerData[i].pts_velocity.size()}); - for (unsigned int j = 0; j < trackerData[i].cur_pts.size(); j++) + for (size_t j = 0; j < draw_count; j++) { double len = std::min(1.0, 1.0 * trackerData[i].track_cnt[j] / WINDOW_SIZE); - cv::circle(tmp_img, trackerData[i].cur_pts[j], 2, cv::Scalar(255 * (1 - len), 0, 255 * len), 2); + + // Calculate velocity magnitude as proxy for depth (closer = faster motion) + double vel_x = trackerData[i].pts_velocity[j].x; + double vel_y = trackerData[i].pts_velocity[j].y; + double vel_mag = std::sqrt(vel_x * vel_x + vel_y * vel_y); + + // Map velocity to circle radius: high velocity (close) = large radius + // Velocity range ~0-50 pixels/sec, map to radius 1-4 + int radius = 1 + static_cast(std::min(3.0, vel_mag / 10.0)); + + cv::circle(tmp_img, trackerData[i].cur_pts[j], radius, cv::Scalar(255 * (1 - len), 0, 255 * len), 2); //draw speed line /* Vector2d tmp_cur_un_pts (trackerData[i].cur_un_pts[j].x, trackerData[i].cur_un_pts[j].y); @@ -195,20 +518,52 @@ void img_callback(const sensor_msgs::ImageConstPtr &img_msg) //cv::putText(tmp_img, name, trackerData[i].cur_pts[j], cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 0)); } } - //cv::imshow("vis", stereo_img); - //cv::waitKey(5); - pub_match.publish(ptr->toImageMsg()); + auto debug_msg = cv_bridge::CvImage( + img_msg->header, sensor_msgs::image_encodings::BGR8, stereo_img).toImageMsg(); + pub_match->publish(*debug_msg); + current_stage.store("output/feature_image_published", std::memory_order_relaxed); + published_debug_image_count++; + if (published_debug_image_count == 1 || published_debug_image_count % 20 == 0) + { + RCLCPP_INFO( + rclcpp::get_logger("feature_tracker"), + "[OUTPUT] published feature image #%d with %zu drawn points on /feature_tracker/feature_img", + published_debug_image_count, trackerData[0].cur_pts.size()); + } + } + + // Print tracking statistics with velocity info + if (ENABLE_VELOCITY_CHECK && (published_debug_image_count == 1 || published_debug_image_count % 20 == 0)) + { + RCLCPP_INFO(rclcpp::get_logger("feature_tracker"), "[TRACKER] Features: %lu/%d | Velocity: %.2f m/s | Time: %.1fms", + trackerData[0].cur_pts.size(), + trackerData[0].adaptive_max_cnt, + trackerData[0].current_velocity, + t_r.toc()); } } - ROS_INFO("whole feature tracker processing costs: %f", t_r.toc()); + current_stage.store("callback/complete", std::memory_order_relaxed); + // RCLCPP_INFO(rclcpp::get_logger("feature_tracker"), "whole feature tracker processing costs: %f", t_r.toc()); } int main(int argc, char **argv) { - ros::init(argc, argv, "feature_tracker"); - ros::NodeHandle n("~"); - ros::console::set_logger_level(ROSCONSOLE_DEFAULT_NAME, ros::console::levels::Info); - readParameters(n); + crash_log_fd = ::open( + "/tmp/feature_tracker_crash.log", + O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, + 0644); + std::signal(SIGSEGV, crashSignalHandler); + std::signal(SIGABRT, crashSignalHandler); + rclcpp::init(argc, argv); + auto node = std::make_shared("feature_tracker"); + g_clock = node->get_clock(); + readParameters(node); + declareRuntimeParameters(node); + RCLCPP_INFO( + node->get_logger(), + "[START] feature_tracker subscribing to image_topic=%s | freq=%d | max_cnt=%d | min_dist=%d | fisheye=%d | show_track=%d", + IMAGE_TOPIC.c_str(), FREQ, MAX_CNT, MIN_DIST, FISHEYE, SHOW_TRACK); + RCLCPP_INFO(node->get_logger(), "[START] image_input_format=%s", IMAGE_INPUT_FORMAT.c_str()); for (int i = 0; i < NUM_OF_CAM; i++) trackerData[i].readIntrinsicParameter(CAM_NAMES[i]); @@ -220,27 +575,67 @@ int main(int argc, char **argv) trackerData[i].fisheye_mask = cv::imread(FISHEYE_MASK, 0); if(!trackerData[i].fisheye_mask.data) { - ROS_INFO("load mask fail"); - ROS_BREAK(); + RCLCPP_ERROR(node->get_logger(), "failed to load feature mask: %s", FISHEYE_MASK.c_str()); + std::abort(); + } + else if (trackerData[i].fisheye_mask.cols != COL || + trackerData[i].fisheye_mask.rows != ROW) + { + RCLCPP_ERROR( + node->get_logger(), + "feature mask size %dx%d does not match configured image %dx%d", + trackerData[i].fisheye_mask.cols, trackerData[i].fisheye_mask.rows, COL, ROW); + std::abort(); } else - ROS_INFO("load mask success"); + { + cv::threshold( + trackerData[i].fisheye_mask, trackerData[i].fisheye_mask, + 127, 255, cv::THRESH_BINARY); + const int enabled_pixels = cv::countNonZero(trackerData[i].fisheye_mask); + const double enabled_percent = 100.0 * enabled_pixels / + static_cast(trackerData[i].fisheye_mask.total()); + RCLCPP_INFO( + node->get_logger(), + "feature mask loaded: %s size=%dx%d enabled=%.1f%%", + FISHEYE_MASK.c_str(), COL, ROW, enabled_percent); + } } } - ros::Subscriber sub_img = n.subscribe(IMAGE_TOPIC, 100, img_callback); + // Keep the handle alive for the lifetime of the node. rqt and + // `ros2 param set` now update the tracker without restarting it. + auto runtime_parameter_callback = registerRuntimeParameterCallback(node); + + auto sub_img = image_transport::create_subscription( + node.get(), IMAGE_TOPIC, img_callback, IMAGE_INPUT_FORMAT); + RCLCPP_INFO( + node->get_logger(), + "[START] image_transport subscription created for base_topic=%s transport=%s", + IMAGE_TOPIC.c_str(), IMAGE_INPUT_FORMAT.c_str()); - pub_img = n.advertise("feature", 1000); - pub_match = n.advertise("feature_img",1000); - pub_restart = n.advertise("restart",1000); + pub_img = node->create_publisher("/feature_tracker/feature", 1000); + pub_match = node->create_publisher("/feature_tracker/feature_img", 1000); + pub_restart = node->create_publisher("/feature_tracker/restart", 1000); + RCLCPP_INFO( + node->get_logger(), + "[START] outputs: feature=/feature_tracker/feature | feature image=/feature_tracker/feature_img (%s)", + SHOW_TRACK ? "enabled" : "disabled by show_track"); /* if (SHOW_TRACK) cv::namedWindow("vis", cv::WINDOW_NORMAL); */ - ros::spin(); + rclcpp::spin(node); + sub_img.shutdown(); + runtime_parameter_callback.reset(); + pub_restart.reset(); + pub_match.reset(); + pub_img.reset(); + g_clock.reset(); + rclcpp::shutdown(); return 0; } // new points velocity is 0, pub or not? -// track cnt > 1 pub? \ No newline at end of file +// track cnt > 1 pub? diff --git a/feature_tracker/src/parameters.cpp b/feature_tracker/src/parameters.cpp index 23c35c824..38cb14d21 100644 --- a/feature_tracker/src/parameters.cpp +++ b/feature_tracker/src/parameters.cpp @@ -1,9 +1,13 @@ #include "parameters.h" +#include "rclcpp/rclcpp.hpp" +#include std::string IMAGE_TOPIC; +std::string IMAGE_INPUT_FORMAT; std::string IMU_TOPIC; std::vector CAM_NAMES; std::string FISHEYE_MASK; +std::string VINS_FOLDER_PATH; int MAX_CNT; int MIN_DIST; int WINDOW_SIZE; @@ -17,35 +21,42 @@ int COL; int FOCAL_LENGTH; int FISHEYE; bool PUB_THIS_FRAME; +int FAST_THRESHOLD; +int USE_BIDIRECTIONAL_FLOW; +int USE_ADVANCED_FLOW; +double GFTT_QUALITY_LEVEL = 0.01; +int ENABLE_FEATURE_AUTO_TUNING = 0; +double FEATURE_AUTO_TUNE_INTERVAL = 5.0; +int FEATURE_AUTO_TUNE_LOG = 1; +int FAST_THRESHOLD_MIN = 5; +int FAST_THRESHOLD_MAX = 60; +double GFTT_QUALITY_MIN = 0.001; +double GFTT_QUALITY_MAX = 0.05; -template -T readParam(ros::NodeHandle &n, std::string name) -{ - T ans; - if (n.getParam(name, ans)) - { - ROS_INFO_STREAM("Loaded " << name << ": " << ans); - } - else - { - ROS_ERROR_STREAM("Failed to load " << name); - n.shutdown(); - } - return ans; -} +// Adaptive feature tracking +double MAX_VELOCITY_THRESHOLD = 5.0; // m/s +int VELOCITY_BOOST_FEATURES = 50; // extra features at high speed +double MIN_PARALLAX_THRESHOLD = 5.0; // pixels +int ENABLE_VELOCITY_CHECK = 1; -void readParameters(ros::NodeHandle &n) +void readParameters(rclcpp::Node::SharedPtr &n) { std::string config_file; - config_file = readParam(n, "config_file"); + n->declare_parameter("config_file", ""); + n->get_parameter("config_file", config_file); cv::FileStorage fsSettings(config_file, cv::FileStorage::READ); if(!fsSettings.isOpened()) { std::cerr << "ERROR: Wrong path to settings" << std::endl; } - std::string VINS_FOLDER_PATH = readParam(n, "vins_folder"); + n->declare_parameter("vins_folder", ""); + n->get_parameter("vins_folder", VINS_FOLDER_PATH); fsSettings["image_topic"] >> IMAGE_TOPIC; + if (!fsSettings["image_input_format"].empty()) + fsSettings["image_input_format"] >> IMAGE_INPUT_FORMAT; + else + IMAGE_INPUT_FORMAT = "raw"; fsSettings["imu_topic"] >> IMU_TOPIC; MAX_CNT = fsSettings["max_cnt"]; MIN_DIST = fsSettings["min_dist"]; @@ -56,8 +67,82 @@ void readParameters(ros::NodeHandle &n) SHOW_TRACK = fsSettings["show_track"]; EQUALIZE = fsSettings["equalize"]; FISHEYE = fsSettings["fisheye"]; + + // Optional parameter for FAST threshold, default to 20 if not set + if (!fsSettings["fast_threshold"].empty()) + FAST_THRESHOLD = fsSettings["fast_threshold"]; + else + FAST_THRESHOLD = 20; + + // Optional parameter for bidirectional flow, default to 1 (enabled) if not set + if (!fsSettings["use_bidirectional_flow"].empty()) + USE_BIDIRECTIONAL_FLOW = fsSettings["use_bidirectional_flow"]; + else + USE_BIDIRECTIONAL_FLOW = 1; + + // Switch between basic and advanced optical flow (default: advanced) + if (!fsSettings["use_advanced_flow"].empty()) + USE_ADVANCED_FLOW = fsSettings["use_advanced_flow"]; + else + USE_ADVANCED_FLOW = 1; + + if (!fsSettings["gftt_quality_level"].empty()) + GFTT_QUALITY_LEVEL = (double)fsSettings["gftt_quality_level"]; + else + GFTT_QUALITY_LEVEL = 0.01; + + ENABLE_FEATURE_AUTO_TUNING = fsSettings["enable_feature_auto_tuning"].empty() ? 0 : (int)fsSettings["enable_feature_auto_tuning"]; + FEATURE_AUTO_TUNE_INTERVAL = fsSettings["feature_auto_tune_interval"].empty() ? 5.0 : (double)fsSettings["feature_auto_tune_interval"]; + FEATURE_AUTO_TUNE_LOG = fsSettings["feature_auto_tune_log"].empty() ? 1 : (int)fsSettings["feature_auto_tune_log"]; + FAST_THRESHOLD_MIN = fsSettings["fast_threshold_min"].empty() ? 5 : (int)fsSettings["fast_threshold_min"]; + FAST_THRESHOLD_MAX = fsSettings["fast_threshold_max"].empty() ? 60 : (int)fsSettings["fast_threshold_max"]; + GFTT_QUALITY_MIN = fsSettings["gftt_quality_min"].empty() ? 0.001 : (double)fsSettings["gftt_quality_min"]; + GFTT_QUALITY_MAX = fsSettings["gftt_quality_max"].empty() ? 0.05 : (double)fsSettings["gftt_quality_max"]; + FEATURE_AUTO_TUNE_INTERVAL = std::max(0.5, FEATURE_AUTO_TUNE_INTERVAL); + if (FAST_THRESHOLD_MIN > FAST_THRESHOLD_MAX) std::swap(FAST_THRESHOLD_MIN, FAST_THRESHOLD_MAX); + if (GFTT_QUALITY_MIN > GFTT_QUALITY_MAX) std::swap(GFTT_QUALITY_MIN, GFTT_QUALITY_MAX); + FAST_THRESHOLD = std::clamp(FAST_THRESHOLD, FAST_THRESHOLD_MIN, FAST_THRESHOLD_MAX); + GFTT_QUALITY_LEVEL = std::clamp(GFTT_QUALITY_LEVEL, GFTT_QUALITY_MIN, GFTT_QUALITY_MAX); + + // Adaptive tracking parameters + ENABLE_VELOCITY_CHECK = fsSettings["enable_velocity_check"].empty() ? 1 : (int)fsSettings["enable_velocity_check"]; + if (!fsSettings["max_velocity_threshold"].empty()) + MAX_VELOCITY_THRESHOLD = (double)fsSettings["max_velocity_threshold"]; + if (!fsSettings["velocity_boost_features"].empty()) + VELOCITY_BOOST_FEATURES = (int)fsSettings["velocity_boost_features"]; + if (!fsSettings["min_parallax_threshold"].empty()) + MIN_PARALLAX_THRESHOLD = (double)fsSettings["min_parallax_threshold"]; + + if (ENABLE_VELOCITY_CHECK) + RCLCPP_INFO(n->get_logger(), "Velocity-adaptive tracking enabled: max_vel=%.1f m/s, boost=%d features", + MAX_VELOCITY_THRESHOLD, VELOCITY_BOOST_FEATURES); + + RCLCPP_INFO(n->get_logger(), "Image input mode: %s", IMAGE_INPUT_FORMAT.c_str()); + RCLCPP_INFO(n->get_logger(), "Image topic: %s", IMAGE_TOPIC.c_str()); + RCLCPP_INFO(n->get_logger(), + "Feature detector: %s, quality=%.4f, min_dist=%d, max_cnt=%d", + USE_ADVANCED_FLOW ? "AGAST" : "Shi-Tomasi", GFTT_QUALITY_LEVEL, + MIN_DIST, MAX_CNT); + if (ENABLE_FEATURE_AUTO_TUNING) + RCLCPP_INFO(n->get_logger(), + "Feature auto-tuning enabled: interval=%.1fs, detector=%s, logging=%s", + FEATURE_AUTO_TUNE_INTERVAL, USE_ADVANCED_FLOW ? "AGAST" : "Shi-Tomasi", + FEATURE_AUTO_TUNE_LOG ? "on" : "off"); + if (FISHEYE == 1) - FISHEYE_MASK = VINS_FOLDER_PATH + "config/fisheye_mask.jpg"; + { + std::string mask_path; + if (!fsSettings["fisheye_mask"].empty()) + fsSettings["fisheye_mask"] >> mask_path; + else + mask_path = "config/fisheye_mask.jpg"; + + if (!mask_path.empty() && mask_path.front() == '/') + FISHEYE_MASK = mask_path; + else + FISHEYE_MASK = VINS_FOLDER_PATH + mask_path; + RCLCPP_INFO(n->get_logger(), "Feature mask: %s", FISHEYE_MASK.c_str()); + } CAM_NAMES.push_back(config_file); WINDOW_SIZE = 20; diff --git a/feature_tracker/src/parameters.h b/feature_tracker/src/parameters.h index 1bb578b32..446f33545 100644 --- a/feature_tracker/src/parameters.h +++ b/feature_tracker/src/parameters.h @@ -1,6 +1,6 @@ #pragma once -#include #include +#include "rclcpp/rclcpp.hpp" extern int ROW; extern int COL; @@ -9,8 +9,10 @@ const int NUM_OF_CAM = 1; extern std::string IMAGE_TOPIC; +extern std::string IMAGE_INPUT_FORMAT; extern std::string IMU_TOPIC; extern std::string FISHEYE_MASK; +extern std::string VINS_FOLDER_PATH; extern std::vector CAM_NAMES; extern int MAX_CNT; extern int MIN_DIST; @@ -22,5 +24,22 @@ extern int STEREO_TRACK; extern int EQUALIZE; extern int FISHEYE; extern bool PUB_THIS_FRAME; +extern int FAST_THRESHOLD; +extern int USE_BIDIRECTIONAL_FLOW; +extern int USE_ADVANCED_FLOW; +extern double GFTT_QUALITY_LEVEL; +extern int ENABLE_FEATURE_AUTO_TUNING; +extern double FEATURE_AUTO_TUNE_INTERVAL; +extern int FEATURE_AUTO_TUNE_LOG; +extern int FAST_THRESHOLD_MIN; +extern int FAST_THRESHOLD_MAX; +extern double GFTT_QUALITY_MIN; +extern double GFTT_QUALITY_MAX; -void readParameters(ros::NodeHandle &n); +// Adaptive feature tracking for high-speed scenarios +extern double MAX_VELOCITY_THRESHOLD; // m/s - threshold to detect high-speed motion +extern int VELOCITY_BOOST_FEATURES; // extra features to add at high speed +extern double MIN_PARALLAX_THRESHOLD; // minimum parallax to accept new keyframe +extern int ENABLE_VELOCITY_CHECK; // enable velocity-based adaptive tracking + +void readParameters(rclcpp::Node::SharedPtr &n); diff --git a/feature_tracker/src/tracking_safety.h b/feature_tracker/src/tracking_safety.h new file mode 100644 index 000000000..7f2093a6b --- /dev/null +++ b/feature_tracker/src/tracking_safety.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include + +namespace tracking_safety +{ + +inline bool imageSizeIsValid( + std::size_t width, std::size_t height, + std::size_t configured_width, std::size_t configured_height, + std::size_t camera_count) +{ + return width >= configured_width && + height >= configured_height * camera_count; +} + +inline std::size_t commonSize(std::initializer_list sizes) +{ + if (sizes.size() == 0) + return 0; + return *std::min_element(sizes.begin(), sizes.end()); +} + +inline bool timestampStepIsValid(double previous, double current) +{ + return current > previous; +} + +inline bool pixelIsInside(int x, int y, int width, int height) +{ + return x >= 0 && y >= 0 && x < width && y < height; +} + +} // namespace tracking_safety diff --git a/pose_graph/CMakeLists.txt b/pose_graph/CMakeLists.txt index cc8a02897..13cb946e0 100644 --- a/pose_graph/CMakeLists.txt +++ b/pose_graph/CMakeLists.txt @@ -1,47 +1,53 @@ -cmake_minimum_required(VERSION 2.8.3) +cmake_minimum_required(VERSION 3.8) project(pose_graph) -set(CMAKE_BUILD_TYPE "Release") -set(CMAKE_CXX_FLAGS "-std=c++11") -#-DEIGEN_USE_MKL_ALL") +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) +endif() +set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_FLAGS_RELEASE "-O3 -Wall -g") -find_package(catkin REQUIRED COMPONENTS - roscpp - std_msgs - nav_msgs - camera_model - cv_bridge - roslib - ) - -find_package(OpenCV) - - +find_package(ament_cmake REQUIRED) +find_package(rclcpp REQUIRED) +find_package(std_msgs REQUIRED) +find_package(sensor_msgs REQUIRED) +find_package(nav_msgs REQUIRED) +find_package(geometry_msgs REQUIRED) +find_package(visualization_msgs REQUIRED) +find_package(tf2_ros REQUIRED) +find_package(cv_bridge REQUIRED) +find_package(image_transport REQUIRED) +find_package(ament_index_cpp REQUIRED) +find_package(Boost REQUIRED COMPONENTS filesystem program_options system) +find_package(camera_model REQUIRED) +find_package(OpenCV REQUIRED) find_package(Ceres REQUIRED) - -set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) -find_package(Eigen3) - -include_directories(${catkin_INCLUDE_DIRS} ${CERES_INCLUDE_DIRS} ${EIGEN3_INCLUDE_DIR}) - -catkin_package() +find_package(Eigen3 REQUIRED) add_executable(pose_graph - src/pose_graph_node.cpp - src/pose_graph.cpp - src/keyframe.cpp - src/utility/CameraPoseVisualization.cpp - src/ThirdParty/DBoW/BowVector.cpp - src/ThirdParty/DBoW/FBrief.cpp - src/ThirdParty/DBoW/FeatureVector.cpp - src/ThirdParty/DBoW/QueryResults.cpp - src/ThirdParty/DBoW/ScoringObject.cpp - src/ThirdParty/DUtils/Random.cpp - src/ThirdParty/DUtils/Timestamp.cpp - src/ThirdParty/DVision/BRIEF.cpp - src/ThirdParty/VocabularyBinary.cpp - ) - -target_link_libraries(pose_graph ${catkin_LIBRARIES} ${OpenCV_LIBS} ${CERES_LIBRARIES}) -# message("catkin_lib ${catkin_LIBRARIES}") + src/pose_graph_node.cpp + src/pose_graph.cpp + src/keyframe.cpp + src/utility/CameraPoseVisualization.cpp + src/ThirdParty/DBoW/BowVector.cpp + src/ThirdParty/DBoW/FBrief.cpp + src/ThirdParty/DBoW/FeatureVector.cpp + src/ThirdParty/DBoW/QueryResults.cpp + src/ThirdParty/DBoW/ScoringObject.cpp + src/ThirdParty/DUtils/Random.cpp + src/ThirdParty/DUtils/Timestamp.cpp + src/ThirdParty/DVision/BRIEF.cpp + src/ThirdParty/VocabularyBinary.cpp) + +ament_target_dependencies(pose_graph + rclcpp std_msgs sensor_msgs nav_msgs geometry_msgs visualization_msgs + tf2_ros cv_bridge image_transport ament_index_cpp camera_model) +target_include_directories(pose_graph PRIVATE ${EIGEN3_INCLUDE_DIRS} ${camera_model_INCLUDE_DIRS}) +target_link_libraries(pose_graph camera_model::camera_model ${OpenCV_LIBS} Ceres::ceres) + +install(TARGETS pose_graph DESTINATION lib/${PROJECT_NAME}) +install(DIRECTORY ../support_files DESTINATION share/${PROJECT_NAME}) +ament_package() diff --git a/pose_graph/package.xml b/pose_graph/package.xml index 6e1238b5a..391d0fb93 100644 --- a/pose_graph/package.xml +++ b/pose_graph/package.xml @@ -1,53 +1,21 @@ - + pose_graph 0.0.0 - pose_graph package - - - - + Loop closure and pose graph optimization for VINS-Mono. tony-ws - - - - - TODO - - - - - - - - - - - - - - - - - - - - - - - - - - catkin - camera_model - camera_model - - - - - - - - - \ No newline at end of file + ament_cmake + rclcpp + std_msgs + sensor_msgs + nav_msgs + geometry_msgs + visualization_msgs + tf2_ros + cv_bridge + image_transport + ament_index_cpp + camera_model + ament_cmake + diff --git a/pose_graph/src/keyframe.cpp b/pose_graph/src/keyframe.cpp index 66774964c..d346aabad 100644 --- a/pose_graph/src/keyframe.cpp +++ b/pose_graph/src/keyframe.cpp @@ -461,9 +461,9 @@ bool KeyFrame::findConnection(KeyFrame* old_kf) */ cv::Mat thumbimage; cv::resize(loop_match_img, thumbimage, cv::Size(loop_match_img.cols / 2, loop_match_img.rows / 2)); - sensor_msgs::ImagePtr msg = cv_bridge::CvImage(std_msgs::Header(), "bgr8", thumbimage).toImageMsg(); - msg->header.stamp = ros::Time(time_stamp); - pub_match_img.publish(msg); + sensor_msgs::msg::Image::SharedPtr msg = cv_bridge::CvImage(std_msgs::msg::Header(), "bgr8", thumbimage).toImageMsg(); + msg->header.stamp = rclcpp::Time(static_cast(time_stamp * 1e9)); + pub_match_img->publish(*msg); } } #endif @@ -487,11 +487,11 @@ bool KeyFrame::findConnection(KeyFrame* old_kf) relative_yaw; if(FAST_RELOCALIZATION) { - sensor_msgs::PointCloud msg_match_points; - msg_match_points.header.stamp = ros::Time(time_stamp); + sensor_msgs::msg::PointCloud msg_match_points; + msg_match_points.header.stamp = rclcpp::Time(static_cast(time_stamp * 1e9)); for (int i = 0; i < (int)matched_2d_old_norm.size(); i++) { - geometry_msgs::Point32 p; + geometry_msgs::msg::Point32 p; p.x = matched_2d_old_norm[i].x; p.y = matched_2d_old_norm[i].y; p.z = matched_id[i]; @@ -500,7 +500,7 @@ bool KeyFrame::findConnection(KeyFrame* old_kf) Eigen::Vector3d T = old_kf->T_w_i; Eigen::Matrix3d R = old_kf->R_w_i; Quaterniond Q(R); - sensor_msgs::ChannelFloat32 t_q_index; + sensor_msgs::msg::ChannelFloat32 t_q_index; t_q_index.values.push_back(T.x()); t_q_index.values.push_back(T.y()); t_q_index.values.push_back(T.z()); @@ -510,7 +510,7 @@ bool KeyFrame::findConnection(KeyFrame* old_kf) t_q_index.values.push_back(Q.z()); t_q_index.values.push_back(index); msg_match_points.channels.push_back(t_q_index); - pub_match_points.publish(msg_match_points); + pub_match_points->publish(msg_match_points); } return true; } diff --git a/pose_graph/src/parameters.h b/pose_graph/src/parameters.h index 4e0cd132b..827dbd412 100644 --- a/pose_graph/src/parameters.h +++ b/pose_graph/src/parameters.h @@ -4,17 +4,17 @@ #include "camodocal/camera_models/CataCamera.h" #include "camodocal/camera_models/PinholeCamera.h" #include -#include -#include -#include -#include -#include +#include "rclcpp/rclcpp.hpp" +#include "sensor_msgs/msg/image.hpp" +#include "sensor_msgs/msg/point_cloud.hpp" +#include "sensor_msgs/image_encodings.hpp" +#include "cv_bridge/cv_bridge.hpp" extern camodocal::CameraPtr m_camera; extern Eigen::Vector3d tic; extern Eigen::Matrix3d qic; -extern ros::Publisher pub_match_img; -extern ros::Publisher pub_match_points; +extern rclcpp::Publisher::SharedPtr pub_match_img; +extern rclcpp::Publisher::SharedPtr pub_match_points; extern int VISUALIZATION_SHIFT_X; extern int VISUALIZATION_SHIFT_Y; extern std::string BRIEF_PATTERN_FILE; diff --git a/pose_graph/src/pose_graph.cpp b/pose_graph/src/pose_graph.cpp index 7029a8fe3..18abf7367 100644 --- a/pose_graph/src/pose_graph.cpp +++ b/pose_graph/src/pose_graph.cpp @@ -1,5 +1,10 @@ #include "pose_graph.h" +static builtin_interfaces::msg::Time timeFromSec(double seconds) +{ + return rclcpp::Time(static_cast(seconds * 1e9)); +} + PoseGraph::PoseGraph() { posegraph_visualization = new CameraPoseVisualization(1.0, 0.0, 1.0, 1.0); @@ -24,13 +29,16 @@ PoseGraph::~PoseGraph() t_optimization.join(); } -void PoseGraph::registerPub(ros::NodeHandle &n) +void PoseGraph::registerPub(rclcpp::Node::SharedPtr n) { - pub_pg_path = n.advertise("pose_graph_path", 1000); - pub_base_path = n.advertise("base_path", 1000); - pub_pose_graph = n.advertise("pose_graph", 1000); + pub_pg_path = n->create_publisher("/pose_graph/pose_graph_path", 1000); + pub_base_path = n->create_publisher("/pose_graph/base_path", 1000); + pub_pose_graph = n->create_publisher("/pose_graph/pose_graph", 1000); for (int i = 1; i < 10; i++) - pub_path[i] = n.advertise("path_" + to_string(i), 1000); + pub_path[i] = n->create_publisher("/pose_graph/path_" + to_string(i), 1000); + + // Create TF broadcaster after ros::init is done (registerPub is called after ros::init) + tf_broadcaster = std::make_shared(n); } void PoseGraph::loadVocabulary(std::string voc_path) @@ -41,6 +49,9 @@ void PoseGraph::loadVocabulary(std::string voc_path) void PoseGraph::addKeyFrame(KeyFrame* cur_kf, bool flag_detect_loop) { + static uint64_t loop_query_count = 0; + static uint64_t loop_candidate_count = 0; + static uint64_t loop_accepted_count = 0; //shift to base frame Vector3d vio_P_cur; Matrix3d vio_R_cur; @@ -66,7 +77,12 @@ void PoseGraph::addKeyFrame(KeyFrame* cur_kf, bool flag_detect_loop) if (flag_detect_loop) { TicToc tmp_t; + loop_query_count++; loop_index = detectLoop(cur_kf, cur_kf->index); + if (loop_query_count == 1 || loop_query_count % 100 == 0) + RCLCPP_INFO(rclcpp::get_logger("pose_graph"), + "[LOOP] queried %lu keyframes, candidates=%lu, accepted=%lu", + loop_query_count, loop_candidate_count, loop_accepted_count); } else { @@ -74,11 +90,18 @@ void PoseGraph::addKeyFrame(KeyFrame* cur_kf, bool flag_detect_loop) } if (loop_index != -1) { - //printf(" %d detect loop with %d \n", cur_kf->index, loop_index); + loop_candidate_count++; + RCLCPP_INFO(rclcpp::get_logger("pose_graph"), + "[LOOP] DBoW candidate: current=%d previous=%d", + cur_kf->index, loop_index); KeyFrame* old_kf = getKeyFrame(loop_index); if (cur_kf->findConnection(old_kf)) { + loop_accepted_count++; + RCLCPP_WARN(rclcpp::get_logger("pose_graph"), + "[LOOP] accepted #%lu: current=%d previous=%d", + loop_accepted_count, cur_kf->index, loop_index); if (earliest_loop_index > loop_index || earliest_loop_index == -1) earliest_loop_index = loop_index; @@ -126,6 +149,12 @@ void PoseGraph::addKeyFrame(KeyFrame* cur_kf, bool flag_detect_loop) optimize_buf.push(cur_kf->index); m_optimize_buf.unlock(); } + else + { + RCLCPP_INFO(rclcpp::get_logger("pose_graph"), + "[LOOP] candidate rejected by geometric verification: current=%d previous=%d", + cur_kf->index, loop_index); + } } m_keyframelist.lock(); Vector3d P; @@ -135,8 +164,8 @@ void PoseGraph::addKeyFrame(KeyFrame* cur_kf, bool flag_detect_loop) R = r_drift * R; cur_kf->updatePose(P, R); Quaterniond Q{R}; - geometry_msgs::PoseStamped pose_stamped; - pose_stamped.header.stamp = ros::Time(cur_kf->time_stamp); + geometry_msgs::msg::PoseStamped pose_stamped; + pose_stamped.header.stamp = timeFromSec(cur_kf->time_stamp); pose_stamped.header.frame_id = "world"; pose_stamped.pose.position.x = P.x() + VISUALIZATION_SHIFT_X; pose_stamped.pose.position.y = P.y() + VISUALIZATION_SHIFT_Y; @@ -148,6 +177,20 @@ void PoseGraph::addKeyFrame(KeyFrame* cur_kf, bool flag_detect_loop) path[sequence_cnt].poses.push_back(pose_stamped); path[sequence_cnt].header = pose_stamped.header; + // Publish corrected pose to TF (world -> camera_pose_graph) + geometry_msgs::msg::TransformStamped transform; + transform.header = pose_stamped.header; + transform.child_frame_id = "camera_pose_graph"; + transform.transform.translation.x = P.x(); + transform.transform.translation.y = P.y(); + transform.transform.translation.z = P.z(); + transform.transform.rotation.x = Q.x(); + transform.transform.rotation.y = Q.y(); + transform.transform.rotation.z = Q.z(); + transform.transform.rotation.w = Q.w(); + if (tf_broadcaster) + tf_broadcaster->sendTransform(transform); + if (SAVE_LOOP_PATH) { ofstream loop_path_file(VINS_RESULT_PATH, ios::app); @@ -239,8 +282,8 @@ void PoseGraph::loadKeyFrame(KeyFrame* cur_kf, bool flag_detect_loop) Matrix3d R; cur_kf->getPose(P, R); Quaterniond Q{R}; - geometry_msgs::PoseStamped pose_stamped; - pose_stamped.header.stamp = ros::Time(cur_kf->time_stamp); + geometry_msgs::msg::PoseStamped pose_stamped; + pose_stamped.header.stamp = timeFromSec(cur_kf->time_stamp); pose_stamped.header.frame_id = "world"; pose_stamped.pose.position.x = P.x() + VISUALIZATION_SHIFT_X; pose_stamped.pose.position.y = P.y() + VISUALIZATION_SHIFT_Y; @@ -439,7 +482,7 @@ void PoseGraph::optimize4DoF() ceres::LossFunction *loss_function; loss_function = new ceres::HuberLoss(0.1); //loss_function = new ceres::CauchyLoss(1.0); - ceres::LocalParameterization* angle_local_parameterization = + ceres::Manifold* angle_local_parameterization = AngleLocalParameterization::Create(); list::iterator it; @@ -467,7 +510,8 @@ void PoseGraph::optimize4DoF() sequence_array[i] = (*it)->sequence; - problem.AddParameterBlock(euler_array[i], 1, angle_local_parameterization); + problem.AddParameterBlock(euler_array[i], 1); + problem.SetManifold(euler_array[i], angle_local_parameterization); problem.AddParameterBlock(t_array[i], 3); if ((*it)->index == first_looped_index || (*it)->sequence == 0) @@ -604,8 +648,8 @@ void PoseGraph::updatePath() Q = R; // printf("path p: %f, %f, %f\n", P.x(), P.z(), P.y() ); - geometry_msgs::PoseStamped pose_stamped; - pose_stamped.header.stamp = ros::Time((*it)->time_stamp); + geometry_msgs::msg::PoseStamped pose_stamped; + pose_stamped.header.stamp = timeFromSec((*it)->time_stamp); pose_stamped.header.frame_id = "world"; pose_stamped.pose.position.x = P.x() + VISUALIZATION_SHIFT_X; pose_stamped.pose.position.y = P.y() + VISUALIZATION_SHIFT_Y; @@ -876,13 +920,13 @@ void PoseGraph::publish() //if (sequence_loop[i] == true || i == base_sequence) if (1 || i == base_sequence) { - pub_pg_path.publish(path[i]); - pub_path[i].publish(path[i]); + pub_pg_path->publish(path[i]); + pub_path[i]->publish(path[i]); posegraph_visualization->publish_by(pub_pose_graph, path[sequence_cnt].header); } } base_path.header.frame_id = "world"; - pub_base_path.publish(base_path); + pub_base_path->publish(base_path); //posegraph_visualization->publish_by(pub_pose_graph, path[sequence_cnt].header); } @@ -920,4 +964,4 @@ void PoseGraph::updateKeyFrameLoop(int index, Eigen::Matrix &_loo m_drift.unlock(); } } -} \ No newline at end of file +} diff --git a/pose_graph/src/pose_graph.h b/pose_graph/src/pose_graph.h index 7a659227f..0edeba16b 100644 --- a/pose_graph/src/pose_graph.h +++ b/pose_graph/src/pose_graph.h @@ -9,11 +9,15 @@ #include #include #include -#include -#include -#include +#include "nav_msgs/msg/path.hpp" +#include "geometry_msgs/msg/point_stamped.hpp" +#include "nav_msgs/msg/odometry.hpp" +#include "tf2_ros/transform_broadcaster.h" +#include "geometry_msgs/msg/transform_stamped.hpp" +#include #include -#include +#include "rclcpp/rclcpp.hpp" +#include "camodocal/gpl/ceres_local_parameterization_compat.h" #include "keyframe.h" #include "utility/tic_toc.h" #include "utility/utility.h" @@ -37,14 +41,14 @@ class PoseGraph public: PoseGraph(); ~PoseGraph(); - void registerPub(ros::NodeHandle &n); + void registerPub(rclcpp::Node::SharedPtr n); void addKeyFrame(KeyFrame* cur_kf, bool flag_detect_loop); void loadKeyFrame(KeyFrame* cur_kf, bool flag_detect_loop); void loadVocabulary(std::string voc_path); void updateKeyFrameLoop(int index, Eigen::Matrix &_loop_info); KeyFrame* getKeyFrame(int index); - nav_msgs::Path path[10]; - nav_msgs::Path base_path; + nav_msgs::msg::Path path[10]; + nav_msgs::msg::Path base_path; CameraPoseVisualization* posegraph_visualization; void savePoseGraph(); void loadPoseGraph(); @@ -80,10 +84,11 @@ class PoseGraph BriefDatabase db; BriefVocabulary* voc; - ros::Publisher pub_pg_path; - ros::Publisher pub_base_path; - ros::Publisher pub_pose_graph; - ros::Publisher pub_path[10]; + rclcpp::Publisher::SharedPtr pub_pg_path; + rclcpp::Publisher::SharedPtr pub_base_path; + rclcpp::Publisher::SharedPtr pub_pose_graph; + rclcpp::Publisher::SharedPtr pub_path[10]; + std::shared_ptr tf_broadcaster; }; template @@ -100,16 +105,22 @@ class AngleLocalParameterization { public: template - bool operator()(const T* theta_radians, const T* delta_theta_radians, - T* theta_radians_plus_delta) const { + bool Plus(const T* theta_radians, const T* delta_theta_radians, + T* theta_radians_plus_delta) const { *theta_radians_plus_delta = NormalizeAngle(*theta_radians + *delta_theta_radians); return true; } - static ceres::LocalParameterization* Create() { - return (new ceres::AutoDiffLocalParameterization + bool Minus(const T* y, const T* x, T* y_minus_x) const { + *y_minus_x = NormalizeAngle(*y - *x); + return true; + } + + static ceres::Manifold* Create() { + return (new ceres::AutoDiffManifold); } }; @@ -245,4 +256,4 @@ struct FourDOFWeightError double relative_yaw, pitch_i, roll_i; double weight; -}; \ No newline at end of file +}; diff --git a/pose_graph/src/pose_graph_node.cpp b/pose_graph/src/pose_graph_node.cpp index 122821c40..9b1f17ab1 100644 --- a/pose_graph/src/pose_graph_node.cpp +++ b/pose_graph/src/pose_graph_node.cpp @@ -1,15 +1,17 @@ #include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "rclcpp/rclcpp.hpp" +#include "image_transport/image_transport.hpp" +#include "nav_msgs/msg/odometry.hpp" +#include "nav_msgs/msg/path.hpp" +#include "sensor_msgs/msg/point_cloud.hpp" +#include "sensor_msgs/msg/image.hpp" +#include "sensor_msgs/image_encodings.hpp" +#include "visualization_msgs/msg/marker.hpp" +#include "visualization_msgs/msg/marker_array.hpp" +#include "cv_bridge/cv_bridge.hpp" +#include "ament_index_cpp/get_package_share_directory.hpp" #include -#include +#include #include #include #include @@ -24,9 +26,9 @@ #define SKIP_FIRST_CNT 10 using namespace std; -queue image_buf; -queue point_buf; -queue pose_buf; +queue image_buf; +queue point_buf; +queue pose_buf; queue odometry_buf; std::mutex m_buf; std::mutex m_process; @@ -52,12 +54,17 @@ int FAST_RELOCALIZATION; camodocal::CameraPtr m_camera; Eigen::Vector3d tic; Eigen::Matrix3d qic; -ros::Publisher pub_match_img; -ros::Publisher pub_match_points; -ros::Publisher pub_camera_pose_visual; -ros::Publisher pub_key_odometrys; -ros::Publisher pub_vio_path; -nav_msgs::Path no_loop_path; +rclcpp::Publisher::SharedPtr pub_match_img; +rclcpp::Publisher::SharedPtr pub_match_points; +rclcpp::Publisher::SharedPtr pub_camera_pose_visual; +rclcpp::Publisher::SharedPtr pub_key_odometrys; +rclcpp::Publisher::SharedPtr pub_vio_path; +nav_msgs::msg::Path no_loop_path; + +static double stampToSec(const builtin_interfaces::msg::Time &stamp) +{ + return static_cast(stamp.sec) + static_cast(stamp.nanosec) * 1e-9; +} std::string BRIEF_PATTERN_FILE; std::string POSE_GRAPH_SAVE_PATH; @@ -65,6 +72,11 @@ std::string VINS_RESULT_PATH; CameraPoseVisualization cameraposevisual(1, 0, 0, 1); Eigen::Vector3d last_t(-100, -100, -100); double last_image_time = -1; +static std::string IMAGE_INPUT_FORMAT = "raw"; +static std::atomic pose_graph_image_count{0}; +static std::atomic pose_graph_point_count{0}; +static std::atomic pose_graph_pose_count{0}; +static std::atomic pose_graph_keyframe_count{0}; void new_sequence() { @@ -73,8 +85,8 @@ void new_sequence() printf("sequence cnt %d \n", sequence); if (sequence > 5) { - ROS_WARN("only support 5 sequences since it's boring to copy code for more sequences."); - ROS_BREAK(); + RCLCPP_ERROR(rclcpp::get_logger("pose_graph"), "only support 5 sequences"); + std::abort(); } posegraph.posegraph_visualization->reset(); posegraph.publish(); @@ -90,35 +102,47 @@ void new_sequence() m_buf.unlock(); } -void image_callback(const sensor_msgs::ImageConstPtr &image_msg) +void image_callback(const sensor_msgs::msg::Image::ConstSharedPtr &image_msg) { //ROS_INFO("image_callback!"); if(!LOOP_CLOSURE) return; m_buf.lock(); image_buf.push(image_msg); + const size_t queued_images = image_buf.size(); m_buf.unlock(); + const auto count = ++pose_graph_image_count; + if (count == 1 || count % 100 == 0) + RCLCPP_INFO(rclcpp::get_logger("pose_graph"), + "[INPUT] image #%lu stamp=%.6f encoding=%s queue=%zu", + count, stampToSec(image_msg->header.stamp), image_msg->encoding.c_str(), queued_images); //printf(" image time %f \n", image_msg->header.stamp.toSec()); // detect unstable camera stream if (last_image_time == -1) - last_image_time = image_msg->header.stamp.toSec(); - else if (image_msg->header.stamp.toSec() - last_image_time > 1.0 || image_msg->header.stamp.toSec() < last_image_time) + last_image_time = stampToSec(image_msg->header.stamp); + else if (stampToSec(image_msg->header.stamp) - last_image_time > 1.0 || stampToSec(image_msg->header.stamp) < last_image_time) { - ROS_WARN("image discontinue! detect a new sequence!"); + RCLCPP_WARN(rclcpp::get_logger("pose_graph"), "image discontinue! detect a new sequence!"); new_sequence(); } - last_image_time = image_msg->header.stamp.toSec(); + last_image_time = stampToSec(image_msg->header.stamp); } -void point_callback(const sensor_msgs::PointCloudConstPtr &point_msg) +void point_callback(const sensor_msgs::msg::PointCloud::ConstSharedPtr &point_msg) { //ROS_INFO("point_callback!"); if(!LOOP_CLOSURE) return; m_buf.lock(); point_buf.push(point_msg); + const size_t queued_points = point_buf.size(); m_buf.unlock(); + const auto count = ++pose_graph_point_count; + if (count == 1 || count % 20 == 0) + RCLCPP_INFO(rclcpp::get_logger("pose_graph"), + "[INPUT] keyframe points #%lu stamp=%.6f points=%zu queue=%zu", + count, stampToSec(point_msg->header.stamp), point_msg->points.size(), queued_points); /* for (unsigned int i = 0; i < point_msg->points.size(); i++) { @@ -131,14 +155,20 @@ void point_callback(const sensor_msgs::PointCloudConstPtr &point_msg) */ } -void pose_callback(const nav_msgs::Odometry::ConstPtr &pose_msg) +void pose_callback(const nav_msgs::msg::Odometry::ConstSharedPtr &pose_msg) { //ROS_INFO("pose_callback!"); if(!LOOP_CLOSURE) return; m_buf.lock(); pose_buf.push(pose_msg); + const size_t queued_poses = pose_buf.size(); m_buf.unlock(); + const auto count = ++pose_graph_pose_count; + if (count == 1 || count % 20 == 0) + RCLCPP_INFO(rclcpp::get_logger("pose_graph"), + "[INPUT] keyframe pose #%lu stamp=%.6f queue=%zu", + count, stampToSec(pose_msg->header.stamp), queued_poses); /* printf("pose t: %f, %f, %f q: %f, %f, %f %f \n", pose_msg->pose.pose.position.x, pose_msg->pose.pose.position.y, @@ -150,7 +180,7 @@ void pose_callback(const nav_msgs::Odometry::ConstPtr &pose_msg) */ } -void imu_forward_callback(const nav_msgs::Odometry::ConstPtr &forward_msg) +void imu_forward_callback(const nav_msgs::msg::Odometry::ConstSharedPtr &forward_msg) { if (VISUALIZE_IMU_FORWARD) { @@ -177,7 +207,7 @@ void imu_forward_callback(const nav_msgs::Odometry::ConstPtr &forward_msg) cameraposevisual.publish_by(pub_camera_pose_visual, forward_msg->header); } } -void relo_relative_pose_callback(const nav_msgs::Odometry::ConstPtr &pose_msg) +void relo_relative_pose_callback(const nav_msgs::msg::Odometry::ConstSharedPtr &pose_msg) { Vector3d relative_t = Vector3d(pose_msg->pose.pose.position.x, pose_msg->pose.pose.position.y, @@ -198,7 +228,7 @@ void relo_relative_pose_callback(const nav_msgs::Odometry::ConstPtr &pose_msg) } -void vio_callback(const nav_msgs::Odometry::ConstPtr &pose_msg) +void vio_callback(const nav_msgs::msg::Odometry::ConstSharedPtr &pose_msg) { //ROS_INFO("vio_callback!"); Vector3d vio_t(pose_msg->pose.pose.position.x, pose_msg->pose.pose.position.y, pose_msg->pose.pose.position.z); @@ -232,14 +262,14 @@ void vio_callback(const nav_msgs::Odometry::ConstPtr &pose_msg) odometry_buf.pop(); } - visualization_msgs::Marker key_odometrys; + visualization_msgs::msg::Marker key_odometrys; key_odometrys.header = pose_msg->header; key_odometrys.header.frame_id = "world"; key_odometrys.ns = "key_odometrys"; - key_odometrys.type = visualization_msgs::Marker::SPHERE_LIST; - key_odometrys.action = visualization_msgs::Marker::ADD; + key_odometrys.type = visualization_msgs::msg::Marker::SPHERE_LIST; + key_odometrys.action = visualization_msgs::msg::Marker::ADD; key_odometrys.pose.orientation.w = 1.0; - key_odometrys.lifetime = ros::Duration(); + key_odometrys.lifetime = builtin_interfaces::msg::Duration(); //static int key_odometrys_id = 0; key_odometrys.id = 0; //key_odometrys_id++; @@ -251,7 +281,7 @@ void vio_callback(const nav_msgs::Odometry::ConstPtr &pose_msg) for (unsigned int i = 0; i < odometry_buf.size(); i++) { - geometry_msgs::Point pose_marker; + geometry_msgs::msg::Point pose_marker; Vector3d vio_t; vio_t = odometry_buf.front(); odometry_buf.pop(); @@ -261,11 +291,11 @@ void vio_callback(const nav_msgs::Odometry::ConstPtr &pose_msg) key_odometrys.points.push_back(pose_marker); odometry_buf.push(vio_t); } - pub_key_odometrys.publish(key_odometrys); + pub_key_odometrys->publish(key_odometrys); if (!LOOP_CLOSURE) { - geometry_msgs::PoseStamped pose_stamped; + geometry_msgs::msg::PoseStamped pose_stamped; pose_stamped.header = pose_msg->header; pose_stamped.header.frame_id = "world"; pose_stamped.pose.position.x = vio_t.x(); @@ -274,11 +304,11 @@ void vio_callback(const nav_msgs::Odometry::ConstPtr &pose_msg) no_loop_path.header = pose_msg->header; no_loop_path.header.frame_id = "world"; no_loop_path.poses.push_back(pose_stamped); - pub_vio_path.publish(no_loop_path); + pub_vio_path->publish(no_loop_path); } } -void extrinsic_callback(const nav_msgs::Odometry::ConstPtr &pose_msg) +void extrinsic_callback(const nav_msgs::msg::Odometry::ConstSharedPtr &pose_msg) { m_process.lock(); tic = Vector3d(pose_msg->pose.pose.position.x, @@ -297,38 +327,60 @@ void process() return; while (true) { - sensor_msgs::ImageConstPtr image_msg = NULL; - sensor_msgs::PointCloudConstPtr point_msg = NULL; - nav_msgs::Odometry::ConstPtr pose_msg = NULL; + sensor_msgs::msg::Image::ConstSharedPtr image_msg; + sensor_msgs::msg::PointCloud::ConstSharedPtr point_msg; + nav_msgs::msg::Odometry::ConstSharedPtr pose_msg; // find out the messages with same time stamp m_buf.lock(); if(!image_buf.empty() && !point_buf.empty() && !pose_buf.empty()) { - if (image_buf.front()->header.stamp.toSec() > pose_buf.front()->header.stamp.toSec()) + if (stampToSec(image_buf.front()->header.stamp) > stampToSec(pose_buf.front()->header.stamp)) { pose_buf.pop(); printf("throw pose at beginning\n"); } - else if (image_buf.front()->header.stamp.toSec() > point_buf.front()->header.stamp.toSec()) + else if (stampToSec(image_buf.front()->header.stamp) > stampToSec(point_buf.front()->header.stamp)) { point_buf.pop(); printf("throw point at beginning\n"); } - else if (image_buf.back()->header.stamp.toSec() >= pose_buf.front()->header.stamp.toSec() - && point_buf.back()->header.stamp.toSec() >= pose_buf.front()->header.stamp.toSec()) + else if (stampToSec(image_buf.back()->header.stamp) >= stampToSec(pose_buf.front()->header.stamp) + && stampToSec(point_buf.back()->header.stamp) >= stampToSec(pose_buf.front()->header.stamp)) { pose_msg = pose_buf.front(); pose_buf.pop(); while (!pose_buf.empty()) pose_buf.pop(); - while (image_buf.front()->header.stamp.toSec() < pose_msg->header.stamp.toSec()) + while (stampToSec(image_buf.front()->header.stamp) < stampToSec(pose_msg->header.stamp)) + { image_buf.pop(); + if (image_buf.empty()) + break; + } + if (image_buf.empty()) + { + RCLCPP_WARN(rclcpp::get_logger("pose_graph"), + "[SYNC] image queue exhausted while matching keyframe pose"); + m_buf.unlock(); + continue; + } image_msg = image_buf.front(); image_buf.pop(); - while (point_buf.front()->header.stamp.toSec() < pose_msg->header.stamp.toSec()) + while (stampToSec(point_buf.front()->header.stamp) < stampToSec(pose_msg->header.stamp)) + { point_buf.pop(); + if (point_buf.empty()) + break; + } + if (point_buf.empty()) + { + RCLCPP_WARN(rclcpp::get_logger("pose_graph"), + "[SYNC] point queue exhausted while matching keyframe pose"); + m_buf.unlock(); + continue; + } point_msg = point_buf.front(); point_buf.pop(); } @@ -360,7 +412,7 @@ void process() cv_bridge::CvImageConstPtr ptr; if (image_msg->encoding == "8UC1") { - sensor_msgs::Image img; + sensor_msgs::msg::Image img; img.header = image_msg->header; img.height = image_msg->height; img.width = image_msg->width; @@ -389,6 +441,18 @@ void process() vector point_2d_normal; vector point_id; + bool channels_valid = point_msg->channels.size() >= point_msg->points.size(); + if (channels_valid) + for (size_t i = 0; i < point_msg->points.size(); ++i) + channels_valid = channels_valid && point_msg->channels[i].values.size() >= 5; + if (!channels_valid) + { + RCLCPP_ERROR(rclcpp::get_logger("pose_graph"), + "[INPUT] invalid keyframe point layout: points=%zu channels=%zu; keyframe dropped", + point_msg->points.size(), point_msg->channels.size()); + continue; + } + for (unsigned int i = 0; i < point_msg->points.size(); i++) { cv::Point3f p_3d; @@ -411,13 +475,18 @@ void process() //printf("u %f, v %f \n", p_2d_uv.x, p_2d_uv.y); } - KeyFrame* keyframe = new KeyFrame(pose_msg->header.stamp.toSec(), frame_index, T, R, image, + KeyFrame* keyframe = new KeyFrame(stampToSec(pose_msg->header.stamp), frame_index, T, R, image, point_3d, point_2d_uv, point_2d_normal, point_id, sequence); m_process.lock(); start_flag = 1; posegraph.addKeyFrame(keyframe, 1); m_process.unlock(); frame_index++; + const auto count = ++pose_graph_keyframe_count; + if (count == 1 || count % 10 == 0) + RCLCPP_INFO(rclcpp::get_logger("pose_graph"), + "[OUTPUT] accepted keyframe #%lu stamp=%.6f features=%zu", + count, stampToSec(pose_msg->header.stamp), point_3d.size()); last_t = T; } } @@ -453,17 +522,17 @@ void command() int main(int argc, char **argv) { - ros::init(argc, argv, "pose_graph"); - ros::NodeHandle n("~"); - posegraph.registerPub(n); + rclcpp::init(argc, argv); + auto node = std::make_shared("pose_graph"); + posegraph.registerPub(node); // read param - n.getParam("visualization_shift_x", VISUALIZATION_SHIFT_X); - n.getParam("visualization_shift_y", VISUALIZATION_SHIFT_Y); - n.getParam("skip_cnt", SKIP_CNT); - n.getParam("skip_dis", SKIP_DIS); + VISUALIZATION_SHIFT_X = node->declare_parameter("visualization_shift_x", 0); + VISUALIZATION_SHIFT_Y = node->declare_parameter("visualization_shift_y", 0); + SKIP_CNT = node->declare_parameter("skip_cnt", 0); + SKIP_DIS = node->declare_parameter("skip_dis", 0.0); std::string config_file; - n.getParam("config_file", config_file); + config_file = node->declare_parameter("config_file", ""); cv::FileStorage fsSettings(config_file, cv::FileStorage::READ); if(!fsSettings.isOpened()) { @@ -482,16 +551,18 @@ int main(int argc, char **argv) { ROW = fsSettings["image_height"]; COL = fsSettings["image_width"]; - std::string pkg_path = ros::package::getPath("pose_graph"); - string vocabulary_file = pkg_path + "/../support_files/brief_k10L6.bin"; + std::string pkg_path = ament_index_cpp::get_package_share_directory("pose_graph"); + string vocabulary_file = pkg_path + "/support_files/brief_k10L6.bin"; cout << "vocabulary_file" << vocabulary_file << endl; posegraph.loadVocabulary(vocabulary_file); - BRIEF_PATTERN_FILE = pkg_path + "/../support_files/brief_pattern.yml"; + BRIEF_PATTERN_FILE = pkg_path + "/support_files/brief_pattern.yml"; cout << "BRIEF_PATTERN_FILE" << BRIEF_PATTERN_FILE << endl; m_camera = camodocal::CameraFactory::instance()->generateCameraFromYamlFile(config_file.c_str()); - fsSettings["image_topic"] >> IMAGE_TOPIC; + fsSettings["image_topic"] >> IMAGE_TOPIC; + if (!fsSettings["image_input_format"].empty()) + fsSettings["image_input_format"] >> IMAGE_INPUT_FORMAT; fsSettings["pose_graph_save_path"] >> POSE_GRAPH_SAVE_PATH; fsSettings["output_path"] >> VINS_RESULT_PATH; fsSettings["save_image"] >> DEBUG_IMAGE; @@ -525,20 +596,26 @@ int main(int argc, char **argv) } fsSettings.release(); - - ros::Subscriber sub_imu_forward = n.subscribe("/vins_estimator/imu_propagate", 2000, imu_forward_callback); - ros::Subscriber sub_vio = n.subscribe("/vins_estimator/odometry", 2000, vio_callback); - ros::Subscriber sub_image = n.subscribe(IMAGE_TOPIC, 2000, image_callback); - ros::Subscriber sub_pose = n.subscribe("/vins_estimator/keyframe_pose", 2000, pose_callback); - ros::Subscriber sub_extrinsic = n.subscribe("/vins_estimator/extrinsic", 2000, extrinsic_callback); - ros::Subscriber sub_point = n.subscribe("/vins_estimator/keyframe_point", 2000, point_callback); - ros::Subscriber sub_relo_relative_pose = n.subscribe("/vins_estimator/relo_relative_pose", 2000, relo_relative_pose_callback); - - pub_match_img = n.advertise("match_image", 1000); - pub_camera_pose_visual = n.advertise("camera_pose_visual", 1000); - pub_key_odometrys = n.advertise("key_odometrys", 1000); - pub_vio_path = n.advertise("no_loop_path", 1000); - pub_match_points = n.advertise("match_points", 100); + RCLCPP_INFO(node->get_logger(), "[START] pose_graph image_input_format=%s image_topic=%s", IMAGE_INPUT_FORMAT.c_str(), IMAGE_TOPIC.c_str()); + + auto sub_imu_forward = node->create_subscription("/vins_estimator/imu_propagate", 2000, imu_forward_callback); + auto sub_vio = node->create_subscription("/vins_estimator/odometry", 2000, vio_callback); + auto sub_image = image_transport::create_subscription( + node.get(), IMAGE_TOPIC, image_callback, IMAGE_INPUT_FORMAT); + RCLCPP_INFO( + node->get_logger(), + "[START] pose_graph image_transport subscription created for base_topic=%s transport=%s", + IMAGE_TOPIC.c_str(), IMAGE_INPUT_FORMAT.c_str()); + auto sub_pose = node->create_subscription("/vins_estimator/keyframe_pose", 2000, pose_callback); + auto sub_extrinsic = node->create_subscription("/vins_estimator/extrinsic", 2000, extrinsic_callback); + auto sub_point = node->create_subscription("/vins_estimator/keyframe_point", 2000, point_callback); + auto sub_relo_relative_pose = node->create_subscription("/vins_estimator/relo_relative_pose", 2000, relo_relative_pose_callback); + + pub_match_img = node->create_publisher("/pose_graph/match_image", 1000); + pub_camera_pose_visual = node->create_publisher("/pose_graph/camera_pose_visual", 1000); + pub_key_odometrys = node->create_publisher("/pose_graph/key_odometrys", 1000); + pub_vio_path = node->create_publisher("/pose_graph/no_loop_path", 1000); + pub_match_points = node->create_publisher("/pose_graph/match_points", 100); std::thread measurement_process; std::thread keyboard_command_process; @@ -547,7 +624,8 @@ int main(int argc, char **argv) keyboard_command_process = std::thread(command); - ros::spin(); + rclcpp::spin(node); + rclcpp::shutdown(); return 0; } diff --git a/pose_graph/src/utility/CameraPoseVisualization.cpp b/pose_graph/src/utility/CameraPoseVisualization.cpp index af037d1b8..3f8852ad0 100644 --- a/pose_graph/src/utility/CameraPoseVisualization.cpp +++ b/pose_graph/src/utility/CameraPoseVisualization.cpp @@ -9,7 +9,7 @@ const Eigen::Vector3d CameraPoseVisualization::lt1 = Eigen::Vector3d(-0.7, -0.2, const Eigen::Vector3d CameraPoseVisualization::lt2 = Eigen::Vector3d(-1.0, -0.2, 1.0); const Eigen::Vector3d CameraPoseVisualization::oc = Eigen::Vector3d(0.0, 0.0, 0.0); -void Eigen2Point(const Eigen::Vector3d& v, geometry_msgs::Point& p) { +void Eigen2Point(const Eigen::Vector3d& v, geometry_msgs::msg::Point& p) { p.x = v.x(); p.y = v.y(); p.z = v.z(); @@ -50,18 +50,18 @@ void CameraPoseVisualization::setLineWidth(double width) { m_line_width = width; } void CameraPoseVisualization::add_edge(const Eigen::Vector3d& p0, const Eigen::Vector3d& p1){ - visualization_msgs::Marker marker; + visualization_msgs::msg::Marker marker; marker.ns = m_marker_ns; marker.id = m_markers.size() + 1; - marker.type = visualization_msgs::Marker::LINE_LIST; - marker.action = visualization_msgs::Marker::ADD; + marker.type = visualization_msgs::msg::Marker::LINE_LIST; + marker.action = visualization_msgs::msg::Marker::ADD; marker.scale.x = 0.01; marker.color.b = 1.0f; marker.color.a = 1.0; - geometry_msgs::Point point0, point1; + geometry_msgs::msg::Point point0, point1; Eigen2Point(p0, point0); Eigen2Point(p1, point1); @@ -74,16 +74,16 @@ void CameraPoseVisualization::add_edge(const Eigen::Vector3d& p0, const Eigen::V void CameraPoseVisualization::add_loopedge(const Eigen::Vector3d& p0, const Eigen::Vector3d& p1){ //m_markers.clear(); - visualization_msgs::Marker marker; + visualization_msgs::msg::Marker marker; marker.ns = m_marker_ns; marker.id = m_markers.size() + 1; //tmp_loop_edge_num++; //if(tmp_loop_edge_num >= LOOP_EDGE_NUM) // tmp_loop_edge_num = 1; - marker.type = visualization_msgs::Marker::LINE_STRIP; - marker.action = visualization_msgs::Marker::ADD; - marker.lifetime = ros::Duration(); + marker.type = visualization_msgs::msg::Marker::LINE_STRIP; + marker.action = visualization_msgs::msg::Marker::ADD; + marker.lifetime = builtin_interfaces::msg::Duration(); //marker.scale.x = 0.4; marker.scale.x = 0.02; marker.color.r = 1.0f; @@ -91,7 +91,7 @@ void CameraPoseVisualization::add_loopedge(const Eigen::Vector3d& p0, const Eige //marker.color.b = 1.0f; marker.color.a = 1.0; - geometry_msgs::Point point0, point1; + geometry_msgs::msg::Point point0, point1; Eigen2Point(p0, point0); Eigen2Point(p1, point1); @@ -104,12 +104,12 @@ void CameraPoseVisualization::add_loopedge(const Eigen::Vector3d& p0, const Eige void CameraPoseVisualization::add_pose(const Eigen::Vector3d& p, const Eigen::Quaterniond& q) { - visualization_msgs::Marker marker; + visualization_msgs::msg::Marker marker; marker.ns = m_marker_ns; marker.id = 0; - marker.type = visualization_msgs::Marker::LINE_STRIP; - marker.action = visualization_msgs::Marker::ADD; + marker.type = visualization_msgs::msg::Marker::LINE_STRIP; + marker.action = visualization_msgs::msg::Marker::ADD; marker.scale.x = m_line_width; marker.pose.position.x = 0.0; @@ -121,7 +121,7 @@ void CameraPoseVisualization::add_pose(const Eigen::Vector3d& p, const Eigen::Qu marker.pose.orientation.z = 0.0; - geometry_msgs::Point pt_lt, pt_lb, pt_rt, pt_rb, pt_oc, pt_lt0, pt_lt1, pt_lt2; + geometry_msgs::msg::Point pt_lt, pt_lb, pt_rt, pt_rb, pt_oc, pt_lt0, pt_lt1, pt_lt2; Eigen2Point(q * (m_scale *imlt) + p, pt_lt); Eigen2Point(q * (m_scale *imlb) + p, pt_lb); @@ -195,8 +195,8 @@ void CameraPoseVisualization::reset() { //image.colors.clear(); } -void CameraPoseVisualization::publish_by( ros::Publisher &pub, const std_msgs::Header &header ) { - visualization_msgs::MarkerArray markerArray_msg; +void CameraPoseVisualization::publish_by(rclcpp::Publisher::SharedPtr pub, const std_msgs::msg::Header &header) { + visualization_msgs::msg::MarkerArray markerArray_msg; //int k = (int)m_markers.size(); /* for (int i = 0; i < 5 && k > 0; i++) @@ -213,13 +213,13 @@ void CameraPoseVisualization::publish_by( ros::Publisher &pub, const std_msgs::H markerArray_msg.markers.push_back(marker); } - pub.publish(markerArray_msg); + pub->publish(markerArray_msg); } -void CameraPoseVisualization::publish_image_by( ros::Publisher &pub, const std_msgs::Header &header ) { +void CameraPoseVisualization::publish_image_by(rclcpp::Publisher::SharedPtr pub, const std_msgs::msg::Header &header) { image.header = header; - pub.publish(image); + pub->publish(image); } /* void CameraPoseVisualization::add_image(const Eigen::Vector3d& T, const Eigen::Matrix3d& R, const cv::Mat &src) diff --git a/pose_graph/src/utility/CameraPoseVisualization.h b/pose_graph/src/utility/CameraPoseVisualization.h index 3e777d007..a4d651b2b 100644 --- a/pose_graph/src/utility/CameraPoseVisualization.h +++ b/pose_graph/src/utility/CameraPoseVisualization.h @@ -1,9 +1,12 @@ #pragma once -#include -#include -#include -#include +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/color_rgba.hpp" +#include "visualization_msgs/msg/marker.hpp" +#include "visualization_msgs/msg/marker_array.hpp" +#include "sensor_msgs/msg/image.hpp" +#include "std_msgs/msg/header.hpp" +#include "builtin_interfaces/msg/duration.hpp" #include #include #include @@ -23,18 +26,18 @@ class CameraPoseVisualization { void add_pose(const Eigen::Vector3d& p, const Eigen::Quaterniond& q); void reset(); - void publish_by(ros::Publisher& pub, const std_msgs::Header& header); + void publish_by(rclcpp::Publisher::SharedPtr pub, const std_msgs::msg::Header& header); void add_edge(const Eigen::Vector3d& p0, const Eigen::Vector3d& p1); void add_loopedge(const Eigen::Vector3d& p0, const Eigen::Vector3d& p1); //void add_image(const Eigen::Vector3d& T, const Eigen::Matrix3d& R, const cv::Mat &src); - void publish_image_by( ros::Publisher &pub, const std_msgs::Header &header); + void publish_image_by(rclcpp::Publisher::SharedPtr pub, const std_msgs::msg::Header &header); private: - std::vector m_markers; - std_msgs::ColorRGBA m_image_boundary_color; - std_msgs::ColorRGBA m_optical_center_connector_color; + std::vector m_markers; + std_msgs::msg::ColorRGBA m_image_boundary_color; + std_msgs::msg::ColorRGBA m_optical_center_connector_color; double m_scale; double m_line_width; - visualization_msgs::Marker image; + visualization_msgs::msg::Marker image; int LOOP_EDGE_NUM; int tmp_loop_edge_num; diff --git a/vins_autotune/config/autotune_params.yaml b/vins_autotune/config/autotune_params.yaml new file mode 100644 index 000000000..5cdd27a2b --- /dev/null +++ b/vins_autotune/config/autotune_params.yaml @@ -0,0 +1,109 @@ +# vins_autotune configuration +# This file defines the tuning search space and node behavior. + +# Topics +vins_odom_topic: "/vins_estimator/odometry" +ground_truth_topic: "/ground_truth/odom" # ← укажи свой топик ground truth + +# Mode: "monitor" (только метрики) или "tune" (автоподбор + правка YAML) +mode: "monitor" + +# Path to VINS config file (required for tune mode) +# config_file: "/home/jetson/ros2_ws/src/VINS-Mono-inno/config/fisheye_bag/fisheye_bag_config.yaml" + +# Evaluation window per trial (seconds) +eval_window: 15.0 + +# Max coordinate-descent passes +max_passes: 3 + +# Trajectory buffer length (seconds) +buffer_window: 30.0 + +# Max time difference for VINS↔GT synchronization (seconds) +sync_dt: 0.05 + +# Metrics publish rate (Hz) +metrics_rate: 1.0 + +# Sim3-align trajectories before computing ATE +align_trajectories: true + +# Publish TF for RViz visualization +publish_tf: true + +# ─── Parameter search space ─── +# Each parameter: [current, min, max, step, dtype] +# dtype: "float" or "int" +parameters: + max_cnt: + value: 450 + min: 250 + max: 600 + step: 50 + dtype: "int" + description: "Target number of tracked features" + + min_dist: + value: 15 + min: 10 + max: 25 + step: 3 + dtype: "int" + description: "Minimum distance between features (px)" + + F_threshold: + value: 2.0 + min: 1.0 + max: 3.5 + step: 0.5 + dtype: "float" + description: "RANSAC fundamental matrix threshold" + + fast_threshold: + value: 15 + min: 7 + max: 45 + step: 4 + dtype: "int" + description: "AGAST detector threshold" + + keyframe_parallax: + value: 10.0 + min: 5.0 + max: 20.0 + step: 2.5 + dtype: "float" + description: "Keyframe selection parallax threshold (px)" + + acc_n: + value: 0.35 + min: 0.1 + max: 1.0 + step: 0.1 + dtype: "float" + description: "Accelerometer noise std (m/s^2/sqrt(Hz))" + + gyr_n: + value: 0.015 + min: 0.005 + max: 0.05 + step: 0.005 + dtype: "float" + description: "Gyroscope noise std (rad/s/sqrt(Hz))" + + acc_w: + value: 0.003 + min: 0.001 + max: 0.01 + step: 0.001 + dtype: "float" + description: "Accelerometer bias random walk" + + gyr_w: + value: 0.001 + min: 0.0003 + max: 0.003 + step: 0.0003 + dtype: "float" + description: "Gyroscope bias random walk" \ No newline at end of file diff --git a/vins_autotune/launch/autotune.launch.py b/vins_autotune/launch/autotune.launch.py new file mode 100644 index 000000000..4d610caad --- /dev/null +++ b/vins_autotune/launch/autotune.launch.py @@ -0,0 +1,46 @@ +"""Launch file for vins_autotune node. + +Run in monitor mode (default) or tune mode. + +Usage: + ros2 launch vins_autotune autotune.launch.py + ros2 launch vins_autotune autotune.launch.py mode:=tune config_file:=/path/to/config.yaml + ros2 launch vins_autotune autotune.launch.py ground_truth_topic:=/lio/odom +""" +import os + +from launch import LaunchDescription +from launch.actions import DeclareLaunchArgument +from launch.substitutions import LaunchConfiguration +from launch_ros.actions import Node +from ament_index_python.packages import get_package_share_directory + + +def generate_launch_description(): + share_dir = get_package_share_directory("vins_autotune") + default_params = os.path.join(share_dir, "config", "autotune_params.yaml") + + return LaunchDescription([ + DeclareLaunchArgument("params_file", default_value=default_params), + DeclareLaunchArgument("mode", default_value="monitor"), + DeclareLaunchArgument("config_file", default_value=""), + DeclareLaunchArgument("ground_truth_topic", default_value="/ground_truth/odom"), + DeclareLaunchArgument("vins_odom_topic", default_value="/vins_estimator/odometry"), + DeclareLaunchArgument("eval_window", default_value="15.0"), + DeclareLaunchArgument("max_passes", default_value="3"), + + Node( + package="vins_autotune", + executable="vins_autotune_node", + name="vins_autotune", + output="screen", + parameters=[ + {"vins_odom_topic": LaunchConfiguration("vins_odom_topic")}, + {"ground_truth_topic": LaunchConfiguration("ground_truth_topic")}, + {"mode": LaunchConfiguration("mode")}, + {"config_file": LaunchConfiguration("config_file")}, + {"eval_window": LaunchConfiguration("eval_window")}, + {"max_passes": LaunchConfiguration("max_passes")}, + ], + ), + ]) \ No newline at end of file diff --git a/vins_autotune/package.xml b/vins_autotune/package.xml new file mode 100644 index 000000000..e622383fe --- /dev/null +++ b/vins_autotune/package.xml @@ -0,0 +1,30 @@ + + + vins_autotune + 0.1.0 + Online parameter auto-tuning for VINS-Mono using ground truth odometry + innospector + GPLv3 + + ament_python + + rclpy + nav_msgs + geometry_msgs + std_msgs + visualization_msgs + tf2_ros + tf2_geometry_msgs + numpy + pyyaml + matplotlib + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + + + ament_python + + \ No newline at end of file diff --git a/vins_autotune/pytest.ini b/vins_autotune/pytest.ini new file mode 100644 index 000000000..ab8c6667f --- /dev/null +++ b/vins_autotune/pytest.ini @@ -0,0 +1,8 @@ + + + + + 120 + + + \ No newline at end of file diff --git a/config/realsense/realsense_zr300 b/vins_autotune/resource/vins_autotune similarity index 100% rename from config/realsense/realsense_zr300 rename to vins_autotune/resource/vins_autotune diff --git a/vins_autotune/setup.cfg b/vins_autotune/setup.cfg new file mode 100644 index 000000000..6376c2131 --- /dev/null +++ b/vins_autotune/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/vins_autotune +[install] +install_scripts=$base/lib/vins_autotune \ No newline at end of file diff --git a/vins_autotune/setup.py b/vins_autotune/setup.py new file mode 100644 index 000000000..0078825f8 --- /dev/null +++ b/vins_autotune/setup.py @@ -0,0 +1,29 @@ +from setuptools import setup + +package_name = "vins_autotune" + +setup( + name=package_name, + version="0.1.0", + packages=[package_name], + data_files=[ + ("share/ament_index/resource_index/packages", + ["resource/" + package_name]), + ("share/" + package_name, ["package.xml"]), + ("share/" + package_name + "/launch", ["launch/autotune.launch.py"]), + ("share/" + package_name + "/config", ["config/autotune_params.yaml"]), + ], + install_requires=["setuptools"], + zip_safe=True, + maintainer="innospector", + maintainer_email="user@innospector.local", + description="Online parameter auto-tuning for VINS-Mono using ground truth odometry", + license="GPLv3", + tests_require=["pytest"], + entry_points={ + "console_scripts": [ + "vins_autotune_node = vins_autotune.autotune_node:main", + "vins_autotune_offline = vins_autotune.offline_tuner:main", + ], + }, +) \ No newline at end of file diff --git a/vins_autotune/vins_autotune/__init__.py b/vins_autotune/vins_autotune/__init__.py new file mode 100644 index 000000000..6ea830292 --- /dev/null +++ b/vins_autotune/vins_autotune/__init__.py @@ -0,0 +1,6 @@ +"""vins_autotune: Online/offline parameter auto-tuning for VINS-Mono. + +Compares VINS odometry against ground truth and optimizes tracker/estimator +parameters using coordinate descent on ATE/RPE metrics. +""" +__version__ = "0.1.0" \ No newline at end of file diff --git a/vins_autotune/vins_autotune/autotune_node.py b/vins_autotune/vins_autotune/autotune_node.py new file mode 100644 index 000000000..b486acf57 --- /dev/null +++ b/vins_autotune/vins_autotune/autotune_node.py @@ -0,0 +1,296 @@ +"""ROS2 node for online VINS parameter auto-tuning using ground truth odometry. + +Subscribes to: + - /vins_estimator/odometry (nav_msgs/Odometry) — VINS output + - (nav_msgs/Odometry) — reference trajectory + +Publishes: + - /vins_autotune/metrics (std_msgs/Float64MultiArray) — ATE/RPE/scale + - /vins_autotune/recommendations (std_msgs/String) — JSON recommendations + - /vins_autotune/status (std_msgs/String) — human-readable status + +The node can operate in two modes: + 1. MONITOR mode (default): only computes and publishes metrics, no config changes. + 2. TUNE mode: runs coordinate-descent optimization, modifies the YAML config + file in-place, and signals the user to restart VINS for each trial. +""" +from __future__ import annotations + +import json +import math +import threading +import time +import logging +from typing import Dict, List, Optional + +import numpy as np +import rclpy +from rclpy.node import Node +from rclpy.qos import QoSProfile, ReliabilityPolicy +from nav_msgs.msg import Odometry +from std_msgs.msg import Float64MultiArray, String +from geometry_msgs.msg import TransformStamped +import tf2_ros + +from .metrics import PoseSample, MetricsResult, compute_metrics +from .trajectory_buffer import TrajectoryBuffer +from .config_editor import ConfigEditor, ParamSpec +from .tuner import CoordinateDescentTuner, TrialResult, default_cost + +_logger = logging.getLogger("vins_autotune") + + +def _odom_to_sample(msg: Odometry) -> PoseSample: + t = msg.header.stamp.sec + msg.header.stamp.nanosec * 1e-9 + p = msg.pose.pose.position + q = msg.pose.pose.orientation + return PoseSample( + timestamp=t, + pos=np.array([p.x, p.y, p.z]), + quat=np.array([q.x, q.y, q.z, q.w]), + ) + + +# Default parameter search space — tuned for fisheye_bag config +DEFAULT_PARAM_SPECS: Dict[str, ParamSpec] = { + "max_cnt": ParamSpec("max_cnt", 450, 250, 600, 50, "feature count target", "int"), + "min_dist": ParamSpec("min_dist", 15, 10, 25, 3, "feature min distance px", "int"), + "F_threshold": ParamSpec("F_threshold", 2.0, 1.0, 3.5, 0.5, "RANSAC F threshold", "float"), + "fast_threshold": ParamSpec("fast_threshold", 15, 7, 45, 4, "AGAST threshold", "int"), + "keyframe_parallax": ParamSpec("keyframe_parallax", 10.0, 5.0, 20.0, 2.5, "keyframe parallax px", "float"), + "acc_n": ParamSpec("acc_n", 0.35, 0.1, 1.0, 0.1, "accel noise std", "float"), + "gyr_n": ParamSpec("gyr_n", 0.015, 0.005, 0.05, 0.005, "gyro noise std", "float"), + "acc_w": ParamSpec("acc_w", 0.003, 0.001, 0.01, 0.001, "accel bias random walk", "float"), + "gyr_w": ParamSpec("gyr_w", 0.001, 0.0003, 0.003, 0.0003, "gyro bias random walk", "float"), +} + + +class VinsAutotuneNode(Node): + def __init__(self): + super().__init__("vins_autotune") + + # --- Parameters --- + self.declare_parameter("vins_odom_topic", "/vins_estimator/odometry") + self.declare_parameter("ground_truth_topic", "/ground_truth/odom") + self.declare_parameter("config_file", "") + self.declare_parameter("mode", "monitor") # "monitor" or "tune" + self.declare_parameter("eval_window", 15.0) + self.declare_parameter("max_passes", 3) + self.declare_parameter("buffer_window", 30.0) + self.declare_parameter("sync_dt", 0.05) + self.declare_parameter("metrics_rate", 1.0) + self.declare_parameter("publish_tf", True) + self.declare_parameter("align_trajectories", True) + + self.vins_topic = self.get_parameter("vins_odom_topic").value + self.gt_topic = self.get_parameter("ground_truth_topic").value + self.config_file = self.get_parameter("config_file").value + self.mode = self.get_parameter("mode").value + self.eval_window = float(self.get_parameter("eval_window").value) + self.max_passes = int(self.get_parameter("max_passes").value) + self.align = bool(self.get_parameter("align_trajectories").value) + + # --- Trajectory buffer --- + self.buffer = TrajectoryBuffer( + window_sec=float(self.get_parameter("buffer_window").value), + max_sync_dt=float(self.get_parameter("sync_dt").value), + ) + + # --- Latest metrics --- + self._latest_metrics: Optional[MetricsResult] = None + self._metrics_lock = threading.Lock() + + # --- Publishers --- + self.pub_metrics = self.create_publisher(Float64MultiArray, "/vins_autotune/metrics", 10) + self.pub_recommendations = self.create_publisher(String, "/vins_autotune/recommendations", 10) + self.pub_status = self.create_publisher(String, "/vins_autotune/status", 10) + self.pub_gt_path = self.create_publisher(String, "/vins_autotune/status", 10) + + if bool(self.get_parameter("publish_tf").value): + self.tf_broadcaster = tf2_ros.TransformBroadcaster(self) + else: + self.tf_broadcaster = None + + # --- Subscribers --- + qos = QoSProfile(depth=200, reliability=ReliabilityPolicy.BEST_EFFORT) + self.create_subscription(Odometry, self.vins_topic, self._vins_cb, qos) + self.create_subscription(Odometry, self.gt_topic, self._gt_cb, qos) + + # --- Metrics timer --- + rate = float(self.get_parameter("metrics_rate").value) + self.create_timer(1.0 / max(rate, 0.1), self._publish_metrics) + + # --- Tuner (optional) --- + self._tuner: Optional[CoordinateDescentTuner] = None + self._tune_thread: Optional[threading.Thread] = None + if self.mode == "tune": + self._start_tuner() + + self.get_logger().info( + f"vins_autotune started: mode={self.mode}, vins={self.vins_topic}, " + f"gt={self.gt_topic}, config={self.config_file or '(none)'}" + ) + self._publish_status(f"Node started in {self.mode} mode") + + # ---------- Callbacks ---------- + + def _vins_cb(self, msg: Odometry) -> None: + self.buffer.add_vins(_odom_to_sample(msg)) + + def _gt_cb(self, msg: Odometry) -> None: + self.buffer.add_gt(_odom_to_sample(msg)) + + # ---------- Metrics ---------- + + def _compute_current_metrics(self) -> Optional[MetricsResult]: + vins, gt = self.buffer.get_synchronized_pairs() + if len(vins) < 10: + return None + return compute_metrics(vins, gt, align=self.align) + + def _publish_metrics(self) -> None: + metrics = self._compute_current_metrics() + if metrics is None: + return + with self._metrics_lock: + self._latest_metrics = metrics + + msg = Float64MultiArray() + msg.data = [ + metrics.ate_trans, + metrics.ate_rot, + metrics.rpe_trans, + metrics.rpe_rot, + metrics.scale_drift, + float(metrics.n_samples), + metrics.vins_path_len, + metrics.gt_path_len, + ] + self.pub_metrics.publish(msg) + + # Broadcast TF: vins_aligned → ground_truth frame for RViz visualization + if self.tf_broadcaster and metrics.n_samples > 0: + self._broadcast_tf(metrics) + + def _broadcast_tf(self, metrics: MetricsResult) -> None: + """Publish a TF from 'map' to 'vins_aligned' showing current ATE offset.""" + tf = TransformStamped() + tf.header.stamp = self.get_clock().now().to_msg() + tf.header.frame_id = "map" + tf.child_frame_id = "vins_aligned" + # Use the last VINS position as a simple visualization + vins, _ = self.buffer.get_all() + if vins: + tf.transform.translation.x = float(vins[-1].pos[0]) + tf.transform.translation.y = float(vins[-1].pos[1]) + tf.transform.translation.z = float(vins[-1].pos[2]) + tf.transform.rotation.w = 1.0 + self.tf_broadcaster.sendTransform(tf) + + def _publish_status(self, text: str) -> None: + msg = String() + msg.data = text + self.pub_status.publish(msg) + self.get_logger().info(text) + + def _publish_recommendation(self, rec: dict) -> None: + msg = String() + msg.data = json.dumps(rec) + self.pub_recommendations.publish(msg) + + # ---------- Tuner ---------- + + def _start_tuner(self) -> None: + if not self.config_file: + self.get_logger().error("TUNE mode requires 'config_file' parameter") + return + + editor = ConfigEditor(self.config_file) + # Load current values from config into specs + specs = {} + for key, spec in DEFAULT_PARAM_SPECS.items(): + val = editor.read_value(key) + if val is not None: + spec.value = val + specs[key] = spec + + def eval_fn(trial_values: Dict[str, float]) -> MetricsResult: + """Evaluate the current running VINS over eval_window seconds.""" + self._publish_status( + f"Evaluating trial: {trial_values} — waiting {self.eval_window}s" + ) + # Clear buffer to get fresh data for this trial + self.buffer.clear() + # Wait for VINS to produce data with the new config + # (User must restart VINS manually, or we wait for new samples) + start = time.time() + while time.time() - start < self.eval_window: + time.sleep(0.5) + if self.buffer.vins_count > 0 and self.buffer.gt_count > 0: + # Wait a bit more to fill the window + pass + metrics = self._compute_current_metrics() + if metrics is None: + self.get_logger().warn("No synchronized data during eval window") + return MetricsResult() + return metrics + + self._tuner = CoordinateDescentTuner( + config_editor=editor, + params=specs, + eval_fn=eval_fn, + eval_window=self.eval_window, + max_passes=self.max_passes, + cost_fn=default_cost, + ) + + def on_trial(tr: TrialResult) -> None: + rec = { + "param": tr.param_key, + "value": tr.tried_value, + "cost": tr.cost, + "ate_trans": tr.metrics.ate_trans, + "ate_rot": tr.metrics.ate_rot, + "scale_drift": tr.metrics.scale_drift, + "accepted": tr.accepted, + "timestamp": time.time(), + } + self._publish_recommendation(rec) + + def run_thread(): + try: + self._publish_status("Starting coordinate-descent tuning") + best = self._tuner.run(on_trial=on_trial) + self._publish_status(f"Tuning complete. Best params: {best}") + self._publish_recommendation({"final_params": best, "done": True}) + except Exception as e: + self.get_logger().error(f"Tuner failed: {e}") + self._publish_status(f"Tuner error: {e}") + + self._tune_thread = threading.Thread(target=run_thread, daemon=True) + self._tune_thread.start() + + # ---------- API for external access ---------- + + def get_latest_metrics(self) -> Optional[MetricsResult]: + with self._metrics_lock: + return self._latest_metrics + + +def main(args=None): + rclpy.init(args=args) + node = VinsAutotuneNode() + try: + rclpy.spin(node) + except (KeyboardInterrupt, rclpy.executors.ExternalShutdownException): + pass + finally: + try: + node.destroy_node() + rclpy.shutdown() + except Exception: + pass + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/vins_autotune/vins_autotune/config_editor.py b/vins_autotune/vins_autotune/config_editor.py new file mode 100644 index 000000000..5e783003e --- /dev/null +++ b/vins_autotune/vins_autotune/config_editor.py @@ -0,0 +1,114 @@ +"""Read and modify VINS YAML config files for auto-tuning. + +Preserves comments and formatting by doing targeted line replacements +rather than full YAML round-trip. +""" +from __future__ import annotations + +import os +import re +import shutil +from dataclasses import dataclass +from typing import Any, Dict, List, Optional + + +@dataclass +class ParamSpec: + key: str + value: Any + min: Any + max: Any + step: Any + description: str = "" + dtype: str = "float" # "float", "int", "bool" + + def clamp(self, value: Any) -> Any: + if self.dtype == "float": + return float(max(self.min, min(self.max, value))) + if self.dtype == "int": + return int(max(self.min, min(self.max, round(value)))) + if self.dtype == "bool": + return 1 if value else 0 + return value + + +class ConfigEditor: + """Edit a VINS YAML config by replacing scalar values in-place. + + Keeps a backup of the original config on first modification. + """ + + def __init__(self, config_path: str): + self.config_path = os.path.expanduser(config_path) + self._backup_path = self.config_path + ".autotune_backup" + self._backed_up = False + + def backup(self) -> None: + if not self._backed_up and os.path.exists(self.config_path): + shutil.copy2(self.config_path, self._backup_path) + self._backed_up = True + + def restore(self) -> None: + if os.path.exists(self._backup_path): + shutil.copy2(self._backup_path, self.config_path) + + def read_value(self, key: str) -> Optional[Any]: + """Read a scalar value for `key` from the YAML file.""" + pattern = re.compile(rf"^\s*{re.escape(key)}\s*:\s*(.+?)(?:\s+#.*)?$") + with open(self.config_path, "r") as f: + for line in f: + m = pattern.match(line) + if m: + raw = m.group(1).strip() + try: + if "." in raw or "e" in raw.lower(): + return float(raw) + return int(raw) + except ValueError: + return raw + return None + + def set_value(self, key: str, value: Any) -> bool: + """Replace the scalar value of `key` in-place, preserving comments.""" + self.backup() + pattern = re.compile(rf"^(\s*{re.escape(key)}\s*:\s*)(.+?)(\s+#.*)?$") + new_lines: List[str] = [] + replaced = False + with open(self.config_path, "r") as f: + for line in f: + m = pattern.match(line) + if m and not replaced: + prefix, _, comment = m.group(1), m.group(2), m.group(3) or "" + new_val = self._format_value(value) + new_lines.append(f"{prefix}{new_val}{comment}\n") + replaced = True + else: + new_lines.append(line) + if replaced: + with open(self.config_path, "w") as f: + f.writelines(new_lines) + return replaced + + @staticmethod + def _format_value(value: Any) -> str: + if isinstance(value, bool): + return "1" if value else "0" + if isinstance(value, float): + if abs(value) < 1.0 and value != 0.0: + return f"{value:.6f}" + return f"{value:.4f}" + if isinstance(value, int): + return str(value) + return str(value) + + def set_many(self, updates: Dict[str, Any]) -> Dict[str, bool]: + """Apply multiple key→value updates. Returns per-key success.""" + results = {} + for key, val in updates.items(): + results[key] = self.set_value(key, val) + return results + + def apply_param_specs(self, specs: Dict[str, ParamSpec]) -> Dict[str, bool]: + """Apply a set of ParamSpec values to the config file.""" + updates = {k: s.clamp(s.value) for k, s in specs.items()} + return self.set_many(updates) \ No newline at end of file diff --git a/vins_autotune/vins_autotune/metrics.py b/vins_autotune/vins_autotune/metrics.py new file mode 100644 index 000000000..d12cde7e8 --- /dev/null +++ b/vins_autotune/vins_autotune/metrics.py @@ -0,0 +1,160 @@ +"""Quality metrics for VINS odometry evaluation against ground truth. + +Computes Absolute Trajectory Error (ATE), Relative Pose Error (RPE), +scale drift and rotation drift on time-synchronized pose pairs. +""" +from __future__ import annotations + +import numpy as np +from dataclasses import dataclass, field +from typing import List, Tuple, Optional + + +@dataclass +class PoseSample: + timestamp: float + pos: np.ndarray # (3,) translation + quat: np.ndarray # (4,) [x, y, z, w] quaternion + + +@dataclass +class MetricsResult: + ate_trans: float = 0.0 # RMSE of translation ATE (meters) + ate_rot: float = 0.0 # RMSE of rotation ATE (degrees) + rpe_trans: float = 0.0 # RMSE relative translation error (meters) + rpe_rot: float = 0.0 # RMSE relative rotation error (degrees) + scale_drift: float = 1.0 # ratio gt_dist / vins_dist + n_samples: int = 0 + vins_path_len: float = 0.0 + gt_path_len: float = 0.0 + extra: dict = field(default_factory=dict) + + def as_dict(self) -> dict: + return { + "ate_trans": self.ate_trans, + "ate_rot": self.ate_rot, + "rpe_trans": self.rpe_trans, + "rpe_rot": self.rpe_rot, + "scale_drift": self.scale_drift, + "n_samples": self.n_samples, + "vins_path_len": self.vins_path_len, + "gt_path_len": self.gt_path_len, + } + + +def quat_to_rotmat(q: np.ndarray) -> np.ndarray: + """Convert [x, y, z, w] quaternion to 3x3 rotation matrix.""" + x, y, z, w = q + n = np.sqrt(x * x + y * y + z * z + w * w) + if n < 1e-12: + return np.eye(3) + x, y, z, w = x / n, y / n, z / n, w / n + return np.array([ + [1 - 2 * (y * y + z * z), 2 * (x * y - z * w), 2 * (x * z + y * w)], + [2 * (x * y + z * w), 1 - 2 * (x * x + z * z), 2 * (y * z - x * w)], + [2 * (x * z - y * w), 2 * (y * z + x * w), 1 - 2 * (x * x + y * y)], + ]) + + +def rotmat_to_angle(R: np.ndarray) -> float: + """Rotation angle (radians) from a rotation matrix.""" + c = np.clip((np.trace(R) - 1.0) / 2.0, -1.0, 1.0) + return np.arccos(c) + + +def umeyama_alignment(model: np.ndarray, target: np.ndarray, + with_scale: bool = True) -> Tuple[float, np.ndarray, np.ndarray]: + """Compute Sim3 (s, R, t) that aligns `model` points to `target` points. + + Returns (scale, rotation 3x3, translation 3). + Uses the standard Umeyama method. + """ + assert model.shape == target.shape + n = model.shape[0] + mu_m = model.mean(axis=0) + mu_t = target.mean(axis=0) + model_c = model - mu_m + target_c = target - mu_t + + # Cross-covariance + H = model_c.T @ target_c / n + U, S, Vt = np.linalg.svd(H) + d = np.linalg.det(Vt.T @ U.T) + D = np.diag([1.0, 1.0, d]) + R = Vt.T @ D @ U.T + + var_m = np.sum(model_c ** 2) / n + if with_scale: + scale = np.trace(np.diag(S) @ D) / var_m + else: + scale = 1.0 + + t = mu_t - scale * R @ mu_m + return scale, R, t + + +def path_length(positions: np.ndarray) -> float: + """Total path length from an (N, 3) array of positions.""" + if positions.shape[0] < 2: + return 0.0 + diffs = np.diff(positions, axis=0) + return float(np.sum(np.linalg.norm(diffs, axis=1))) + + +def compute_metrics(vins: List[PoseSample], gt: List[PoseSample], + align: bool = True) -> MetricsResult: + """Compute ATE/RPE/scale between two synchronized pose trajectories. + + The lists must already be time-synchronized (same length, same order). + If `align` is True, a Sim3 Umeyama alignment is computed on the full + trajectory and applied to VINS before error computation. + """ + res = MetricsResult() + n = min(len(vins), len(gt)) + if n < 2: + return res + + vins_pos = np.array([s.pos for s in vins[:n]]) + gt_pos = np.array([s.pos for s in gt[:n]]) + vins_rot = [quat_to_rotmat(s.quat) for s in vins[:n]] + gt_rot = [quat_to_rotmat(s.quat) for s in gt[:n]] + + res.vins_path_len = path_length(vins_pos) + res.gt_path_len = path_length(gt_pos) + + # Scale + alignment + if align: + scale, R_align, t_align = umeyama_alignment(vins_pos, gt_pos, with_scale=True) + res.scale_drift = scale + vins_pos_aligned = (scale * (R_align @ vins_pos.T)).T + t_align + vins_rot_aligned = [R_align @ R for R in vins_rot] + else: + vins_pos_aligned = vins_pos + vins_rot_aligned = vins_rot + + # ATE translation + trans_err = np.linalg.norm(vins_pos_aligned - gt_pos, axis=1) + res.ate_trans = float(np.sqrt(np.mean(trans_err ** 2))) + + # ATE rotation (degrees) + rot_errs = [] + for R_v, R_g in zip(vins_rot_aligned, gt_rot): + R_err = R_g @ R_v.T + rot_errs.append(np.degrees(rotmat_to_angle(R_err))) + res.ate_rot = float(np.sqrt(np.mean(np.array(rot_errs) ** 2))) + + # RPE — consecutive relative motion + rpe_t = [] + rpe_r = [] + for i in range(1, n): + d_gt_t = gt_pos[i] - gt_pos[i - 1] + d_vins_t = vins_pos_aligned[i] - vins_pos_aligned[i - 1] + rpe_t.append(np.linalg.norm(d_gt_t - d_vins_t)) + d_gt_r = gt_rot[i] @ gt_rot[i - 1].T + d_vins_r = vins_rot_aligned[i] @ vins_rot_aligned[i - 1].T + rpe_r.append(np.degrees(rotmat_to_angle(d_gt_r @ d_vins_r.T))) + res.rpe_trans = float(np.sqrt(np.mean(np.array(rpe_t) ** 2))) + res.rpe_rot = float(np.sqrt(np.mean(np.array(rpe_r) ** 2))) + + res.n_samples = n + return res \ No newline at end of file diff --git a/vins_autotune/vins_autotune/offline_tuner.py b/vins_autotune/vins_autotune/offline_tuner.py new file mode 100644 index 000000000..83884ed86 --- /dev/null +++ b/vins_autotune/vins_autotune/offline_tuner.py @@ -0,0 +1,210 @@ +"""Offline parameter tuner: runs VINS on a rosbag with different configs, +collects ATE against ground truth, and finds the best parameter set. + +Usage: + ros2 run vins_autotune vins_autotune_offline \ + --config config/fisheye_bag/fisheye_bag_config.yaml \ + --bag /path/to/rosbag \ + --gt-topic /ground_truth/odom \ + --vins-topic /vins_estimator/odometry \ + --eval-window 20 \ + --passes 3 +""" +from __future__ import annotations + +import argparse +import json +import os +import subprocess +import sys +import time +import logging +from typing import Dict, List, Optional, Tuple + +import numpy as np + +# Add the package to path when run standalone +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from vins_autotune.config_editor import ConfigEditor, ParamSpec +from vins_autotune.metrics import PoseSample, MetricsResult, compute_metrics +from vins_autotune.tuner import CoordinateDescentTuner, TrialResult, default_cost + +_logger = logging.getLogger("vins_autotune.offline") + + +def play_bag_and_collect( + bag_path: str, + vins_topic: str, + gt_topic: str, + duration: float, + use_sim_time: bool = True, +) -> Tuple[List[PoseSample], List[PoseSample]]: + """Play a rosbag for `duration` seconds and collect odometry messages. + + Uses `ros2 bag play` and subscribes via a temporary rclpy node. + """ + import rclpy + from rclpy.node import Node + from rclpy.qos import QoSProfile, ReliabilityPolicy + from nav_msgs.msg import Odometry + + vins_samples: List[PoseSample] = [] + gt_samples: List[PoseSample] = [] + + class Collector(Node): + def __init__(self): + super().__init__("offline_collector") + qos = QoSProfile(depth=500, reliability=ReliabilityPolicy.BEST_EFFORT) + self.create_subscription(Odometry, vins_topic, self._vins, qos) + self.create_subscription(Odometry, gt_topic, self._gt, qos) + + def _vins(self, msg): + vins_samples.append(_odom_to_sample(msg)) + + def _gt(self, msg): + gt_samples.append(_odom_to_sample(msg)) + + def _odom_to_sample(msg: Odometry) -> PoseSample: + t = msg.header.stamp.sec + msg.header.stamp.nanosec * 1e-9 + p = msg.pose.pose.position + q = msg.pose.pose.orientation + return PoseSample( + timestamp=t, + pos=np.array([p.x, p.y, p.z]), + quat=np.array([q.x, q.y, q.z, q.w]), + ) + + rclpy.init() + node = Collector() + + # Start bag playback in a subprocess + cmd = ["ros2", "bag", "play", bag_path] + if use_sim_time: + cmd.extend(["--clock", "100"]) + _logger.info("Starting bag playback: %s", " ".join(cmd)) + proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE) + + start = time.time() + while time.time() - start < duration and proc.poll() is None: + rclpy.spin_once(node, timeout_sec=0.1) + + proc.terminate() + proc.wait(timeout=5) + node.destroy_node() + rclpy.shutdown() + + _logger.info("Collected %d VINS samples, %d GT samples", + len(vins_samples), len(gt_samples)) + return vins_samples, gt_samples + + +def sync_samples( + vins: List[PoseSample], + gt: List[PoseSample], + max_dt: float = 0.05, +) -> Tuple[List[PoseSample], List[PoseSample]]: + """Time-synchronize two pose lists by nearest timestamp.""" + if not vins or not gt: + return [], [] + gt_times = np.array([s.timestamp for s in gt]) + mv, mg = [], [] + for v in vins: + idx = int(np.argmin(np.abs(gt_times - v.timestamp))) + if abs(gt_times[idx] - v.timestamp) <= max_dt: + mv.append(v) + mg.append(gt[idx]) + return mv, mg + + +def run_offline_tuning(args: argparse.Namespace) -> None: + logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(name)s] %(message)s") + + editor = ConfigEditor(args.config) + editor.backup() + + # Build param specs from defaults, override with current config values + from vins_autotune.autotune_node import DEFAULT_PARAM_SPECS + specs = {} + for key, spec in DEFAULT_PARAM_SPECS.items(): + val = editor.read_value(key) + if val is not None: + spec.value = val + specs[key] = spec + + # Optionally restrict to a subset of parameters + if args.params: + specs = {k: specs[k] for k in args.params if k in specs} + + def eval_fn(trial_values: Dict[str, float]) -> MetricsResult: + _logger.info("Evaluating with params: %s", trial_values) + vins, gt = play_bag_and_collect( + bag_path=args.bag, + vins_topic=args.vins_topic, + gt_topic=args.gt_topic, + duration=args.eval_window, + ) + vins_sync, gt_sync = sync_samples(vins, gt, max_dt=args.sync_dt) + if len(vins_sync) < 10: + _logger.warn("Insufficient synchronized samples (%d)", len(vins_sync)) + return MetricsResult() + return compute_metrics(vins_sync, gt_sync, align=True) + + tuner = CoordinateDescentTuner( + config_editor=editor, + params=specs, + eval_fn=eval_fn, + eval_window=args.eval_window, + max_passes=args.passes, + cost_fn=default_cost, + ) + + history: List[dict] = [] + + def on_trial(tr: TrialResult) -> None: + history.append({ + "param": tr.param_key, + "value": tr.tried_value, + "cost": tr.cost, + "ate_trans": tr.metrics.ate_trans, + "ate_rot": tr.metrics.ate_rot, + "scale_drift": tr.metrics.scale_drift, + "accepted": tr.accepted, + }) + + best = tuner.run(on_trial=on_trial) + + result = {"best_params": best, "history": history} + print("\n=== Tuning Results ===") + print(json.dumps(result, indent=2)) + + if args.output: + with open(args.output, "w") as f: + json.dump(result, f, indent=2) + _logger.info("Results saved to %s", args.output) + + +def main(): + parser = argparse.ArgumentParser(description="Offline VINS parameter auto-tuner") + parser.add_argument("--config", required=True, help="Path to VINS YAML config") + parser.add_argument("--bag", required=True, help="Path to ROS2 bag") + parser.add_argument("--gt-topic", default="/ground_truth/odom", + help="Ground truth odometry topic") + parser.add_argument("--vins-topic", default="/vins_estimator/odometry", + help="VINS odometry topic") + parser.add_argument("--eval-window", type=float, default=20.0, + help="Seconds of bag to evaluate per trial") + parser.add_argument("--passes", type=int, default=3, + help="Max coordinate-descent passes") + parser.add_argument("--sync-dt", type=float, default=0.05, + help="Max sync time difference (s)") + parser.add_argument("--params", nargs="*", default=None, + help="Subset of parameters to tune (default: all)") + parser.add_argument("--output", default=None, + help="Path to save JSON results") + args = parser.parse_args() + run_offline_tuning(args) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/vins_autotune/vins_autotune/trajectory_buffer.py b/vins_autotune/vins_autotune/trajectory_buffer.py new file mode 100644 index 000000000..88e9a5528 --- /dev/null +++ b/vins_autotune/vins_autotune/trajectory_buffer.py @@ -0,0 +1,84 @@ +"""Time-synchronized trajectory buffer for VINS vs ground truth comparison.""" +from __future__ import annotations + +import threading +from collections import deque +from typing import Deque, List, Tuple, Optional + +import numpy as np + +from .metrics import PoseSample + + +class TrajectoryBuffer: + """Thread-safe buffer that stores VINS and ground-truth poses and + provides time-synchronized pairs within a sliding window. + + The window is defined in seconds. Older samples are evicted automatically. + """ + + def __init__(self, window_sec: float = 30.0, max_sync_dt: float = 0.05): + self._lock = threading.Lock() + self._vins: Deque[PoseSample] = deque() + self._gt: Deque[PoseSample] = deque() + self._window_sec = window_sec + self._max_sync_dt = max_sync_dt + + def add_vins(self, sample: PoseSample) -> None: + with self._lock: + self._vins.append(sample) + self._evict(self._vins, sample.timestamp) + + def add_gt(self, sample: PoseSample) -> None: + with self._lock: + self._gt.append(sample) + self._evict(self._gt, sample.timestamp) + + def _evict(self, buf: Deque[PoseSample], cur_time: float) -> None: + threshold = cur_time - self._window_sec + while buf and buf[0].timestamp < threshold: + buf.popleft() + + def get_synchronized_pairs(self) -> Tuple[List[PoseSample], List[PoseSample]]: + """Return time-matched (vins, gt) pose lists. + + For each VINS sample, finds the closest GT sample within max_sync_dt. + """ + with self._lock: + vins_list = list(self._vins) + gt_list = list(self._gt) + + if not vins_list or not gt_list: + return [], [] + + gt_times = np.array([s.timestamp for s in gt_list]) + matched_vins: List[PoseSample] = [] + matched_gt: List[PoseSample] = [] + + for v in vins_list: + idx = int(np.argmin(np.abs(gt_times - v.timestamp))) + dt = abs(gt_times[idx] - v.timestamp) + if dt <= self._max_sync_dt: + matched_vins.append(v) + matched_gt.append(gt_list[idx]) + + return matched_vins, matched_gt + + def get_all(self) -> Tuple[List[PoseSample], List[PoseSample]]: + with self._lock: + return list(self._vins), list(self._gt) + + def clear(self) -> None: + with self._lock: + self._vins.clear() + self._gt.clear() + + @property + def vins_count(self) -> int: + with self._lock: + return len(self._vins) + + @property + def gt_count(self) -> int: + with self._lock: + return len(self._gt) \ No newline at end of file diff --git a/vins_autotune/vins_autotune/tuner.py b/vins_autotune/vins_autotune/tuner.py new file mode 100644 index 000000000..d19d09c5a --- /dev/null +++ b/vins_autotune/vins_autotune/tuner.py @@ -0,0 +1,172 @@ +"""Coordinate-descent parameter tuner. + +Evaluates one parameter at a time: tries a step up and a step down, +keeps the direction that improves the cost (ATE). Falls back gracefully +when no improvement is found. +""" +from __future__ import annotations + +import math +import time +import logging +from dataclasses import dataclass, field +from typing import Callable, Dict, List, Optional, Tuple + +from .config_editor import ConfigEditor, ParamSpec +from .metrics import MetricsResult + +_logger = logging.getLogger("vins_autotune.tuner") + + +@dataclass +class TrialResult: + param_key: str + tried_value: float + cost: float + metrics: MetricsResult + accepted: bool + + +@dataclass +class TunerState: + # Current best cost (lower is better) + best_cost: float = float("inf") + # History of all trials + history: List[TrialResult] = field(default_factory=list) + # Per-parameter convergence flag + converged: Dict[str, bool] = field(default_factory=dict) + # Full pass counter + pass_count: int = 0 + # Whether the tuner has a valid baseline + initialized: bool = False + + +def default_cost(metrics: MetricsResult) -> float: + """Weighted combination of ATE translation and rotation.""" + return metrics.ate_trans + 0.02 * metrics.ate_rot + + +class CoordinateDescentTuner: + """One-at-a-time parameter optimizer. + + Workflow per pass: + 1. For each parameter in order: + a. Try value + step → measure cost over eval_window seconds. + b. Try value - step → measure cost. + c. Keep the better value; revert if neither improves. + 2. Repeat until no parameter improves or max_passes reached. + + The `eval_fn` callback is called with a dict of param→value to apply, + and must return a MetricsResult after running VINS for `eval_window` sec. + """ + + def __init__( + self, + config_editor: ConfigEditor, + params: Dict[str, ParamSpec], + eval_fn: Callable[[Dict[str, float]], MetricsResult], + eval_window: float = 15.0, + max_passes: int = 5, + cost_fn: Callable[[MetricsResult], float] = default_cost, + min_improvement: float = 0.01, + ): + self.editor = config_editor + self.params = dict(params) + self.eval_fn = eval_fn + self.eval_window = eval_window + self.max_passes = max_passes + self.cost_fn = cost_fn + self.min_improvement = min_improvement + self.state = TunerState() + for k in params: + self.state.converged[k] = False + + def _current_values(self) -> Dict[str, float]: + return {k: float(s.value) for k, s in self.params.items()} + + def _apply_and_eval(self, trial_values: Dict[str, float]) -> Tuple[float, MetricsResult]: + self.editor.apply_param_specs( + {k: ParamSpec(key=k, value=v, min=self.params[k].min, + max=self.params[k].max, step=self.params[k].step, + dtype=self.params[k].dtype) + for k, v in trial_values.items()} + ) + # Give the system time to restart / settle with new params + time.sleep(2.0) + metrics = self.eval_fn(trial_values) + cost = self.cost_fn(metrics) + return cost, metrics + + def run(self, on_trial: Optional[Callable[[TrialResult], None]] = None) -> Dict[str, float]: + """Run the full tuning loop. Returns the best parameter values found.""" + # Baseline + if not self.state.initialized: + _logger.info("Tuner: establishing baseline with current config") + base_vals = self._current_values() + cost, metrics = self._apply_and_eval(base_vals) + self.state.best_cost = cost + self.state.initialized = True + _logger.info("Baseline cost=%.4f (ATE_t=%.3f, ATE_r=%.2f, scale=%.3f)", + cost, metrics.ate_trans, metrics.ate_rot, metrics.scale_drift) + + for p in range(self.max_passes): + self.state.pass_count = p + _logger.info("=== Tuning pass %d/%d ===", p + 1, self.max_passes) + improved_this_pass = False + + for key, spec in self.params.items(): + if self.state.converged.get(key, False): + continue + + current_val = float(spec.value) + results: List[TrialResult] = [] + + for direction in (+1, -1): + trial_val = current_val + direction * spec.step + trial_val = spec.clamp(trial_val) + if trial_val == current_val: + continue # hit boundary + + trial_vals = self._current_values() + trial_vals[key] = trial_val + _logger.info("Trying %s = %s (dir %+d)", key, trial_val, direction) + cost, metrics = self._apply_and_eval(trial_vals) + tr = TrialResult(key, trial_val, cost, metrics, + accepted=cost < self.state.best_cost - self.min_improvement) + results.append(tr) + if on_trial: + on_trial(tr) + + if tr.accepted: + # Accept this improvement + self.params[key].value = trial_val + self.state.best_cost = cost + improved_this_pass = True + _logger.info("ACCEPTED %s = %s → cost=%.4f", key, trial_val, cost) + break # don't try the other direction + else: + _logger.info("Rejected %s = %s → cost=%.4f (best=%.4f)", + key, trial_val, cost, self.state.best_cost) + + if not any(r.accepted for r in results): + # No improvement in either direction → reduce step / converge + self.params[key].step = max(spec.step * 0.5, spec.step * 0.1) + if self.params[key].step < abs(spec.step) * 0.1: + self.state.converged[key] = True + _logger.info("Parameter %s converged", key) + + if not improved_this_pass: + _logger.info("No improvement in pass %d — stopping", p + 1) + break + + best = self._current_values() + _logger.info("Tuning complete. Best cost=%.4f. Final params: %s", + self.state.best_cost, best) + # Ensure final config reflects best values + self.editor.apply_param_specs( + {k: ParamSpec(key=k, value=v, min=self.params[k].min, + max=self.params[k].max, step=self.params[k].step, + dtype=self.params[k].dtype) + for k, v in best.items()} + ) + return best \ No newline at end of file diff --git a/vins_bringup/CMakeLists.txt b/vins_bringup/CMakeLists.txt new file mode 100644 index 000000000..aec435947 --- /dev/null +++ b/vins_bringup/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.8) +project(vins_bringup) + +find_package(ament_cmake REQUIRED) + +install(DIRECTORY launch + DESTINATION share/${PROJECT_NAME}) + +install(DIRECTORY ../config + DESTINATION share/${PROJECT_NAME}) + +ament_package() diff --git a/vins_bringup/launch/3dm.launch.py b/vins_bringup/launch/3dm.launch.py new file mode 100644 index 000000000..cc3060e54 --- /dev/null +++ b/vins_bringup/launch/3dm.launch.py @@ -0,0 +1,13 @@ +import os +import sys + +from launch import LaunchDescription + +sys.path.append(os.path.dirname(os.path.abspath(__file__))) +from common import common_nodes + + +def generate_launch_description(): + return LaunchDescription( + common_nodes("3dm/3dm_config.yaml", include_pose_graph=False) + ) diff --git a/vins_bringup/launch/black_box.launch.py b/vins_bringup/launch/black_box.launch.py new file mode 100644 index 000000000..2556eac47 --- /dev/null +++ b/vins_bringup/launch/black_box.launch.py @@ -0,0 +1,19 @@ +import os +import sys + +from launch import LaunchDescription + +sys.path.append(os.path.dirname(os.path.abspath(__file__))) +from common import common_nodes + + +def generate_launch_description(): + return LaunchDescription( + common_nodes( + "black_box/black_box_config.yaml", + pose_graph_skip_dis=0.1, + feature_tracker_prefix="taskset -c 3", + vins_estimator_prefix="taskset -c 1", + pose_graph_prefix="taskset -c 2", + ) + ) diff --git a/vins_bringup/launch/cla.launch.py b/vins_bringup/launch/cla.launch.py new file mode 100644 index 000000000..6bdeda53f --- /dev/null +++ b/vins_bringup/launch/cla.launch.py @@ -0,0 +1,11 @@ +import os +import sys + +from launch import LaunchDescription + +sys.path.append(os.path.dirname(os.path.abspath(__file__))) +from common import common_nodes + + +def generate_launch_description(): + return LaunchDescription(common_nodes("cla/cla_config.yaml")) diff --git a/vins_bringup/launch/common.py b/vins_bringup/launch/common.py new file mode 100644 index 000000000..5f9b6cd4c --- /dev/null +++ b/vins_bringup/launch/common.py @@ -0,0 +1,78 @@ +from pathlib import Path + +from ament_index_python.packages import get_package_share_directory +from launch_ros.actions import Node + + +def _share_dir() -> Path: + return Path(get_package_share_directory("vins_bringup")) + + +def config_file_path(filename: str) -> str: + return str(_share_dir() / "config" / filename) + + +def vins_folder_path() -> str: + return str(_share_dir()) + "/" + + +def common_nodes( + config_filename: str, + include_pose_graph: bool = True, + pose_graph_skip_dis: float = 0.0, + feature_tracker_prefix: str | None = None, + vins_estimator_prefix: str | None = None, + pose_graph_prefix: str | None = None, +): + config_path = config_file_path(config_filename) + vins_path = vins_folder_path() + feature_tracker_kwargs = { + "package": "feature_tracker", + "executable": "feature_tracker", + "name": "feature_tracker", + "output": "screen", + "parameters": [ + {"config_file": config_path}, + {"vins_folder": vins_path}, + ], + } + if feature_tracker_prefix: + feature_tracker_kwargs["prefix"] = feature_tracker_prefix + + vins_estimator_kwargs = { + "package": "vins_estimator", + "executable": "vins_estimator", + "name": "vins_estimator", + "output": "screen", + "parameters": [ + {"config_file": config_path}, + {"vins_folder": vins_path}, + ], + } + if vins_estimator_prefix: + vins_estimator_kwargs["prefix"] = vins_estimator_prefix + + nodes = [ + Node(**feature_tracker_kwargs), + Node(**vins_estimator_kwargs), + ] + + if include_pose_graph: + pose_graph_kwargs = { + "package": "pose_graph", + "executable": "pose_graph", + "name": "pose_graph", + "output": "screen", + "parameters": [ + {"config_file": config_path}, + {"visualization_shift_x": 0}, + {"visualization_shift_y": 0}, + {"skip_cnt": 0}, + {"skip_dis": pose_graph_skip_dis}, + ], + } + if pose_graph_prefix: + pose_graph_kwargs["prefix"] = pose_graph_prefix + nodes.append(Node(**pose_graph_kwargs)) + + return nodes diff --git a/vins_bringup/launch/euroc.launch.py b/vins_bringup/launch/euroc.launch.py new file mode 100644 index 000000000..b59ec5d0f --- /dev/null +++ b/vins_bringup/launch/euroc.launch.py @@ -0,0 +1,11 @@ +import os +import sys + +from launch import LaunchDescription + +sys.path.append(os.path.dirname(os.path.abspath(__file__))) +from common import common_nodes + + +def generate_launch_description(): + return LaunchDescription(common_nodes("euroc/euroc_config.yaml")) diff --git a/vins_bringup/launch/euroc_no_extrinsic_param.launch.py b/vins_bringup/launch/euroc_no_extrinsic_param.launch.py new file mode 100644 index 000000000..3af29afb5 --- /dev/null +++ b/vins_bringup/launch/euroc_no_extrinsic_param.launch.py @@ -0,0 +1,11 @@ +import os +import sys + +from launch import LaunchDescription + +sys.path.append(os.path.dirname(os.path.abspath(__file__))) +from common import common_nodes + + +def generate_launch_description(): + return LaunchDescription(common_nodes("euroc/euroc_config_no_extrinsic.yaml")) diff --git a/vins_bringup/launch/innospector_cam.launch.py b/vins_bringup/launch/innospector_cam.launch.py new file mode 100644 index 000000000..7a0f49c3e --- /dev/null +++ b/vins_bringup/launch/innospector_cam.launch.py @@ -0,0 +1,11 @@ +import os +import sys + +from launch import LaunchDescription + +sys.path.append(os.path.dirname(os.path.abspath(__file__))) +from common import common_nodes + + +def generate_launch_description(): + return LaunchDescription(common_nodes("fisheye_bag/fisheye_bag_config.yaml")) diff --git a/vins_bringup/launch/realsense_color.launch.py b/vins_bringup/launch/realsense_color.launch.py new file mode 100644 index 000000000..a42772e31 --- /dev/null +++ b/vins_bringup/launch/realsense_color.launch.py @@ -0,0 +1,11 @@ +import os +import sys + +from launch import LaunchDescription + +sys.path.append(os.path.dirname(os.path.abspath(__file__))) +from common import common_nodes + + +def generate_launch_description(): + return LaunchDescription(common_nodes("realsense/realsense_color_config.yaml")) diff --git a/vins_bringup/launch/realsense_fisheye.launch.py b/vins_bringup/launch/realsense_fisheye.launch.py new file mode 100644 index 000000000..952c1a36b --- /dev/null +++ b/vins_bringup/launch/realsense_fisheye.launch.py @@ -0,0 +1,11 @@ +import os +import sys + +from launch import LaunchDescription + +sys.path.append(os.path.dirname(os.path.abspath(__file__))) +from common import common_nodes + + +def generate_launch_description(): + return LaunchDescription(common_nodes("realsense/realsense_fisheye_config.yaml")) diff --git a/vins_bringup/launch/termal_cam.launch.py b/vins_bringup/launch/termal_cam.launch.py new file mode 100644 index 000000000..3e6775714 --- /dev/null +++ b/vins_bringup/launch/termal_cam.launch.py @@ -0,0 +1,11 @@ +import os +import sys + +from launch import LaunchDescription + +sys.path.append(os.path.dirname(os.path.abspath(__file__))) +from common import common_nodes + + +def generate_launch_description(): + return LaunchDescription(common_nodes("termal_cam_config.yaml")) diff --git a/vins_bringup/launch/usb_cam.launch.py b/vins_bringup/launch/usb_cam.launch.py new file mode 100644 index 000000000..30736b673 --- /dev/null +++ b/vins_bringup/launch/usb_cam.launch.py @@ -0,0 +1,11 @@ +import os +import sys + +from launch import LaunchDescription + +sys.path.append(os.path.dirname(os.path.abspath(__file__))) +from common import common_nodes + + +def generate_launch_description(): + return LaunchDescription(common_nodes("usb_cam_config.yaml")) diff --git a/vins_bringup/launch/vins_rviz.launch.py b/vins_bringup/launch/vins_rviz.launch.py new file mode 100644 index 000000000..0da0d33c0 --- /dev/null +++ b/vins_bringup/launch/vins_rviz.launch.py @@ -0,0 +1,20 @@ +from pathlib import Path + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription +from launch_ros.actions import Node + + +def generate_launch_description(): + rviz_config = Path(get_package_share_directory("vins_bringup")) / "config" / "vins_rviz_config.rviz" + return LaunchDescription( + [ + Node( + package="rviz2", + executable="rviz2", + name="rviz2", + output="log", + arguments=["-d", str(rviz_config)], + ) + ] + ) diff --git a/vins_bringup/package.xml b/vins_bringup/package.xml new file mode 100644 index 000000000..35689a553 --- /dev/null +++ b/vins_bringup/package.xml @@ -0,0 +1,25 @@ + + + vins_bringup + 0.0.0 + Launch package for VINS-Mono ROS2 bringup. + + qintong + TODO + + ament_cmake + + ament_index_python + image_transport + compressed_image_transport + launch + launch_ros + rviz2 + feature_tracker + vins_estimator + pose_graph + + + ament_cmake + + diff --git a/vins_estimator/CMakeLists.txt b/vins_estimator/CMakeLists.txt index 31bcca37d..4b3e8215f 100644 --- a/vins_estimator/CMakeLists.txt +++ b/vins_estimator/CMakeLists.txt @@ -1,37 +1,32 @@ -cmake_minimum_required(VERSION 2.8.3) +cmake_minimum_required(VERSION 3.8) project(vins_estimator) -set(CMAKE_BUILD_TYPE "Release") -set(CMAKE_CXX_FLAGS "-std=c++11") -#-DEIGEN_USE_MKL_ALL") -set(CMAKE_CXX_FLAGS_RELEASE "-O3 -Wall -g") +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release") +endif() -find_package(catkin REQUIRED COMPONENTS - roscpp - std_msgs - geometry_msgs - nav_msgs - tf - cv_bridge - visualization_msgs - ) +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) +endif() -find_package(OpenCV REQUIRED) - -# message(WARNING "OpenCV_VERSION: ${OpenCV_VERSION}") +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_FLAGS_RELEASE "-O3 -Wall") +find_package(ament_cmake REQUIRED) +find_package(rclcpp REQUIRED) +find_package(std_msgs REQUIRED) +find_package(geometry_msgs REQUIRED) +find_package(nav_msgs REQUIRED) +find_package(sensor_msgs REQUIRED) +find_package(visualization_msgs REQUIRED) +find_package(tf2_ros REQUIRED) +find_package(tf2_geometry_msgs REQUIRED) +find_package(cv_bridge REQUIRED) +find_package(camera_model REQUIRED) +find_package(Boost REQUIRED COMPONENTS filesystem program_options system) +find_package(OpenCV REQUIRED) find_package(Ceres REQUIRED) - -include_directories(${catkin_INCLUDE_DIRS} ${CERES_INCLUDE_DIRS}) - -set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) -find_package(Eigen3) -include_directories( - ${catkin_INCLUDE_DIRS} - ${EIGEN3_INCLUDE_DIR} -) - -catkin_package() +find_package(Eigen3 REQUIRED) add_executable(vins_estimator src/estimator_node.cpp @@ -51,7 +46,28 @@ add_executable(vins_estimator src/initial/initial_ex_rotation.cpp ) +ament_target_dependencies(vins_estimator + rclcpp + std_msgs + geometry_msgs + nav_msgs + sensor_msgs + visualization_msgs + tf2_ros + tf2_geometry_msgs + cv_bridge + camera_model +) + +target_link_libraries(vins_estimator camera_model::camera_model ${OpenCV_LIBS} ${CERES_LIBRARIES}) + +target_include_directories(vins_estimator PRIVATE + ${EIGEN3_INCLUDE_DIR} + ${camera_model_INCLUDE_DIRS} + ${CERES_INCLUDE_DIRS}) -target_link_libraries(vins_estimator ${catkin_LIBRARIES} ${OpenCV_LIBS} ${CERES_LIBRARIES}) +install(TARGETS vins_estimator + DESTINATION lib/${PROJECT_NAME}) +ament_package() diff --git a/vins_estimator/launch/3dm.launch b/vins_estimator/launch/3dm.launch deleted file mode 100644 index 5991b883d..000000000 --- a/vins_estimator/launch/3dm.launch +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/vins_estimator/launch/black_box.launch b/vins_estimator/launch/black_box.launch deleted file mode 100644 index 879b25ecc..000000000 --- a/vins_estimator/launch/black_box.launch +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/vins_estimator/launch/cla.launch b/vins_estimator/launch/cla.launch deleted file mode 100644 index 22c4ce7dd..000000000 --- a/vins_estimator/launch/cla.launch +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/vins_estimator/launch/euroc.launch b/vins_estimator/launch/euroc.launch deleted file mode 100644 index 0d6695542..000000000 --- a/vins_estimator/launch/euroc.launch +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/vins_estimator/launch/euroc_no_extrinsic_param.launch b/vins_estimator/launch/euroc_no_extrinsic_param.launch deleted file mode 100644 index c00d5349e..000000000 --- a/vins_estimator/launch/euroc_no_extrinsic_param.launch +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/vins_estimator/launch/realsense_color.launch b/vins_estimator/launch/realsense_color.launch deleted file mode 100644 index b4ca394e5..000000000 --- a/vins_estimator/launch/realsense_color.launch +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/vins_estimator/launch/realsense_fisheye.launch b/vins_estimator/launch/realsense_fisheye.launch deleted file mode 100644 index 15a1b9035..000000000 --- a/vins_estimator/launch/realsense_fisheye.launch +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/vins_estimator/launch/simulation.launch b/vins_estimator/launch/simulation.launch deleted file mode 100644 index 3e4246d18..000000000 --- a/vins_estimator/launch/simulation.launch +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/vins_estimator/launch/tum.launch b/vins_estimator/launch/tum.launch deleted file mode 100644 index b67c0c29f..000000000 --- a/vins_estimator/launch/tum.launch +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/vins_estimator/launch/vins_rviz.launch b/vins_estimator/launch/vins_rviz.launch deleted file mode 100644 index 69e46cbcd..000000000 --- a/vins_estimator/launch/vins_rviz.launch +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/vins_estimator/package.xml b/vins_estimator/package.xml index 08e65625b..ef3720b3c 100644 --- a/vins_estimator/package.xml +++ b/vins_estimator/package.xml @@ -1,55 +1,26 @@ - + vins_estimator 0.0.0 The vins_estimator package - - - qintong - - - - - TODO + ament_cmake - - - - - - - - - - - + rclcpp + std_msgs + geometry_msgs + nav_msgs + sensor_msgs + visualization_msgs + tf2_ros + tf2_geometry_msgs + cv_bridge + camera_model - - - - - - - - - - - - catkin - roscpp - roscpp - - - - - - - - + ament_cmake diff --git a/vins_estimator/src/estimator.cpp b/vins_estimator/src/estimator.cpp index cc198768a..4cb34d19b 100644 --- a/vins_estimator/src/estimator.cpp +++ b/vins_estimator/src/estimator.cpp @@ -1,8 +1,11 @@ #include "estimator.h" +#include Estimator::Estimator(): f_manager{Rs} { - ROS_INFO("init begins"); + RCLCPP_INFO(rclcpp::get_logger("vins_estimator"), "init begins"); + last_td_opt_time = -1.0; + td_updated = false; clearState(); } @@ -79,6 +82,26 @@ void Estimator::clearState() drift_correct_r = Matrix3d::Identity(); drift_correct_t = Vector3d::Zero(); + + last_td_opt_time = -1.0; + + // Lidar odometry initialization state + lidar_init_done = false; + lidar_init_pose_applied = false; + lidar_init_pos = Vector3d::Zero(); + lidar_init_rot = Matrix3d::Identity(); + lidar_init_time = -1.0; + vins_init_pos = Vector3d::Zero(); + vins_init_rot = Matrix3d::Identity(); + vins_init_time = -1.0; + + // Lidar-assisted deep init + lidar_pos_buf.clear(); + lidar_rot_buf.clear(); + lidar_time_buf.clear(); + lidar_scale_estimate = 1.0; + lidar_scale_valid = false; + lidar_init_samples = 0; } void Estimator::processIMU(double dt, const Vector3d &linear_acceleration, const Vector3d &angular_velocity) @@ -112,42 +135,56 @@ void Estimator::processIMU(double dt, const Vector3d &linear_acceleration, const Vector3d un_acc = 0.5 * (un_acc_0 + un_acc_1); Ps[j] += dt * Vs[j] + 0.5 * dt * dt * un_acc; Vs[j] += dt * un_acc; + + // Zero Velocity Update (ZUPT) - detect stationary state + if (ENABLE_ZUPT) + { + double vel_norm = Vs[j].norm(); + double acc_deviation = std::abs(linear_acceleration.norm() - G.norm()); + + // If velocity is low and acceleration is close to gravity (stationary) + if (vel_norm < ZUPT_VEL_THRESHOLD && acc_deviation < ZUPT_ACC_THRESHOLD) + { + Vs[j] = Vector3d::Zero(); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "ZUPT applied: velocity reset to zero"); + } + } } acc_0 = linear_acceleration; gyr_0 = angular_velocity; } -void Estimator::processImage(const map>>> &image, const std_msgs::Header &header) +void Estimator::processImage(const map>>> &image, const std_msgs::msg::Header &header) { - ROS_DEBUG("new image coming ------------------------------------------"); - ROS_DEBUG("Adding feature points %lu", image.size()); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "new image coming ------------------------------------------"); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "Adding feature points %lu", image.size()); if (f_manager.addFeatureCheckParallax(frame_count, image, td)) marginalization_flag = MARGIN_OLD; else marginalization_flag = MARGIN_SECOND_NEW; - ROS_DEBUG("this frame is--------------------%s", marginalization_flag ? "reject" : "accept"); - ROS_DEBUG("%s", marginalization_flag ? "Non-keyframe" : "Keyframe"); - ROS_DEBUG("Solving %d", frame_count); - ROS_DEBUG("number of feature: %d", f_manager.getFeatureCount()); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "this frame is--------------------%s", marginalization_flag ? "reject" : "accept"); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "%s", marginalization_flag ? "Non-keyframe" : "Keyframe"); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "Solving %d", frame_count); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "number of feature: %d", f_manager.getFeatureCount()); Headers[frame_count] = header; - ImageFrame imageframe(image, header.stamp.toSec()); + ImageFrame imageframe(image, stampToSec(header.stamp)); imageframe.pre_integration = tmp_pre_integration; - all_image_frame.insert(make_pair(header.stamp.toSec(), imageframe)); + all_image_frame.insert(make_pair(stampToSec(header.stamp), imageframe)); tmp_pre_integration = new IntegrationBase{acc_0, gyr_0, Bas[frame_count], Bgs[frame_count]}; if(ESTIMATE_EXTRINSIC == 2) { - ROS_INFO("calibrating extrinsic param, rotation movement is needed"); + RCLCPP_INFO(rclcpp::get_logger("vins_estimator"), "calibrating extrinsic param, rotation movement is needed"); if (frame_count != 0) { vector> corres = f_manager.getCorresponding(frame_count - 1, frame_count); Matrix3d calib_ric; if (initial_ex_rotation.CalibrationExRotation(corres, pre_integrations[frame_count]->delta_q, calib_ric)) { - ROS_WARN("initial extrinsic rotation calib success"); - ROS_WARN_STREAM("initial extrinsic rotation: " << endl << calib_ric); + RCLCPP_WARN(rclcpp::get_logger("vins_estimator"), "initial extrinsic rotation calib success"); + RCLCPP_WARN_STREAM(rclcpp::get_logger("vins_estimator"), "initial extrinsic rotation: " << endl << calib_ric); ric[0] = calib_ric; RIC[0] = calib_ric; ESTIMATE_EXTRINSIC = 1; @@ -160,10 +197,10 @@ void Estimator::processImage(const map 0.1) + if( ESTIMATE_EXTRINSIC != 2 && (stampToSec(header.stamp) - initial_timestamp) > 0.1) { result = initialStructure(); - initial_timestamp = header.stamp.toSec(); + initial_timestamp = stampToSec(header.stamp); } if(result) { @@ -171,7 +208,7 @@ void Estimator::processImage(const mapfirst) == Headers[i].stamp.toSec()) + if((frame_it->first) == stampToSec(Headers[i].stamp)) { frame_it->second.is_key_frame = true; frame_it->second.R = Q[i].toRotationMatrix() * RIC[0].transpose(); @@ -299,7 +336,7 @@ bool Estimator::initialStructure() i++; continue; } - if((frame_it->first) > Headers[i].stamp.toSec()) + if((frame_it->first) > stampToSec(Headers[i].stamp)) { i++; } @@ -333,12 +370,12 @@ bool Estimator::initialStructure() if(pts_3_vector.size() < 6) { cout << "pts_3_vector size " << pts_3_vector.size() << endl; - ROS_DEBUG("Not enough points for solve pnp !"); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "Not enough points for solve pnp !"); return false; } if (! cv::solvePnP(pts_3_vector, pts_2_vector, K, D, rvec, t, 1)) { - ROS_DEBUG("solve pnp fail!"); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "solve pnp fail!"); return false; } cv::Rodrigues(rvec, r); @@ -351,11 +388,35 @@ bool Estimator::initialStructure() frame_it->second.R = R_pnp * RIC[0].transpose(); frame_it->second.T = T_pnp; } + + // ─── Lidar-assisted scale estimation ─── + // If lidar odometry is available, estimate the metric scale from it + // and use it as a prior/constraint for the visual-inertial alignment. + // This provides a metric scale reference before IMU alignment runs. + if (ENABLE_LIDAR_INIT && !lidar_pos_buf.empty()) + { + // Set visual poses from SFM into Ps[] for scale comparison + for (int i = 0; i <= frame_count; i++) + { + Ps[i] = all_image_frame[stampToSec(Headers[i].stamp)].T; + Rs[i] = all_image_frame[stampToSec(Headers[i].stamp)].R; + } + + double lidar_scale = estimateScaleFromLidar(); + if (lidar_scale_valid) + { + RCLCPP_INFO(rclcpp::get_logger("vins_estimator"), + "[LIDAR_DEEP_INIT] Using lidar scale %.4f as prior for VINS init", lidar_scale); + // We don't apply it directly — LinearAlignment will estimate its own scale + // from IMU. The lidar scale is stored for validation and soft alignment. + } + } + if (visualInitialAlign()) return true; else { - ROS_INFO("misalign visual structure with IMU"); + RCLCPP_INFO(rclcpp::get_logger("vins_estimator"), "misalign visual structure with IMU"); return false; } @@ -369,18 +430,18 @@ bool Estimator::visualInitialAlign() bool result = VisualIMUAlignment(all_image_frame, Bgs, g, x); if(!result) { - ROS_DEBUG("solve g failed!"); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "solve g failed!"); return false; } // change state for (int i = 0; i <= frame_count; i++) { - Matrix3d Ri = all_image_frame[Headers[i].stamp.toSec()].R; - Vector3d Pi = all_image_frame[Headers[i].stamp.toSec()].T; + Matrix3d Ri = all_image_frame[stampToSec(Headers[i].stamp)].R; + Vector3d Pi = all_image_frame[stampToSec(Headers[i].stamp)].T; Ps[i] = Pi; Rs[i] = Ri; - all_image_frame[Headers[i].stamp.toSec()].is_key_frame = true; + all_image_frame[stampToSec(Headers[i].stamp)].is_key_frame = true; } VectorXd dep = f_manager.getDepthVector(); @@ -433,8 +494,8 @@ bool Estimator::visualInitialAlign() Rs[i] = rot_diff * Rs[i]; Vs[i] = rot_diff * Vs[i]; } - ROS_DEBUG_STREAM("g0 " << g.transpose()); - ROS_DEBUG_STREAM("my R0 " << Utility::R2ypr(Rs[0]).transpose()); + RCLCPP_DEBUG_STREAM(rclcpp::get_logger("vins_estimator"), "g0 " << g.transpose()); + RCLCPP_DEBUG_STREAM(rclcpp::get_logger("vins_estimator"), "my R0 " << Utility::R2ypr(Rs[0]).transpose()); return true; } @@ -462,7 +523,7 @@ bool Estimator::relativePose(Matrix3d &relative_R, Vector3d &relative_T, int &l) if(average_parallax * 460 > 30 && m_estimator.solveRelativeRT(corres, relative_R, relative_T)) { l = i; - ROS_DEBUG("average_parallax %f choose l %d and newest frame to triangulate the whole structure", average_parallax * 460, l); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "average_parallax %f choose l %d and newest frame to triangulate the whole structure", average_parallax * 460, l); return true; } } @@ -478,7 +539,7 @@ void Estimator::solveOdometry() { TicToc t_tri; f_manager.triangulate(Ps, tic, ric); - ROS_DEBUG("triangulation costs %f", t_tri.toc()); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "triangulation costs %f", t_tri.toc()); optimization(); } } @@ -547,7 +608,7 @@ void Estimator::double2vector() Matrix3d rot_diff = Utility::ypr2R(Vector3d(y_diff, 0, 0)); if (abs(abs(origin_R0.y()) - 90) < 1.0 || abs(abs(origin_R00.y()) - 90) < 1.0) { - ROS_DEBUG("euler singular point!"); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "euler singular point!"); rot_diff = Rs[0] * Quaterniond(para_Pose[0][6], para_Pose[0][3], para_Pose[0][4], @@ -622,35 +683,35 @@ bool Estimator::failureDetection() { if (f_manager.last_track_num < 2) { - ROS_INFO(" little feature %d", f_manager.last_track_num); + RCLCPP_INFO(rclcpp::get_logger("vins_estimator"), " little feature %d", f_manager.last_track_num); //return true; } - if (Bas[WINDOW_SIZE].norm() > 2.5) + if (Bas[WINDOW_SIZE].norm() > 5.0) { - ROS_INFO(" big IMU acc bias estimation %f", Bas[WINDOW_SIZE].norm()); + RCLCPP_INFO(rclcpp::get_logger("vins_estimator"), " big IMU acc bias estimation %f", Bas[WINDOW_SIZE].norm()); return true; } if (Bgs[WINDOW_SIZE].norm() > 1.0) { - ROS_INFO(" big IMU gyr bias estimation %f", Bgs[WINDOW_SIZE].norm()); + RCLCPP_INFO(rclcpp::get_logger("vins_estimator"), " big IMU gyr bias estimation %f", Bgs[WINDOW_SIZE].norm()); return true; } /* if (tic(0) > 1) { - ROS_INFO(" big extri param estimation %d", tic(0) > 1); + RCLCPP_INFO(rclcpp::get_logger("vins_estimator"), " big extri param estimation %d", tic(0) > 1); return true; } */ Vector3d tmp_P = Ps[WINDOW_SIZE]; - if ((tmp_P - last_P).norm() > 5) + if ((tmp_P - last_P).norm() > 20) // increased for lidar-assisted init jumps { - ROS_INFO(" big translation"); + RCLCPP_INFO(rclcpp::get_logger("vins_estimator"), " big translation"); return true; } - if (abs(tmp_P.z() - last_P.z()) > 1) + if (abs(tmp_P.z() - last_P.z()) > 3) // increased threshold for altitude changes { - ROS_INFO(" big z translation"); + RCLCPP_INFO(rclcpp::get_logger("vins_estimator"), " big z translation"); return true; } Matrix3d tmp_R = Rs[WINDOW_SIZE]; @@ -660,7 +721,7 @@ bool Estimator::failureDetection() delta_angle = acos(delta_Q.w()) * 2.0 / 3.14 * 180.0; if (delta_angle > 50) { - ROS_INFO(" big delta_angle "); + RCLCPP_INFO(rclcpp::get_logger("vins_estimator"), " big delta_angle "); //return true; } return false; @@ -673,28 +734,43 @@ void Estimator::optimization() ceres::LossFunction *loss_function; //loss_function = new ceres::HuberLoss(1.0); loss_function = new ceres::CauchyLoss(1.0); + + // Run td optimization only periodically to reduce overhead + bool estimate_td_active = false; + td_updated = false; + if (ESTIMATE_TD) + { + double now = std::chrono::duration(std::chrono::system_clock::now().time_since_epoch()).count(); + if (last_td_opt_time < 0 || now - last_td_opt_time >= TD_ESTIMATION_PERIOD) + { + estimate_td_active = true; + td_updated = true; + last_td_opt_time = now; + } + } for (int i = 0; i < WINDOW_SIZE + 1; i++) { - ceres::LocalParameterization *local_parameterization = new PoseLocalParameterization(); - problem.AddParameterBlock(para_Pose[i], SIZE_POSE, local_parameterization); + ceres::Manifold *local_parameterization = new PoseLocalParameterization(); + problem.AddParameterBlock(para_Pose[i], SIZE_POSE); problem.SetManifold(para_Pose[i], local_parameterization); problem.AddParameterBlock(para_SpeedBias[i], SIZE_SPEEDBIAS); } for (int i = 0; i < NUM_OF_CAM; i++) { - ceres::LocalParameterization *local_parameterization = new PoseLocalParameterization(); - problem.AddParameterBlock(para_Ex_Pose[i], SIZE_POSE, local_parameterization); + ceres::Manifold *local_parameterization = new PoseLocalParameterization(); + problem.AddParameterBlock(para_Ex_Pose[i], SIZE_POSE); problem.SetManifold(para_Ex_Pose[i], local_parameterization); if (!ESTIMATE_EXTRINSIC) { - ROS_DEBUG("fix extinsic param"); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "fix extinsic param"); problem.SetParameterBlockConstant(para_Ex_Pose[i]); } else - ROS_DEBUG("estimate extinsic param"); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "estimate extinsic param"); } if (ESTIMATE_TD) { problem.AddParameterBlock(para_Td[0], 1); - //problem.SetParameterBlockConstant(para_Td[0]); + if (!estimate_td_active) + problem.SetParameterBlockConstant(para_Td[0]); } TicToc t_whole, t_prepare; @@ -738,7 +814,7 @@ void Estimator::optimization() continue; } Vector3d pts_j = it_per_frame.point; - if (ESTIMATE_TD) + if (ESTIMATE_TD && estimate_td_active) { ProjectionTdFactor *f_td = new ProjectionTdFactor(pts_i, pts_j, it_per_id.feature_per_frame[0].velocity, it_per_frame.velocity, it_per_id.feature_per_frame[0].cur_td, it_per_frame.cur_td, @@ -763,14 +839,14 @@ void Estimator::optimization() } } - ROS_DEBUG("visual measurement count: %d", f_m_cnt); - ROS_DEBUG("prepare for ceres: %f", t_prepare.toc()); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "visual measurement count: %d", f_m_cnt); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "prepare for ceres: %f", t_prepare.toc()); if(relocalization_info) { //printf("set relocalization factor! \n"); - ceres::LocalParameterization *local_parameterization = new PoseLocalParameterization(); - problem.AddParameterBlock(relo_Pose, SIZE_POSE, local_parameterization); + ceres::Manifold *local_parameterization = new PoseLocalParameterization(); + problem.AddParameterBlock(relo_Pose, SIZE_POSE); problem.SetManifold(relo_Pose, local_parameterization); int retrive_feature_index = 0; int feature_index = -1; for (auto &it_per_id : f_manager.feature) @@ -817,8 +893,8 @@ void Estimator::optimization() ceres::Solver::Summary summary; ceres::Solve(options, &problem, &summary); //cout << summary.BriefReport() << endl; - ROS_DEBUG("Iterations : %d", static_cast(summary.iterations.size())); - ROS_DEBUG("solver costs: %f", t_solver.toc()); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "Iterations : %d", static_cast(summary.iterations.size())); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "solver costs: %f", t_solver.toc()); double2vector(); @@ -880,7 +956,7 @@ void Estimator::optimization() continue; Vector3d pts_j = it_per_frame.point; - if (ESTIMATE_TD) + if (ESTIMATE_TD && estimate_td_active) { ProjectionTdFactor *f_td = new ProjectionTdFactor(pts_i, pts_j, it_per_id.feature_per_frame[0].velocity, it_per_frame.velocity, it_per_id.feature_per_frame[0].cur_td, it_per_frame.cur_td, @@ -904,11 +980,11 @@ void Estimator::optimization() TicToc t_pre_margin; marginalization_info->preMarginalize(); - ROS_DEBUG("pre marginalization %f ms", t_pre_margin.toc()); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "pre marginalization %f ms", t_pre_margin.toc()); TicToc t_margin; marginalization_info->marginalize(); - ROS_DEBUG("marginalization %f ms", t_margin.toc()); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "marginalization %f ms", t_margin.toc()); std::unordered_map addr_shift; for (int i = 1; i <= WINDOW_SIZE; i++) @@ -943,7 +1019,7 @@ void Estimator::optimization() vector drop_set; for (int i = 0; i < static_cast(last_marginalization_parameter_blocks.size()); i++) { - ROS_ASSERT(last_marginalization_parameter_blocks[i] != para_SpeedBias[WINDOW_SIZE - 1]); + assert(last_marginalization_parameter_blocks[i] != para_SpeedBias[WINDOW_SIZE - 1]); if (last_marginalization_parameter_blocks[i] == para_Pose[WINDOW_SIZE - 1]) drop_set.push_back(i); } @@ -957,14 +1033,14 @@ void Estimator::optimization() } TicToc t_pre_margin; - ROS_DEBUG("begin marginalization"); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "begin marginalization"); marginalization_info->preMarginalize(); - ROS_DEBUG("end pre marginalization, %f ms", t_pre_margin.toc()); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "end pre marginalization, %f ms", t_pre_margin.toc()); TicToc t_margin; - ROS_DEBUG("begin marginalization"); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "begin marginalization"); marginalization_info->marginalize(); - ROS_DEBUG("end marginalization, %f ms", t_margin.toc()); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "end marginalization, %f ms", t_margin.toc()); std::unordered_map addr_shift; for (int i = 0; i <= WINDOW_SIZE; i++) @@ -997,9 +1073,9 @@ void Estimator::optimization() } } - ROS_DEBUG("whole marginalization costs: %f", t_whole_marginalization.toc()); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "whole marginalization costs: %f", t_whole_marginalization.toc()); - ROS_DEBUG("whole time for ceres: %f", t_whole.toc()); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "whole time for ceres: %f", t_whole.toc()); } void Estimator::slideWindow() @@ -1007,7 +1083,7 @@ void Estimator::slideWindow() TicToc t_margin; if (marginalization_flag == MARGIN_OLD) { - double t_0 = Headers[0].stamp.toSec(); + double t_0 = stampToSec(Headers[0].stamp); back_R0 = Rs[0]; back_P0 = Ps[0]; if (frame_count == WINDOW_SIZE) @@ -1135,7 +1211,7 @@ void Estimator::setReloFrame(double _frame_stamp, int _frame_index, vector 1.2) { + RCLCPP_WARN(rclcpp::get_logger("vins_estimator"), "Scale ratio out of bounds: %.3f (MAVLink: %.2f m, VINS: %.2f m), skipping correction", + scale_ratio, mavlink_altitude, vins_altitude); + return; + } + + // Apply gentle correction with smoothing factor + const double CORRECTION_FACTOR = 0.1; // 10% correction per update + double correction = 1.0 + (scale_ratio - 1.0) * CORRECTION_FACTOR; + + RCLCPP_INFO(rclcpp::get_logger("vins_estimator"), "Altitude-based scale correction: %.4f (MAVLink: %.2f m, VINS: %.2f m)", + correction, mavlink_altitude, vins_altitude); + + // Apply scale correction to positions and velocities in the sliding window + for (int i = 0; i <= WINDOW_SIZE; i++) + { + Ps[i] = Ps[i] * correction; + Vs[i] = Vs[i] * correction; + } + + // Also correct feature depths in feature manager + f_manager.scaleDepth(correction); + + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "Corrected VINS altitude: %.2f m, velocity: %.2f m/s", Ps[WINDOW_SIZE].z(), Vs[WINDOW_SIZE].z()); +} + +void Estimator::initializeWithLidarOdom(const Vector3d &lidar_pos, const Matrix3d &lidar_rot, + double lidar_time, double vins_time) +{ + // Apply initial pose and scale from lidar odometry once, right after VINS initialization completes. + // This aligns the VINS coordinate frame with the lidar odometry frame so both outputs match. + if (solver_flag != NON_LINEAR) + return; + + if (lidar_init_done) + return; + + // Record the lidar and VINS poses at the moment of initialization + lidar_init_pos = lidar_pos; + lidar_init_rot = lidar_rot; + lidar_init_time = lidar_time; + vins_init_pos = Ps[WINDOW_SIZE]; + vins_init_rot = Rs[WINDOW_SIZE]; + vins_init_time = vins_time; + + // Compute the rigid transform that maps VINS frame to lidar frame: + // P_lidar = R_align * P_vins + t_align + // We set R_align so that the VINS yaw is corrected to match the lidar yaw, + // and t_align so that the current VINS position maps to the lidar position. + Matrix3d R_align = lidar_init_rot * vins_init_rot.transpose(); + Vector3d t_align = lidar_init_pos - R_align * vins_init_pos; + + RCLCPP_INFO(rclcpp::get_logger("vins_estimator"), + "[LIDAR_INIT] Aligning VINS to lidar odom: t=[%.3f, %.3f, %.3f], yaw_diff=%.1f deg", + t_align.x(), t_align.y(), t_align.z(), + Utility::R2ypr(R_align).x() * 180.0 / M_PI); + + // Apply the alignment transform to all states in the sliding window + for (int i = 0; i <= WINDOW_SIZE; i++) + { + Ps[i] = R_align * Ps[i] + t_align; + Rs[i] = R_align * Rs[i]; + Vs[i] = R_align * Vs[i]; + } + + lidar_init_done = true; + lidar_init_pose_applied = true; + + RCLCPP_INFO(rclcpp::get_logger("vins_estimator"), + "[LIDAR_INIT] Initial pose set from lidar odom: pos=[%.3f, %.3f, %.3f]", + Ps[WINDOW_SIZE].x(), Ps[WINDOW_SIZE].y(), Ps[WINDOW_SIZE].z()); +} + +void Estimator::correctScaleWithLidarOdom(const Vector3d &lidar_pos, double lidar_time, double vins_time) +{ + // Ongoing gentle scale correction using lidar odometry as a drift-free reference. + // Compares the displacement since initialization between VINS and lidar to estimate scale drift. + if (solver_flag != NON_LINEAR) + return; + if (!lidar_init_done) + return; + + // Displacement since initialization + Vector3d vins_disp = Ps[WINDOW_SIZE] - vins_init_pos; + Vector3d lidar_disp = lidar_pos - lidar_init_pos; + + double vins_norm = vins_disp.norm(); + double lidar_norm = lidar_disp.norm(); + + // Need sufficient motion to estimate scale reliably + if (lidar_norm < LIDAR_INIT_POS_THRESHOLD) + return; + if (vins_norm < 0.5) + return; + + double scale_ratio = lidar_norm / vins_norm; + + // Reject only extreme scale ratios (allow wide range for initial drift correction) + if (scale_ratio < 0.1 || scale_ratio > 10.0) + { + RCLCPP_WARN(rclcpp::get_logger("vins_estimator"), + "[LIDAR_SCALE] Ratio out of bounds: %.3f (lidar: %.2f m, vins: %.2f m), skipping", + scale_ratio, lidar_norm, vins_norm); + return; + } + + // Adaptive correction: stronger factor when drift is large, gentle when close + double drift = std::abs(scale_ratio - 1.0); + double adaptive_factor = (drift > 0.5) ? std::min(0.3, LIDAR_SCALE_CORRECTION_FACTOR * 3.0) : LIDAR_SCALE_CORRECTION_FACTOR; + double correction = 1.0 + (scale_ratio - 1.0) * adaptive_factor; + + RCLCPP_INFO(rclcpp::get_logger("vins_estimator"), + "[LIDAR_SCALE] Scale correction: %.4f (lidar: %.2f m, vins: %.2f m)", + correction, lidar_norm, vins_norm); + + // Apply scale correction about the initialization origin so the initial pose stays fixed + for (int i = 0; i <= WINDOW_SIZE; i++) + { + Ps[i] = vins_init_pos + correction * (Ps[i] - vins_init_pos); + Vs[i] = Vs[i] * correction; + } + + f_manager.scaleDepth(correction); +} + +bool Estimator::getLidarInitStatus() +{ + return lidar_init_done; +} + +// ============================================================================ +// Lidar-assisted deep initialization +// ============================================================================ + +void Estimator::setLidarOdomBuffer(const std::vector &lidar_positions, + const std::vector &lidar_rotations, + const std::vector &lidar_timestamps) +{ + // Store a snapshot of the lidar odometry buffer for scale/pose estimation. + // Called from estimator_node before VINS initialization (during INITIAL phase). + lidar_pos_buf = lidar_positions; + lidar_rot_buf = lidar_rotations; + lidar_time_buf = lidar_timestamps; + lidar_init_samples = static_cast(lidar_pos_buf.size()); + + RCLCPP_INFO(rclcpp::get_logger("vins_estimator"), + "[LIDAR_DEEP_INIT] Received %d lidar samples for assisted init", + lidar_init_samples); +} + +double Estimator::estimateScaleFromLidar() +{ + // Estimate the metric scale by comparing lidar displacement with VINS + // visual structure displacement over the same time window. + // + // VINS visual SFM produces poses in an arbitrary scale. By comparing + // the total path length or net displacement with the lidar (which is + // already metric), we get the scale factor: s = lidar_disp / vins_disp. + // + // This must be called AFTER initialStructure() has computed visual poses + // but BEFORE visualInitialAlign() applies the IMU-aligned scale. + + if (lidar_pos_buf.size() < 10) + { + RCLCPP_WARN(rclcpp::get_logger("vins_estimator"), + "[LIDAR_DEEP_INIT] Not enough lidar samples (%zu) for scale estimation", + lidar_pos_buf.size()); + lidar_scale_valid = false; + return 1.0; + } + + // Compute lidar displacement over the buffered window + Vector3d lidar_start = lidar_pos_buf.front(); + Vector3d lidar_end = lidar_pos_buf.back(); + double lidar_disp = (lidar_end - lidar_start).norm(); + + // Compute VINS visual displacement (in visual SFM scale) + // Ps[] are set from visual SFM in initialStructure before visualInitialAlign + double vins_disp = 0.0; + int valid_frames = 0; + for (int i = 0; i <= frame_count; i++) + { + if (Ps[i].norm() > 0.01) + valid_frames++; + } + + if (valid_frames < 2) + { + RCLCPP_WARN(rclcpp::get_logger("vins_estimator"), + "[LIDAR_DEEP_INIT] Not enough VINS frames for scale estimation"); + lidar_scale_valid = false; + return 1.0; + } + + // Use the full window displacement + Vector3d vins_start = Ps[0]; + Vector3d vins_end = Ps[frame_count]; + vins_disp = (vins_end - vins_start).norm(); + + if (vins_disp < 0.01) + { + RCLCPP_WARN(rclcpp::get_logger("vins_estimator"), + "[LIDAR_DEEP_INIT] VINS displacement too small (%.4f)", vins_disp); + lidar_scale_valid = false; + return 1.0; + } + + double scale = lidar_disp / vins_disp; + + // Sanity check: scale should be positive and reasonable + if (scale < 0.01 || scale > 100.0) + { + RCLCPP_WARN(rclcpp::get_logger("vins_estimator"), + "[LIDAR_DEEP_INIT] Scale out of bounds: %.4f (lidar=%.3f, vins=%.3f), rejecting", + scale, lidar_disp, vins_disp); + lidar_scale_valid = false; + return 1.0; + } + + lidar_scale_estimate = scale; + lidar_scale_valid = true; + + RCLCPP_INFO(rclcpp::get_logger("vins_estimator"), + "[LIDAR_DEEP_INIT] Scale estimated from lidar: %.4f (lidar_disp=%.3f m, vins_disp=%.3f)", + scale, lidar_disp, vins_disp); + + return scale; +} + +void Estimator::applyLidarScalePrior(double scale) +{ + // Apply the lidar-estimated scale to the visual structure. + // This is called instead of (or in addition to) the IMU-based scale from + // LinearAlignment. The visual poses Ps[] are scaled to metric units. + + RCLCPP_INFO(rclcpp::get_logger("vins_estimator"), + "[LIDAR_DEEP_INIT] Applying lidar scale prior: %.4f", scale); + + // Scale all positions about the origin + for (int i = 0; i <= frame_count; i++) + { + Ps[i] = scale * Ps[i]; + } + + // Scale feature depths + for (auto &it_per_id : f_manager.feature) + { + it_per_id.used_num = it_per_id.feature_per_frame.size(); + if (!(it_per_id.used_num >= 2 && it_per_id.start_frame < WINDOW_SIZE - 2)) + continue; + it_per_id.estimated_depth *= scale; + } +} + +void Estimator::softAlignWithLidar() +{ + // Soft (yaw-only) alignment with lidar odometry after VINS initialization. + // + // Instead of applying a full rigid transform (which caused the yaw_diff + // catastrophe), this method: + // 1. Aligns only the yaw (heading) to match the lidar reference. + // 2. Translates the origin to the lidar position. + // 3. Does NOT change roll/pitch (VINS gravity alignment is more reliable). + // + // This avoids the large yaw_diff problem because it only corrects the + // heading, not the full rotation. + + if (solver_flag != NON_LINEAR) + return; + if (lidar_init_done) + return; + if (lidar_pos_buf.empty() || lidar_rot_buf.empty()) + { + RCLCPP_WARN(rclcpp::get_logger("vins_estimator"), + "[LIDAR_DEEP_INIT] No lidar buffer for soft alignment"); + return; + } + + // Use the latest lidar sample as the reference + Vector3d lidar_pos = lidar_pos_buf.back(); + Matrix3d lidar_rot = lidar_rot_buf.back(); + + // Record init state + lidar_init_pos = lidar_pos; + lidar_init_rot = lidar_rot; + vins_init_pos = Ps[WINDOW_SIZE]; + vins_init_rot = Rs[WINDOW_SIZE]; + + // Extract yaw from both VINS and lidar + Vector3d vins_ypr = Utility::R2ypr(Rs[WINDOW_SIZE]); + Vector3d lidar_ypr = Utility::R2ypr(lidar_rot); + + // Compute yaw-only correction + double yaw_diff = lidar_ypr.x() - vins_ypr.x(); + + RCLCPP_INFO(rclcpp::get_logger("vins_estimator"), + "[LIDAR_DEEP_INIT] Soft align: yaw_diff=%.1f deg, vins_yaw=%.1f, lidar_yaw=%.1f", + yaw_diff, vins_ypr.x(), lidar_ypr.x()); + + // Build rotation that only corrects yaw + Matrix3d R_yaw_correction = Utility::ypr2R(Vector3d(yaw_diff, 0, 0)); + + // Apply yaw correction + translation to all states + for (int i = 0; i <= WINDOW_SIZE; i++) + { + Ps[i] = R_yaw_correction * Ps[i]; + Rs[i] = R_yaw_correction * Rs[i]; + Vs[i] = R_yaw_correction * Vs[i]; + } + + // Translate origin to match lidar position + Vector3d t_correction = lidar_pos - Ps[WINDOW_SIZE]; + for (int i = 0; i <= WINDOW_SIZE; i++) + { + Ps[i] += t_correction; + } + + lidar_init_done = true; + lidar_init_pose_applied = true; + + // Update failure detection reference points to avoid false "big translation" + // triggers caused by the alignment changing Ps/RS in one step. + last_P = Ps[WINDOW_SIZE]; + last_R = Rs[WINDOW_SIZE]; + last_P0 = Ps[0]; + last_R0 = Rs[0]; + + RCLCPP_INFO(rclcpp::get_logger("vins_estimator"), + "[LIDAR_DEEP_INIT] Soft alignment done. VINS pos=[%.3f, %.3f, %.3f]", + Ps[WINDOW_SIZE].x(), Ps[WINDOW_SIZE].y(), Ps[WINDOW_SIZE].z()); +} diff --git a/vins_estimator/src/estimator.h b/vins_estimator/src/estimator.h index 2390aa0a9..c27fb3f37 100644 --- a/vins_estimator/src/estimator.h +++ b/vins_estimator/src/estimator.h @@ -8,8 +8,7 @@ #include "initial/initial_sfm.h" #include "initial/initial_alignment.h" #include "initial/initial_ex_rotation.h" -#include -#include +#include #include #include "factor/imu_factor.h" @@ -32,8 +31,21 @@ class Estimator // interface void processIMU(double t, const Vector3d &linear_acceleration, const Vector3d &angular_velocity); - void processImage(const map>>> &image, const std_msgs::Header &header); + void processImage(const map>>> &image, const std_msgs::msg::Header &header); void setReloFrame(double _frame_stamp, int _frame_index, vector &_match_points, Vector3d _relo_t, Matrix3d _relo_r); + void correctScaleWithAltitude(double mavlink_altitude, double mavlink_vz); + void initializeWithLidarOdom(const Vector3d &lidar_pos, const Matrix3d &lidar_rot, + double lidar_time, double vins_time); + void correctScaleWithLidarOdom(const Vector3d &lidar_pos, double lidar_time, double vins_time); + bool getLidarInitStatus(); + + // Lidar-assisted initialization (deep integration) + void setLidarOdomBuffer(const std::vector &lidar_positions, + const std::vector &lidar_rotations, + const std::vector &lidar_timestamps); + double estimateScaleFromLidar(); + void applyLidarScalePrior(double scale); + void softAlignWithLidar(); // internal void clearState(); @@ -80,7 +92,7 @@ class Estimator Matrix3d back_R0, last_R, last_R0; Vector3d back_P0, last_P, last_P0; - std_msgs::Header Headers[(WINDOW_SIZE + 1)]; + std_msgs::msg::Header Headers[(WINDOW_SIZE + 1)]; IntegrationBase *pre_integrations[(WINDOW_SIZE + 1)]; Vector3d acc_0, gyr_0; @@ -114,6 +126,10 @@ class Estimator double para_Td[1][1]; double para_Tr[1][1]; + // Time offset estimation gating + double last_td_opt_time; // wall time of last td optimization (seconds) + bool td_updated; // flag: td was optimized in current frame + int loop_window_index; MarginalizationInfo *last_marginalization_info; @@ -136,4 +152,22 @@ class Estimator Vector3d relo_relative_t; Quaterniond relo_relative_q; double relo_relative_yaw; + + // Lidar odometry initialization & scale correction + bool lidar_init_done; // true after initial pose/scale set from lidar + Vector3d lidar_init_pos; // lidar position at the moment of VINS init + Matrix3d lidar_init_rot; // lidar rotation at the moment of VINS init + double lidar_init_time; // timestamp of lidar init sample + Vector3d vins_init_pos; // VINS position when lidar init was applied + Matrix3d vins_init_rot; // VINS rotation when lidar init was applied + double vins_init_time; // VINS timestamp when lidar init was applied + bool lidar_init_pose_applied; // true after initial coordinate alignment applied + + // Lidar-assisted deep init: buffer of lidar poses for scale/pose estimation + std::vector lidar_pos_buf; // lidar positions over time + std::vector lidar_rot_buf; // lidar rotations over time + std::vector lidar_time_buf; // lidar timestamps + double lidar_scale_estimate; // scale estimated from lidar (gt/vins displacement) + bool lidar_scale_valid; // true if lidar_scale_estimate is reliable + int lidar_init_samples; // number of lidar samples collected before VINS init }; diff --git a/vins_estimator/src/estimator_node.cpp b/vins_estimator/src/estimator_node.cpp index 1297936ad..aa906a416 100644 --- a/vins_estimator/src/estimator_node.cpp +++ b/vins_estimator/src/estimator_node.cpp @@ -4,9 +4,16 @@ #include #include #include -#include -#include +#include +#include "rclcpp/rclcpp.hpp" +#include "cv_bridge/cv_bridge.hpp" #include +#include "geometry_msgs/msg/twist_stamped.hpp" +#include "nav_msgs/msg/odometry.hpp" +#include "sensor_msgs/msg/imu.hpp" +#include "sensor_msgs/msg/point_cloud.hpp" +#include "std_msgs/msg/bool.hpp" +#include "std_msgs/msg/header.hpp" #include "estimator.h" #include "parameters.h" @@ -17,9 +24,9 @@ Estimator estimator; std::condition_variable con; double current_time = -1; -queue imu_buf; -queue feature_buf; -queue relo_buf; +queue imu_buf; +queue feature_buf; +queue relo_buf; int sum_of_wait = 0; std::mutex m_buf; @@ -38,10 +45,44 @@ Eigen::Vector3d gyr_0; bool init_feature = 0; bool init_imu = 1; double last_imu_t = 0; +static bool first_feature_logged = false; +static int feature_msg_count = 0; +static int imu_msg_count = 0; + +// IMU filtering variables +Eigen::Vector3d prev_acc(0, 0, 0); +Eigen::Vector3d filtered_acc(0, 0, 0); +bool acc_initialized = false; +const double MAX_ACC_CHANGE = 50.0; +const double ACC_FILTER_ALPHA = 0.8; + +// Altitude-based scale correction from MAVLink velocity +double mavlink_altitude = 0.0; +double mavlink_vz = 0.0; +bool mavlink_odom_received = false; +std::mutex m_odom; + +// Lidar odometry initialization & scale correction +struct LidarOdomSample +{ + double time; + Eigen::Vector3d pos; + Eigen::Matrix3d rot; +}; +std::queue lidar_odom_buf; +std::vector lidar_odom_history; // full history for deep init +std::mutex m_lidar; +bool lidar_odom_received = false; +double lidar_first_recv_time = -1.0; + +static inline double stamp_to_sec(const builtin_interfaces::msg::Time &stamp) +{ + return static_cast(stamp.sec) + static_cast(stamp.nanosec) * 1e-9; +} -void predict(const sensor_msgs::ImuConstPtr &imu_msg) +void predict(const sensor_msgs::msg::Imu::ConstSharedPtr &imu_msg) { - double t = imu_msg->header.stamp.toSec(); + double t = stamp_to_sec(imu_msg->header.stamp); if (init_imu) { latest_time = t; @@ -55,6 +96,26 @@ void predict(const sensor_msgs::ImuConstPtr &imu_msg) double dy = imu_msg->linear_acceleration.y; double dz = imu_msg->linear_acceleration.z; Eigen::Vector3d linear_acceleration{dx, dy, dz}; + + // Accelerometer filtering: outlier rejection + exponential smoothing + if (!acc_initialized) { + prev_acc = linear_acceleration; + filtered_acc = linear_acceleration; + acc_initialized = true; + } else { + // Step 1: Outlier detection - reject sudden spikes + Eigen::Vector3d acc_diff = linear_acceleration - prev_acc; + if (acc_diff.norm() > MAX_ACC_CHANGE) { + RCLCPP_WARN(rclcpp::get_logger("vins_estimator"), "IMU accelerometer spike detected: %.2f m/s² change, using filtered value", acc_diff.norm()); + linear_acceleration = filtered_acc; + } + + // Step 2: Exponential moving average filter for smoothing + filtered_acc = ACC_FILTER_ALPHA * filtered_acc + (1.0 - ACC_FILTER_ALPHA) * linear_acceleration; + linear_acceleration = filtered_acc; + + prev_acc = linear_acceleration; + } double rx = imu_msg->angular_velocity.x; double ry = imu_msg->angular_velocity.y; @@ -89,72 +150,78 @@ void update() acc_0 = estimator.acc_0; gyr_0 = estimator.gyr_0; - queue tmp_imu_buf = imu_buf; - for (sensor_msgs::ImuConstPtr tmp_imu_msg; !tmp_imu_buf.empty(); tmp_imu_buf.pop()) + queue tmp_imu_buf = imu_buf; + for (sensor_msgs::msg::Imu::ConstSharedPtr tmp_imu_msg; !tmp_imu_buf.empty(); tmp_imu_buf.pop()) predict(tmp_imu_buf.front()); } -std::vector, sensor_msgs::PointCloudConstPtr>> +std::vector, sensor_msgs::msg::PointCloud::ConstSharedPtr>> getMeasurements() { - std::vector, sensor_msgs::PointCloudConstPtr>> measurements; + std::vector, sensor_msgs::msg::PointCloud::ConstSharedPtr>> measurements; while (true) { if (imu_buf.empty() || feature_buf.empty()) return measurements; - if (!(imu_buf.back()->header.stamp.toSec() > feature_buf.front()->header.stamp.toSec() + estimator.td)) + if (!(stamp_to_sec(imu_buf.back()->header.stamp) > stamp_to_sec(feature_buf.front()->header.stamp) + estimator.td)) { - //ROS_WARN("wait for imu, only should happen at the beginning"); sum_of_wait++; return measurements; } - if (!(imu_buf.front()->header.stamp.toSec() < feature_buf.front()->header.stamp.toSec() + estimator.td)) + if (!(stamp_to_sec(imu_buf.front()->header.stamp) < stamp_to_sec(feature_buf.front()->header.stamp) + estimator.td)) { - ROS_WARN("throw img, only should happen at the beginning"); + RCLCPP_WARN(rclcpp::get_logger("vins_estimator"), "throw img, only should happen at the beginning"); feature_buf.pop(); continue; } - sensor_msgs::PointCloudConstPtr img_msg = feature_buf.front(); + sensor_msgs::msg::PointCloud::ConstSharedPtr img_msg = feature_buf.front(); feature_buf.pop(); - std::vector IMUs; - while (imu_buf.front()->header.stamp.toSec() < img_msg->header.stamp.toSec() + estimator.td) + std::vector IMUs; + while (stamp_to_sec(imu_buf.front()->header.stamp) < stamp_to_sec(img_msg->header.stamp) + estimator.td) { IMUs.emplace_back(imu_buf.front()); imu_buf.pop(); } IMUs.emplace_back(imu_buf.front()); if (IMUs.empty()) - ROS_WARN("no imu between two image"); + RCLCPP_WARN(rclcpp::get_logger("vins_estimator"), "no imu between two image"); measurements.emplace_back(IMUs, img_msg); } return measurements; } -void imu_callback(const sensor_msgs::ImuConstPtr &imu_msg) +void imu_callback(const sensor_msgs::msg::Imu::ConstSharedPtr &imu_msg) { - if (imu_msg->header.stamp.toSec() <= last_imu_t) + imu_msg_count++; + double t = stamp_to_sec(imu_msg->header.stamp); + if (t <= last_imu_t) { - ROS_WARN("imu message in disorder!"); + RCLCPP_WARN(rclcpp::get_logger("vins_estimator"), "imu message in disorder!"); return; } - last_imu_t = imu_msg->header.stamp.toSec(); + last_imu_t = t; + if (imu_msg_count == 1 || imu_msg_count % 500 == 0) + { + RCLCPP_INFO( + rclcpp::get_logger("vins_estimator"), + "[INPUT] imu #%d stamp=%.6f queue_imu=%zu queue_feat=%zu", + imu_msg_count, t, imu_buf.size(), feature_buf.size()); + } m_buf.lock(); imu_buf.push(imu_msg); m_buf.unlock(); con.notify_one(); - last_imu_t = imu_msg->header.stamp.toSec(); - { std::lock_guard lg(m_state); predict(imu_msg); - std_msgs::Header header = imu_msg->header; + std_msgs::msg::Header header = imu_msg->header; header.frame_id = "world"; if (estimator.solver_flag == Estimator::SolverFlag::NON_LINEAR) pubLatestOdometry(tmp_P, tmp_Q, tmp_V, header); @@ -162,12 +229,28 @@ void imu_callback(const sensor_msgs::ImuConstPtr &imu_msg) } -void feature_callback(const sensor_msgs::PointCloudConstPtr &feature_msg) +void feature_callback(const sensor_msgs::msg::PointCloud::ConstSharedPtr &feature_msg) { + feature_msg_count++; + if (!first_feature_logged) + { + first_feature_logged = true; + RCLCPP_INFO( + rclcpp::get_logger("vins_estimator"), + "[INPUT] first feature packet received: stamp=%.6f points=%zu", + stamp_to_sec(feature_msg->header.stamp), feature_msg->points.size()); + } + else if (feature_msg_count % 20 == 0) + { + RCLCPP_INFO( + rclcpp::get_logger("vins_estimator"), + "[INPUT] feature packet #%d stamp=%.6f points=%zu", + feature_msg_count, stamp_to_sec(feature_msg->header.stamp), feature_msg->points.size()); + } if (!init_feature) { - //skip the first detected feature, which doesn't contain optical flow speed init_feature = 1; + RCLCPP_INFO(rclcpp::get_logger("vins_estimator"), "[STATE] estimator primed on first feature packet"); return; } m_buf.lock(); @@ -176,11 +259,11 @@ void feature_callback(const sensor_msgs::PointCloudConstPtr &feature_msg) con.notify_one(); } -void restart_callback(const std_msgs::BoolConstPtr &restart_msg) +void restart_callback(const std_msgs::msg::Bool::ConstSharedPtr &restart_msg) { if (restart_msg->data == true) { - ROS_WARN("restart the estimator!"); + RCLCPP_WARN(rclcpp::get_logger("vins_estimator"), "restart the estimator!"); m_buf.lock(); while(!feature_buf.empty()) feature_buf.pop(); @@ -197,20 +280,106 @@ void restart_callback(const std_msgs::BoolConstPtr &restart_msg) return; } -void relocalization_callback(const sensor_msgs::PointCloudConstPtr &points_msg) +void relocalization_callback(const sensor_msgs::msg::PointCloud::ConstSharedPtr &points_msg) { - //printf("relocalization callback! \n"); m_buf.lock(); relo_buf.push(points_msg); m_buf.unlock(); } +void mavlink_velocity_callback(const geometry_msgs::msg::TwistStamped::ConstSharedPtr &vel_msg) +{ + m_odom.lock(); + mavlink_vz = vel_msg->twist.linear.z; + + static double last_vel_time = -1; + double current_vel_time = stamp_to_sec(vel_msg->header.stamp); + + if (last_vel_time > 0) { + double dt = current_vel_time - last_vel_time; + if (dt > 0 && dt < 0.5) { + mavlink_altitude += mavlink_vz * dt; + } + } + last_vel_time = current_vel_time; + + mavlink_odom_received = true; + m_odom.unlock(); +} + +void lidar_odom_callback(const nav_msgs::msg::Odometry::ConstSharedPtr &odom_msg) +{ + LidarOdomSample sample; + sample.time = stamp_to_sec(odom_msg->header.stamp); + sample.pos = Eigen::Vector3d( + odom_msg->pose.pose.position.x, + odom_msg->pose.pose.position.y, + odom_msg->pose.pose.position.z); + Eigen::Quaterniond q( + odom_msg->pose.pose.orientation.w, + odom_msg->pose.pose.orientation.x, + odom_msg->pose.pose.orientation.y, + odom_msg->pose.pose.orientation.z); + sample.rot = q.toRotationMatrix(); + + m_lidar.lock(); + lidar_odom_buf.push(sample); + // Keep buffer bounded — store at most 200 samples (~20 s at 10 Hz) + while (lidar_odom_buf.size() > 200) + lidar_odom_buf.pop(); + // Also accumulate full history for deep init (capped at 600 samples ~60s) + lidar_odom_history.push_back(sample); + if (lidar_odom_history.size() > 600) + lidar_odom_history.erase(lidar_odom_history.begin()); + if (lidar_first_recv_time < 0) + lidar_first_recv_time = sample.time; + lidar_odom_received = true; + m_lidar.unlock(); +} + +// Find the lidar odometry sample closest to the given timestamp. +// Returns true if a sample within LIDAR_SYNC_MAX_DT was found and removes older samples. +bool getLidarOdomAtTime(double query_time, LidarOdomSample &out) +{ + std::lock_guard lk(m_lidar); + if (lidar_odom_buf.empty()) + return false; + + // Drop samples that are older than the query time minus the sync window + while (!lidar_odom_buf.empty() && lidar_odom_buf.front().time < query_time - LIDAR_SYNC_MAX_DT) + lidar_odom_buf.pop(); + + if (lidar_odom_buf.empty()) + return false; + + // Find the closest sample in the remaining buffer + LidarOdomSample best = lidar_odom_buf.front(); + double best_dt = std::abs(best.time - query_time); + std::queue tmp = lidar_odom_buf; + while (!tmp.empty()) + { + double dt = std::abs(tmp.front().time - query_time); + if (dt < best_dt) + { + best = tmp.front(); + best_dt = dt; + } + tmp.pop(); + } + + if (best_dt > LIDAR_SYNC_MAX_DT) + return false; + + out = best; + return true; +} + // thread: visual-inertial odometry void process() { while (true) { - std::vector, sensor_msgs::PointCloudConstPtr>> measurements; + std::vector, sensor_msgs::msg::PointCloud::ConstSharedPtr>> measurements; std::unique_lock lk(m_buf); con.wait(lk, [&] { @@ -224,33 +393,61 @@ void process() double dx = 0, dy = 0, dz = 0, rx = 0, ry = 0, rz = 0; for (auto &imu_msg : measurement.first) { - double t = imu_msg->header.stamp.toSec(); - double img_t = img_msg->header.stamp.toSec() + estimator.td; + double t = stamp_to_sec(imu_msg->header.stamp); + double img_t = stamp_to_sec(img_msg->header.stamp) + estimator.td; if (t <= img_t) { if (current_time < 0) current_time = t; double dt = t - current_time; - ROS_ASSERT(dt >= 0); + assert(dt >= 0); current_time = t; dx = imu_msg->linear_acceleration.x; dy = imu_msg->linear_acceleration.y; dz = imu_msg->linear_acceleration.z; + + // Apply accelerometer filtering + Eigen::Vector3d raw_acc(dx, dy, dz); + if (!acc_initialized) { + prev_acc = raw_acc; + filtered_acc = raw_acc; + acc_initialized = true; + } else { + Eigen::Vector3d acc_diff = raw_acc - prev_acc; + if (acc_diff.norm() > MAX_ACC_CHANGE) { + RCLCPP_WARN(rclcpp::get_logger("vins_estimator"), "IMU spike in process(): %.2f m/s², using filtered", acc_diff.norm()); + raw_acc = filtered_acc; + } + filtered_acc = ACC_FILTER_ALPHA * filtered_acc + (1.0 - ACC_FILTER_ALPHA) * raw_acc; + raw_acc = filtered_acc; + prev_acc = raw_acc; + } + dx = raw_acc.x(); + dy = raw_acc.y(); + dz = raw_acc.z(); + rx = imu_msg->angular_velocity.x; ry = imu_msg->angular_velocity.y; rz = imu_msg->angular_velocity.z; estimator.processIMU(dt, Vector3d(dx, dy, dz), Vector3d(rx, ry, rz)); - //printf("imu: dt:%f a: %f %f %f w: %f %f %f\n",dt, dx, dy, dz, rx, ry, rz); } else { double dt_1 = img_t - current_time; double dt_2 = t - img_t; + + if (dt_1 < 0) + { + RCLCPP_WARN(rclcpp::get_logger("vins_estimator"), "Negative dt_1 detected: %.6f (img_t=%.6f, current_time=%.6f). Skipping frame.", + dt_1, img_t, current_time); + current_time = img_t; + continue; + } + current_time = img_t; - ROS_ASSERT(dt_1 >= 0); - ROS_ASSERT(dt_2 >= 0); - ROS_ASSERT(dt_1 + dt_2 > 0); + assert(dt_2 >= 0); + assert(dt_1 + dt_2 > 0); double w1 = dt_2 / (dt_1 + dt_2); double w2 = dt_1 / (dt_1 + dt_2); dx = w1 * dx + w2 * imu_msg->linear_acceleration.x; @@ -260,11 +457,10 @@ void process() ry = w1 * ry + w2 * imu_msg->angular_velocity.y; rz = w1 * rz + w2 * imu_msg->angular_velocity.z; estimator.processIMU(dt_1, Vector3d(dx, dy, dz), Vector3d(rx, ry, rz)); - //printf("dimu: dt:%f a: %f %f %f w: %f %f %f\n",dt_1, dx, dy, dz, rx, ry, rz); } } // set relocalization frame - sensor_msgs::PointCloudConstPtr relo_msg = NULL; + sensor_msgs::msg::PointCloud::ConstSharedPtr relo_msg = NULL; while (!relo_buf.empty()) { relo_msg = relo_buf.front(); @@ -273,7 +469,7 @@ void process() if (relo_msg != NULL) { vector match_points; - double frame_stamp = relo_msg->header.stamp.toSec(); + double frame_stamp = stamp_to_sec(relo_msg->header.stamp); for (unsigned int i = 0; i < relo_msg->points.size(); i++) { Vector3d u_v_id; @@ -290,7 +486,7 @@ void process() estimator.setReloFrame(frame_stamp, frame_index, match_points, relo_t, relo_r); } - ROS_DEBUG("processing vision data with stamp %f \n", img_msg->header.stamp.toSec()); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "processing vision data with stamp %f \n", stamp_to_sec(img_msg->header.stamp)); TicToc t_s; map>>> image; @@ -306,17 +502,68 @@ void process() double p_v = img_msg->channels[2].values[i]; double velocity_x = img_msg->channels[3].values[i]; double velocity_y = img_msg->channels[4].values[i]; - ROS_ASSERT(z == 1); + assert(z == 1); Eigen::Matrix xyz_uv_velocity; xyz_uv_velocity << x, y, z, p_u, p_v, velocity_x, velocity_y; image[feature_id].emplace_back(camera_id, xyz_uv_velocity); } estimator.processImage(image, img_msg->header); + + // Apply altitude-based scale correction using MAVLink odometry + m_odom.lock(); + if (mavlink_odom_received && estimator.solver_flag == Estimator::NON_LINEAR) + { + estimator.correctScaleWithAltitude(mavlink_altitude, mavlink_vz); + } + m_odom.unlock(); + + // Lidar odometry: deep integration with initialization + ongoing scale correction + if (ENABLE_LIDAR_INIT && lidar_odom_received) + { + double vins_time = stamp_to_sec(img_msg->header.stamp); + + if (estimator.solver_flag == Estimator::INITIAL) + { + // ─── Deep init phase: feed lidar buffer to estimator ─── + // Before VINS initializes, provide the accumulated lidar history + // so the estimator can estimate metric scale and initial pose. + m_lidar.lock(); + std::vector lpos; + std::vector lrot; + std::vector ltime; + for (const auto &s : lidar_odom_history) + { + lpos.push_back(s.pos); + lrot.push_back(s.rot); + ltime.push_back(s.time); + } + m_lidar.unlock(); + estimator.setLidarOdomBuffer(lpos, lrot, ltime); + } + else if (estimator.solver_flag == Estimator::NON_LINEAR) + { + LidarOdomSample lidar_sample; + if (getLidarOdomAtTime(vins_time, lidar_sample)) + { + if (!estimator.getLidarInitStatus()) + { + // Soft alignment: yaw-only + translation, no full rigid transform + estimator.softAlignWithLidar(); + } + else + { + // Ongoing gentle scale correction against lidar reference + estimator.correctScaleWithLidarOdom( + lidar_sample.pos, lidar_sample.time, vins_time); + } + } + } + } double whole_t = t_s.toc(); printStatistics(estimator, whole_t); - std_msgs::Header header = img_msg->header; - header.frame_id = "world"; + std_msgs::msg::Header header = img_msg->header; + header.frame_id = WORLD_FRAME_ID; pubOdometry(estimator, header); pubKeyPoses(estimator, header); @@ -326,7 +573,6 @@ void process() pubKeyframe(estimator); if (relo_msg != NULL) pubRelocalization(estimator); - //ROS_ERROR("end: %f, at %f", img_msg->header.stamp.toSec(), ros::Time::now().toSec()); } m_estimator.unlock(); m_buf.lock(); @@ -340,25 +586,39 @@ void process() int main(int argc, char **argv) { - ros::init(argc, argv, "vins_estimator"); - ros::NodeHandle n("~"); - ros::console::set_logger_level(ROSCONSOLE_DEFAULT_NAME, ros::console::levels::Info); - readParameters(n); + rclcpp::init(argc, argv); + auto node = std::make_shared("vins_estimator"); + readParameters(node); estimator.setParameter(); #ifdef EIGEN_DONT_PARALLELIZE - ROS_DEBUG("EIGEN_DONT_PARALLELIZE"); + RCLCPP_DEBUG(node->get_logger(), "EIGEN_DONT_PARALLELIZE"); #endif - ROS_WARN("waiting for image and imu..."); - - registerPub(n); - - ros::Subscriber sub_imu = n.subscribe(IMU_TOPIC, 2000, imu_callback, ros::TransportHints().tcpNoDelay()); - ros::Subscriber sub_image = n.subscribe("/feature_tracker/feature", 2000, feature_callback); - ros::Subscriber sub_restart = n.subscribe("/feature_tracker/restart", 2000, restart_callback); - ros::Subscriber sub_relo_points = n.subscribe("/pose_graph/match_points", 2000, relocalization_callback); + RCLCPP_WARN(node->get_logger(), "waiting for image and imu..."); + RCLCPP_INFO(node->get_logger(), "[START] vins_estimator is up and waiting for synchronized measurement pairs"); + + registerPub(node); + + // QoS for sensor data (best effort replaces tcpNoDelay) + rclcpp::QoS sensor_qos(2000); + sensor_qos.best_effort(); + + auto sub_imu = node->create_subscription( + IMU_TOPIC, sensor_qos, imu_callback); + auto sub_image = node->create_subscription( + "/feature_tracker/feature", 2000, feature_callback); + auto sub_restart = node->create_subscription( + "/feature_tracker/restart", 2000, restart_callback); + auto sub_relo_points = node->create_subscription( + "/pose_graph/match_points", 2000, relocalization_callback); + auto sub_mavlink_vel = node->create_subscription( + "/mavros/local_position/velocity_local", 100, mavlink_velocity_callback); + auto sub_lidar_odom = node->create_subscription( + LIDAR_ODOM_TOPIC, rclcpp::QoS(200).best_effort(), lidar_odom_callback); + RCLCPP_INFO(node->get_logger(), "[START] subscribed IMU=%s | feature=/feature_tracker/feature | relo=/pose_graph/match_points | lidar=%s", + IMU_TOPIC.c_str(), LIDAR_ODOM_TOPIC.c_str()); std::thread measurement_process{process}; - ros::spin(); - + rclcpp::spin(node); + rclcpp::shutdown(); return 0; } diff --git a/vins_estimator/src/factor/imu_factor.h b/vins_estimator/src/factor/imu_factor.h index 355264331..7819c71eb 100644 --- a/vins_estimator/src/factor/imu_factor.h +++ b/vins_estimator/src/factor/imu_factor.h @@ -1,6 +1,7 @@ #pragma once -#include +#include #include +#include "rclcpp/rclcpp.hpp" #include #include "../utility/utility.h" @@ -78,7 +79,7 @@ class IMUFactor : public ceres::SizedCostFunction<15, 7, 9, 7, 9> if (pre_integration->jacobian.maxCoeff() > 1e8 || pre_integration->jacobian.minCoeff() < -1e8) { - ROS_WARN("numerical unstable in preintegration"); + RCLCPP_WARN(rclcpp::get_logger("vins_estimator"), "numerical unstable in preintegration"); //std::cout << pre_integration->jacobian << std::endl; /// ROS_BREAK(); } @@ -104,7 +105,7 @@ class IMUFactor : public ceres::SizedCostFunction<15, 7, 9, 7, 9> if (jacobian_pose_i.maxCoeff() > 1e8 || jacobian_pose_i.minCoeff() < -1e8) { - ROS_WARN("numerical unstable in preintegration"); + RCLCPP_WARN(rclcpp::get_logger("vins_estimator"), "numerical unstable in preintegration"); //std::cout << sqrt_info << std::endl; //ROS_BREAK(); } diff --git a/vins_estimator/src/factor/marginalization_factor.cpp b/vins_estimator/src/factor/marginalization_factor.cpp index 7e073c01f..1d70d391e 100644 --- a/vins_estimator/src/factor/marginalization_factor.cpp +++ b/vins_estimator/src/factor/marginalization_factor.cpp @@ -70,7 +70,7 @@ void ResidualBlockInfo::Evaluate() MarginalizationInfo::~MarginalizationInfo() { - //ROS_WARN("release marginlizationinfo"); + //RCLCPP_WARN(rclcpp::get_logger("vins_estimator"), "release marginlizationinfo"); for (auto it = parameter_block_data.begin(); it != parameter_block_data.end(); ++it) delete[] it->second; @@ -224,7 +224,7 @@ void MarginalizationInfo::marginalize() b.segment(idx_i, size_i) += jacobian_i.transpose() * it->residuals; } } - ROS_INFO("summing up costs %f ms", t_summing.toc()); + RCLCPP_INFO(rclcpp::get_logger("vins_estimator"), "summing up costs %f ms", t_summing.toc()); */ //multi thread @@ -249,8 +249,8 @@ void MarginalizationInfo::marginalize() int ret = pthread_create( &tids[i], NULL, ThreadsConstructA ,(void*)&(threadsstruct[i])); if (ret != 0) { - ROS_WARN("pthread_create error"); - ROS_BREAK(); + RCLCPP_WARN(rclcpp::get_logger("vins_estimator"), "pthread_create error"); + std::abort(); } } for( int i = NUM_THREADS - 1; i >= 0; i--) @@ -260,7 +260,7 @@ void MarginalizationInfo::marginalize() b += threadsstruct[i].b; } //ROS_DEBUG("thread summing up costs %f ms", t_thread_summing.toc()); - //ROS_INFO("A diff %f , b diff %f ", (A - tmp_A).sum(), (b - tmp_b).sum()); + //RCLCPP_INFO(rclcpp::get_logger("vins_estimator"), "A diff %f , b diff %f ", (A - tmp_A).sum(), (b - tmp_b).sum()); //TODO diff --git a/vins_estimator/src/factor/marginalization_factor.h b/vins_estimator/src/factor/marginalization_factor.h index 1edcef64d..26f2eab36 100644 --- a/vins_estimator/src/factor/marginalization_factor.h +++ b/vins_estimator/src/factor/marginalization_factor.h @@ -1,7 +1,7 @@ #pragma once -#include -#include +#include "rclcpp/rclcpp.hpp" +#include #include #include #include diff --git a/vins_estimator/src/factor/pose_local_parameterization.h b/vins_estimator/src/factor/pose_local_parameterization.h index b9d856c1a..2882a2e3b 100644 --- a/vins_estimator/src/factor/pose_local_parameterization.h +++ b/vins_estimator/src/factor/pose_local_parameterization.h @@ -2,6 +2,7 @@ #include #include +#include "camodocal/gpl/ceres_local_parameterization_compat.h" #include "../utility/utility.h" class PoseLocalParameterization : public ceres::LocalParameterization diff --git a/vins_estimator/src/factor/projection_factor.h b/vins_estimator/src/factor/projection_factor.h index 92de6edb6..a03fb7d79 100644 --- a/vins_estimator/src/factor/projection_factor.h +++ b/vins_estimator/src/factor/projection_factor.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include #include "../utility/utility.h" diff --git a/vins_estimator/src/factor/projection_td_factor.h b/vins_estimator/src/factor/projection_td_factor.h index d797d2618..bf11f88ac 100644 --- a/vins_estimator/src/factor/projection_td_factor.h +++ b/vins_estimator/src/factor/projection_td_factor.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include #include "../utility/utility.h" diff --git a/vins_estimator/src/feature_manager.cpp b/vins_estimator/src/feature_manager.cpp index 7d5aed9d8..03e11b10a 100644 --- a/vins_estimator/src/feature_manager.cpp +++ b/vins_estimator/src/feature_manager.cpp @@ -44,8 +44,8 @@ int FeatureManager::getFeatureCount() bool FeatureManager::addFeatureCheckParallax(int frame_count, const map>>> &image, double td) { - ROS_DEBUG("input feature: %d", (int)image.size()); - ROS_DEBUG("num of feature: %d", getFeatureCount()); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "input feature: %d", (int)image.size()); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "num of feature: %d", getFeatureCount()); double parallax_sum = 0; int parallax_num = 0; last_track_num = 0; @@ -90,30 +90,30 @@ bool FeatureManager::addFeatureCheckParallax(int frame_count, const map= MIN_PARALLAX; } } void FeatureManager::debugShow() { - ROS_DEBUG("debug show"); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "debug show"); for (auto &it : feature) { - ROS_ASSERT(it.feature_per_frame.size() != 0); - ROS_ASSERT(it.start_frame >= 0); - ROS_ASSERT(it.used_num >= 0); + assert(it.feature_per_frame.size() != 0); + assert(it.start_frame >= 0); + assert(it.used_num >= 0); - ROS_DEBUG("%d,%d,%d ", it.feature_id, it.used_num, it.start_frame); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "%d,%d,%d ", it.feature_id, it.used_num, it.start_frame); int sum = 0; for (auto &j : it.feature_per_frame) { - ROS_DEBUG("%d,", int(j.is_used)); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "%d,", int(j.is_used)); sum += j.is_used; printf("(%lf,%lf) ",j.point(0), j.point(1)); } - ROS_ASSERT(it.used_num == sum); + assert(it.used_num == sum); } } @@ -211,7 +211,7 @@ void FeatureManager::triangulate(Vector3d Ps[], Vector3d tic[], Matrix3d ric[]) continue; int imu_i = it_per_id.start_frame, imu_j = imu_i - 1; - ROS_ASSERT(NUM_OF_CAM == 1); + assert(NUM_OF_CAM == 1); Eigen::MatrixXd svd_A(2 * it_per_id.feature_per_frame.size(), 4); int svd_idx = 0; @@ -239,7 +239,7 @@ void FeatureManager::triangulate(Vector3d Ps[], Vector3d tic[], Matrix3d ric[]) if (imu_i == imu_j) continue; } - ROS_ASSERT(svd_idx == svd_A.rows()); + assert(svd_idx == svd_A.rows()); Eigen::Vector4d svd_V = Eigen::JacobiSVD(svd_A, Eigen::ComputeThinV).matrixV().rightCols<1>(); double svd_method = svd_V[2] / svd_V[3]; //it_per_id->estimated_depth = -b / A; @@ -258,7 +258,7 @@ void FeatureManager::triangulate(Vector3d Ps[], Vector3d tic[], Matrix3d ric[]) void FeatureManager::removeOutlier() { - ROS_BREAK(); + std::abort(); int i = -1; for (auto it = feature.begin(), it_next = feature.begin(); it != feature.end(); it = it_next) @@ -272,6 +272,19 @@ void FeatureManager::removeOutlier() } } +void FeatureManager::scaleDepth(double scale) +{ + // Scale all feature depths (inverse depth parameterization) + for (auto &it_per_id : feature) + { + if (it_per_id.estimated_depth > 0) + { + // Inverse depth: 1/d, so scaling distance means dividing inverse depth + it_per_id.estimated_depth = it_per_id.estimated_depth / scale; + } + } +} + void FeatureManager::removeBackShiftDepth(Eigen::Matrix3d marg_R, Eigen::Vector3d marg_P, Eigen::Matrix3d new_R, Eigen::Vector3d new_P) { for (auto it = feature.begin(), it_next = feature.begin(); diff --git a/vins_estimator/src/feature_manager.h b/vins_estimator/src/feature_manager.h index 4e5d3ce21..ef1eb7eea 100644 --- a/vins_estimator/src/feature_manager.h +++ b/vins_estimator/src/feature_manager.h @@ -10,8 +10,9 @@ using namespace std; #include using namespace Eigen; -#include -#include +#include + +#include "rclcpp/rclcpp.hpp" #include "parameters.h" @@ -90,6 +91,7 @@ class FeatureManager void removeBack(); void removeFront(int frame_count); void removeOutlier(); + void scaleDepth(double scale); list feature; int last_track_num; diff --git a/vins_estimator/src/initial/initial_aligment.cpp b/vins_estimator/src/initial/initial_aligment.cpp index c2f287c9f..bd78a39ea 100644 --- a/vins_estimator/src/initial/initial_aligment.cpp +++ b/vins_estimator/src/initial/initial_aligment.cpp @@ -24,7 +24,7 @@ void solveGyroscopeBias(map &all_image_frame, Vector3d* Bgs) } delta_bg = A.ldlt().solve(b); - ROS_WARN_STREAM("gyroscope bias initial calibration " << delta_bg.transpose()); + RCLCPP_WARN_STREAM(rclcpp::get_logger("vins_estimator"), "gyroscope bias initial calibration " << delta_bg.transpose()); for (int i = 0; i <= WINDOW_SIZE; i++) Bgs[i] += delta_bg; @@ -178,9 +178,9 @@ bool LinearAlignment(map &all_image_frame, Vector3d &g, Vect b = b * 1000.0; x = A.ldlt().solve(b); double s = x(n_state - 1) / 100.0; - ROS_DEBUG("estimated scale: %f", s); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "estimated scale: %f", s); g = x.segment<3>(n_state - 4); - ROS_DEBUG_STREAM(" result g " << g.norm() << " " << g.transpose()); + RCLCPP_DEBUG_STREAM(rclcpp::get_logger("vins_estimator"), " result g " << g.norm() << " " << g.transpose()); if(fabs(g.norm() - G.norm()) > 1.0 || s < 0) { return false; @@ -189,7 +189,7 @@ bool LinearAlignment(map &all_image_frame, Vector3d &g, Vect RefineGravity(all_image_frame, g, x); s = (x.tail<1>())(0) / 100.0; (x.tail<1>())(0) = s; - ROS_DEBUG_STREAM(" refine " << g.norm() << " " << g.transpose()); + RCLCPP_DEBUG_STREAM(rclcpp::get_logger("vins_estimator"), " refine " << g.norm() << " " << g.transpose()); if(s < 0.0 ) return false; else diff --git a/vins_estimator/src/initial/initial_alignment.h b/vins_estimator/src/initial/initial_alignment.h index 49bc466ea..6efe9aa56 100644 --- a/vins_estimator/src/initial/initial_alignment.h +++ b/vins_estimator/src/initial/initial_alignment.h @@ -3,7 +3,7 @@ #include #include "../factor/imu_factor.h" #include "../utility/utility.h" -#include +#include "rclcpp/rclcpp.hpp" #include #include "../feature_manager.h" diff --git a/vins_estimator/src/initial/initial_ex_rotation.cpp b/vins_estimator/src/initial/initial_ex_rotation.cpp index dc99d1c4f..bbbc851e4 100644 --- a/vins_estimator/src/initial/initial_ex_rotation.cpp +++ b/vins_estimator/src/initial/initial_ex_rotation.cpp @@ -24,7 +24,8 @@ bool InitialEXRotation::CalibrationExRotation(vector> c Quaterniond r2(Rc_g[i]); double angular_distance = 180 / M_PI * r1.angularDistance(r2); - ROS_DEBUG( + RCLCPP_DEBUG( + rclcpp::get_logger("vins_estimator"), "%d %f", i, angular_distance); double huber = angular_distance > 5.0 ? 5.0 / angular_distance : 1.0; @@ -120,7 +121,7 @@ double InitialEXRotation::testTriangulation(const vector &l, if (p_3d_l(2) > 0 && p_3d_r(2) > 0) front_count++; } - ROS_DEBUG("MotionEstimator: %f", 1.0 * front_count / pointcloud.cols); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "MotionEstimator: %f", 1.0 * front_count / pointcloud.cols); return 1.0 * front_count / pointcloud.cols; } diff --git a/vins_estimator/src/initial/initial_ex_rotation.h b/vins_estimator/src/initial/initial_ex_rotation.h index 902b6fa1a..857d11448 100644 --- a/vins_estimator/src/initial/initial_ex_rotation.h +++ b/vins_estimator/src/initial/initial_ex_rotation.h @@ -8,7 +8,7 @@ using namespace std; #include using namespace Eigen; -#include +#include "rclcpp/rclcpp.hpp" /* This class help you to calibrate extrinsic rotation between imu and camera when your totally don't konw the extrinsic parameter */ class InitialEXRotation diff --git a/vins_estimator/src/initial/initial_sfm.cpp b/vins_estimator/src/initial/initial_sfm.cpp index 6fbcc17f2..9aa2f38a2 100644 --- a/vins_estimator/src/initial/initial_sfm.cpp +++ b/vins_estimator/src/initial/initial_sfm.cpp @@ -231,7 +231,7 @@ bool GlobalSFM::construct(int frame_num, Quaterniond* q, Vector3d* T, int l, */ //full BA ceres::Problem problem; - ceres::LocalParameterization* local_parameterization = new ceres::QuaternionParameterization(); + ceres::Manifold* local_parameterization = new ceres::QuaternionManifold(); //cout << " begin full BA " << endl; for (int i = 0; i < frame_num; i++) { @@ -243,7 +243,7 @@ bool GlobalSFM::construct(int frame_num, Quaterniond* q, Vector3d* T, int l, c_rotation[i][1] = c_Quat[i].x(); c_rotation[i][2] = c_Quat[i].y(); c_rotation[i][3] = c_Quat[i].z(); - problem.AddParameterBlock(c_rotation[i], 4, local_parameterization); + problem.AddParameterBlock(c_rotation[i], 4); problem.SetManifold(c_rotation[i], local_parameterization); problem.AddParameterBlock(c_translation[i], 3); if (i == l) { diff --git a/vins_estimator/src/initial/solve_5pts.h b/vins_estimator/src/initial/solve_5pts.h index 5a807a949..f62392fc3 100644 --- a/vins_estimator/src/initial/solve_5pts.h +++ b/vins_estimator/src/initial/solve_5pts.h @@ -8,7 +8,7 @@ using namespace std; #include using namespace Eigen; -#include +#include "rclcpp/rclcpp.hpp" class MotionEstimator { diff --git a/vins_estimator/src/parameters.cpp b/vins_estimator/src/parameters.cpp index 1885e84be..727bf5221 100644 --- a/vins_estimator/src/parameters.cpp +++ b/vins_estimator/src/parameters.cpp @@ -16,30 +16,41 @@ double SOLVER_TIME; int NUM_ITERATIONS; int ESTIMATE_EXTRINSIC; int ESTIMATE_TD; +double TD_ESTIMATION_PERIOD = 7.0; // seconds between td optimization bursts int ROLLING_SHUTTER; std::string EX_CALIB_RESULT_PATH; std::string VINS_RESULT_PATH; std::string IMU_TOPIC; double ROW, COL; double TD, TR; +int ENABLE_ZUPT; +double ZUPT_VEL_THRESHOLD; +double ZUPT_ACC_THRESHOLD; + +// Altitude scale correction (MAVLink) +int ENABLE_ALTITUDE_SCALE_CORRECTION; +double ALTITUDE_CORRECTION_FACTOR; + +// Lidar odometry initialization & scale correction +int ENABLE_LIDAR_INIT; +std::string LIDAR_ODOM_TOPIC; +double LIDAR_INIT_TIMEOUT; +double LIDAR_INIT_POS_THRESHOLD; +double LIDAR_SCALE_CORRECTION_FACTOR; +double LIDAR_SYNC_MAX_DT; +std::string WORLD_FRAME_ID; template -T readParam(ros::NodeHandle &n, std::string name) +T readParam(rclcpp::Node::SharedPtr &n, std::string name) { T ans; - if (n.getParam(name, ans)) - { - ROS_INFO_STREAM("Loaded " << name << ": " << ans); - } - else - { - ROS_ERROR_STREAM("Failed to load " << name); - n.shutdown(); - } + n->declare_parameter(name, T{}); + n->get_parameter(name, ans); + RCLCPP_INFO_STREAM(n->get_logger(), "Loaded " << name << ": " << ans); return ans; } -void readParameters(ros::NodeHandle &n) +void readParameters(rclcpp::Node::SharedPtr n) { std::string config_file; config_file = readParam(n, "config_file"); @@ -74,12 +85,12 @@ void readParameters(ros::NodeHandle &n) G.z() = fsSettings["g_norm"]; ROW = fsSettings["image_height"]; COL = fsSettings["image_width"]; - ROS_INFO("ROW: %f COL: %f ", ROW, COL); + RCLCPP_INFO(n->get_logger(), "ROW: %f COL: %f ", ROW, COL); ESTIMATE_EXTRINSIC = fsSettings["estimate_extrinsic"]; if (ESTIMATE_EXTRINSIC == 2) { - ROS_WARN("have no prior about extrinsic param, calibrate extrinsic param"); + RCLCPP_WARN(n->get_logger(), "have no prior about extrinsic param, calibrate extrinsic param"); RIC.push_back(Eigen::Matrix3d::Identity()); TIC.push_back(Eigen::Vector3d::Zero()); EX_CALIB_RESULT_PATH = OUTPUT_PATH + "/extrinsic_parameter.csv"; @@ -89,11 +100,11 @@ void readParameters(ros::NodeHandle &n) { if ( ESTIMATE_EXTRINSIC == 1) { - ROS_WARN(" Optimize extrinsic param around initial guess!"); + RCLCPP_WARN(n->get_logger(), " Optimize extrinsic param around initial guess!"); EX_CALIB_RESULT_PATH = OUTPUT_PATH + "/extrinsic_parameter.csv"; } if (ESTIMATE_EXTRINSIC == 0) - ROS_WARN(" fix extrinsic param "); + RCLCPP_WARN(n->get_logger(), " fix extrinsic param "); cv::Mat cv_R, cv_T; fsSettings["extrinsicRotation"] >> cv_R; @@ -106,8 +117,8 @@ void readParameters(ros::NodeHandle &n) eigen_R = Q.normalized(); RIC.push_back(eigen_R); TIC.push_back(eigen_T); - ROS_INFO_STREAM("Extrinsic_R : " << std::endl << RIC[0]); - ROS_INFO_STREAM("Extrinsic_T : " << std::endl << TIC[0].transpose()); + RCLCPP_INFO_STREAM(n->get_logger(), "Extrinsic_R : " << std::endl << RIC[0]); + RCLCPP_INFO_STREAM(n->get_logger(), "Extrinsic_T : " << std::endl << TIC[0].transpose()); } @@ -117,21 +128,54 @@ void readParameters(ros::NodeHandle &n) TD = fsSettings["td"]; ESTIMATE_TD = fsSettings["estimate_td"]; + if (!fsSettings["estimate_td_period"].empty()) + TD_ESTIMATION_PERIOD = (double)fsSettings["estimate_td_period"]; if (ESTIMATE_TD) - ROS_INFO_STREAM("Unsynchronized sensors, online estimate time offset, initial td: " << TD); + RCLCPP_INFO_STREAM(n->get_logger(), "Unsynchronized sensors, online estimate time offset, initial td: " << TD << ", period: " << TD_ESTIMATION_PERIOD << " s"); else - ROS_INFO_STREAM("Synchronized sensors, fix time offset: " << TD); + RCLCPP_INFO_STREAM(n->get_logger(), "Synchronized sensors, fix time offset: " << TD); ROLLING_SHUTTER = fsSettings["rolling_shutter"]; if (ROLLING_SHUTTER) { TR = fsSettings["rolling_shutter_tr"]; - ROS_INFO_STREAM("rolling shutter camera, read out time per line: " << TR); + RCLCPP_INFO_STREAM(n->get_logger(), "rolling shutter camera, read out time per line: " << TR); } else { TR = 0; } + // Zero Velocity Update parameters + ENABLE_ZUPT = fsSettings["enable_zupt"].empty() ? 0 : (int)fsSettings["enable_zupt"]; + ZUPT_VEL_THRESHOLD = fsSettings["zupt_vel_threshold"].empty() ? 0.05 : (double)fsSettings["zupt_vel_threshold"]; + ZUPT_ACC_THRESHOLD = fsSettings["zupt_acc_threshold"].empty() ? 0.1 : (double)fsSettings["zupt_acc_threshold"]; + if (ENABLE_ZUPT) + RCLCPP_INFO_STREAM(n->get_logger(), "Zero Velocity Update enabled, vel_threshold: " << ZUPT_VEL_THRESHOLD << ", acc_threshold: " << ZUPT_ACC_THRESHOLD); + + // Altitude scale correction (MAVLink) + ENABLE_ALTITUDE_SCALE_CORRECTION = fsSettings["enable_altitude_scale_correction"].empty() ? 0 : (int)fsSettings["enable_altitude_scale_correction"]; + ALTITUDE_CORRECTION_FACTOR = fsSettings["altitude_correction_factor"].empty() ? 0.1 : (double)fsSettings["altitude_correction_factor"]; + if (ENABLE_ALTITUDE_SCALE_CORRECTION) + RCLCPP_INFO_STREAM(n->get_logger(), "Altitude scale correction enabled, factor: " << ALTITUDE_CORRECTION_FACTOR); + + // Lidar odometry initialization & scale correction + ENABLE_LIDAR_INIT = fsSettings["enable_lidar_init"].empty() ? 0 : (int)fsSettings["enable_lidar_init"]; + fsSettings["lidar_odom_topic"] >> LIDAR_ODOM_TOPIC; + if (LIDAR_ODOM_TOPIC.empty()) LIDAR_ODOM_TOPIC = "/lio/odom"; + LIDAR_INIT_TIMEOUT = fsSettings["lidar_init_timeout"].empty() ? 30.0 : (double)fsSettings["lidar_init_timeout"]; + LIDAR_INIT_POS_THRESHOLD = fsSettings["lidar_init_pos_threshold"].empty() ? 1.0 : (double)fsSettings["lidar_init_pos_threshold"]; + LIDAR_SCALE_CORRECTION_FACTOR = fsSettings["lidar_scale_correction_factor"].empty() ? 0.05 : (double)fsSettings["lidar_scale_correction_factor"]; + LIDAR_SYNC_MAX_DT = fsSettings["lidar_sync_max_dt"].empty() ? 0.1 : (double)fsSettings["lidar_sync_max_dt"]; + if (ENABLE_LIDAR_INIT) + RCLCPP_INFO_STREAM(n->get_logger(), "Lidar odometry init enabled, topic: " << LIDAR_ODOM_TOPIC + << ", timeout: " << LIDAR_INIT_TIMEOUT << "s, pos_threshold: " << LIDAR_INIT_POS_THRESHOLD + << "m, scale_factor: " << LIDAR_SCALE_CORRECTION_FACTOR << ", sync_dt: " << LIDAR_SYNC_MAX_DT << "s"); + + // World frame ID for published odometry and visualization + fsSettings["world_frame_id"] >> WORLD_FRAME_ID; + if (WORLD_FRAME_ID.empty()) WORLD_FRAME_ID = "world"; + RCLCPP_INFO_STREAM(n->get_logger(), "World frame_id: " << WORLD_FRAME_ID); + fsSettings.release(); } diff --git a/vins_estimator/src/parameters.h b/vins_estimator/src/parameters.h index 6d206cb70..6af1cf667 100644 --- a/vins_estimator/src/parameters.h +++ b/vins_estimator/src/parameters.h @@ -1,6 +1,6 @@ #pragma once -#include +#include "rclcpp/rclcpp.hpp" #include #include #include "utility/utility.h" @@ -35,11 +35,28 @@ extern std::string IMU_TOPIC; extern double TD; extern double TR; extern int ESTIMATE_TD; +extern double TD_ESTIMATION_PERIOD; // seconds between td optimization bursts extern int ROLLING_SHUTTER; extern double ROW, COL; +extern int ENABLE_ZUPT; +extern double ZUPT_VEL_THRESHOLD; +extern double ZUPT_ACC_THRESHOLD; +// Altitude scale correction (MAVLink) +extern int ENABLE_ALTITUDE_SCALE_CORRECTION; +extern double ALTITUDE_CORRECTION_FACTOR; -void readParameters(ros::NodeHandle &n); +// Lidar odometry initialization & scale correction +extern int ENABLE_LIDAR_INIT; +extern std::string LIDAR_ODOM_TOPIC; +extern double LIDAR_INIT_TIMEOUT; // seconds to wait for lidar odom at startup +extern double LIDAR_INIT_POS_THRESHOLD; // min displacement (m) before using lidar for scale init +extern double LIDAR_SCALE_CORRECTION_FACTOR; // gentle ongoing scale correction strength +extern double LIDAR_SYNC_MAX_DT; // max time diff (s) for lidar-vins sync + +extern std::string WORLD_FRAME_ID; // TF/frame_id for published odometry and visualization + +void readParameters(rclcpp::Node::SharedPtr n); enum SIZE_PARAMETERIZATION { diff --git a/vins_estimator/src/utility/CameraPoseVisualization.cpp b/vins_estimator/src/utility/CameraPoseVisualization.cpp index df97dbaf4..fd4b674cd 100644 --- a/vins_estimator/src/utility/CameraPoseVisualization.cpp +++ b/vins_estimator/src/utility/CameraPoseVisualization.cpp @@ -9,7 +9,7 @@ const Eigen::Vector3d CameraPoseVisualization::lt1 = Eigen::Vector3d(-0.7, -0.2, const Eigen::Vector3d CameraPoseVisualization::lt2 = Eigen::Vector3d(-1.0, -0.2, 1.0); const Eigen::Vector3d CameraPoseVisualization::oc = Eigen::Vector3d(0.0, 0.0, 0.0); -void Eigen2Point(const Eigen::Vector3d& v, geometry_msgs::Point& p) { +void Eigen2Point(const Eigen::Vector3d& v, geometry_msgs::msg::Point& p) { p.x = v.x(); p.y = v.y(); p.z = v.z(); @@ -48,18 +48,18 @@ void CameraPoseVisualization::setLineWidth(double width) { m_line_width = width; } void CameraPoseVisualization::add_edge(const Eigen::Vector3d& p0, const Eigen::Vector3d& p1){ - visualization_msgs::Marker marker; + visualization_msgs::msg::Marker marker; marker.ns = m_marker_ns; marker.id = m_markers.size() + 1; - marker.type = visualization_msgs::Marker::LINE_LIST; - marker.action = visualization_msgs::Marker::ADD; + marker.type = visualization_msgs::msg::Marker::LINE_LIST; + marker.action = visualization_msgs::msg::Marker::ADD; marker.scale.x = 0.005; marker.color.g = 1.0f; marker.color.a = 1.0; - geometry_msgs::Point point0, point1; + geometry_msgs::msg::Point point0, point1; Eigen2Point(p0, point0); Eigen2Point(p1, point1); @@ -71,12 +71,12 @@ void CameraPoseVisualization::add_edge(const Eigen::Vector3d& p0, const Eigen::V } void CameraPoseVisualization::add_loopedge(const Eigen::Vector3d& p0, const Eigen::Vector3d& p1){ - visualization_msgs::Marker marker; + visualization_msgs::msg::Marker marker; marker.ns = m_marker_ns; marker.id = m_markers.size() + 1; - marker.type = visualization_msgs::Marker::LINE_LIST; - marker.action = visualization_msgs::Marker::ADD; + marker.type = visualization_msgs::msg::Marker::LINE_LIST; + marker.action = visualization_msgs::msg::Marker::ADD; marker.scale.x = 0.04; //marker.scale.x = 0.3; @@ -84,7 +84,7 @@ void CameraPoseVisualization::add_loopedge(const Eigen::Vector3d& p0, const Eige marker.color.b = 1.0f; marker.color.a = 1.0; - geometry_msgs::Point point0, point1; + geometry_msgs::msg::Point point0, point1; Eigen2Point(p0, point0); Eigen2Point(p1, point1); @@ -97,12 +97,12 @@ void CameraPoseVisualization::add_loopedge(const Eigen::Vector3d& p0, const Eige void CameraPoseVisualization::add_pose(const Eigen::Vector3d& p, const Eigen::Quaterniond& q) { - visualization_msgs::Marker marker; + visualization_msgs::msg::Marker marker; marker.ns = m_marker_ns; marker.id = m_markers.size() + 1; - marker.type = visualization_msgs::Marker::LINE_STRIP; - marker.action = visualization_msgs::Marker::ADD; + marker.type = visualization_msgs::msg::Marker::LINE_STRIP; + marker.action = visualization_msgs::msg::Marker::ADD; marker.scale.x = m_line_width; marker.pose.position.x = 0.0; @@ -114,7 +114,7 @@ void CameraPoseVisualization::add_pose(const Eigen::Vector3d& p, const Eigen::Qu marker.pose.orientation.z = 0.0; - geometry_msgs::Point pt_lt, pt_lb, pt_rt, pt_rb, pt_oc, pt_lt0, pt_lt1, pt_lt2; + geometry_msgs::msg::Point pt_lt, pt_lb, pt_rt, pt_rb, pt_oc, pt_lt0, pt_lt1, pt_lt2; Eigen2Point(q * (m_scale *imlt) + p, pt_lt); Eigen2Point(q * (m_scale *imlb) + p, pt_lb); @@ -186,13 +186,13 @@ void CameraPoseVisualization::reset() { m_markers.clear(); } -void CameraPoseVisualization::publish_by( ros::Publisher &pub, const std_msgs::Header &header ) { - visualization_msgs::MarkerArray markerArray_msg; +void CameraPoseVisualization::publish_by( rclcpp::Publisher::SharedPtr pub, const std_msgs::msg::Header &header ) { + visualization_msgs::msg::MarkerArray markerArray_msg; for(auto& marker : m_markers) { marker.header = header; markerArray_msg.markers.push_back(marker); } - pub.publish(markerArray_msg); + pub->publish(markerArray_msg); } \ No newline at end of file diff --git a/vins_estimator/src/utility/CameraPoseVisualization.h b/vins_estimator/src/utility/CameraPoseVisualization.h index a7d168401..6afd81691 100644 --- a/vins_estimator/src/utility/CameraPoseVisualization.h +++ b/vins_estimator/src/utility/CameraPoseVisualization.h @@ -1,9 +1,9 @@ #pragma once -#include -#include -#include -#include +#include "rclcpp/rclcpp.hpp" +#include +#include +#include #include #include @@ -21,13 +21,13 @@ class CameraPoseVisualization { void add_pose(const Eigen::Vector3d& p, const Eigen::Quaterniond& q); void reset(); - void publish_by(ros::Publisher& pub, const std_msgs::Header& header); + void publish_by(rclcpp::Publisher::SharedPtr pub, const std_msgs::msg::Header& header); void add_edge(const Eigen::Vector3d& p0, const Eigen::Vector3d& p1); void add_loopedge(const Eigen::Vector3d& p0, const Eigen::Vector3d& p1); private: - std::vector m_markers; - std_msgs::ColorRGBA m_image_boundary_color; - std_msgs::ColorRGBA m_optical_center_connector_color; + std::vector m_markers; + std_msgs::msg::ColorRGBA m_image_boundary_color; + std_msgs::msg::ColorRGBA m_optical_center_connector_color; double m_scale; double m_line_width; diff --git a/vins_estimator/src/utility/utility.h b/vins_estimator/src/utility/utility.h index 1d41df9bf..ad321c5d9 100644 --- a/vins_estimator/src/utility/utility.h +++ b/vins_estimator/src/utility/utility.h @@ -8,6 +8,13 @@ #include #include #include +#include + +// Helper: convert builtin_interfaces::Time to seconds (replaces ROS1 stamp.toSec()) +static inline double stampToSec(const builtin_interfaces::msg::Time &stamp) +{ + return static_cast(stamp.sec) + static_cast(stamp.nanosec) * 1e-9; +} class Utility { diff --git a/vins_estimator/src/utility/visualization.cpp b/vins_estimator/src/utility/visualization.cpp index 167e91314..0f72c1e34 100644 --- a/vins_estimator/src/utility/visualization.cpp +++ b/vins_estimator/src/utility/visualization.cpp @@ -1,40 +1,46 @@ #include "visualization.h" -using namespace ros; using namespace Eigen; -ros::Publisher pub_odometry, pub_latest_odometry; -ros::Publisher pub_path, pub_relo_path; -ros::Publisher pub_point_cloud, pub_margin_cloud; -ros::Publisher pub_key_poses; -ros::Publisher pub_relo_relative_pose; -ros::Publisher pub_camera_pose; -ros::Publisher pub_camera_pose_visual; -nav_msgs::Path path, relo_path; - -ros::Publisher pub_keyframe_pose; -ros::Publisher pub_keyframe_point; -ros::Publisher pub_extrinsic; + +rclcpp::Publisher::SharedPtr pub_odometry; +rclcpp::Publisher::SharedPtr pub_latest_odometry; +rclcpp::Publisher::SharedPtr pub_path; +rclcpp::Publisher::SharedPtr pub_relo_path; +rclcpp::Publisher::SharedPtr pub_point_cloud; +rclcpp::Publisher::SharedPtr pub_margin_cloud; +rclcpp::Publisher::SharedPtr pub_key_poses; +rclcpp::Publisher::SharedPtr pub_relo_relative_pose; +rclcpp::Publisher::SharedPtr pub_camera_pose; +rclcpp::Publisher::SharedPtr pub_camera_pose_visual; +rclcpp::Publisher::SharedPtr pub_keyframe_pose; +rclcpp::Publisher::SharedPtr pub_keyframe_point; +rclcpp::Publisher::SharedPtr pub_extrinsic; +nav_msgs::msg::Path path; +nav_msgs::msg::Path relo_path; +std::shared_ptr tf_broadcaster; CameraPoseVisualization cameraposevisual(0, 1, 0, 1); CameraPoseVisualization keyframebasevisual(0.0, 0.0, 1.0, 1.0); static double sum_of_path = 0; static Vector3d last_path(0.0, 0.0, 0.0); -void registerPub(ros::NodeHandle &n) +void registerPub(rclcpp::Node::SharedPtr n) { - pub_latest_odometry = n.advertise("imu_propagate", 1000); - pub_path = n.advertise("path", 1000); - pub_relo_path = n.advertise("relocalization_path", 1000); - pub_odometry = n.advertise("odometry", 1000); - pub_point_cloud = n.advertise("point_cloud", 1000); - pub_margin_cloud = n.advertise("history_cloud", 1000); - pub_key_poses = n.advertise("key_poses", 1000); - pub_camera_pose = n.advertise("camera_pose", 1000); - pub_camera_pose_visual = n.advertise("camera_pose_visual", 1000); - pub_keyframe_pose = n.advertise("keyframe_pose", 1000); - pub_keyframe_point = n.advertise("keyframe_point", 1000); - pub_extrinsic = n.advertise("extrinsic", 1000); - pub_relo_relative_pose= n.advertise("relo_relative_pose", 1000); + pub_latest_odometry = n->create_publisher("/vins_estimator/imu_propagate", 1000); + pub_path = n->create_publisher("/vins_estimator/path", 1000); + pub_relo_path = n->create_publisher("/vins_estimator/relocalization_path", 1000); + pub_odometry = n->create_publisher("/vins_estimator/odometry", 1000); + pub_point_cloud = n->create_publisher("/vins_estimator/point_cloud", 1000); + pub_margin_cloud = n->create_publisher("/vins_estimator/history_cloud", 1000); + pub_key_poses = n->create_publisher("/vins_estimator/key_poses", 1000); + pub_camera_pose = n->create_publisher("/vins_estimator/camera_pose", 1000); + pub_camera_pose_visual = n->create_publisher("/vins_estimator/camera_pose_visual", 1000); + pub_keyframe_pose = n->create_publisher("/vins_estimator/keyframe_pose", 1000); + pub_keyframe_point = n->create_publisher("/vins_estimator/keyframe_point", 1000); + pub_extrinsic = n->create_publisher("/vins_estimator/extrinsic", 1000); + pub_relo_relative_pose = n->create_publisher("/vins_estimator/relo_relative_pose", 1000); + + tf_broadcaster = std::make_shared(n); cameraposevisual.setScale(1); cameraposevisual.setLineWidth(0.05); @@ -42,13 +48,13 @@ void registerPub(ros::NodeHandle &n) keyframebasevisual.setLineWidth(0.01); } -void pubLatestOdometry(const Eigen::Vector3d &P, const Eigen::Quaterniond &Q, const Eigen::Vector3d &V, const std_msgs::Header &header) +void pubLatestOdometry(const Eigen::Vector3d &P, const Eigen::Quaterniond &Q, const Eigen::Vector3d &V, const std_msgs::msg::Header &header) { Eigen::Quaterniond quadrotor_Q = Q ; - nav_msgs::Odometry odometry; + nav_msgs::msg::Odometry odometry; odometry.header = header; - odometry.header.frame_id = "world"; + odometry.header.frame_id = WORLD_FRAME_ID; odometry.pose.pose.position.x = P.x(); odometry.pose.pose.position.y = P.y(); odometry.pose.pose.position.z = P.z(); @@ -59,7 +65,7 @@ void pubLatestOdometry(const Eigen::Vector3d &P, const Eigen::Quaterniond &Q, co odometry.twist.twist.linear.x = V.x(); odometry.twist.twist.linear.y = V.y(); odometry.twist.twist.linear.z = V.z(); - pub_latest_odometry.publish(odometry); + pub_latest_odometry->publish(odometry); } void printStatistics(const Estimator &estimator, double t) @@ -67,13 +73,12 @@ void printStatistics(const Estimator &estimator, double t) if (estimator.solver_flag != Estimator::SolverFlag::NON_LINEAR) return; printf("position: %f, %f, %f\r", estimator.Ps[WINDOW_SIZE].x(), estimator.Ps[WINDOW_SIZE].y(), estimator.Ps[WINDOW_SIZE].z()); - ROS_DEBUG_STREAM("position: " << estimator.Ps[WINDOW_SIZE].transpose()); - ROS_DEBUG_STREAM("orientation: " << estimator.Vs[WINDOW_SIZE].transpose()); + RCLCPP_DEBUG_STREAM(rclcpp::get_logger("vins_estimator"), "position: " << estimator.Ps[WINDOW_SIZE].transpose()); + RCLCPP_DEBUG_STREAM(rclcpp::get_logger("vins_estimator"), "orientation: " << estimator.Vs[WINDOW_SIZE].transpose()); for (int i = 0; i < NUM_OF_CAM; i++) { - //ROS_DEBUG("calibration result for camera %d", i); - ROS_DEBUG_STREAM("extirnsic tic: " << estimator.tic[i].transpose()); - ROS_DEBUG_STREAM("extrinsic ric: " << Utility::R2ypr(estimator.ric[i]).transpose()); + RCLCPP_DEBUG_STREAM(rclcpp::get_logger("vins_estimator"), "extirnsic tic: " << estimator.tic[i].transpose()); + RCLCPP_DEBUG_STREAM(rclcpp::get_logger("vins_estimator"), "extrinsic ric: " << Utility::R2ypr(estimator.ric[i]).transpose()); if (ESTIMATE_EXTRINSIC) { cv::FileStorage fs(EX_CALIB_RESULT_PATH, cv::FileStorage::WRITE); @@ -93,24 +98,37 @@ void printStatistics(const Estimator &estimator, double t) static int sum_of_calculation = 0; sum_of_time += t; sum_of_calculation++; - ROS_DEBUG("vo solver costs: %f ms", t); - ROS_DEBUG("average of time %f ms", sum_of_time / sum_of_calculation); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "vo solver costs: %f ms", t); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "average of time %f ms", sum_of_time / sum_of_calculation); sum_of_path += (estimator.Ps[WINDOW_SIZE] - last_path).norm(); last_path = estimator.Ps[WINDOW_SIZE]; - ROS_DEBUG("sum of path %f", sum_of_path); - if (ESTIMATE_TD) - ROS_INFO("td %f", estimator.td); + RCLCPP_DEBUG(rclcpp::get_logger("vins_estimator"), "sum of path %f", sum_of_path); + + // Calculate horizontal and vertical velocity + double vel_horizontal = sqrt(estimator.Vs[WINDOW_SIZE].x() * estimator.Vs[WINDOW_SIZE].x() + + estimator.Vs[WINDOW_SIZE].y() * estimator.Vs[WINDOW_SIZE].y()); + double vel_vertical = estimator.Vs[WINDOW_SIZE].z(); + + RCLCPP_INFO(rclcpp::get_logger("vins_estimator"), "Pose: x=%.3f y=%.3f z=%.3f | Vel: horiz=%.3f vert=%.3f m/s", + estimator.Ps[WINDOW_SIZE].x(), + estimator.Ps[WINDOW_SIZE].y(), + estimator.Ps[WINDOW_SIZE].z(), + vel_horizontal, + vel_vertical); + + if (ESTIMATE_TD && estimator.td_updated) + RCLCPP_INFO(rclcpp::get_logger("vins_estimator"), "td updated: %.6f", estimator.td); } -void pubOdometry(const Estimator &estimator, const std_msgs::Header &header) +void pubOdometry(const Estimator &estimator, const std_msgs::msg::Header &header) { if (estimator.solver_flag == Estimator::SolverFlag::NON_LINEAR) { - nav_msgs::Odometry odometry; + nav_msgs::msg::Odometry odometry; odometry.header = header; - odometry.header.frame_id = "world"; - odometry.child_frame_id = "world"; + odometry.header.frame_id = WORLD_FRAME_ID; + odometry.child_frame_id = WORLD_FRAME_ID; Quaterniond tmp_Q; tmp_Q = Quaterniond(estimator.Rs[WINDOW_SIZE]); odometry.pose.pose.position.x = estimator.Ps[WINDOW_SIZE].x(); @@ -123,16 +141,16 @@ void pubOdometry(const Estimator &estimator, const std_msgs::Header &header) odometry.twist.twist.linear.x = estimator.Vs[WINDOW_SIZE].x(); odometry.twist.twist.linear.y = estimator.Vs[WINDOW_SIZE].y(); odometry.twist.twist.linear.z = estimator.Vs[WINDOW_SIZE].z(); - pub_odometry.publish(odometry); + pub_odometry->publish(odometry); - geometry_msgs::PoseStamped pose_stamped; + geometry_msgs::msg::PoseStamped pose_stamped; pose_stamped.header = header; - pose_stamped.header.frame_id = "world"; + pose_stamped.header.frame_id = WORLD_FRAME_ID; pose_stamped.pose = odometry.pose.pose; path.header = header; - path.header.frame_id = "world"; + path.header.frame_id = WORLD_FRAME_ID; path.poses.push_back(pose_stamped); - pub_path.publish(path); + pub_path->publish(path); Vector3d correct_t; Vector3d correct_v; @@ -149,15 +167,15 @@ void pubOdometry(const Estimator &estimator, const std_msgs::Header &header) pose_stamped.pose = odometry.pose.pose; relo_path.header = header; - relo_path.header.frame_id = "world"; + relo_path.header.frame_id = WORLD_FRAME_ID; relo_path.poses.push_back(pose_stamped); - pub_relo_path.publish(relo_path); + pub_relo_path->publish(relo_path); // write result to file ofstream foutC(VINS_RESULT_PATH, ios::app); foutC.setf(ios::fixed, ios::floatfield); foutC.precision(0); - foutC << header.stamp.toSec() * 1e9 << ","; + foutC << rclcpp::Time(header.stamp).seconds() * 1e9 << ","; foutC.precision(5); foutC << estimator.Ps[WINDOW_SIZE].x() << "," << estimator.Ps[WINDOW_SIZE].y() << "," @@ -173,21 +191,21 @@ void pubOdometry(const Estimator &estimator, const std_msgs::Header &header) } } -void pubKeyPoses(const Estimator &estimator, const std_msgs::Header &header) +void pubKeyPoses(const Estimator &estimator, const std_msgs::msg::Header &header) { if (estimator.key_poses.size() == 0) return; - visualization_msgs::Marker key_poses; + visualization_msgs::msg::Marker key_poses; key_poses.header = header; - key_poses.header.frame_id = "world"; + key_poses.header.frame_id = WORLD_FRAME_ID; key_poses.ns = "key_poses"; - key_poses.type = visualization_msgs::Marker::SPHERE_LIST; - key_poses.action = visualization_msgs::Marker::ADD; + key_poses.type = visualization_msgs::msg::Marker::SPHERE_LIST; + key_poses.action = visualization_msgs::msg::Marker::ADD; key_poses.pose.orientation.w = 1.0; - key_poses.lifetime = ros::Duration(); + builtin_interfaces::msg::Duration zero_dur; + key_poses.lifetime = zero_dur; - //static int key_poses_id = 0; - key_poses.id = 0; //key_poses_id++; + key_poses.id = 0; key_poses.scale.x = 0.05; key_poses.scale.y = 0.05; key_poses.scale.z = 0.05; @@ -196,7 +214,7 @@ void pubKeyPoses(const Estimator &estimator, const std_msgs::Header &header) for (int i = 0; i <= WINDOW_SIZE; i++) { - geometry_msgs::Point pose_marker; + geometry_msgs::msg::Point pose_marker; Vector3d correct_pose; correct_pose = estimator.key_poses[i]; pose_marker.x = correct_pose.x(); @@ -204,10 +222,10 @@ void pubKeyPoses(const Estimator &estimator, const std_msgs::Header &header) pose_marker.z = correct_pose.z(); key_poses.points.push_back(pose_marker); } - pub_key_poses.publish(key_poses); + pub_key_poses->publish(key_poses); } -void pubCameraPose(const Estimator &estimator, const std_msgs::Header &header) +void pubCameraPose(const Estimator &estimator, const std_msgs::msg::Header &header) { int idx2 = WINDOW_SIZE - 1; @@ -217,9 +235,9 @@ void pubCameraPose(const Estimator &estimator, const std_msgs::Header &header) Vector3d P = estimator.Ps[i] + estimator.Rs[i] * estimator.tic[0]; Quaterniond R = Quaterniond(estimator.Rs[i] * estimator.ric[0]); - nav_msgs::Odometry odometry; + nav_msgs::msg::Odometry odometry; odometry.header = header; - odometry.header.frame_id = "world"; + odometry.header.frame_id = WORLD_FRAME_ID; odometry.pose.pose.position.x = P.x(); odometry.pose.pose.position.y = P.y(); odometry.pose.pose.position.z = P.z(); @@ -228,7 +246,7 @@ void pubCameraPose(const Estimator &estimator, const std_msgs::Header &header) odometry.pose.pose.orientation.z = R.z(); odometry.pose.pose.orientation.w = R.w(); - pub_camera_pose.publish(odometry); + pub_camera_pose->publish(odometry); cameraposevisual.reset(); cameraposevisual.add_pose(P, R); @@ -237,9 +255,9 @@ void pubCameraPose(const Estimator &estimator, const std_msgs::Header &header) } -void pubPointCloud(const Estimator &estimator, const std_msgs::Header &header) +void pubPointCloud(const Estimator &estimator, const std_msgs::msg::Header &header) { - sensor_msgs::PointCloud point_cloud, loop_point_cloud; + sensor_msgs::msg::PointCloud point_cloud, loop_point_cloud; point_cloud.header = header; loop_point_cloud.header = header; @@ -256,17 +274,17 @@ void pubPointCloud(const Estimator &estimator, const std_msgs::Header &header) Vector3d pts_i = it_per_id.feature_per_frame[0].point * it_per_id.estimated_depth; Vector3d w_pts_i = estimator.Rs[imu_i] * (estimator.ric[0] * pts_i + estimator.tic[0]) + estimator.Ps[imu_i]; - geometry_msgs::Point32 p; + geometry_msgs::msg::Point32 p; p.x = w_pts_i(0); p.y = w_pts_i(1); p.z = w_pts_i(2); point_cloud.points.push_back(p); } - pub_point_cloud.publish(point_cloud); + pub_point_cloud->publish(point_cloud); // pub margined potin - sensor_msgs::PointCloud margin_cloud; + sensor_msgs::msg::PointCloud margin_cloud; margin_cloud.header = header; for (auto &it_per_id : estimator.f_manager.feature) @@ -275,8 +293,6 @@ void pubPointCloud(const Estimator &estimator, const std_msgs::Header &header) used_num = it_per_id.feature_per_frame.size(); if (!(used_num >= 2 && it_per_id.start_frame < WINDOW_SIZE - 2)) continue; - //if (it_per_id->start_frame > WINDOW_SIZE * 3.0 / 4.0 || it_per_id->solve_flag != 1) - // continue; if (it_per_id.start_frame == 0 && it_per_id.feature_per_frame.size() <= 2 && it_per_id.solve_flag == 1 ) @@ -285,63 +301,65 @@ void pubPointCloud(const Estimator &estimator, const std_msgs::Header &header) Vector3d pts_i = it_per_id.feature_per_frame[0].point * it_per_id.estimated_depth; Vector3d w_pts_i = estimator.Rs[imu_i] * (estimator.ric[0] * pts_i + estimator.tic[0]) + estimator.Ps[imu_i]; - geometry_msgs::Point32 p; + geometry_msgs::msg::Point32 p; p.x = w_pts_i(0); p.y = w_pts_i(1); p.z = w_pts_i(2); margin_cloud.points.push_back(p); } } - pub_margin_cloud.publish(margin_cloud); + pub_margin_cloud->publish(margin_cloud); } -void pubTF(const Estimator &estimator, const std_msgs::Header &header) +void pubTF(const Estimator &estimator, const std_msgs::msg::Header &header) { if( estimator.solver_flag != Estimator::SolverFlag::NON_LINEAR) return; - static tf::TransformBroadcaster br; - tf::Transform transform; - tf::Quaternion q; + // body frame Vector3d correct_t; Quaterniond correct_q; correct_t = estimator.Ps[WINDOW_SIZE]; correct_q = estimator.Rs[WINDOW_SIZE]; - transform.setOrigin(tf::Vector3(correct_t(0), - correct_t(1), - correct_t(2))); - q.setW(correct_q.w()); - q.setX(correct_q.x()); - q.setY(correct_q.y()); - q.setZ(correct_q.z()); - transform.setRotation(q); - br.sendTransform(tf::StampedTransform(transform, header.stamp, "world", "body")); + geometry_msgs::msg::TransformStamped transform; + transform.header.stamp = header.stamp; + transform.header.frame_id = WORLD_FRAME_ID; + transform.child_frame_id = "body"; + transform.transform.translation.x = correct_t(0); + transform.transform.translation.y = correct_t(1); + transform.transform.translation.z = correct_t(2); + transform.transform.rotation.w = correct_q.w(); + transform.transform.rotation.x = correct_q.x(); + transform.transform.rotation.y = correct_q.y(); + transform.transform.rotation.z = correct_q.z(); + tf_broadcaster->sendTransform(transform); // camera frame - transform.setOrigin(tf::Vector3(estimator.tic[0].x(), - estimator.tic[0].y(), - estimator.tic[0].z())); - q.setW(Quaterniond(estimator.ric[0]).w()); - q.setX(Quaterniond(estimator.ric[0]).x()); - q.setY(Quaterniond(estimator.ric[0]).y()); - q.setZ(Quaterniond(estimator.ric[0]).z()); - transform.setRotation(q); - br.sendTransform(tf::StampedTransform(transform, header.stamp, "body", "camera")); - - nav_msgs::Odometry odometry; + transform.header.frame_id = "body"; + transform.child_frame_id = "camera"; + transform.transform.translation.x = estimator.tic[0].x(); + transform.transform.translation.y = estimator.tic[0].y(); + transform.transform.translation.z = estimator.tic[0].z(); + Quaterniond tmp_q{estimator.ric[0]}; + transform.transform.rotation.w = tmp_q.w(); + transform.transform.rotation.x = tmp_q.x(); + transform.transform.rotation.y = tmp_q.y(); + transform.transform.rotation.z = tmp_q.z(); + tf_broadcaster->sendTransform(transform); + + nav_msgs::msg::Odometry odometry; odometry.header = header; - odometry.header.frame_id = "world"; + odometry.header.frame_id = WORLD_FRAME_ID; odometry.pose.pose.position.x = estimator.tic[0].x(); odometry.pose.pose.position.y = estimator.tic[0].y(); odometry.pose.pose.position.z = estimator.tic[0].z(); - Quaterniond tmp_q{estimator.ric[0]}; odometry.pose.pose.orientation.x = tmp_q.x(); odometry.pose.pose.orientation.y = tmp_q.y(); odometry.pose.pose.orientation.z = tmp_q.z(); odometry.pose.pose.orientation.w = tmp_q.w(); - pub_extrinsic.publish(odometry); + pub_extrinsic->publish(odometry); } @@ -351,13 +369,12 @@ void pubKeyframe(const Estimator &estimator) if (estimator.solver_flag == Estimator::SolverFlag::NON_LINEAR && estimator.marginalization_flag == 0) { int i = WINDOW_SIZE - 2; - //Vector3d P = estimator.Ps[i] + estimator.Rs[i] * estimator.tic[0]; Vector3d P = estimator.Ps[i]; Quaterniond R = Quaterniond(estimator.Rs[i]); - nav_msgs::Odometry odometry; + nav_msgs::msg::Odometry odometry; odometry.header = estimator.Headers[WINDOW_SIZE - 2]; - odometry.header.frame_id = "world"; + odometry.header.frame_id = WORLD_FRAME_ID; odometry.pose.pose.position.x = P.x(); odometry.pose.pose.position.y = P.y(); odometry.pose.pose.position.z = P.z(); @@ -365,12 +382,11 @@ void pubKeyframe(const Estimator &estimator) odometry.pose.pose.orientation.y = R.y(); odometry.pose.pose.orientation.z = R.z(); odometry.pose.pose.orientation.w = R.w(); - //printf("time: %f t: %f %f %f r: %f %f %f %f\n", odometry.header.stamp.toSec(), P.x(), P.y(), P.z(), R.w(), R.x(), R.y(), R.z()); - pub_keyframe_pose.publish(odometry); + pub_keyframe_pose->publish(odometry); - sensor_msgs::PointCloud point_cloud; + sensor_msgs::msg::PointCloud point_cloud; point_cloud.header = estimator.Headers[WINDOW_SIZE - 2]; for (auto &it_per_id : estimator.f_manager.feature) { @@ -382,14 +398,14 @@ void pubKeyframe(const Estimator &estimator) Vector3d pts_i = it_per_id.feature_per_frame[0].point * it_per_id.estimated_depth; Vector3d w_pts_i = estimator.Rs[imu_i] * (estimator.ric[0] * pts_i + estimator.tic[0]) + estimator.Ps[imu_i]; - geometry_msgs::Point32 p; + geometry_msgs::msg::Point32 p; p.x = w_pts_i(0); p.y = w_pts_i(1); p.z = w_pts_i(2); point_cloud.points.push_back(p); int imu_j = WINDOW_SIZE - 2 - it_per_id.start_frame; - sensor_msgs::ChannelFloat32 p_2d; + sensor_msgs::msg::ChannelFloat32 p_2d; p_2d.values.push_back(it_per_id.feature_per_frame[imu_j].point.x()); p_2d.values.push_back(it_per_id.feature_per_frame[imu_j].point.y()); p_2d.values.push_back(it_per_id.feature_per_frame[imu_j].uv.x()); @@ -399,15 +415,17 @@ void pubKeyframe(const Estimator &estimator) } } - pub_keyframe_point.publish(point_cloud); + pub_keyframe_point->publish(point_cloud); } } void pubRelocalization(const Estimator &estimator) { - nav_msgs::Odometry odometry; - odometry.header.stamp = ros::Time(estimator.relo_frame_stamp); - odometry.header.frame_id = "world"; + nav_msgs::msg::Odometry odometry; + int64_t ns = static_cast(estimator.relo_frame_stamp * 1e9); + odometry.header.stamp.sec = static_cast(ns / 1000000000LL); + odometry.header.stamp.nanosec = static_cast(ns % 1000000000LL); + odometry.header.frame_id = WORLD_FRAME_ID; odometry.pose.pose.position.x = estimator.relo_relative_t.x(); odometry.pose.pose.position.y = estimator.relo_relative_t.y(); odometry.pose.pose.position.z = estimator.relo_relative_t.z(); @@ -418,5 +436,5 @@ void pubRelocalization(const Estimator &estimator) odometry.twist.twist.linear.x = estimator.relo_relative_yaw; odometry.twist.twist.linear.y = estimator.relo_frame_index; - pub_relo_relative_pose.publish(odometry); -} \ No newline at end of file + pub_relo_relative_pose->publish(odometry); +} diff --git a/vins_estimator/src/utility/visualization.h b/vins_estimator/src/utility/visualization.h index bf7d1d1f3..1a2d2c775 100644 --- a/vins_estimator/src/utility/visualization.h +++ b/vins_estimator/src/utility/visualization.h @@ -1,51 +1,59 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "rclcpp/rclcpp.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "CameraPoseVisualization.h" #include #include "../estimator.h" #include "../parameters.h" #include -extern ros::Publisher pub_odometry; -extern ros::Publisher pub_path, pub_pose; -extern ros::Publisher pub_cloud, pub_map; -extern ros::Publisher pub_key_poses; -extern ros::Publisher pub_ref_pose, pub_cur_pose; -extern ros::Publisher pub_key; -extern nav_msgs::Path path; -extern ros::Publisher pub_pose_graph; -extern int IMAGE_ROW, IMAGE_COL; - -void registerPub(ros::NodeHandle &n); - -void pubLatestOdometry(const Eigen::Vector3d &P, const Eigen::Quaterniond &Q, const Eigen::Vector3d &V, const std_msgs::Header &header); +extern rclcpp::Publisher::SharedPtr pub_odometry; +extern rclcpp::Publisher::SharedPtr pub_latest_odometry; +extern rclcpp::Publisher::SharedPtr pub_path; +extern rclcpp::Publisher::SharedPtr pub_relo_path; +extern rclcpp::Publisher::SharedPtr pub_point_cloud; +extern rclcpp::Publisher::SharedPtr pub_margin_cloud; +extern rclcpp::Publisher::SharedPtr pub_key_poses; +extern rclcpp::Publisher::SharedPtr pub_camera_pose; +extern rclcpp::Publisher::SharedPtr pub_camera_pose_visual; +extern rclcpp::Publisher::SharedPtr pub_keyframe_pose; +extern rclcpp::Publisher::SharedPtr pub_keyframe_point; +extern rclcpp::Publisher::SharedPtr pub_extrinsic; +extern rclcpp::Publisher::SharedPtr pub_relo_relative_pose; +extern nav_msgs::msg::Path path; +extern nav_msgs::msg::Path relo_path; +extern std::shared_ptr tf_broadcaster; + +void registerPub(rclcpp::Node::SharedPtr n); + +void pubLatestOdometry(const Eigen::Vector3d &P, const Eigen::Quaterniond &Q, const Eigen::Vector3d &V, const std_msgs::msg::Header &header); void printStatistics(const Estimator &estimator, double t); -void pubOdometry(const Estimator &estimator, const std_msgs::Header &header); +void pubOdometry(const Estimator &estimator, const std_msgs::msg::Header &header); -void pubInitialGuess(const Estimator &estimator, const std_msgs::Header &header); +void pubInitialGuess(const Estimator &estimator, const std_msgs::msg::Header &header); -void pubKeyPoses(const Estimator &estimator, const std_msgs::Header &header); +void pubKeyPoses(const Estimator &estimator, const std_msgs::msg::Header &header); -void pubCameraPose(const Estimator &estimator, const std_msgs::Header &header); +void pubCameraPose(const Estimator &estimator, const std_msgs::msg::Header &header); -void pubPointCloud(const Estimator &estimator, const std_msgs::Header &header); +void pubPointCloud(const Estimator &estimator, const std_msgs::msg::Header &header); -void pubTF(const Estimator &estimator, const std_msgs::Header &header); +void pubTF(const Estimator &estimator, const std_msgs::msg::Header &header); void pubKeyframe(const Estimator &estimator);