ros(1-1) 图像和GPS发布节点 c++版本

MKT-porter / 2024-08-23 / 原文

 

运行指令

#=============文件结构
ros_cgg/
├── build
├── devel
└── src
    └── image_gps_package
        ├── CMakeLists.txt
        ├── package.xml
        └── src
            ├── image_gps_publisher.cpp
            └── image_gps_subscriber.cpp

#==============编译
cd ~/ros_cgg
catkin_make
source devel/setup.bash


#==============手动运行
#0开启source确保找到
cd ~/ros_cgg
source devel/setup.bash
# 手动开启三个窗口
#1启动ROS Master:
roscore
#2运行你的C++发布节点:
rosrun image_gps_package image_gps_publisher
#3运行c++接受节点
rosrun image_gps_package image_gps_subscriber


#============ 脚本运行
sudo chmod +x run_ros_nodes.sh

  

运行脚本

run_ros_nodes.sh

#!/bin/bash

#外部给与执行权限
#sudo chmod +x run_ros_nodes.sh

# 定义 ROS 安装路径  #安装时候添加到系统路径了 不需要每次都source
ROS_SETUP="/opt/ros/noetic/setup.bash" 
# 定义工作目录路径  自己的工程没有加到系统路径,每次需要source
WORKSPACE_DIR="/home/dongdong/2project/1salm/2Mycode/ros/ros_cgg"


#指定目录

# 启动 ROS Master
echo "Starting ROS 总结点..."
gnome-terminal -- bash -c "\
cd $WORKSPACE_DIR; source devel/setup.bash; \
roscore; \
exec bash"

# 等待 ROS Master 启动
sleep 5

# 运行 C++ 发布节点
echo "Running C++ 发布节点..."
gnome-terminal -- bash -c "\
cd $WORKSPACE_DIR; source devel/setup.bash; \
rosrun image_gps_package image_gps_publisher; \
exec bash"

# 运行 C++ 接受节点
echo "Running C++ 订阅节点..."
gnome-terminal -- bash -c "\
cd $WORKSPACE_DIR; source devel/setup.bash; \
rosrun image_gps_package image_gps_subscriber; \
exec bash"

  

 

1创建工程

 

 

 

 

 CMakeLists.txt

cmake_minimum_required(VERSION 3.0.2)
project(image_gps_package)

find_package(catkin REQUIRED COMPONENTS
  roscpp
  sensor_msgs
  cv_bridge
)

find_package(OpenCV REQUIRED)
find_package(Boost REQUIRED COMPONENTS filesystem)

catkin_package(
  CATKIN_DEPENDS roscpp sensor_msgs cv_bridge
  DEPENDS Boost
)

include_directories(
  ${catkin_INCLUDE_DIRS}
  ${OpenCV_INCLUDE_DIRS}
  ${Boost_INCLUDE_DIRS}
)

# 编译发布节点
add_executable(image_gps_publisher src/image_gps_publisher.cpp)
target_link_libraries(image_gps_publisher
  ${catkin_LIBRARIES}
  ${OpenCV_LIBRARIES}
  ${Boost_LIBRARIES}
)

# 编译接受节点
add_executable(image_gps_subscriber src/image_gps_subscriber.cpp)
target_link_libraries(image_gps_subscriber
  ${catkin_LIBRARIES}
  ${OpenCV_LIBRARIES}
  ${Boost_LIBRARIES}
)

  package.xml

<?xml version="1.0"?>
<package format="2">
  <name>image_gps_package</name>
  <version>0.0.1</version>
  <description>
    A package to publish and subscribe to images and GPS data using ROS.
  </description>

  <!-- Maintainer of the package -->
  <maintainer email="your_email@example.com">Your Name</maintainer>

  <!-- License of the package -->
  <license>MIT</license>

  <!-- Build tool required to build this package -->
  <buildtool_depend>catkin</buildtool_depend>

  <!-- Dependencies of the package during build and runtime -->
  <build_depend>roscpp</build_depend>
  <build_depend>sensor_msgs</build_depend>
  <build_depend>cv_bridge</build_depend>
  
  <exec_depend>roscpp</exec_depend>
  <exec_depend>sensor_msgs</exec_depend>
  <exec_depend>cv_bridge</exec_depend>

  <!-- Declare additional dependencies required for building this package -->
  <build_export_depend>roscpp</build_export_depend>
  <build_export_depend>sensor_msgs</build_export_depend>
  <build_export_depend>cv_bridge</build_export_depend>

  <!-- Export information, can be used by other packages -->
  <export>
    <!-- Export any specific information here -->
  </export>
