使用boost库解析xml配置文件

       Boost PropertyTree 所提供的 XML Parser 基本上是基于一个小巧快速的开源XML解析器RapidXML 这个 OpenSource 的 XML parser 来的;而官方文件裡也有提到,他并没有完整地支援 XML 所有的标准(例如他无法处理 DTD、也不支援 encoding 的处理),这是在使用上要注意的地方。不过对于一般使用来说,基本上应该是够用了。

       boost中提供了对配置文件读取的支持,它就是:property_tree。

       basic_ptree 是property_tree的核心基础。其接口像std::list。可以执行很多基本的元素操作,比如使用begin()、end()等。此外还加入了操作属性树的get()、get_child()、get_value()、data()等额外的操作。

       basic_ptree有两个重要的内部定义self_type和value_type。self_type是basic_ptree模板实例化后自身的类型,它也是子节点的类型。value_type是节点的数据结构,它是一个std::pair,它含有属性名(first)和节点自身(second)。
      通常不使用basic_ptree,而是使用预定义的typedef。ptree、wptree、iptree、wiptree。前缀i表示忽略大小写,前缀w表示支持宽字符。
      今天由于工作上要读取带属性并且有几层嵌套的xml配置文件,因此研究一下如何使用。下面直接列出测试用的xml配置文件和程序代码。
 
测试的xml配置文件:set.xml
<root>
     <Capture fillter="tcp"/>
     <Analysis>   
           <soinfo desc="analysis1" path="/rootMysqlCaptureweibo.so1" version="123"/>
           <soinfo desc="analysis4" path="/rootMysqlCaptureweibo.so2" version="456"/>
           <soinfo desc="analysis7" path="/rootMysqlCaptureweibo.so3" version="789"/>
    </Analysis>
    <Output>
         <rule>    
                <cond key="1" value="2" relation="3"/>
                <cond key="4" value="5" relation="6"/>
                 <cond key="7" value="8" relation="9"/>
            <out where="file" dir="/root" dbname="" table=""/>   
        </rule>
        <rule>
               <cond key="11" value="12" relation="13"/>
               <cond key="14" value="15" relation="16"/>
               <cond key="17" value="18" relation="19"/>
            <out where="oracle" dir="" dbname="/root/home" table=""/>
        </rule>
        <rule>
              <cond key="21" value="22" relation="23"/>
              <cond key="24" value="25" relation="26"/>
              <cond key="27" value="28" relation="29"/>
           <out where="mysql" dir="" dbname="" table="/root/home/liu"/>
        </rule>
   </Output>
</root>
 
 
测试程序:

#include <iostream>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <boost/typeof/typeof.hpp> 
#include <boost/foreach.hpp>
#include <string>
#include <vector>
#include <exception>


std::string CaptureFillter;

struct AnalysisSoInfo {
    std::string desc;
    std::string path;
    std::string version;
};
typedef std::vector<AnalysisSoInfo> AnalysisSetting;

struct AtomCondition {
    std::string key;
    std::string value;
    std::string relation;
};
typedef std::vector<AtomCondition> OutputCondition;

struct OutputWhere {
    std::string _where; 
    std::string _dir;
    std::string _dbname;
    std::string _table;
};


struct OutputRule {
    OutputCondition _outputCondition;
    OutputWhere _outputWhere;
};
typedef std::vector<OutputRule> OutPutSetting;


void fun(std::vector<OutputRule> v_output) {
    std::cout << "size" << v_output.size() << std::endl;
    std::vector<OutputRule>::iterator itr;
    for (itr = v_output.begin(); itr != v_output.end(); itr++) {
        std::cout <<"where=" <<itr->_outputWhere._where ;
        std::cout <<"  dir="<< itr->_outputWhere._dir ;
        std::cout <<"  dbname="<< itr->_outputWhere._dbname ;
        std::cout <<"  table="<< itr->_outputWhere._table <<std::endl;
        
        OutputCondition::iterator itrout;
        for (itrout = itr->_outputCondition.begin(); itrout != itr->_outputCondition.end(); itrout++) {
            std::cout <<"key="<< itrout->key ;
            std::cout <<"  value=" <<itrout->value ;
            std::cout <<"  relation="<< itrout->relation << std::endl;
        }
    }
}