</package>

 

 

image_gps_publisher(复件).cpp

单线程发布

image_gps_publisher.cpp

#include <ros/ros.h>
#include <sensor_msgs/Image.h>
#include <sensor_msgs/NavSatFix.h>
#include <cv_bridge/cv_bridge.h>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <boost/filesystem.hpp>
#include <fstream>
#include <sstream>
#include <iostream>

class ImageGPS_Reader {


public:

    std::vector<boost::filesystem::path> image_files_;
    struct GPSData {
        std::string timestamp;
        double lat, lon, alt;
    };
    std::map<std::string, GPSData> gps_data_;


    ImageGPS_Reader() {
        std::string data_dir="/home/dongdong/2project/0data/NWPU/";
        std::string gps_dir=data_dir+"/config/gps.txt";
        std::string img_dir=data_dir+"img";
        // Load GPS data
        loadGPSData(gps_dir);

        // Load image filenames
        loadImageFilenames(img_dir);
    }

    void loadGPSData(const std::string& gps_file) {
        std::ifstream file(gps_file);
        if (!file.is_open()) {
            ROS_ERROR("Failed to open GPS file: %s", gps_file.c_str());
            return;
        }

        std::string line;
        while (std::getline(file, line)) {
            std::istringstream iss(line);
            std::string timestamp;
            double lat, lon, alt;
            if (!(iss >> timestamp >> lat >> lon >> alt)) { break; }
            gps_data_[timestamp] = {timestamp,lat, lon, alt};
        }
        file.close();
    }

    void loadImageFilenames(const std::string& img_folder) {
        namespace fs = boost::filesystem;
        fs::directory_iterator end_itr;
        for (fs::directory_iterator itr(img_folder); itr != end_itr; ++itr) {
            if (fs::is_regular_file(itr->status()) && itr->path().extension() == ".jpg") {
                image_files_.push_back(itr->path());
            }
        }
    }



};

int main(int argc, char** argv) {
    ros::init(argc, argv, "image_gps_publisher");

    ImageGPS_Reader reader;
    
    // Initialize ROS node handle
    ros::NodeHandle nh;
    // Create publishers
    ros::Publisher image_pub_ = nh.advertise<sensor_msgs::Image>("/camera/image_raw", 10);
    ros::Publisher gps_pub_ = nh.advertise<sensor_msgs::NavSatFix>("/gps/fix", 10);


    ros::Rate rate(10);  // 10 Hz

    while (ros::ok()) {
        if (!reader.image_files_.empty() && !reader.gps_data_.empty()) {
            for (const auto& img_file : reader.image_files_) {
                std::string timestamp = img_file.stem().string();  // Extract timestamp from filename
                
                if (reader.gps_data_.find(timestamp) != reader.gps_data_.end()) {// 确保GNSS对应图像存在
                    std::string img_path = img_file.string();
                    cv::Mat cv_image = cv::imread(img_path, cv::IMREAD_COLOR);
                    if (!cv_image.empty()) {
                        // 发布图像

                        sensor_msgs::ImagePtr msg = cv_bridge::CvImage(std_msgs::Header(), "bgr8", cv_image).toImageMsg();
                        msg->header.stamp = ros::Time(std::stod(timestamp));                
                        image_pub_.publish(msg);

                        // 发布GNSS信息
                        auto gps = reader.gps_data_[timestamp];
                        sensor_msgs::NavSatFix gps_msg;
                        gps_msg.header.stamp = ros::Time(std::stod(timestamp));                      
                        gps_msg.latitude = gps.lat;
                        gps_msg.longitude = gps.lon;
                        gps_msg.altitude = gps.alt;
                        gps_pub_.publish(gps_msg);

                        ROS_INFO("Published image: %s and GPS data", img_file.filename().string().c_str());
                        rate.sleep(); // 按照设定的频率休眠
                    }
                }
            }
        }
        //处理一次所有的回调函数。这个调用会处理所有的回调并清空消息队列。对于消息处理来说,它确保了消息的及时处理。
        ros::spinOnce();
    }//while

    return 0;
}

  

 多线程发布

如果用的时间是发布当前时间还可以对齐,但是时间是历史时间而非系统时间,对齐有问题

#include <ros/ros.h>
#include <sensor_msgs/Image.h>
#include <sensor_msgs/NavSatFix.h>
#include <cv_bridge/cv_bridge.h>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <boost/filesystem.hpp>
#include <fstream>
#include <sstream>
#include <iostream>
#include <thread>
#include <mutex>

class ImageGPS_Reader {
public:
    std::vector<boost::filesystem::path> image_files_;
    struct GPSData {
        std::string timestamp;
        double lat, lon, alt;
    };
    std::map<std::string, GPSData> gps_data_;

    ImageGPS_Reader() {
        std::string data_dir = "/home/dongdong/2project/0data/NWPU/";
        std::string gps_dir = data_dir + "/config/gps.txt";
        std::string img_dir = data_dir + "img";
        // Load GPS data
        loadGPSData(gps_dir);

        // Load image filenames
        loadImageFilenames(img_dir);
    }

    void loadGPSData(const std::string& gps_file) {
        std::ifstream file(gps_file);
        if (!file.is_open()) {
            ROS_ERROR("Failed to open GPS file: %s", gps_file.c_str());
            return;
        }

        std::string line;
        while (std::getline(file, line)) {
            std::istringstream iss(line);
            std::string timestamp;
            double lat, lon, alt;
            if (!(iss >> timestamp >> lat >> lon >> alt)) { break; }
            gps_data_[timestamp] = {timestamp, lat, lon, alt};
        }
        file.close();
    }

    void loadImageFilenames(const std::string& img_folder) {
        namespace fs = boost::filesystem;
        fs::directory_iterator end_itr;
        for (fs::directory_iterator itr(img_folder); itr != end_itr; ++itr) {
            if (fs::is_regular_file(itr->status()) && itr->path().extension() == ".jpg") {
                image_files_.push_back(itr->path());
            }
        }
    }
};

// Global variables for shared state and mutex
ImageGPS_Reader reader;
ros::Publisher image_pub_;
ros::Publisher gps_pub_;
std::mutex mtx;

void publishImages() {
    
    std::vector<boost::filesystem::path> image_files_copy;
    {
        std::lock_guard<std::mutex> lock(mtx);
        image_files_copy = reader.image_files_;
    }

    ros::Rate rate(10);  // 10 Hz
    while (ros::ok()) {
        for (const auto& img_file : image_files_copy) {
            std::string timestamp = img_file.stem().string();  // Extract timestamp from filename
            
            cv::Mat cv_image = cv::imread(img_file.string(), cv::IMREAD_COLOR);
            if (!cv_image.empty()) {
                // 发布图像
                sensor_msgs::ImagePtr msg = cv_bridge::CvImage(std_msgs::Header(), "bgr8", cv_image).toImageMsg();
                msg->header.stamp = ros::Time(std::stod(timestamp));                
                image_pub_.publish(msg);
                ROS_INFO("Published image: %s", img_file.filename().string().c_str());
            }
            rate.sleep();
        }
    }
}

void publishGPSData() {

    std::map<std::string, ImageGPS_Reader::GPSData> gps_data_copy;
    {
        std::lock_guard<std::mutex> lock(mtx);
        gps_data_copy = reader.gps_data_;
    }

    ros::Rate rate(10);  // 10 Hz
    while (ros::ok()) {

        for (const auto& entry : gps_data_copy) {
            const auto& timestamp = entry.first;
            const auto& gps = entry.second;

            // 发布GNSS信息
            sensor_msgs::NavSatFix gps_msg;
            gps_msg.header.stamp = ros::Time(std::stod(timestamp));                      
            gps_msg.latitude = gps.lat;
            gps_msg.longitude = gps.lon;
            gps_msg.altitude = gps.alt;
            gps_pub_.publish(gps_msg);
            ROS_INFO("Published GPS data for timestamp: %s", timestamp.c_str());
            rate.sleep();
        }
    }
}

int main(int argc, char** argv) {
    ros::init(argc, argv, "image_gps_publisher");
    ros::NodeHandle nh;

    // Create publishers
    image_pub_ = nh.advertise<sensor_msgs::Image>("/camera/image_raw", 10);
    gps_pub_ = nh.advertise<sensor_msgs::NavSatFix>("/gps/fix", 10);

    // Start threads for publishing images and GPS data
    std::thread image_thread(publishImages);
    std::thread gps_thread(publishGPSData);

    // Wait for threads to finish (they run indefinitely in this example)
    image_thread.join();
    gps_thread.join();

    return 0;
}

  

 

订阅节点

image_gps_subscriber.cpp

 

#include <ros/ros.h>
#include <sensor_msgs/Image.h>
#include <sensor_msgs/NavSatFix.h>
#include <cv_bridge/cv_bridge.h>
#include <opencv2/opencv.hpp>
#include <deque>
#include <utility>