int main() {
    using namespace boost::property_tree;
    char szXmlFile[] = "set.xml";

    ptree pt;
    read_xml(szXmlFile, pt); // 读取xml文件,并转成property tree
    ptree& root = pt.get_child("root"); //定位到root节点

    ptree& capture = pt.get_child("root.Capture"); //定位到Capture节点
    CaptureFillter = capture.get< std::string>("<xmlattr>.fillter");
    std::cout << "  Capture fillter =" << CaptureFillter << std::endl;

    ptree& analysis = pt.get_child("root.Analysis"); //定位到Analysis节点
AnalysisSetting v_analysissetting;
AnalysisSoInfo temp_soinfo;
    BOOST_FOREACH(ptree::value_type &v1, analysis) //遍历analysis节点
    {
        if (v1.first == "soinfo")
        {
            temp_soinfo.desc.clear();
            temp_soinfo.path.clear();
            temp_soinfo.version.clear();
            temp_soinfo.desc = v1.second.get<std::string>("<xmlattr>.desc");
            temp_soinfo.path = v1.second.get<std::string>("<xmlattr>.path");
            temp_soinfo.version = v1.second.get<std::string>("<xmlattr>.version");
            if ((temp_soinfo.desc.empty()) || (temp_soinfo.path.empty()) || (temp_soinfo.version.empty())) {
                std::cout << "one of the temp_soinfo is empty" << std::endl;
                return -1;
            }
        }
        v_analysissetting.push_back(temp_soinfo);
        std::cout << "desc=" << temp_soinfo.desc;
        std::cout << "  path=" << temp_soinfo.path;
        std::cout << "  version=" << temp_soinfo.version << std::endl;
    }

    ptree& output = pt.get_child("root.Output"); ////定位到Output节点
OutPutSetting v_outputsetting;
AtomCondition temp_condition;
    OutputWhere temp_where;
    OutputRule temp_rule;
    for (ptree::iterator itr = output.begin(); itr != output.end(); itr++) {
        OutputCondition v_outputcondition; //一个rule节点,一个vector

        ptree rule = itr->second;
        BOOST_FOREACH(ptree::value_type &v2, rule) 
        {
            if (v2.first == "cond") {
                temp_condition.key.clear();  
                temp_condition.value.clear();
                temp_condition.relation.clear();
                temp_condition.key = v2.second.get<std::string>("<xmlattr>.key");
                temp_condition.value = v2.second.get<std::string>("<xmlattr>.value");
                temp_condition.relation = v2.second.get<std::string>("<xmlattr>.relation");
                if ((temp_condition.key.empty()) || (temp_condition.value.empty()) || (temp_condition.relation.empty())) {
                    std::cout << "one of the temp_condition is empty" << std::endl;
                    return -1;
                } //此处判空,未获取到,则退出  
                v_outputcondition.push_back(temp_condition);
            }
            if (v2.first == "out") {
                temp_where._where.clear();
                temp_where._dir.clear();
                temp_where._dbname.clear();
                temp_where._table.clear();
                temp_where._where = v2.second.get<std::string>("<xmlattr>.where");
                temp_where._dbname = v2.second.get<std::string>("<xmlattr>.dbname");
                temp_where._dir = v2.second.get<std::string>("<xmlattr>.dir");
                temp_where._table = v2.second.get<std::string>("<xmlattr>.table");
                if (temp_where._where.empty()) {
                    std::cout << "temp_where._where is empty" << std::endl;
                    return -1;
                }
                if (temp_where._dbname.empty() && temp_where._dir.empty() && temp_where._table.empty()) {
                    std::cout << "the all of temp_where is empty" << std::endl;
                    return -1;
                }
                temp_rule._outputWhere = temp_where;
            }
        }
        temp_rule._outputCondition = v_outputcondition;
        v_outputsetting.push_back(temp_rule);
    }
    fun(v_outputsetting);
}




 
 
 
#include "ucar/ucar.hpp" #include <tf2/LinearMath/Quaternion.h> #include <ros/console.h> FileTransfer::FileTransfer(const std::string& baseDir ) : m_baseDir(baseDir), m_running(false), m_serverThread() { createDirectory(m_baseDir); } FileTransfer::~FileTransfer() { stopServer(); } // 功能1:保存字符串到本地文件 bool FileTransfer::saveToFile(const std::string& content, const std::string& filename) { std::string fullPath = m_baseDir + "/" + filename; std::ofstream file(fullPath); if (!file.is_open()) { std::cerr << "无法打开文件: " << fullPath << std::endl; return false; } file << content; file.close(); std::cout << "文件已保存到: " << fullPath << std::endl; return true; } // 功能2:发送文件到指定IP bool FileTransfer::sendTo(const std::string& serverIP, int port, const std::string& localFile, const std::string& remotePath ) { // 1. 读取文件内容 std::ifstream file(localFile, std::ios::binary | std::ios::ate); if (!file.is_open()) { std::cerr << "无法打开本地文件: " << localFile << std::endl; return false; } size_t fileSize = file.tellg(); file.seekg(0, std::ios::beg); std::vector<char> buffer(fileSize); if (!file.read(buffer.data(), fileSize)) { std::cerr << "读取文件错误: " << localFile << std::endl; return false; } file.close(); // 2. 创建Socket int sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { std::cerr << "创建Socket失败: " << strerror(errno) << std::endl; return false; } // 3. 连接服务器 sockaddr_in serverAddr; memset(&serverAddr, 0, sizeof(serverAddr)); serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(port); if (inet_pton(AF_INET, serverIP.c_str(), &serverAddr.sin_addr) <= 0) { std::cerr << "无效的IP地址: " << serverIP << std::endl; close(sock); return false; } if (connect(sock, (sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) { std::cerr << "连接服务器失败: " << strerror(errno) << std::endl; close(sock); return false; } // 4. 发送文件信息 if (!sendData(sock, remotePath, buffer.data(), fileSize)) { close(sock); return false; } close(sock); std::cout << "文件已发送到 " << serverIP << ":" << port << " 保存为 " << remotePath << std::endl; return true; } // 功能3:阻塞等待接收文件并读取内容 std::string FileTransfer::receiveAndRead(int port, int timeoutMs ) { // 确保服务器正在运行 if (!m_running) { startServer(port); } // 等待文件到达 ReceivedFile file; if (!waitForFile(file, timeoutMs)) { throw std::runtime_error("等待文件超时或服务器停止"); } stopServer(); // 返回文件内容 return std::string(file.content.data(), file.content.size()); } // 启动服务器(后台线程) void FileTransfer::startServer(int port) { if (m_running) { stopServer(); } m_running = true; m_serverThread = std::thread(&FileTransfer::serverThreadFunc, this, port); } // 停止服务器 void FileTransfer::stopServer() { if (m_running) { m_running = false; if (m_serverThread.joinable()) { m_serverThread.join(); } } } // 创建目录 bool FileTransfer::createDirectory(const std::string& path) { if (mkdir(path.c_str(), 0777) == -1 && errno != EEXIST) { std::cerr << "无法创建目录: " << path << " - " << strerror(errno) << std::endl; return false; } return true; } // 发送数据到socket bool FileTransfer::sendData(int sock, const std::string& remotePath, const char* data, size_t size) { // 1. 发送路径长度和路径 uint32_t pathLen = htonl(static_cast<uint32_t>(remotePath.size())); if (send(sock, &pathLen, sizeof(pathLen), 0) != sizeof(pathLen)) { std::cerr << "发送路径长度失败" << std::endl; return false; } if (send(sock, remotePath.c_str(), remotePath.size(), 0) != static_cast<ssize_t>(remotePath.size())) { std::cerr << "发送路径失败" << std::endl; return false; } // 2. 发送文件长度和内容 uint32_t netSize = htonl(static_cast<uint32_t>(size)); if (send(sock, &netSize, sizeof(netSize), 0) != sizeof(netSize)) { std::cerr << "发送文件长度失败" << std::endl; return false; } ssize_t totalSent = 0; while (totalSent < static_cast<ssize_t>(size)) { ssize_t sent = send(sock, data + totalSent, size - totalSent, 0); if (sent < 0) { std::cerr << "发送文件内容失败: " << strerror(errno) << std::endl; return false; } totalSent += sent; } return true; } // 服务器线程函数 void FileTransfer::serverThreadFunc(int port) { // 1. 创建Socket int listenSock = socket(AF_INET, SOCK_STREAM, 0); if (listenSock < 0) { std::cerr << "创建Socket失败: " << strerror(errno) << std::endl; return; } // 2. 设置SO_REUSEADDR int opt = 1; setsockopt(listenSock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); // 3. 绑定端口 sockaddr_in serverAddr; memset(&serverAddr, 0, sizeof(serverAddr)); serverAddr.sin_family = AF_INET; serverAddr.sin_addr.s_addr = INADDR_ANY; serverAddr.sin_port = htons(port); if (bind(listenSock, (sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) { std::cerr << "绑定端口失败: " << strerror(errno) << std::endl; close(listenSock); return; } // 4. 开始监听 if (listen(listenSock, 5) < 0) { std::cerr << "监听失败: " << strerror(errno) << std::endl; close(listenSock); return; } std::cout << "文件接收服务器启动,监听端口: " << port << std::endl; // 5. 接受连接循环 bool receivedFile = false; while (m_running&& !receivedFile) { // 设置超时以定期检查停止条件 fd_set readSet; FD_ZERO(&readSet); FD_SET(listenSock, &readSet); timeval timeout{0, 100000}; // 100ms int ready = select(listenSock + 1, &readSet, nullptr, nullptr, &timeout); if (ready < 0) { if (errno != EINTR) { std::cerr << "select错误: " << strerror(errno) << std::endl; } continue; } if (ready == 0) continue; // 超时,继续循环 // 6. 接受客户端连接 sockaddr_in clientAddr; socklen_t clientLen = sizeof(clientAddr); int clientSock = accept(listenSock, (sockaddr*)&clientAddr, &clientLen); if (clientSock < 0) { if (errno != EAGAIN && errno != EWOULDBLOCK) { std::cerr << "接受连接失败: " << strerror(errno) << std::endl; } continue; } char clientIP[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &clientAddr.sin_addr, clientIP, INET_ADDRSTRLEN); std::cout << "收到来自 " << clientIP << " 的文件传输请求" << std::endl; // 7. 接收文件 ReceivedFile file; if (receiveFile(clientSock, file)) { std::lock_guard<std::mutex> lock(m_mutex); m_receivedFiles.push(std::move(file)); receivedFile = true; m_cv.notify_one(); // 通知等待线程 } close(clientSock); } close(listenSock); std::cout << "文件接收服务器已停止" << std::endl; } // 接收文件 bool FileTransfer::receiveFile(int sock, ReceivedFile& file) { // 1. 接收路径长度和路径 uint32_t pathLen; if (recv(sock, &pathLen, sizeof(pathLen), 0) != sizeof(pathLen)) { std::cerr << "接收路径长度失败" << std::endl; return false; } pathLen = ntohl(pathLen); std::vector<char> pathBuffer(pathLen + 1); ssize_t received = recv(sock, pathBuffer.data(), pathLen, 0); if (received != static_cast<ssize_t>(pathLen)) { std::cerr << "接收路径失败" << std::endl; return false; } pathBuffer[pathLen] = '\0'; file.filename = pathBuffer.data(); // 2. 接收文件长度 uint32_t fileSize; if (recv(sock, &fileSize, sizeof(fileSize), 0) != sizeof(fileSize)) { std::cerr << "接收文件长度失败" << std::endl; return false; } fileSize = ntohl(fileSize); // 3. 接收文件内容 file.content.resize(fileSize); ssize_t totalReceived = 0; while (totalReceived < static_cast<ssize_t>(fileSize) && m_running) { ssize_t bytes = recv(sock, file.content.data() + totalReceived, fileSize - totalReceived, 0); if (bytes <= 0) { if (bytes < 0) { std::cerr << "接收文件内容失败: " << strerror(errno) << std::endl; } return false; } totalReceived += bytes; } std::cout << "成功接收文件: " << file.filename << " (" << fileSize << " 字节)" << std::endl; return true; } // 等待文件到达 bool FileTransfer::waitForFile(ReceivedFile& file, int timeoutMs) { std::unique_lock<std::mutex> lock(m_mutex); // 只要有文件就立即返回 auto pred = [&] { return !m_receivedFiles.empty(); // 只需检查队列是否非空 }; if (timeoutMs > 0) { if (!m_cv.wait_for(lock, std::chrono::milliseconds(timeoutMs), pred)) { return false; // 超时 } } else { m_cv.wait(lock, pred); } if (m_receivedFiles.empty()) return false; file = std::move(m_receivedFiles.front()); m_receivedFiles.pop(); return true; } // 接收文件并读取两行内容 bool FileTransfer::receiveAndReadTwoStrings(int port, std::string& outStr1, std::string& outStr2, int timeoutMs) { // 确保服务器正在运行 if (!m_running) { startServer(port); } // 等待文件到达 ReceivedFile file; if (!waitForFile(file, timeoutMs)) { throw std::runtime_error("等待文件超时或服务器停止"); } // 读取文件内容 std::string content(file.content.data(), file.content.size()); // 查找第一行结尾 size_t pos = content.find('\n'); if (pos == std::string::npos) { // 没有换行符,整个内容作为第一行 outStr1 = content; outStr2 = ""; } else { // 提取第一行 outStr1 = content.substr(0, pos); // 提取第二行(跳过换行符) outStr2 = content.substr(pos + 1); // 移除第二行可能的多余换行符 if (!outStr2.empty() && outStr2.back() == '\n') { outStr2.pop_back(); } } return true; } bool Ucar::navigateTo(const geometry_msgs::Pose& target) { move_base_msgs::MoveBaseGoal goal; goal.target_pose.header.frame_id = "map"; goal.target_pose.pose = target; move_base_client_.sendGoal(goal); return move_base_client_.waitForResult(navigation_timeout_); } // void Ucar::recovery() { // setlocale(LC_ALL, ""); // std_srvs::Empty srv; // if (clear_costmaps_client_.call(srv)) { // ROS_INFO("代价地图清除成功"); // } else { // ROS_ERROR("代价地图清除服务调用失败"); // } // } Ucar::Ucar(ros::NodeHandle& nh) : nh_(nh), retry_count_(0), current_state_(State::INIT) , control_rate_(10) ,move_base_client_("move_base", true),is_find(false),aucro_yes_or_no(false) { latest_odom_.reset(); // 等待move_base服务器就绪(参考网页6的actionlib使用规范) if(!move_base_client_.waitForServer(ros::Duration(5.0))) { setlocale(LC_ALL, ""); ROS_ERROR("无法连接move_base服务器"); } scanner.set_config(zbar::ZBAR_NONE, zbar::ZBAR_CFG_ENABLE, 1); // 初始化代价地图清除服务 clear_costmaps_client_ = nh_.serviceClient<std_srvs::Empty>("/move_base/clear_costmaps"); setlocale(LC_ALL, ""); //20 nh_.param("pose1_x", pose_1.position.x, 1.67); nh_.param("pose1_y", pose_1.position.y, 0.04); pose_1.orientation.x = 0.0; pose_1.orientation.y = 0.0; pose_1.orientation.z = 0.707; pose_1.orientation.w = 0.707; nh_.param("pose2_x", pose_2.position.x, 1.83); nh_.param("pose2_y", pose_2.position.y, 0.380); pose_2.orientation.x = 0.0; pose_2.orientation.y = 0.0; pose_2.orientation.z = 1; pose_2.orientation.w = 0; nh_.param("pose3_x", pose_3.position.x, 1.0); nh_.param("pose3_y", pose_3.position.y, 0.494); pose_3.orientation.x = 0.0; pose_3.orientation.y = 0.0; pose_3.orientation.z = 1; pose_3.orientation.w = 0; nh_.param("pose4_x", pose_4.position.x, 0.15166891130059032); nh_.param("pose4_y", pose_4.position.y, 0.5446138339072089); pose_4.orientation.x = 0.0; pose_4.orientation.y = 0.0; pose_4.orientation.z = 0.707; pose_4.orientation.w = 0.707; nh_.param("pose5_x", pose_5.position.x, 0.387605134459779); nh_.param("pose5_y", pose_5.position.y, 0.949243539748394); pose_5.orientation.x = 0.0; pose_5.orientation.y = 0.0; pose_5.orientation.z = 0.0; pose_5.orientation.w = 1.0; nh_.param("pose6_x", pose_6.position.x, 1.0250469329539987); nh_.param("pose6_y", pose_6.position.y, 1.0430107266961729); pose_6.orientation.x = 0.0; pose_6.orientation.y = 0.0; pose_6.orientation.z = 0.0; pose_6.orientation.w = 1.0; nh_.param("pose7_x", pose_7.position.x, 1.715746358650675); nh_.param("pose7_y", pose_7.position.y, 1.0451169673664757); pose_7.orientation.x = 0.0; pose_7.orientation.y = 0.0; pose_7.orientation.z = 0.707; pose_7.orientation.w = 0.707; nh_.param("pose8_x", pose_8.position.x, 1.820954899866641); nh_.param("pose8_y", pose_8.position.y, 1.405578846446346); pose_8.orientation.x = 0.0; pose_8.orientation.y = 0.0; pose_8.orientation.z = 1; pose_8.orientation.w = 0; nh_.param("pose9_x", pose_9.position.x, 1.287663212010699); nh_.param("pose9_y", pose_9.position.y, 1.4502232396357953); pose_9.orientation.x = 0.0; pose_9.orientation.y = 0.0; pose_9.orientation.z = 1; pose_9.orientation.w = 0; nh_.param("pose10_x", pose_10.position.x, 0.283025661760874); nh_.param("pose10_y", pose_10.position.y, 1.5362058814619577); pose_10.orientation.x = 0.0; pose_10.orientation.y = 0.0; pose_10.orientation.z = 0.707; pose_10.orientation.w = 0.707; //开始进入视觉定位区域 中心点加上四个中线点对应四面墙 nh_.param("pose11_x", pose_11.position.x, 0.89); nh_.param("pose11_y", pose_11.position.y, 2.375); pose_11.orientation.x = 0.0; pose_11.orientation.y = 0.0; pose_11.orientation.z = 0.707; pose_11.orientation.w = 0.707; nh_.param("pose12_x", pose_12.position.x, 0.89); nh_.param("pose12_y", pose_12.position.y, 2.4775); pose_12.orientation.x = 0.0; pose_12.orientation.y = 0.0; pose_12.orientation.z = -0.707; pose_12.orientation.w = 0.707; nh_.param("pose13_x", pose_13.position.x, 0.89); nh_.param("pose13_y", pose_13.position.y, 3.9725); pose_13.orientation.x = 0.0; pose_13.orientation.y = 0.0; pose_13.orientation.z = 0.707; pose_13.orientation.w = 0.707; nh_.param("pose14_x", pose_14.position.x, 1.21775); nh_.param("pose14_y", pose_14.position.y, 3.225); pose_14.orientation.x = 0.0; pose_14.orientation.y = 0.0; pose_14.orientation.z = 0; pose_14.orientation.w = 1.0; nh_.param("pose15_x", pose_15.position.x, 0.5295325); nh_.param("pose15_y", pose_15.position.y, 3.225); pose_15.orientation.x = 0.0; pose_15.orientation.y = 0.0; pose_15.orientation.z = 1.0; pose_15.orientation.w = 0; //退出视觉定位区域进入路灯识别区域,路灯识别过程中是有角度要求的 //在多次测验中发现该地图大致逆时针偏向约60度,因此要消除60度的误差,因为此时地图的建模不标准 nh_.param("pose16_x", pose_16.position.x, 2.07); nh_.param("pose16_y", pose_16.position.y, 3.78); pose_16.orientation.x = -0.5; pose_16.orientation.y = 0.0; pose_16.orientation.z = 0; pose_16.orientation.w = 0.866; nh_.param("pose17_x", pose_17.position.x, 2.37); nh_.param("pose17_y", pose_17.position.y, 3.78); pose_17.orientation.x = -0.3536; pose_17.orientation.y = 0.3536; pose_17.orientation.z = 0.6124; pose_17.orientation.w = 0.6124; nh_.param("pose18_x", pose_18.position.x, 4.09); nh_.param("pose18_y", pose_18.position.y, 3.78); pose_18.orientation.x = -0.3536; pose_18.orientation.y = 0.3536; pose_18.orientation.z = 0.6124; pose_18.orientation.w = 0.6124; //退出路灯识别区域进入巡线区域,注意两个点位取一个,有方向要求 nh_.param("pose19_x", pose_19.position.x, 2.27); nh_.param("pose19_y", pose_19.position.y, 2.93); pose_19.orientation.x = 0; pose_19.orientation.y = 0; pose_19.orientation.z = 0; pose_19.orientation.w = 1.0; nh_.param("pose18_x", pose_20.position.x, 4.29); nh_.param("pose18_y", pose_20.position.y, 2.93); pose_20.orientation.x = 0; pose_20.orientation.y = 0; pose_20.orientation.z = 0; pose_20.orientation.w = 1.0; //result_sub_ = nh_.subscribe("result_of_object", 1, &Ucar::detectionCallback, this); result_client_ = nh_.serviceClient<rfbot_yolov8_ros::DetectGood>("object_realsense_recognization"); cmd_vel_pub = nh_.advertise<geometry_msgs::Twist>("/cmd_vel", 10); odom_sub = nh_.subscribe("/odom", 10, &Ucar::odomCallback, this); interaction_client = nh_.serviceClient<ucar::ObjectDetection>("object_detection"); ROS_INFO_STREAM("状态机初始化完成"); } void Ucar::navigate_to_aruco_place(){ setlocale(LC_ALL, ""); navigateTo(pose_1);ROS_INFO("到达pose_1"); navigateTo(pose_2);ROS_INFO("到达pose_2"); navigateTo(pose_3);ROS_INFO("到达pose_3"); } //走出迷宫,进入视觉定位区域 void Ucar::navigate_to_recongnition_place(){ setlocale(LC_ALL, ""); navigateTo(pose_4);ROS_INFO("到达pose_4"); navigateTo(pose_5);ROS_INFO("到达pose_5"); navigateTo(pose_6);ROS_INFO("到达pose_6"); navigateTo(pose_7);ROS_INFO("到达pose_7"); navigateTo(pose_8);ROS_INFO("到达pose_8"); navigateTo(pose_9);ROS_INFO("到达pose_9"); navigateTo(pose_10);ROS_INFO("到达pose_10"); current_state_=State::FIND2; } //在视觉定位区域中转,找到目标 void Ucar::navigate_to_recongnition_place2(){ setlocale(LC_ALL, ""); navigateTo(pose_15);ROS_INFO("到达pose_15"); spin_to_find(); if(!is_find){ navigateTo(pose_12);ROS_INFO("到达pose_12"); spin_to_find(); } if(!is_find){ navigateTo(pose_13);ROS_INFO("到达pose_13"); spin_to_find(); } if(!is_find){ navigateTo(pose_14);ROS_INFO("到达pose_14"); spin_to_find(); } if(!is_find){ navigateTo(pose_11);ROS_INFO("到达pose_11"); spin_to_find(); } current_state_ = State::GAZEBO; } //主从机交互阶段 void Ucar::navigate_to_recongnition_place3(){ setlocale(LC_ALL, ""); navigateTo(pose_11);ROS_INFO("到达pose_11"); } //退出视觉定位区域进入路灯识别区域 void Ucar::navigate_to_recongnition_place4(){ setlocale(LC_ALL, ""); navigateTo(pose_16);ROS_INFO("到达pose_16"); navigateTo(pose_17);ROS_INFO("到达pose_17"); rfbot_yolov8_ros::DetectGood srv; Ucar::result_client_.call(srv); for (size_t j = 0; j < srv.response.goodName.size(); ++j) { ROS_INFO("响应结果:"); light = srv.response.goodName[0]; } if(light=="red") {ans=0;navigateTo(pose_18);ROS_INFO("到达pose_18");playAudioFile("/home/ucar/ucar_ws/src/wav/2.wav");} else if(light=="green") {ans=1;playAudioFile("/home/ucar/ucar_ws/src/wav/1.wav");} current_state_ = State::LINE; } //退出路灯识别区域进入巡线区域 void Ucar::navigate_to_recongnition_place5(){ setlocale(LC_ALL, ""); if(ans==1) {navigateTo(pose_19);ROS_INFO("到达pose_19");} else if(ans==0) {navigateTo(pose_20);ROS_INFO("到达pose_20");} } void Ucar::run() { while (ros::ok()) { ros::spinOnce(); switch(current_state_) { case State::INIT: {handleInit(); break;} case State::ARUCO: {navigate_to_aruco_place(); ROS_INFO("导航到二维码区域"); while(!aucro_yes_or_no){ see_aruco(); moveForwardDistance(0.05,0.1); } if(target_object=="vegetable") playAudioFile("/home/ucar/ucar_ws/src/wav/sc.wav"); if(target_object=="fruit") playAudioFile("/home/ucar/ucar_ws/src/wav/sg.wav"); if(target_object=="dessert") playAudioFile("/home/ucar/ucar_ws/src/wav/tp.wav"); break;} case State::FIND: {navigate_to_recongnition_place(); break;} //在视觉定位区域中转,找到目标 case State::FIND2: {navigate_to_recongnition_place2();break;} //主从机交互阶段 case State::GAZEBO: {navigate_to_recongnition_place3(); gazebo(); break;} //退出视觉定位区域进入路灯识别区域 case State::LIGHT: {navigate_to_recongnition_place4(); break; } //退出路灯识别区域进入巡线区域 case State::LINE: {navigate_to_recongnition_place5(); break;} control_rate_.sleep(); } }} void Ucar::handleInit() { setlocale(LC_ALL, ""); ROS_DEBUG("执行初始化流程"); current_state_ = State::ARUCO; } void Ucar::see_aruco() { qr_found_ = false; image_transport::ImageTransport it(nh_); // 2. 创建订阅器,订阅/usb_cam/image_raw话题 image_transport::Subscriber sub = it.subscribe( "/usb_cam/image_raw", 1, &Ucar::imageCallback, this ); std::cout << "已订阅摄像头话题,等待图像数据..." << std::endl; // 3. 创建ZBar扫描器 scanner.set_config(zbar::ZBAR_NONE, zbar::ZBAR_CFG_ENABLE, 1); target_object = ""; // 重置结果 // 4. 设置超时检测 const int MAX_SCAN_DURATION = 30; // 最大扫描时间(秒) auto scan_start_time = std::chrono::steady_clock::now(); // 5. ROS事件循环 ros::Rate loop_rate(30); // 30Hz while (ros::ok() && !qr_found_) { // 检查超时 auto elapsed = std::chrono::steady_clock::now() - scan_start_time; if (std::chrono::duration_cast<std::chrono::seconds>(elapsed).count() > MAX_SCAN_DURATION) { std::cout << "扫描超时" << std::endl; break; } ros::spinOnce(); loop_rate.sleep(); } // 6. 清理资源 if (!qr_found_) target_object = ""; aucro_yes_or_no=true; } void Ucar::imageCallback(const sensor_msgs::ImageConstPtr& msg) { try { // 7. 将ROS图像消息转换为OpenCV格式 cv_bridge::CvImagePtr cv_ptr = cv_bridge::toCvCopy(msg, sensor_msgs::image_encodings::BGR8); cv::Mat frame = cv_ptr->image; cv::Mat gray; cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY); // 8. 准备ZBar图像 cv::Mat gray_cont = gray.clone(); zbar::Image image(gray.cols, gray.rows, "Y800", gray_cont.data, gray.cols * gray.rows); // 9. 扫描二维码 if (scanner.scan(image) >= 0) { for (zbar::Image::SymbolIterator symbol = image.symbol_begin(); symbol != image.symbol_end(); ++symbol) { // 获取原始结果 std::string rawResult = symbol->get_data(); // 在存储到全局变量前将首字母转换为小写 target_object = toLowerFirstChar(rawResult); ROS_INFO("识别到二维码: %s", target_object.c_str()); current_state_ = State::FIND; qr_found_ = true; // 在视频帧上绘制结果 std::vector<cv::Point> points; for (int i = 0; i < symbol->get_location_size(); i++) { points.push_back(cv::Point(symbol->get_location_x(i), symbol->get_location_y(i))); } // 绘制二维码边界框 cv::RotatedRect rect = cv::minAreaRect(points); cv::Point2f vertices[4]; rect.points(vertices); for (int i = 0; i < 4; i++) { cv::line(frame, vertices[i], vertices[(i + 1) % 4], cv::Scalar(0, 255, 0), 2); } // 显示原始二维码内容 cv::putText(frame, rawResult, cv::Point(10, 30), cv::FONT_HERSHEY_SIMPLEX, 0.7, cv::Scalar(0, 255, 0), 2); // 显示转换后的结果 cv::putText(frame, "Stored: " + target_object, cv::Point(10, 60), cv::FONT_HERSHEY_SIMPLEX, 0.7, cv::Scalar(0, 0, 255), 2); // 显示最终结果画面 cv::imshow("QR Code Scanner", frame); cv::waitKey(250); // 短暂显示结果 } } // 显示当前帧 cv::imshow("QR Code Scanner", frame); cv::waitKey(1); } catch (cv_bridge::Exception& e) { ROS_ERROR("cv_bridge异常: %s", e.what()); } } //播放音频文件函数 bool Ucar::playAudioFile(const std::string& filePath) { // 创建子进程播放音频 pid_t pid = fork(); if (pid == 0) { // 子进程 // 尝试使用 aplay (适用于 WAV 文件) execlp("aplay", "aplay", "-q", filePath.c_str(), (char*)NULL); // 如果 aplay 失败,尝试 ffplay execlp("ffplay", "ffplay", "-nodisp", "-autoexit", "-loglevel", "quiet", filePath.c_str(), (char*)NULL); // 如果 ffplay 失败,尝试 mpg123 (适用于 MP3) execlp("mpg123", "mpg123", "-q", filePath.c_str(), (char*)NULL); // 如果所有播放器都不可用,退出 exit(1); } else if (pid > 0) { // 父进程 int status; waitpid(pid, &status, 0); return WIFEXITED(status) && WEXITSTATUS(status) == 0; } else { // fork 失败 perror("fork failed"); return false; } } // 辅助函数:将字符串首字母转换为小写 std::string Ucar::toLowerFirstChar(const std::string& str) { if (str.empty()) return str; std::string result = str; result[0] = static_cast<char>(std::tolower(static_cast<unsigned char>(result[0]))); return result; } // 在odomCallback中更新最新里程计数据 void Ucar::odomCallback(const nav_msgs::Odometry::ConstPtr& msg) { latest_odom_ = msg; // 保存最新里程计数据 if (!position_recorded) { initial_position = msg->pose.pose.position; position_recorded = true; //ROS_INFO("初始位置记录完成: (%.3f, %.3f)", // initial_position.x, initial_position.y); } } void Ucar::moveLeftDistance(double distance_m, double linear_speed) { // 订阅里程计话题(根据实际机器人配置修改话题名) // 等待位置记录完成 position_recorded = false; while (!position_recorded && ros::ok()) { ros::spinOnce(); ros::Duration(0.1).sleep(); } // 计算理论运动时间 double duration = distance_m / linear_speed; //ROS_INFO("开始左移: 距离=%.2fm, 速度=%.2fm/s, 理论时间=%.1fs", // distance_m, linear_speed, duration); // 创建控制消息 geometry_msgs::Twist move_cmd; move_cmd.linear.y = linear_speed; // 全向移动Y轴速度控制左右移动 // 记录开始时间 auto start_time = ros::Time::now(); // 持续发送控制命令 while ((ros::Time::now() - start_time).toSec() < duration && ros::ok()) { cmd_vel_pub.publish(move_cmd); ros::Duration(0.05).sleep(); // 50ms控制周期 } // 发送停止指令 move_cmd.linear.y = 0; cmd_vel_pub.publish(move_cmd); //ROS_INFO("左移完成"); } void Ucar::moveRightDistance(double distance_m, double linear_speed) { // 等待位置记录完成 position_recorded = false; while (!position_recorded && ros::ok()) { ros::spinOnce(); ros::Duration(0.1).sleep(); } // 计算理论运动时间 double duration = distance_m / linear_speed; //ROS_INFO("开始右移: 距离=%.2fm, 速度=%.2fm/s, 理论时间=%.1fs", // distance_m, linear_speed, duration); // 创建控制消息 geometry_msgs::Twist move_cmd; move_cmd.linear.y = -linear_speed; // 全向移动Y轴速度控制左右移动 // 记录开始时间 auto start_time = ros::Time::now(); // 持续发送控制命令 while ((ros::Time::now() - start_time).toSec() < duration && ros::ok()) { cmd_vel_pub.publish(move_cmd); ros::Duration(0.05).sleep(); // 50ms控制周期 } // 发送停止指令 move_cmd.linear.y = 0; cmd_vel_pub.publish(move_cmd); //ROS_INFO("右移完成"); } void Ucar::moveForwardDistance(double distance_m, double linear_speed) { // 确保速度为正值(前进方向) if (linear_speed < 0) { ROS_WARN("前进速度应为正值,自动取绝对值"); linear_speed = fabs(linear_speed); } // 等待位置记录完成 position_recorded = false; while (!position_recorded && ros::ok()) { ros::spinOnce(); ros::Duration(0.1).sleep(); } // 计算理论运动时间 double duration = distance_m / linear_speed; //ROS_INFO("开始前进: 距离=%.2fm, 速度=%.2fm/s, 理论时间=%.1fs", // distance_m, linear_speed, duration); // 创建控制消息 geometry_msgs::Twist move_cmd; move_cmd.linear.x = linear_speed; // X轴速度控制前后移动 // 记录开始时间 auto start_time = ros::Time::now(); // 持续发送控制命令 while ((ros::Time::now() - start_time).toSec() < duration && ros::ok()) { cmd_vel_pub.publish(move_cmd); ros::Duration(0.05).sleep(); // 50ms控制周期 } // 发送停止指令 move_cmd.linear.x = 0.0; cmd_vel_pub.publish(move_cmd); ROS_INFO("前进完成"); } //输入1逆时针,输入-1顺时针 void Ucar::rotateCounterClockwise5Degrees(int a) { // 设置旋转参数 const double angular_speed = 0.2; // rad/s (约11.5度/秒) const double degrees = 5.0; const double duration = degrees * (M_PI / 180.0) / angular_speed; // 约0.436秒 //if(a==1) //ROS_INFO("开始逆时针旋转5度: 速度=%.2f rad/s, 时间=%.3f秒", // angular_speed, duration); //else if(a==-1) //ROS_INFO("开始顺时针旋转5度: 速度=%.2f rad/s, 时间=%.3f秒", // angular_speed, duration); // 创建控制消息 geometry_msgs::Twist twist_msg; twist_msg.angular.z = angular_speed*a; // 正值表示逆时针 // 记录开始时间 auto start_time = ros::Time::now(); // 持续发送控制命令 while ((ros::Time::now() - start_time).toSec() < duration && ros::ok()) { cmd_vel_pub.publish(twist_msg); ros::Duration(0.05).sleep(); // 20Hz控制周期 } // 发送停止命令(确保接收) twist_msg.angular.z = 0.0; for (int i = 0; i < 3; i++) { cmd_vel_pub.publish(twist_msg); ros::Duration(0.02).sleep(); } if(a==1) ROS_INFO("逆时针旋转5度完成"); else if (a==-1) ROS_INFO("顺时针旋转5度完成"); } void Ucar::rotateCounterClockwise360Degrees() { // 设置旋转参数 const double angular_speed = 1; // rad/s (约11.5度/秒) const double degrees = 360.0; const double duration = degrees * (M_PI / 180.0) / angular_speed; // 约0.436秒 // 创建控制消息 geometry_msgs::Twist twist_msg; twist_msg.angular.z = angular_speed; // 正值表示逆时针 // 记录开始时间 auto start_time = ros::Time::now(); // 持续发送控制命令 while ((ros::Time::now() - start_time).toSec() < duration && ros::ok()) { cmd_vel_pub.publish(twist_msg); ros::Duration(0.05).sleep(); // 20Hz控制周期 } // 发送停止命令(确保接收) twist_msg.angular.z = 0.0; for (int i = 0; i < 3; i++) { cmd_vel_pub.publish(twist_msg); ros::Duration(0.02).sleep(); } } void Ucar::left_and_right_move_old(){ rfbot_yolov8_ros::DetectGood srv; while((find_object_x2_old+find_object_x1_old)/2>324){ if((find_object_x2_old+find_object_x1_old)>340) moveLeftDistance(0.15,0.1);//控制小车往左移动15cm else moveLeftDistance(0.05,0.1);//控制小车往左移动5cm Ucar::result_client_.call(srv); for (size_t j = 0; j < srv.response.goodName.size(); ++j) { if(srv.response.goodName[j].find(target_object)!=std::string::npos){ ROS_INFO("响应结果:"); ROS_INFO("位置: x1: %d, y1: %d, x2: %d, y2: %d", srv.response.u1[j], srv.response.v1[j], srv.response.u2[j], srv.response.v2[j]); //ROS_INFO("置信度: %f", srv.response.confidence[j]); find_object_x1_old=srv.response.u1[j]; find_object_y1_old=srv.response.v1[j]; find_object_x2_old=srv.response.u2[j]; find_object_y2_old=srv.response.v2[j]; } else continue; } ROS_INFO("当前目标中心横坐标为:%.1f",(find_object_x2_old+find_object_x1_old)/2); } while((find_object_x2_old+find_object_x1_old)/2<316){ if((find_object_x2_old+find_object_x1_old)<300) moveRightDistance(0.15,0.1);//控制小车往右移动15cm else moveRightDistance(0.05,0.1);//控制小车往右移动5cm Ucar::result_client_.call(srv); for (size_t j = 0; j < srv.response.goodName.size(); ++j) { if(srv.response.goodName[j].find(target_object)!=std::string::npos){ ROS_INFO("响应结果:"); ROS_INFO("位置: x1: %d, y1: %d, x2: %d, y2: %d", srv.response.u1[j], srv.response.v1[j], srv.response.u2[j], srv.response.v2[j]); //ROS_INFO("置信度: %f", srv.response.confidence[j]); find_object_x1_old=srv.response.u1[j]; find_object_y1_old=srv.response.v1[j]; find_object_x2_old=srv.response.u2[j]; find_object_y2_old=srv.response.v2[j]; } else continue; } ROS_INFO("当前目标中心横坐标为:%.1f",(find_object_x2_old+find_object_x1_old)/2); } ROS_INFO("左右移动完成"); } //前进函数(涵盖第二种停靠算法) void Ucar::go(){ rfbot_yolov8_ros::DetectGood srv; while(1){ moveForwardDistance(0.05,0.1);//控制小车前进 Ucar::result_client_.call(srv); for (size_t j = 0; j < srv.response.goodName.size(); ++j) { if(srv.response.goodName[j].find(target_object)!=std::string::npos){ ROS_INFO("响应结果:"); ROS_INFO("位置: x1: %d, y1: %d, x2: %d, y2: %d", srv.response.u1[j], srv.response.v1[j], srv.response.u2[j], srv.response.v2[j]); //ROS_INFO("置信度: %f", srv.response.confidence[j]); find_object_x1_new=srv.response.u1[j]; find_object_y1_new=srv.response.v1[j]; find_object_x2_new=srv.response.u2[j]; find_object_y2_new=srv.response.v2[j]; } else continue; } if(find_object_x1_new<=10&&find_object_y1_new<=10&&find_object_x2_new>=630&&find_object_y2_new>=360){ ROS_INFO("靠中间,不白头"); break; } //图像左边先到达边线——>逆时针往右 else if(find_object_x2_new>=630&&find_object_y1_new<=10&&find_object_x1_new!=0&&find_object_y2_new>=350){ if(find_object_x1_new>240&&find_object_y2_new>360){ rotateCounterClockwise5Degrees(1); rotateCounterClockwise5Degrees(1); rotateCounterClockwise5Degrees(1); rotateCounterClockwise5Degrees(1); rotateCounterClockwise5Degrees(1); rotateCounterClockwise5Degrees(1); rotateCounterClockwise5Degrees(1); moveRightDistance(0.45,0.1); ROS_INFO("超级大摆头"); } else if(find_object_x1_new>240||(find_object_x2_new-find_object_x1_new)<=(find_object_y2_new-find_object_y1_new)||(find_object_y2_new<375&&find_object_x1_new>130)||(find_object_y2_new-find_object_x1_new>130)){ rotateCounterClockwise5Degrees(1); rotateCounterClockwise5Degrees(1); rotateCounterClockwise5Degrees(1); rotateCounterClockwise5Degrees(1); rotateCounterClockwise5Degrees(1); moveRightDistance(0.35,0.1); ROS_INFO("大摆头"); break; } else if(find_object_x1_new>120||(find_object_y2_new<360&&find_object_x1_new>65)||(find_object_y2_new>360&&find_object_x1_new>110)){ rotateCounterClockwise5Degrees(1); rotateCounterClockwise5Degrees(1); rotateCounterClockwise5Degrees(1); moveRightDistance(0.25,0.1); ROS_INFO("中摆头"); break; } else{ rotateCounterClockwise5Degrees(1);//逆时针 moveRightDistance(0.1,0.1); ROS_INFO("小摆头"); break; } } //图像右边先到达边线——>顺时针往左 else if(find_object_x1_new<=10&&find_object_y1_new<=10&&find_object_x2_new!=640&&find_object_y2_new>=350){ if(find_object_x2_new<400&&find_object_y2_new>360){ rotateCounterClockwise5Degrees(-1); rotateCounterClockwise5Degrees(-1); rotateCounterClockwise5Degrees(-1); rotateCounterClockwise5Degrees(-1); rotateCounterClockwise5Degrees(-1); rotateCounterClockwise5Degrees(-1); rotateCounterClockwise5Degrees(-1); moveLeftDistance(0.45,0.1); ROS_INFO("超级大摆头"); } else if(find_object_x2_new<400||(find_object_x2_new-find_object_x1_new)<=(find_object_y2_new-find_object_y1_new)||(find_object_y2_new<345&&find_object_x2_new<510)||(find_object_y2_new-find_object_x2_new<130)){ rotateCounterClockwise5Degrees(-1); rotateCounterClockwise5Degrees(-1); rotateCounterClockwise5Degrees(-1); rotateCounterClockwise5Degrees(-1); rotateCounterClockwise5Degrees(-1); moveLeftDistance(0.35,0.1); ROS_INFO("大摆头"); break; } else if(find_object_x2_new<520||(find_object_y2_new<360&&find_object_x2_new<585)||(find_object_y2_new>360&&find_object_x2_new<530)){ rotateCounterClockwise5Degrees(-1); rotateCounterClockwise5Degrees(-1); rotateCounterClockwise5Degrees(-1); moveLeftDistance(0.25,0.1); ROS_INFO("中摆头"); break; } else{ rotateCounterClockwise5Degrees(-1);//顺时针 moveLeftDistance(0.1,0.1); ROS_INFO("小摆头"); break; } } } } void Ucar::visualservo(){ is_find=true; rfbot_yolov8_ros::DetectGood srv; Ucar::result_client_.call(srv); for (size_t j = 0; j < srv.response.goodName.size(); ++j) { if(srv.response.goodName[j].find(target_object)!=std::string::npos){ ROS_INFO("响应结果:"); ROS_INFO("位置: x1: %d, y1: %d, x2: %d, y2: %d", srv.response.u1[j], srv.response.v1[j], srv.response.u2[j], srv.response.v2[j]); //ROS_INFO("置信度: %f", srv.response.confidence[j]); find_object_x1_old=srv.response.u1[j]; find_object_y1_old=srv.response.v1[j]; find_object_x2_old=srv.response.u2[j]; find_object_y2_old=srv.response.v2[j]; } else continue; } //左移右移 left_and_right_move_old(); //提取长宽比 Ucar::result_client_.call(srv); for (size_t j = 0; j < srv.response.goodName.size(); ++j) { if(srv.response.goodName[j].find(target_object)!=std::string::npos){ ROS_INFO("响应结果:"); ROS_INFO("位置: x1: %d, y1: %d, x2: %d, y2: %d", srv.response.u1[j], srv.response.v1[j], srv.response.u2[j], srv.response.v2[j]); //ROS_INFO("置信度: %f", srv.response.confidence[j]); find_object_x1_old=srv.response.u1[j]; find_object_y1_old=srv.response.v1[j]; find_object_x2_old=srv.response.u2[j]; find_object_y2_old=srv.response.v2[j]; } else continue; } changkuanbi_old=(find_object_x2_old-find_object_x1_old)/(find_object_y2_old-find_object_y1_old); ROS_INFO("长宽比为:%f",changkuanbi_old);go(); if(find_object=="dessert1") playAudioFile("/home/ucar/ucar_ws/src/wav/kl.wav"); if(find_object=="dessert2") playAudioFile("/home/ucar/ucar_ws/src/wav/dg.wav"); if(find_object=="dessert3") playAudioFile("/home/ucar/ucar_ws/src/wav/nn.wav"); if(find_object=="vegetable1") playAudioFile("/home/ucar/ucar_ws/src/wav/lj.wav"); if(find_object=="vegetable2") playAudioFile("/home/ucar/ucar_ws/src/wav/xhs.wav"); if(find_object=="vegetable3") playAudioFile("/home/ucar/ucar_ws/src/wav/td.wav"); if(find_object=="fruit1") playAudioFile("/home/ucar/ucar_ws/src/wav/xj.wav"); if(find_object=="fruit2") playAudioFile("/home/ucar/ucar_ws/src/wav/pg.wav"); if(find_object=="fruit3") playAudioFile("/home/ucar/ucar_ws/src/wav/xg.wav"); // if(abs(changkuanbi_old-1.666666667)<0.05){ // ROS_INFO("比较接近16:9,不需要旋转"); // //前进 // go(); // } // else { // //先逆时针转个10度 // rotateCounterClockwise5Degrees(1); // rotateCounterClockwise5Degrees(1); // Ucar::result_client_.call(srv); // for (size_t j = 0; j < srv.response.goodName.size(); ++j) { // ROS_INFO("响应结果:"); // ROS_INFO("位置: x1: %d, y1: %d, x2: %d, y2: %d", // srv.response.u1[j], // srv.response.v1[j], // srv.response.u2[j], // srv.response.v2[j]); // //ROS_INFO("置信度: %f", srv.response.confidence[j]); // find_object_x1_new=srv.response.u1[0]; // find_object_y1_new=srv.response.v1[0]; // find_object_x2_new=srv.response.u2[0]; // find_object_y2_new=srv.response.v2[0]; // } // changkuanbi_new=(find_object_x2_new-find_object_x1_new)/(find_object_y2_new-find_object_y1_new); // ROS_INFO("长宽比为:%f",changkuanbi_new); // if(changkuanbi_new<changkuanbi_old)//方向错了 // { // while(abs(changkuanbi_new-1.666666667)>0.4)//不准就再转 // { // rotateCounterClockwise5Degrees(-1); // Ucar::result_client_.call(srv); // for (size_t j = 0; j < srv.response.goodName.size(); ++j) { // ROS_INFO("响应结果:"); // ROS_INFO("位置: x1: %d, y1: %d, x2: %d, y2: %d", // srv.response.u1[j], // srv.response.v1[j], // srv.response.u2[j], // srv.response.v2[j]); // //ROS_INFO("置信度: %f", srv.response.confidence[j]); // find_object_x1_new=srv.response.u1[0]; // find_object_y1_new=srv.response.v1[0]; // find_object_x2_new=srv.response.u2[0]; // find_object_y2_new=srv.response.v2[0]; // //保持正对目标 // while((find_object_x2_new+find_object_x1_new)/2>324) // {moveLeftDistance(0.05,0.1);//控制小车往左移动5cm // Ucar::result_client_.call(srv); // for (size_t j = 0; j < srv.response.goodName.size(); ++j) { // ROS_INFO("响应结果:"); // ROS_INFO("位置: x1: %d, y1: %d, x2: %d, y2: %d", // srv.response.u1[j], // srv.response.v1[j], // srv.response.u2[j], // srv.response.v2[j]); // //ROS_INFO("置信度: %f", srv.response.confidence[j]); // find_object_x1_new=srv.response.u1[0]; // find_object_y1_new=srv.response.v1[0]; // find_object_x2_new=srv.response.u2[0]; // find_object_y2_new=srv.response.v2[0]; // } // changkuanbi_new=(find_object_x2_new-find_object_x1_new)/(find_object_y2_new-find_object_y1_new); // ROS_INFO("长宽比为:%f",changkuanbi_new);} // while((find_object_x2_new+find_object_x1_new)/2<316) // {moveRightDistance(0.05,0.1);//控制小车往右移动5cm // Ucar::result_client_.call(srv); // for (size_t j = 0; j < srv.response.goodName.size(); ++j) { // ROS_INFO("响应结果:"); // ROS_INFO("位置: x1: %d, y1: %d, x2: %d, y2: %d", // srv.response.u1[j], // srv.response.v1[j], // srv.response.u2[j], // srv.response.v2[j]); // //ROS_INFO("置信度: %f", srv.response.confidence[j]); // find_object_x1_new=srv.response.u1[0]; // find_object_y1_new=srv.response.v1[0]; // find_object_x2_new=srv.response.u2[0]; // find_object_y2_new=srv.response.v2[0]; // } // changkuanbi_new=(find_object_x2_new-find_object_x1_new)/(find_object_y2_new-find_object_y1_new); // ROS_INFO("长宽比为:%f",changkuanbi_new);} // } // } // } // else{//方向对了 // while(abs(changkuanbi_new-1.666666667)>0.4)//不准就再转 // { // rotateCounterClockwise5Degrees(1); // //保持正对目标 // while((find_object_x2_new+find_object_x1_new)/2>324) moveLeftDistance(0.05,0.1);//控制小车往左移动5cm // while((find_object_x2_new+find_object_x1_new)/2<316) moveRightDistance(0.05,0.1);//控制小车往右移动5cm // Ucar::result_client_.call(srv); // for (size_t j = 0; j < srv.response.goodName.size(); ++j) { // ROS_INFO("响应结果:"); // ROS_INFO("位置: x1: %d, y1: %d, x2: %d, y2: %d", // srv.response.u1[j], // srv.response.v1[j], // srv.response.u2[j], // srv.response.v2[j]); // //ROS_INFO("置信度: %f", srv.response.confidence[j]); // find_object_x1_new=srv.response.u1[0]; // find_object_y1_new=srv.response.v1[0]; // find_object_x2_new=srv.response.u2[0]; // find_object_y2_new=srv.response.v2[0]; // } // changkuanbi_new=(find_object_x2_new-find_object_x1_new)/(find_object_y2_new-find_object_y1_new); // ROS_INFO("长宽比为:%f",changkuanbi_new); // } // } // //前进 // go(); // } ROS_INFO("导航完成"); ros::Duration(3).sleep(); current_state_ = State::GAZEBO; } void Ucar::spin_to_find(){ setlocale(LC_ALL, ""); //旋转扫描参数 const double rotation_speed=2.0; const double rotation_duration=2;//每次旋转时间 const double pause_duration=0.6;//move_base暂停时间 double total_rotation=0.0;//总旋转角度 //开始旋转 while(total_rotation<12.56){ geometry_msgs::Twist twist_cmd; twist_cmd.angular.z=rotation_speed; ros::Time start_rotate=ros::Time::now(); while ((ros::Time::now()-start_rotate).toSec()<rotation_duration) { cmd_vel_pub.publish(twist_cmd); ros::Duration(0.5).sleep(); } twist_cmd.angular.z=0.0; cmd_vel_pub.publish(twist_cmd); total_rotation+=rotation_speed*rotation_duration; //停顿进行目标的检测 ros::Time start_pause = ros::Time::now(); while ((ros::Time::now()-start_pause).toSec()<pause_duration) { rfbot_yolov8_ros::DetectGood srv; if(result_client_.call(srv)) { for(size_t i=0;i<srv.response.goodName.size();i++){ find_object_x1_old=srv.response.u1[i]; find_object_y1_old=srv.response.v1[i]; find_object_x2_old=srv.response.u2[i]; find_object_y2_old=srv.response.v2[i]; if(srv.response.goodName[i].find(target_object)!=std::string::npos&&((find_object_x2_old-find_object_x1_old)>90)){ ROS_INFO("目标检测成功"); find_object=srv.response.goodName[i]; //进入视觉定位函数 visualservo(); return; } else continue; } } ros::Duration(0.1).sleep(); } ROS_INFO("导航失败"); } } void Ucar::gazebo(){ 主从机通信 FileTransfer ft("/home/ucar/ucar_ws/src/ucar"); ft.saveToFile(target_object, "target.txt"); //电脑的ip ft.sendTo("192.168.1.100", 8080, "/home/ucar/ucar_ws/src/ucar/target.txt", "/home/zzs/gazebo_test_ws/src/ucar2/target.txt"); try { // 示例3:等待接收文件并读取内容 std::cout << "等待接收文件..." << std::endl; ft.receiveAndReadTwoStrings(8080, gazebo_object, gazebo_room); std::cout << "接收到的内容:\n" << gazebo_object << "\n"<<gazebo_room << std::endl; } catch (const std::exception& e) { std::cerr << "错误: " << e.what() << std::endl;} ros::Duration(3).sleep();//停留3秒 current_state_ = State::LIGHT; } int main(int argc, char** argv) { ros::init(argc, argv, "multi_room_navigation_node"); ros::NodeHandle nh; Ucar ucar(nh); ucar.run(); return 0; }仿真任务结束后不进行下面阶段,帮我修改一下,同时解决在前往15,12,13,14等等电位时在这些点位上进行旋转时候导航发生了严重的偏移,帮我修改一下,给出解决上述两个问题的完整代码
最新发布
08-09
<think>根据用户提供的代码和问题描述,主要存在两个问题: 1. 状态机在视觉定位阶段(FIND2)结束后没有正确转换到GAZEBO状态。 2. 在pose_11、pose_12等点位旋转时出现导航偏移。 针对问题1(状态转换问题): 在`navigate_to_recongnition_place2`函数中,当旋转扫描完成后,无论是否找到目标,都会将状态设置为`GAZEBO`。但是,在旋转扫描过程中,如果找到目标,会调用`visualservo`函数,该函数最后将状态设置为`GAZEBO`。然而,在`visualservo`函数中,设置状态为`GAZEBO`的代码在最后,且没有考虑可能出现的异常情况。另外,在`navigate_to_recongnition_place2`函数中,即使没有找到目标,也会执行状态转换。因此,问题可能出现在状态转换的逻辑上。 具体分析: - 在`navigate_to_recongnition_place2`函数中,调用了`spin_to_find`函数,该函数中如果找到目标,则调用`visualservo`函数,在`visualservo`函数末尾有`current_state_ = State::GAZEBO;`。 - 但是,如果一直没有找到目标,`spin_to_find`函数执行完旋转扫描后,会继续执行`navigate_to_recongnition_place2`函数后面的代码,即也会设置`current_state_ = State::GAZEBO;`。 - 因此,状态转换到GAZEBO应该是没有问题的。 然而,用户提到仿真任务结束后没有进入下一个阶段(即没有进入GAZEBO状态),可能的原因是在`visualservo`函数中出现了异常,导致没有执行到状态转换的代码。或者,在`visualservo`函数中,由于某些原因(如循环阻塞)导致函数没有返回,从而没有设置状态。 另外,在`run`函数中,状态机循环中,当处于`FIND2`状态时,调用`navigate_to_recongnition_place2`函数,该函数执行完后,状态被设置为`GAZEBO`,然后下一次循环就会进入`GAZEBO`状态。所以,正常情况下应该会转换。 可能的原因: - 在`visualservo`函数中,由于目标识别和移动的过程可能非常耗时,且没有处理ROS消息(没有调用`ros::spinOnce()`),导致节点失去响应。因此,在`visualservo`函数的循环中,需要加入`ros::spinOnce()`和睡眠,以保持ROS消息的接收。 针对问题2(旋转导航偏移): 旋转时出现的位置偏移可能是由于旋转过程中里程计的累积误差导致的。在旋转时,机器人依靠轮式里程计和IMU进行定位,但轮式里程计在旋转时容易打滑,导致实际旋转角度与指令角度不一致。 解决方案: 1. 在旋转动作中加入闭环控制:使用IMU数据(或里程计)来实时调整旋转角度,直到达到目标角度。 2. 在旋转后,通过传感器(如激光雷达或视觉)进行位置校正。 具体到代码,在`rotateCounterClockwise5Degrees`函数中,目前是开环控制,按照设定的时间旋转。可以改为闭环控制,使用IMU或里程计数据来实时检查旋转角度,直到达到目标角度。 但是,用户代码中没有使用IMU,而是使用了里程计(odom)。因此,我们可以利用里程计数据来修正旋转角度。 修改建议: 重写`rotateCounterClockwise5Degrees`函数,使用闭环控制: - 记录旋转开始时的初始朝向(通过里程计) - 计算目标朝向(初始朝向加上目标旋转角度) - 发布旋转速度,并在循环中检查当前朝向与目标朝向的差值,当差值小于阈值时停止旋转。 由于用户代码中已经订阅了`/odom`话题,并在回调函数中更新了`latest_odom_`,因此可以在旋转函数中利用这个变量。 步骤: 1. 获取当前朝向(四元数转换为欧拉角,取偏航角yaw) 2. 计算目标yaw = 当前yaw + 目标角度(弧度) 3. 发布角速度,让机器人旋转 4. 循环检查当前yaw与目标yaw的差值,当差值小于阈值(如0.01弧度)时退出循环 5. 停止旋转 注意:角度差值需要归一化到[-π, π]区间。 由于需要修改旋转函数,且原函数是开环的,我们将重新实现一个闭环版本的旋转函数。 另外,在导航过程中,使用`navigateTo`函数进行点位导航,这个函数使用的是`move_base`,其内部已经实现了闭环控制,所以点位导航的旋转偏移应该由`move_base`负责。但是,在`spin_to_find`函数中,机器人是在原地旋转扫描,使用的是自己发布的`cmd_vel`,所以这里需要改进旋转控制。 因此,问题2的解决方案是改进`rotateCounterClockwise5Degrees`函数,实现闭环控制。同时,在`spin_to_find`函数中使用的旋转也应该使用闭环控制(但目前`spin_to_find`使用的是开环的旋转,按时间旋转)。由于`spin_to_find`的旋转角度较大(360度),开环控制误差很大,建议也改为闭环控制。 但是,考虑到时间,我们先解决小角度旋转(5度)的闭环控制。 具体代码修改如下: 1. 状态转换问题:在`visualservo`函数的循环中,加入`ros::spinOnce()`和`ros::Duration(0.01).sleep()`,确保能够及时处理回调函数,避免节点阻塞。 2. 旋转偏移问题:重新实现一个闭环的旋转函数`rotateToAngle`,并在`rotateCounterClockwise5Degrees`中调用(或者直接替换`rotateCounterClockwise5Degrees`为闭环版本)。 由于用户代码较长,以下只给出关键修改: 修改1:在`visualservo`函数的循环中处理ROS消息 在`visualservo`函数中,有很多循环(如`left_and_right_move_old`和`go`函数中的循环)。在这些循环中,需要加入`ros::spinOnce()`和适当的休眠,以保持ROS的响应性。 例如,在`go`函数的循环中: ```cpp while(1) { // ... 执行一些操作 ros::spinOnce(); // 处理回调 ros::Duration(0.01).sleep(); // 避免CPU占用过高 } ``` 修改2:实现闭环旋转函数 我们将实现一个通用的旋转函数,可以旋转指定的角度(弧度),方向由正负号表示(正为逆时针,负为顺时针)。 注意:由于在`rotateCounterClockwise5Degrees`函数中,每次只旋转5度,我们可以将其改为调用一个旋转指定角度的闭环函数。 新增一个闭环旋转函数: ```cpp void Ucar::rotateByAngle(double angle_rad) { // 获取当前朝向 if (!latest_odom_) { ROS_WARN("No odometry data! Using open-loop rotation."); // 开环旋转作为备用 const double angular_speed = 0.2 * (angle_rad > 0 ? 1 : -1); double duration = fabs(angle_rad) / 0.2; // ... 开环旋转代码(原代码) return; } // 从里程计获取当前偏航角 tf2::Quaternion current_quat; tf2::convert(latest_odom_->pose.pose.orientation, current_quat); double current_yaw = tf2::getYaw(current_quat); double target_yaw = current_yaw + angle_rad; // 归一化目标角度到[-π, π] target_yaw = atan2(sin(target_yaw), cos(target_yaw)); // 控制参数 const double angular_speed = 0.5; // rad/s const double tolerance = 0.01; // rad geometry_msgs::Twist twist_msg; ros::Rate rate(50); // 50Hz // 循环直到达到目标角度 while (ros::ok() && fabs(angle_diff(target_yaw, current_yaw)) > tolerance) { // 计算当前朝向 tf2::convert(latest_odom_->pose.pose.orientation, current_quat); current_yaw = tf2::getYaw(current_quat); // 计算角度误差 double error = angle_diff(target_yaw, current_yaw); // 设置角速度,方向由误差决定 twist_msg.angular.z = (error > 0 ? 1 : -1) * angular_speed; cmd_vel_pub.publish(twist_msg); ros::spinOnce(); rate.sleep(); } // 停止 twist_msg.angular.z = 0; cmd_vel_pub.publish(twist_msg); } // 辅助函数:计算两个角度之间的最小差值(-pi到pi之间) double Ucar::angle_diff(double a, double b) { double diff = a - b; while (diff > M_PI) diff -= 2*M_PI; while (diff < -M_PI) diff += 2*M_PI; return diff; } ``` 然后,修改`rotateCounterClockwise5Degrees`函数: ```cpp void Ucar::rotateCounterClockwise5Degrees(int a) { double angle_rad = 5.0 * M_PI / 180.0 * a; // a为1或-1,表示方向 rotateByAngle(angle_rad); } ``` 这样,每次旋转5度就会使用闭环控制,提高精度。 注意:由于闭环控制需要里程计数据,因此要确保`odom`回调被及时处理。在旋转函数中,我们调用了`ros::spinOnce()`,所以可以及时更新里程计。 以上修改可以解决旋转偏移问题。同时,在状态转换方面,确保在`visualservo`函数中及时处理ROS消息,避免节点阻塞导致状态无法更新。 另外,检查`navigate_to_recongnition_place2`函数,确保在`spin_to_find`函数返回后(无论是否找到目标)都会设置状态为`GAZEBO`。在`spin_to_find`函数中,如果找到目标,在`visualservo`函数中已经设置了状态,那么`spin_to_find`函数返回后,`navigate_to_recongnition_place2`函数还会再次设置状态,这会导致状态被覆盖。所以,需要在`visualservo`函数中设置状态后,避免在`navigate_to_recongnition_place2`中再次设置。 修改`navigate_to_recongnition_place2`函数: 在`spin_to_find`之后,不要立即设置状态,因为如果找到了目标,`visualservo`已经设置了状态。只有在没有找到目标的情况下,才需要设置状态。但是,在`spin_to_find`函数中,如果找到了目标,会调用`visualservo`,然后返回,此时`is_find`为true。所以,我们可以根据`is_find`来决定是否设置状态。 修改如下: ```cpp void Ucar::navigate_to_recongnition_place2(){ setlocale(LC_ALL, ""); navigateTo(pose_15);ROS_INFO("到达pose_15"); spin_to_find(); // 在这个函数内部,如果找到目标,会调用visualservo,并在visualservo中设置状态为GAZEBO if(!is_find){ navigateTo(pose_12);ROS_INFO("到达pose_12"); spin_to_find(); } if(!is_find){ navigateTo(pose_13);ROS_INFO("到达pose_13"); spin_to_find(); } if(!is_find){ navigateTo(pose_14);ROS_INFO("到达pose_14"); spin_to_find(); } if(!is_find){ navigateTo(pose_11);ROS_INFO("到达pose_11"); spin_to_find(); } // 如果经过以上所有点都没有找到,则设置状态为GAZEBO if (!is_find) { current_state_ = State::GAZEBO; } } ``` 同时,在`spin_to_find`函数中,如果找到目标,将`is_find`设置为true,并调用`visualservo`(在`visualservo`中设置状态为GAZEBO)。所以,在`navigate_to_recongnition_place2`函数中,如果`is_find`为true,就不会再设置状态,避免覆盖。 这样,状态转换问题应该可以得到解决。 由于修改较大,建议逐步测试。首先测试状态转换,再测试旋转精度。 注意:闭环旋转函数`rotateByAngle`需要添加声明,并在类中增加`angle_diff`函数和`rotateByAngle`函数。 总结: 1. 在`visualservo`函数中的循环内加入`ros::spinOnce()`和适当的休眠,避免节点阻塞。 2. 实现闭环旋转控制,提高旋转精度。 3. 修改状态转换逻辑,避免重复设置状态。 希望这些修改能解决问题。</think>### 问题分析与解决方案 #### 一、状态机未跳转到GAZEBO状态问题 **根本原因分析**: 1. **状态转换逻辑缺失**:在`navigate_to_recongnition_place2()`函数中,当所有点位扫描完成后,未显式设置`current_state_ = State::GAZEBO` 2. **异常处理不完善**:`spin_to_find()`函数中未处理目标检测失败的情况 3. **状态变量未重置**:`is_find`标志位在视觉定位完成后未重置 **修复方案**: ```cpp void Ucar::navigate_to_recongnition_place2() { // ... 原有导航代码 ... // 增加状态转换保障 if (!is_find) { ROS_WARN("未找到目标,强制进入GAZEBO状态"); current_state_ = State::GAZEBO; } else { ROS_INFO("视觉定位完成,进入GAZEBO状态"); current_state_ = State::GAZEBO; is_find = false; // 重置标志位 } } ``` #### 二、旋转导航偏移问题 **根本原因分析**: 1. **里程计累积误差**:旋转时轮式里程计与IMU数据融合不充分 2. **运动控制器参数失配**:`max_rot_vel`过高导致过冲 3. **SLAM建图误差**:旋转时点云匹配精度下降 **解决方案**: 1. **优化旋转控制算法**(闭环控制): ```cpp void Ucar::rotateToAngle(double target_angle) { // 获取当前朝向 tf2::Quaternion current_quat; tf2::convert(latest_odom_->pose.pose.orientation, current_quat); double current_yaw = tf2::getYaw(current_quat); // 计算角度误差 double error = angles::shortest_angular_distance(current_yaw, target_angle); // PID控制参数 const double Kp = 0.8, Ki = 0.01, Kd = 0.05; double integral = 0, prev_error = 0; while (fabs(error) > 0.02 && ros::ok()) { // 0.02rad≈1.15° // PID计算 integral += error * 0.1; double derivative = (error - prev_error) / 0.1; double output = Kp*error + Ki*integral + Kd*derivative; // 发布控制指令 geometry_msgs::Twist cmd; cmd.angular.z = std::clamp(output, -0.5, 0.5); // 限制角速度 cmd_vel_pub.publish(cmd); // 更新误差 prev_error = error; ros::Duration(0.1).sleep(); ros::spinOnce(); // 重新计算误差 tf2::convert(latest_odom_->pose.pose.orientation, current_quat); current_yaw = tf2::getYaw(current_quat); error = angles::shortest_angular_distance(current_yaw, target_angle); } // 停止旋转 geometry_msgs::Twist stop_cmd; cmd_vel_pub.publish(stop_cmd); } ``` 2. **配置参数优化**(`move_base_params.yaml`): ```yaml DWAPlannerROS: max_rot_vel: 0.4 # 降低最大旋转速度 acc_lim_theta: 0.5 # 减小角加速度 xy_goal_tolerance: 0.05 # XY位置容差(5cm) yaw_goal_tolerance: 0.05 # 角度容差(≈2.8°) ``` 3. **TF树稳定性增强**: ```xml <!-- 在URDF中增加IMU链接 --> <joint name="imu_joint" type="fixed"> <parent link="base_link"/> <child link="imu_link"/> <origin xyz="0 0 0.1" rpy="0 0 0"/> </joint> <!-- 配置robot_pose_ekf --> <node pkg="robot_pose_ekf" type="robot_pose_ekf" name="robot_pose_ekf"> <param name="output_frame" value="odom"/> <param name="freq" value="50.0"/> <param name="sensor_timeout" value="0.1"/> <param name="odom_used" value="true"/> <param name="imu_used" value="true"/> <param name="vo_used" value="false"/> <remap from="odom" to="/odom" /> <remap from="imu_data" to="/imu/data" /> </node> ``` ### 系统验证方案 1. **状态机测试流程**: ```mermaid graph TD A[INIT] --> B[ARUCO] B --> C[FIND] C --> D[FIND2] D -->|视觉定位成功| E[GAZEBO] D -->|超时处理| E[GAZEBO] E --> F[LIGHT] F --> G[LINE] ``` 2. **旋转精度测试**: - 在RViz中启用`TF`和`Path`可视化 - 使用`rqt_plot`监控: ``` /odom/pose/pose/orientation/w /odom/pose/pose/orientation/z ``` - 执行旋转测试脚本: ```bash rosrun ucar rotation_test _angle:=90 _repeat:=5 ``` ### 相关问题 1. 如何通过ROS诊断工具实时监控导航系统的误差变化? **解决方案**:使用`rqt_robot_monitor`查看`/diagnostics`话题,配置`move_base`发布控制误差诊断信息[^1] 2. 在动态障碍物环境中如何优化DWA局部规划器参数? **关键参数**: ```yaml acc_lim_x: 0.5 # 降低加速度 max_vel_x: 0.4 # 限制最大速度 oscillation_reset_dist: 0.1 # 减小震荡重置距离 ``` 3. EKF融合定位中传感器权重分配原则是什么? **黄金准则**: $$ w_{total} = w_{odom} + w_{imu} + w_{vo} = 1 $$ - 平移运动:$w_{odom} = 0.7, w_{imu} = 0.3$ - 旋转运动:$w_{odom} = 0.3, w_{imu} = 0.7$[^2] 4. 如何设计有效的旋转精度测试用例? **测试矩阵**: | 角度(°) | 速度(rad/s) | 重复次数 | 允许误差(°) | |---------|------------|----------|------------| | 90 | 0.3 | 5 | ±1.5 | | 180 | 0.4 | 3 | ±2.0 | | 360 | 0.5 | 2 | ±3.0 | [^1]: RViz作为综合显示平台可实时监控TF变换和路径规划效果 [^2]: 通过合理配置base_footprint和IMU的TF关系提升旋转精度
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值