std::deque<std::pair<cv::Mat, ros::Time>> image_queue;
std::queue<sensor_msgs::NavSatFixConstPtr> gpsQueue;
std::mutex gpsQueue_Lock;// 队列锁

void gpsCallback(const sensor_msgs::NavSatFixConstPtr& gps_msg) {
    gpsQueue_Lock.lock();
    gpsQueue.push(gps_msg);
    gpsQueue_Lock.unlock();
}


void imageCallback(const sensor_msgs::ImageConstPtr& image_msg) {
    // Convert ROS image message to OpenCV image
    cv_bridge::CvImagePtr cv_image;
    try {
        cv_image = cv_bridge::toCvCopy(image_msg, sensor_msgs::image_encodings::BGR8);
    } catch (cv_bridge::Exception& e) {
        ROS_ERROR("cv_bridge exception: %s", e.what());
        return;
    }
    
    // Get the timestamp of the image
    double image_timestamp = image_msg->header.stamp.toSec();

    // Store image and timestamp in the queue
    image_queue.emplace_back(cv_image->image, image_timestamp);

    
    gpsQueue_Lock.lock();

    while(!gpsQueue.empty()){

        sensor_msgs::NavSatFixConstPtr GPS_msg = gpsQueue.front();
        double gps_timestamp = GPS_msg->header.stamp.toSec();
        double time_diffrence = image_timestamp - gps_timestamp;
        printf("img t: %f, gps t: %f , time_diffrence: %f \n", image_timestamp, gps_timestamp,time_diffrence);
       

        if(abs(image_timestamp - gps_timestamp)<=0.01){// 图像和gps时间差在0.01s=10ms内的算匹配成功 vins-fusion参考值
            // 在线阶段,假设图像20帧/S 相邻帧间隔50ms 中间值间隔25ms算阈值考虑。
            // 离线阶段,一般会提前手动处理图像和GPS时间戳对齐,图像名字以时间戳保存,GNSS的第一列也是一样的时间戳。

            // 经纬度、海拔
            double latitude = GPS_msg->latitude;
            double longitude = GPS_msg->longitude;
            double altitude = GPS_msg->altitude;
            gpsQueue.pop();// 使用完毕 丢弃
            
            std::cout << std::fixed << std::setprecision(9)
            << "图像和GNSS匹配成功" 
            <<" 经度: "<<latitude
            <<" 纬度: "<<longitude
            <<" 高度: "<<altitude
            <<std::endl;

            // 送入 位姿估计线程

            cv::imshow("Matched Image", cv_image->image);
            cv::waitKey(1);
            break;

          
        }
        else if(gps_timestamp < image_timestamp - 0.01)// GPS先开始采集,图像滞后,比图像早期的GPS丢弃
        {
             gpsQueue.pop();
             //break;
        }
        else if(gps_timestamp > image_timestamp + 0.01)// GPS采集快,图像处理慢,比图像快的GPS保留,等待后续图像来匹配
        {
            break;
        }

    }


    gpsQueue_Lock.unlock();// gps
}



int main(int argc, char** argv) {
    // Initialize ROS
    ros::init(argc, argv, "image_gps_matcher");
    ros::NodeHandle nh;

    // Subscribe to image and GPS topics
    ros::Subscriber image_sub = nh.subscribe("/camera/image_raw", 10, imageCallback);
    ros::Subscriber gps_sub = nh.subscribe("/gps/fix", 10, gpsCallback);
    cv::namedWindow("Matched Image", cv::WINDOW_AUTOSIZE);

    // 使用 MultiThreadedSpinner,指定线程数为 4
    //ros::MultiThreadedSpinner spinner(4);
    // 调用 spinner 来处理回调函数
    //spinner.spin();


    /*
        阻塞调用,处理回调函数
        ros::spin() 使节点进入一个循环,不断处理所有回调函数,直到节点被关闭或中断。
        适用于那些只依赖于回调函数的节点,例如只处理订阅消息和服务请求的节点。
        它会阻塞当前线程,使得节点在处理回调函数时不会执行其他代码。
        如果你的节点没有其他需要执行的任务,使用 ros::spin() 可以保持节点活跃,确保所有回调函数得到处理。
    */
    ros::spin();
     /*
        非阻塞调用 
        处理所有到达的回调函数一次,然后返回。
        它是非阻塞的,允许主循环继续执行其他代码。
        当你需要在节点中执行除回调处理之外的其他逻辑时,例如定时任务、计算或其他非 ROS 相关的操作。
        你可以在主循环中调用 ros::spinOnce(),使其在处理回调函数的同时执行其他任务。
     */
    
    //ros::spinOnce();

    return 0;
}