ROS2入门教程(看这一篇就足够啦!!!)

开源四足机器人仿真与ROS实战指南 本文详细介绍了开源四足机器人的仿真开发与ROS实战指南。通过ROS和Gazebo工具链,从环境搭建、URDF模型创建到运动控制算法实现,逐步讲解如何降低硬件开发成本并验证算法。重点涵盖了四足机器人仿真入门、步态生成及从仿真到实体的迁移策略,为开发者提供完整的实践路径。 阅读详情

        本篇博客是我花费了大量时间整理得出的,本文介绍了ROS2的基础知识和实践操作,包括Linux常用命令、ROS2核心概念、节点创建与通信、服务与参数通信等内容。后续我还会写一篇关于ros2工具,导航,建模之类的博客,大家也可以去看看

大家要及时关注博主哦,以后想看的时候不会迷路

目录

linux基本操作

ros2命令行基本操作

常见问题

1很总要的一个问题,关于includePath问题

第一章基本代码编写

1在linux下面编写代码

第二章在功能包里面组织节点

python实现

cpp实现

功能包

多线程

python实现

cpp实现

第三章话题通信

python

发布话题

订阅话题

cpp

发布话题

订阅话题,实现闭环控制

实践小项目

第一个创建消息接口包

发布信息的包

订阅信息的包

qt的尝试

实现

第四章服务

python实现

创建服务消息接口

python人脸检测

人脸检测服务实现​编辑

人脸检测客户端的实现

cpp实现

创建消息接口

cpp服务端实现​编辑

cpp客户端的实现

在python中声明参数

添加回调函数更新参数

修改其他节点的参数

在cpp中声明参数

添加回调函数更新参数

修改其他节点的参数

使用launch启动脚本

cmakelist​编辑

setup.py

使用launch传递参数

launch使用进阶

linux基本操作

~ :代表用户的操作空间的目录

/ :代表最主目录

ls:查看当前路径的文件

./+文件名字: 就是直接执行文件(可执行文件为前提)

chmod a+x + 文件名字 :可以给予普通文件执行权限,变绿,变为可执行文件(更改文件或目录的权限)

python3 +文件名.py : 直接去调用编译器去执行

cat + 文件名字 :直接去显示文本里面的内容

nano :用nano编辑器去编辑文本

mkdir + 目录名: 创建一个目录/文件夹

touch + 文件名: 创建一个空文件

cd + 目录路径 :改变工作目录,未指定路径时回到用户主目录

pwd : 显示当前目录路径

rm : 删除文件或目录

echo :

ros2命令行基本操作

 ros2 run 包名 文件名

ros2 node info 节点名称 : 查看节点的详细信息

ros2 topic echo 话题名称 : 输出话题数据

ros2 topic info 话题名称 : 查看话题的具体信息

ros2 interface show 消息接口 : 查看消息接口的详细定义

ros2 topic pub 话题名称 消息接口 "{给参数}" : 话题发布  

ros2 topic list : 看到话题清单

ros2 service list -t : 查看服务列表, -t 显示服务的接口类型

接口类型分为请求接口和相应接口两部分

ros2 interface show + 服务接口: ---------分割线上面的时请求接口, 下面的是响应接口

ros2 service call + 服务名字 + 服务接口类型 + Request数据(按照请求接口给)"{x= 1}":调用指定的服务

服务名字后面带有Parameter的都是参数通信

ros2 param list : 查看当前所有节点的参数列表

ros2 param describe +节点名字 +参数名字: 查看指定节点的参数描述 

ros2 param get + 节点 + 参数名字: 获得参数值

ros2 param set + 节点 + 参数名字 + n :设定参数值

ros2 run 包名 + 可执行文件名字 --ros-args -p + 参数名字:值 : 这就是启动的时候设定参数

ros2 run tf2_ros tf2_echo 父   子 :查看父 和子的坐标关系 

rviz2 中可以保存配置文件, 用ctrl  + shift + s 

然后用rviz2 -d ~/chapt5/....     就是用绝对路径就可以直接打开

用ros2 bag record + 话题 (/turtle/cmd_vel海龟控制) : 就可以记录我们的操作

用rso2 bag play + 生成的文件 : 就可以播放我们的命令 

apt info : 查看某个包的具体信息

ros2 run teleop_twist_keyboard teleop_twist_keyboard : 这是用键盘控制机器人 

ros2 run nav2_map_server map_saver_cli -f room(保存地点) : 这是一个地图保存的命令

当代码报错的时候用 --debug,就会打印出详细的日志,哪里出错了

环境变量:存储用户的信息 

在linux中 ,以 . 开头的文件是隐藏文件

INFO :是日志的级别 也可以是warn

 pirntenv:查看环境变量 (在后面加上 | grep + 环境变量的信息, 就可以进行过滤)

c++经过编译生成a.out才可以运行 

常见问题

1很总要的一个问题,关于includePath问题

我们一般急用code .打开xxx_ws而不要用打开xxx,ws下会自动包含我们所要的依赖路径,如果直接打开xxx尽管我们配置了includePath但是还是会报错,这时候code 打开ws,然后将这个生成的配置拷贝到xxx下也可以

因为打开xxx的时候不会对xxx_ws的includePath进行设置

如果ubuntu突然没有了网,可以用这个指令去打开

nmcli networking on

没网就用这个指令!!!

第一章基本代码编写

1在linux下面编写代码

python3 文件.py   就可以直接执行python 文件

对于c++的文件,一个直接用g++取编译,但是这个依赖多了就不行了

所以用CMakeList.txt  这种CMake来进行这种操作

在这个文件里面编写

然后cmake .

就能生成一个Makefile,再用make 就出现了一个可执行文件learn_cmake

ctrl+shift+5 :vs里面再打开一个终端

if __name__=='__main__':
    main()
在python里面这个是让这个文件当作脚本的时候可以调用main函数

第二章在功能包里面组织节点

其实在实际当中,可以直接先写一个单文件,然后直接编译运行,测试没问题之后再放到功能包里面

注意:在用ros2的时候就要去init我们使用的客户端库

ros2 pkg create :基本指令

--build-type ament_python :构建的类型(ament_cmake)

--license Apache-2.0 :证书

cy_python_pkg : 名字

-----------------------------------------------

colcon build : 构建功能包,在当前目录下扫面所有的功能包

source install/setup.bash : 修改环境变量,才能找到包

ros2 run 包名 文件名字:运行功能包

python实现

import rclpy-->这个是导入库
from rclpy.node import Node-->这个是从某个文件引入Node这个类

1要去setup.py注册节点

"可执行文件名 = 包名.文件名:main"

2 package.xml 文件里面添加依赖(你在代码导入的库要添加对应的依赖)

<depend>rclpy</depend>

cpp实现

std::make_shared<类>(),创建这个类,并返回一个共享指针

与python一样再package.xml里面添加依赖

还有

cmake_minimum_required(VERSION 3.8)
project(ros2_cpp)

add_executable(ros2_cpp_node ros2_cpp_node.cpp)

find_package(rclcpp REQUIRED) #直接茶找到对应的头文件和库文件
message(STATUS ${rclcpp_INCLUDE_DIRS}) #头文件和rclcpp依赖的文件
message(STATUS ${rclcpp_LIBRARIES}) #库文件和rclcpp依赖的库文件
target_include_directories(ros2_cpp_node PUBLIC ${rclcpp_INCLUDE_DIRS}) #头文件的包含
target_link_libraries(ros2_cpp_node ${rclcpp_LIBRARIES}) #库文件链接

这个是不用功能包来用c++创建一个节点,并且用CMakeLists.txt来组织,因为要包含rclcpp,那么就要查找对应的头文件和库文件并且包含和连接

还有这个message可以不要,只是打印信息

如果是用功能包的话,也是要再CMakeLists.txt

添加找包,可执行,然后链接可以用ros2提供的一个更方便的

还有在c++中不像python里面会把可执行文件拷贝到Install目录下lib里面,这时候我们就要自己去拷贝

添加

install(TARGETS
  cpp_node
  DESTINATION lib/${PROJECT_NAME}
)

功能包

在功能包的构建中,如果一个功能包依赖于另外一个功能包构建的结果

那么在xml里面加上<depend>功能包名</depend>这样就可以先去构建这个指定功能包

这个就是我们以后创建功能包的格式,在ws下的src/创建功能包,然后在ws下构建功能包

多线程

每个线程都能独立运行,速度快 

python实现

多线程库threading

threading.Tread(target= , args = (    ))

网上资源的下载库 requests

responst = requests.get(url)

response.encoding = 'utf-8'

url:网址

callback_world_count:统计次数的回调函数

python3 -m http.server  : 启动一个服务器

import threading
import requests

class Downloadnovel:
    def download(self,url,callback_word_count):
        print(f"{threading.get_ident()}url:{url}")
        response = requests.get(url)
        response.encoding='utf-8'
        callback_word_count(url,response.text)


    def start_download(self,url,callback_word_count):
        thread = threading.Thread(target=self.download,args=(url,callback_word_count))
        thread.start()

    
def word_count(url,result):
    print(f'{url}:{len(result)}->{result[:5]}')

def main():
    download  = Downloadnovel()
    download.start_download('http://127.0.0.1:8000/text1.txt',word_count)
    download.start_download('http://127.0.0.1:8000/text2.txt',word_count)
    download.start_download('http://127.0.0.1:8000/text3.txt',word_count)

cpp实现

python 里面有requests库,直接可以再网络上请求c++不可以,需要导入第三方库

<memory> 用智能指针包这个头文件

<funcitional>用函数包装器

C++用http的时候要用到第三方库

include_directories(include)#包含头文件的目录(这个cmakelist里面加上,因为我们拷贝到cpp_pkg的include目录下了)

#include <iostream>
#include <thread>
#include <chrono>//时间相关
#include <functional>
#include "cpp-httplib/httplib.h"//下载器

class Download
{
public:
    //有两个参数,域名和地址分开的,用函数包装器来组织
    void download(const std::string& host,const std::string& path,
    const std::function<void(const std::string&,const std::string&)> &callback_word_count
    )
    {
        std::cout<<"线程"<<std::this_thread::get_id()<<std::endl;
        //c++的客户端是这样创建的
        httplib::Client client(host);
        auto response = client.Get(path);
        if(response && response->status == 200)
        {
            callback_word_count(path,response->body);
        }
    }

    void start_download(const std::string& host,const std::string& path,
    const std::function<void(const std::string&,const std::string&)> &callback_word_count
    )
    {
        //c++11的语法,包装起来
        auto download_fun = std::bind(&Download::download,this,std::placeholders::_1,
        std::placeholders::_2,std::placeholders::_3);
        std::thread thread(download_fun,host,path,callback_word_count);
        thread.detach();//线程分开(c++线程立刻运行,堵塞无法退出,这个分离就可以运行其他线程)
    }
private:
};

int main()
{
    auto d = Download();
    auto word_count = [](const std::string& path,const std::string& result) ->void {
        std::cout<<"下载完成"<<path<<":"<<result.length()<<"->"<<result.substr(0,5)
        <<std::endl;
    };
    d.start_download("http://127.0.0.1:8000","/text1.txt",word_count);
    d.start_download("http://127.0.0.1:8000","/text2.txt",word_count);
    d.start_download("http://127.0.0.1:8000","/text3.txt",word_count);
    std::this_thread::sleep_for(std::chrono::milliseconds(1000*10));
    return 0;

}

第三章话题通信

话题的名字

话题的接口 : 就是话题的数据类型

ecample_interfaces 是一个接口库,里面有我们用的/msg/String接口,我们是依照这个模板来写

python

话题发布小说

逐行,5秒一次,创建队列,每一行都存进去,然后每一行去发布

发布完成可以去查看话题是否发布了

发布话题

import rclpy
from rclpy.node import Node
import requests
from example_interfaces.msg import String
from queue import Queue
class NovelPubNode(Node):
    def __init__(self,node_name):
        super().__init__(node_name)
        self.get_logger().info(f'{node_name},启动')
        self.novel_queue_ = Queue()
        #创建话题发布着,接口,名字,队列大小
        self.novel_publisher_ = self.create_publisher(String,'novel',10)
        #创建定时器定时发布
        self.novel_timer_ = self.create_timer(5,self.timer_callback)

    def timer_callback(self):
        if self.novel_queue_.qsize()> 0:
            msg = String()
            msg.data = self.novel_queue_.get()
            self.novel_publisher_.publish(msg)
            self.get_logger().info(f'发布了:{msg}')

    

    def download(self,url):
        response = requests.get(url)
        response.encoding='utf-8'
        text = response.text
        self.get_logger().info(f'下载{url},{len(text)}')
        #text.splitlines
        for line in text.splitlines():
            self.novel_queue_.put(line)


def main():
    rclpy.init()
    node = NovelPubNode('novel_pub')
    node.download('http://0.0.0.0:8000/text1.txt')
    rclpy.spin(node)
    rclpy.shutdown()

订阅话题

用新的线程去朗读收到的话题

rclpy.ok()检测上下文是否ok

import rclpy
from rclpy.node import Node
import espeakng
from example_interfaces.msg import String
from queue import Queue
import threading
import time
class Novel_PubNode(Node):
    def __init__(self,node_name):
        super().__init__(node_name)
        self.novel_queue_ = Queue()
        self.novel_subscriber_ = self.create_subscription(String,'novel',self.novel_callback,10)
        self.thread_ = threading.Thread(target=self.speak_thread)
        self.thread_.start()

    #受到发布的信息就会调用这个函数
    def novel_callback(self,msg):
        self.novel_queue_.put(msg.data)

    def speak_thread(self):
        speaker = espeakng.Speaker()
        speaker.voice = 'zh'
        
        while rclpy.ok():
            if self.novel_queue_.qsize()>0:
                text = self.novel_queue_.get()
                self.get_logger().info(f'开始朗读:{text}')
                speaker.say(text)
                speaker.wait()#等待说完
            else:
                time.sleep(1)



def main():
    rclpy.init()
    node = Novel_PubNode('novel_sub')
    rclpy.spin(node)
    rclpy.shutdown()

cpp

发布速度控制海龟画圆

cmd_vel 和 pose 我们需要关注 这两个消息接口所在的地方我们需要添加依赖

在Include rclcpp/rclcpp.hpp的时候要添加配置/opt/ros/humble/include/**

explicit 关键字是防止构造函数隐式类型转换

c++中用指针都用共享指针

发布话题

创建和发布都在Node方法里面,但是c++需要用智能指针去管理

还有要包含消息接口

#include "rclcpp/rclcpp.hpp"
#include <chrono>
#include "geometry_msgs/msg/twist.hpp"//引入这个消息接口

using namespace std::chrono_literals;

class TurtleCircleNode: public rclcpp::Node
{
public:
    explicit TurtleCircleNode(const std::string& node_name)
    :Node(node_name)
    {
        publisher_ = this->create_publisher<geometry_msgs::msg::Twist>("/turtle1/cmd_vel",10);
        timer_ = this->create_wall_timer(1000ms,std::bind(&TurtleCircleNode::time_callback,this));
    }
//这个定时器里面发布消息!!核心!!
    void time_callback()
    {
        auto msg = geometry_msgs::msg::Twist();
        msg.linear.x = 1.0;
        msg.angular.z = 0.5;
        publisher_->publish(msg);
    }

private:
    rclcpp::TimerBase::SharedPtr timer_;
    rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr publisher_;
};

int main(char argc,char* argv[])
{
    rclcpp::init(argc,argv);
    auto node = std::make_shared<TurtleCircleNode>("turtle_circle");
    rclcpp::spin(node);
    rclcpp::shutdown();
}

订阅话题,实现闭环控制

#include "rclcpp/rclcpp.hpp"
#include <chrono>
#include "geometry_msgs/msg/twist.hpp"//引入这个消息接口
#include "turtlesim/msg/pose.hpp"
using namespace std::chrono_literals;

class TurtleControlNode: public rclcpp::Node
{
public:
    explicit TurtleControlNode(const std::string& node_name)
    :Node(node_name)
    {
        publisher_ = this->create_publisher<geometry_msgs::msg::Twist>("/turtle1/cmd_vel",10);
        subscripter_ = this->create_subscription<turtlesim::msg::Pose>("/turtle1/pose",10,
        std::bind(&TurtleControlNode::on_pose_receivered_,this,std::placeholders::_1));
    }
这里要注意,订阅者的回调函数可以用lamda表达式还可以写一个成员函数bind包装起来
注意有参数,参数就是一个共享指针

    void on_pose_receivered_(const turtlesim::msg::Pose::SharedPtr pose)//收到参数的共享指针
    {
        //1获取当前位置
        auto current_x = pose->x;
        auto current_y = pose->y;
        RCLCPP_INFO(get_logger(),"当前x:%f,y=%f",current_x,current_y);
        //2计算当前位置和目标位置的距离差和角度差
        auto distance = std::sqrt((target_x_-current_x)*(target_x_-current_x)+
        (target_y_-current_y)*(target_y_-current_y));
        auto angle = std::atan2((target_y_-current_y),(target_x_-current_x))-pose->theta;
        //3控制策略
        auto msg = geometry_msgs::msg::Twist();
        if(distance>0.1)
        {
            if(fabs(angle)>0.2)
            {
                msg.angular.z = fabs(angle);
            }
            else
            {
                msg.linear.x = k_*distance;
            }
        }
        
        //4限制线速度最大值
        if(msg.linear.x>max_speed_)
        {
            msg.linear.x = max_speed_;
        }

       
        publisher_->publish(msg);
    }

private:
    rclcpp::Subscription<turtlesim::msg::Pose>::SharedPtr subscripter_;
    rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr publisher_;
    double target_x_{1.0};
    double target_y_{1.0};
    double k_{1.0};//比例系数
    double max_speed_{3.0};
};

int main(char argc,char* argv[])
{
    rclcpp::init(argc,argv);
    auto node = std::make_shared<TurtleControlNode>("turtle_control");
    rclcpp::spin(node);
    rclcpp::shutdown();
}

注意:我们这里用了共享是指针,lamda表达式,函数包装器等等c++11的语法

我们的订阅和发布,都搞一个私有的成员变量,声明其共享指针,然后在构造函数中去初始化这个共享指针,这个rclcpp下有Subscription这个类,然后传入消息接口(其实这个消息接口也变成了类),这个类里面还有SharedPtr这个共享指针

实践小项目

第一个创建消息接口包

c++方便用qt,python方便用库

之前我们都是用现成的接口,现在这个就是要自己去创建一个

创建接口都是用c++ 无法用python(这个自动类型推导)

依赖:

builtin_interfaces:ros2中已有的一个消息接口功能包,可以使用其时间接口Time,表示记录时间信息

rosidl_default_genterators:用于将自定义的消息文件转换为c++,python源码的模块(转化为头文件或者库,方便调用)

这个首先创建出功能包,将下面的src,include删掉,重新创建一个msg目录,然后在这个目录下创建SystemStatus.msg文件

builtin_interfaces/Time stamp #记录时间戳
string host_name #系统名称
float32 cpu_percent #cpu使用率
float32 memory_percent #内存使用率
float32 memory_total #内存总量
float32 memory_available #剩余内存
float32 net_sent #网络发送数据量1MB=8Mb
float32 net_recv #网络接受数据量

然后再cmakelist里面加上

声明这个功能包是一个消息接口的功能包

发布信息的包

import rclpy
from rclpy.node import Node 
from status_interfaces.msg import SystemStatus
import psutil
import platform

class SysStatusPub(Node):
    def __init__(self, node_name):
        super().__init__(node_name)
        self.status_publisher_ = self.create_publisher(SystemStatus,"sys_status",10)
        self.timer_ = self.create_timer(1,self.time_callback)

    def time_callback(self):
        """
        builtin_interfaces/Time stamp #记录时间戳
        string host_name #系统名称
        float32 cpu_percent #cpu使用率
        float32 memory_percent #内存使用率
        float32 memory_total #内存总量
        float32 memory_available #剩余内存
        float32 net_sent #网络发送数据量1MB=8Mb
        float32 net_recv #网络接受数据量
        """
        cpu_percent = psutil.cpu_percent()
        memory_info = psutil.virtual_memory()
        net_io_counters = psutil.net_io_counters()
        msg = SystemStatus()
        msg.stamp = self.get_clock().now().to_msg()
        msg.host_name = platform.node()
        msg.cpu_percent = cpu_percent
        msg.memory_percent = memory_info.percent
        msg.memory_total = memory_info.total /1024/1024
        msg.memory_available = memory_info.available /1024/1024
        msg.net_sent = net_io_counters.bytes_sent /1024/1024
        msg.net_recv = net_io_counters.bytes_recv /1024/1024
        self.get_logger().info(f'发布{str(msg)}')
        self.status_publisher_.publish(msg)


def main():
    rclpy.init()
    node = SysStatusPub('sys_status_pub')
    rclpy.spin(node)
    rclpy.shutdown()

如果另外一个电脑上想要看到发布的订阅信息, 那么那个电脑上就要有这个消息接口来解析这个数据,所以把这个包拷贝过去source一下就可以里

订阅信息的包

qt的尝试
#include <QApplication> //提供类
#include <QLabel> //显示文本组件
#include <QString> //存字符窜

int main(int argc, char* argv[])
{
    QApplication app(argc, argv);
    QLabel* label = new QLabel();
    QString message = QString::fromStdString("Hello Qt! and cy");
    label->setText(message);//显示内容的方法
    label->show();
    app.exec();
    return 0;
}

Widgets是Qt5下面的图形界面组件

实现
#include "rclcpp/rclcpp.hpp"
#include <QApplication> //提供类
#include <QLabel> //显示文本组件
#include <QString> //存字符窜
#include "status_interfaces/msg/system_status.hpp"//一定要修改包含的配置才能用
using SystemStatus = status_interfaces::msg::SystemStatus;//这样方便使用
class SysStatusDisplay:public rclcpp::Node
{
private:
    rclcpp::Subscription<SystemStatus>::SharedPtr subscripter_;
public:
    SysStatusDisplay()
    :Node("sys_status_display")
    {
        QLabel* label_ = new QLabel();
        subscripter_ = this->create_subscription<SystemStatus>("sys_status",10,
        [&](const SystemStatus::SharedPtr msg)->void
        {
            label_->setText(get_qstr_from_msg(msg));//显示内容的方法
        });
        label_->setText(get_qstr_from_msg(
            std::make_shared<SystemStatus>()//传入空的共享指针
        ));
        label_->show();
    }

    QString get_qstr_from_msg(const SystemStatus::SharedPtr msg)
    {
        std::stringstream show_str;
        show_str
        << "===========系统状态可视化显示工具============\n"
        << "数 据 时 间:\t" << msg->stamp.sec << "\ts\n"
        << "用  户  名:\t" << msg->host_name << "\t\n"
        << "CPU使用率:\t" << msg->cpu_percent << "\t%\n"
        << "内存使用率:\t" << msg->memory_percent << "\t%\n"
        << "内存总大小:\t" << msg->memory_total << "\tMB\n"
        << "剩余有效内存:\t" << msg->memory_available << "\tMB\n"
        << "网络发送量:\t" << msg->net_sent << "\tMB\n"
        << "网络接收量:\t" << msg->net_recv << "\tMB\n"
        << "==========================================";
        return QString::fromStdString(show_str.str());
    }
};

int main(int argc, char* argv[])
{
    rclcpp::init(argc,argv);
    QApplication app(argc, argv);
    auto node = std::make_shared<SysStatusDisplay>();
    std::thread spin_thread([&]()->void
    {
        rclcpp::spin(node);//单独开一个线程去运行spin
    });
    spin_thread.detach();
    app.exec();//执行应用,阻塞代码
    rclcpp::shutdown();
    return 0;
}

依赖的不同处理方法

第四章服务

服务通信是由两个话题构成的,参数通信就是由服务通信构成的

服务通信也是有消息接口的,请求服务可以使用命令行也可以用rqt

参数是视为节点的设置,是基于服务通信

python实现

创建服务消息接口

服务的消息接口是srv,与话题的消息接口msg不同在创建包的时候同时要添加需要其他消息接口的依赖

sensor_msgs/Image image #原始图像
---
int16 number #人脸数
float32 use_time #识别时间
int32[] top
int32[] right
int32[] bottom
int32[] left

这里是在srv下创建FaceDetector.srv

sensor_msgs是添加的一个消息接口依赖

python人脸检测

在网上下载的图片下载到包的resource 里面,但是构建功能包的时候不回去拷贝到install下面,需要手动去拷贝

os库  os.path.join():这是拼接路径的时候自动给/ ,防止我们自己拼接的时候忘记加了

import face_recognition 
import cv2
from ament_index_python.packages import get_package_share_directory
import os

def main():
    #获取图像的真实路径
    default_image_path = os.path.join(get_package_share_directory('cy_python_detect'),
    'resource/default.jpg')
    #使用opencv加载图像
    image = cv2.imread(default_image_path)
    #查找人脸
    face_locations = face_recognition.face_locations(image,
    number_of_times_to_upsample=1,model='hog')
    #绘制边框
    for top,right,bottom,left in face_locations:
        cv2.rectangle(image,(left,top),(right,bottom),(255,0,0),4)
    #显示结果
    cv2.imshow('Face_Detection',image)
    cv2.waitKey(0)

人脸检测服务实现

cv_bridge 中CvBridge中有格式转换

import rclpy
from rclpy.node import Node
from cv_bridge import CvBridge
from chapt4_interfaces.srv import FaceDetector
import face_recognition 
import cv2
from ament_index_python.packages import get_package_share_directory
import os
import time

class FaceDetectorionNode(Node):
    def __init__(self):
        super().__init__('face_detect_node')
        self.brdge_ = CvBridge()
        self.service_ = self.create_service(FaceDetector,'/face_detect',
        self.detect_face_callback)
        self.default_image_path_ = default_image_path = os.path.join(get_package_share_directory('cy_python_detect'),'resource/default.jpg')
        self.number_of_times_to_upsample_=1
        self.model_='hog'
        self.get_logger().info('人脸检测服务已经启动')


    def detect_face_callback(self,request,response):
        if request.image.data:
            cv_image = self.brdge_.imgmsg_to_cv2(request.image)
        else:
            cv_image = cv2.imread(self.default_image_path_)
            self.get_logger().info('没有传入图像,使用默认图像')
        start_time = time.time()
        face_locations = face_recognition.face_locations(cv_image,
        number_of_times_to_upsample=self.number_of_times_to_upsample_,model=self.model_)
        end_time = time.time()
        self.get_logger().info(f'人脸检测完成用时:{end_time-start_time}')
        response.number = len(face_locations)
        response.use_time = end_time-start_time
        for top,right,bottom,left in face_locations:
            response.top.append(top)
            response.right.append(right)
            response.bottom.append(bottom)
            response.left.append(left)

        return response


def main():
    rclpy.init()
    node = FaceDetectorionNode()
    rclpy.spin(node)
    rclpy.shutdown()

这里我们手动去call这个服务去检查正确与否

人脸检测客户端的实现

构造request的时候是我们自己创建的消息接口的一个类(自动生成 的类)

而且在发送请求的时候会返回一个future,要判断future.done()来知道是否完成

import rclpy
from rclpy.node import Node
from cv_bridge import CvBridge
from chapt4_interfaces.srv import FaceDetector
import face_recognition 
import cv2
from ament_index_python.packages import get_package_share_directory
import os
import time


class FaceDetectorClientNode(Node):
    def __init__(self):
        super().__init__('face_detect_client_node')
        self.brdge_ = CvBridge()
        self.client_ = self.create_client(FaceDetector,'/face_detect')
        self.default_image_path_ = default_image_path = os.path.join(get_package_share_directory('cy_python_detect'),
        'resource/test1.jpg')
        self.image_ = cv2.imread(self.default_image_path_)
        
    def sent_request(self):
        #判断服务端是否上线
        while self.client_.wait_for_service(timeout_sec=1) is False:
            self.get_logger().info('等待服务端上线....')
        #构造request
        request = FaceDetector.Request()
        request.image = self.brdge_.cv2_to_imgmsg(self.image_)
        #发送请求,并且等待服务完成
        future = self.client_.call_async(request)#需要等待服务端处理完成后才会把结果放到future里面
下面的两种方法本质都是等待,更好的是用回调函数
        # while future.done():
        #     time.sleep(1)#休眠当前的线程,等待服务处理完成,但是线程已经休眠了,那么就无法再接收到来自
        #服务端的返回
        #rclpy.spin_until_future_complete(self,future)#这里是等的方法,其实也可以用回调函数
        def result_callback(result_future):
            response = result_future.result()
            self.get_logger().info(f'收到响应,共检测到{response.number}张人脸,耗时{response.use_time}s')
            self.image_show(response)
        future.add_done_callback(result_callback)



    def image_show(self,response):
        #绘制人脸
        for i in range(response.number):
            top = response.top[i]
            right = response.right[i]
            bottom = response.bottom[i]
            left = response.left[i]
            cv2.rectangle(self.image_,(left,top),(right,bottom),(255,0,0),4)
        cv2.imshow('Face_Detection',self.image_)
        cv2.waitKey(0)#这里也会阻塞,也会导致spin无法正常运行,但是这里请求了一次,所以还可以用
        #如果多次请求的话就不能用了



def main():
    rclpy.init()
    node = FaceDetectorClientNode()
    node.sent_request()
    rclpy.spin(node)
    rclpy.shutdown()

cpp实现

做一个巡逻海龟

创建消息接口

sensor_msgs/Image image #原始图像
---
int16 number #人脸数
float32 use_time #识别时间
int32[] top
int32[] right
int32[] bottom
int32[] left

cpp服务端实现

#include "rclcpp/rclcpp.hpp"
#include <chrono>
#include "geometry_msgs/msg/twist.hpp"//引入这个消息接口
#include "turtlesim/msg/pose.hpp"
#include "chapt4_interfaces/srv/partrol.hpp"
using Partrol = chapt4_interfaces::srv::Partrol;
using namespace std::chrono_literals;

class TurtleControlNode: public rclcpp::Node
{
public:
    explicit TurtleControlNode(const std::string& node_name)
    :Node(node_name)
    {
其实关键就是这一步,通过服务获得request然后返回response
        service_ = this->create_service<Partrol>("partrol",[&](const Partrol::Request::SharedPtr  request,
//这里还可以用std::make_sharedptr来获得共享指针
        Partrol::Response::SharedPtr response)->void{
            if((0<request->target_x && request->target_x<12)
            && (0<request->target_y && request->target_y<12))
            {
                target_x_ = request->target_x;
                target_y_ = request->target_y;
                response->result = Partrol::Response::SUCCESS;
            }
            else
            {
                response->result = Partrol::Response::FALL;
            }
        });
        publisher_ = this->create_publisher<geometry_msgs::msg::Twist>("/turtle1/cmd_vel",10);
        subscripter_ = this->create_subscription<turtlesim::msg::Pose>("/turtle1/pose",10,
        std::bind(&TurtleControlNode::on_pose_receivered_,this,std::placeholders::_1));
    }

    void on_pose_receivered_(const turtlesim::msg::Pose::SharedPtr pose)//收到参数的共享指针
    {
        //1获取当前位置
        auto current_x = pose->x;
        auto current_y = pose->y;
        RCLCPP_INFO(get_logger(),"当前x:%f,y=%f",current_x,current_y);
        //2计算当前位置和目标位置的距离差和角度差
        auto distance = std::sqrt((target_x_-current_x)*(target_x_-current_x)+
        (target_y_-current_y)*(target_y_-current_y));
        auto angle = std::atan2((target_y_-current_y),(target_x_-current_x))-pose->theta;
        //3控制策略
        auto msg = geometry_msgs::msg::Twist();
        if(distance>0.1)
        {
            if(fabs(angle)>0.2)
            {
                msg.angular.z = fabs(angle);
            }
            else
            {
                msg.linear.x = k_*distance;
            }
        }
        
        //4限制线速度最大值
        if(msg.linear.x>max_speed_)
        {
            msg.linear.x = max_speed_;
        }

       
        publisher_->publish(msg);
    }

private:
    rclcpp::Subscription<turtlesim::msg::Pose>::SharedPtr subscripter_;
    rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr publisher_;
    rclcpp::Service<Partrol>::SharedPtr service_;
    double target_x_{1.0};
    double target_y_{1.0};
    double k_{1.0};//比例系数
    double max_speed_{3.0};
};

int main(int argc,char* argv[])
{
    rclcpp::init(argc,argv);
    auto node = std::make_shared<TurtleControlNode>("turtle_control");
    rclcpp::spin(node);
    rclcpp::shutdown();
}

cpp客户端的实现

定时去call这个服务,生成随机点

引入<ctime >然后初始化随机数种子

async :是异步的意思

#include "rclcpp/rclcpp.hpp"
#include <chrono>
#include "chapt4_interfaces/srv/partrol.hpp"
using Partrol = chapt4_interfaces::srv::Partrol;
using namespace std::chrono_literals;

class PartrolClient: public rclcpp::Node
{
public:
    PartrolClient()
    :Node("partrol_client")
    {
        srand(time(NULL));
        partrol_client_ = this->create_client<Partrol>("partrol");
        timer_ = this->create_wall_timer(10s,[&]()->void {
            //1检测服务端是否上线
            while(!partrol_client_->wait_for_service(1s))
            {
                if(!rclcpp::ok())
                {
                    RCLCPP_WARN(this->get_logger(),"等待服务的过程中被打断");
                    return;
                }
                RCLCPP_INFO(this->get_logger(),"等待服务端上线...");
            }
            //2准备request,创建一个共享指针(都使用的共享指针来管理)
            auto request = std::make_shared<Partrol::Request>();
            request->target_x = rand()%15;
            request->target_y = rand()%15;
            RCLCPP_INFO(this->get_logger(),"目标点准备完成x:%f,y:%f",request->target_x,request->target_y);
            //3发送请求,然后等待返回,返回的时候哦调用回调函数
            partrol_client_->async_send_request(request,
            [&](rclcpp::Client<Partrol>::SharedFuture result_future)->void{
                //这里有个参数result_future是发送请求后返回的一个future值
                auto response = result_future.get();
                if(response->result==Partrol::Response::SUCCESS)
                {
                    RCLCPP_INFO(this->get_logger(),"目标点处理成功");
                }
                if(response->result==Partrol::Response::FALL)
                {
                    RCLCPP_WARN(this->get_logger(),"目标点处理失败");
                }
            });
            
        });  

    };
       

private:
    rclcpp::TimerBase::SharedPtr timer_;
    rclcpp::Client<Partrol>::SharedPtr partrol_client_;
};

int main(int argc,char* argv[])
{
    rclcpp::init(argc,argv);
    auto node = std::make_shared<PartrolClient>();
    rclcpp::spin(node);
    rclcpp::shutdown();
}

在python中声明参数

self.declare_parameter('number_of_times_to_upsample_',1)
self.declare_parameter('model_','hog')
self.number_of_times_to_upsample_=self.get_parameter('number_of_times_to_upsample_').value
self.model_=self.get_parameter('model_').value

参数是基于服务实现的,调用服务就是通过回调函数实现的

要不然这个参数的设定只能是在初始化的时候完成的,在运行的时候就无法实现更新

添加回调函数更新参数

导入消息接口,用于构建参数处理结果(参数回调函数必须要有成功的返回值)    
from rcl_interfaces.msg import SetParametersResult        
    self.add_on_set_parameters_callback(self.parameters_callback)
    
    def parameters_callback(self,parameters):
        for parameter in parameters:
            self.get_logger().info(f'设置参数参数{parameter.name}->{parameter.value}')
            if parameter.name == 'number_of_times_to_upsample':
                self.number_of_times_to_upsample_ = parameter.value
            if parameter.name == 'model':
                self.model_ = parameter.value
        return SetParametersResult(successful=True)

#更新自身节点的参数的方法(在服务端的init里面)
self.set_parameters([rclpy.parameter('model',rclpy.Parameter.Type.String,'cnn')])

修改其他节点的参数

通过请求服务来修改节点的参数(服务端启动后这个节点就会提供一些消息接口可以进行请求参数服务)

查看这个消息接口

import rclpy
from rclpy.node import Node
from cv_bridge import CvBridge
from chapt4_interfaces.srv import FaceDetector
import face_recognition 
import cv2
from ament_index_python.packages import get_package_share_directory
import os
import time
from rcl_interfaces.srv import SetParameters
from rcl_interfaces.msg import Parameter,ParameterValue,ParameterType


class FaceDetectorClientNode(Node):
    def __init__(self):
        super().__init__('face_detect_client_node')
        self.brdge_ = CvBridge()
        self.client_ = self.create_client(FaceDetector,'/face_detect')
        self.default_image_path_ = default_image_path = os.path.join(get_package_share_directory('cy_python_detect'),
        'resource/test1.jpg')
        self.image_ = cv2.imread(self.default_image_path_)

    def call_set_parameters(self,parameters):
        #1判断服务端是否上线
        parameters_client = self.create_client(SetParameters,'/face_detect_node/set_parameters')
        while parameters_client.wait_for_service(timeout_sec=1) is False:
            self.get_logger().info('等待参数设置服务端上线...')
        #2构造request
        request = SetParameters.Request()
        request.parameters = parameters
        #3发送请求,等待服务完成
        future = parameters_client.call_async(request)
        rclpy.spin_until_future_complete(self,future)#这里建议等待
        response = future.result()
        return response
分两个函数,因为这个构造request比较复杂
    def update_detect_model(self,model='hog'):
        #创建参数对象
        param = Parameter()
        param.name = 'model'
        #创建参数值并且赋值
        param_value = ParameterValue()
        param_value.string_value = model
        param_value.type = ParameterType.PARAMETER_STRING
        param.value = param_value
        #请求更新参数并且处理
        #注意这里就是请求服务,相当于会调用call_set_parameters,通过这个来进行对服务端的请求
        response = self.call_set_parameters([param])
        for result in response.results:
            self.get_logger().info(f'设置参数结果{result.successful}原因:{result.reason}')

        
    def sent_request(self):
        #判断服务端是否上线
        while self.client_.wait_for_service(timeout_sec=1) is False:
            self.get_logger().info('等待服务端上线....')
        #构造request
        request = FaceDetector.Request()
        request.image = self.brdge_.cv2_to_imgmsg(self.image_)
        #发送请求,并且等待服务完成
        future = self.client_.call_async(request)#需要等待服务端处理完成后才会把结果放到future里面
        # while future.done():
        #     time.sleep(1)#休眠当前的线程,等待服务处理完成,但是线程已经休眠了,那么就无法再接收到来自
        #服务端的返回
        #rclpy.spin_until_future_complete(self,future)#这里是等的方法,其实也可以用回调函数
        def result_callback(result_future):
            response = result_future.result()
            self.get_logger().info(f'收到响应,共检测到{response.number}张人脸,耗时{response.use_time}s')
            #self.image_show(response)
        future.add_done_callback(result_callback)



    def image_show(self,response):
        #绘制人脸
        for i in range(response.number):
            top = response.top[i]
            right = response.right[i]
            bottom = response.bottom[i]
            left = response.left[i]
            cv2.rectangle(self.image_,(left,top),(right,bottom),(255,0,0),4)
        cv2.imshow('Face_Detection',self.image_)
        cv2.waitKey(0)#这里也会阻塞,也会导致spin无法正常运行,但是这里请求了一次,所以还可以用
        #如果多次请求的话就不能用了



def main():
    rclpy.init()
    node = FaceDetectorClientNode()
    node.update_detect_model('hog')
    node.sent_request()
    node.update_detect_model('cnn')
    node.sent_request()
    rclpy.spin(node)
    rclpy.shutdown()

在cpp中声明参数

        this->declare_parameter("k_",1.0);
        this->declare_parameter("max_speed_",3.0);
        this->get_parameter("k_",k_);
        this->get_parameter("max_speed_",max_speed_);

我们用的rqt之类的改变参数,只是更新ros2中参数的值,没有拷贝到成员变量中去

添加回调函数更新参数

#include "rcl_interfaces/msg/set_parameters_result.hpp"
using SetParametersResult = rcl_interfaces::msg::SetParametersResult;
using Partrol = chapt4_interfaces::srv::Partrol;
这是在构造函数里面实现的
而且这个回调函数找add_on_set.....这个函数定义,里面有一个模板可以用
 //this->set_parameter(rclcpp::Parameter("k_",2.0));//这是在内部修改参数
        parameters_callback_handel_ = this->add_on_set_parameters_callback(
            [&](const std::vector<rclcpp::Parameter> & parameters)
            ->rcl_interfaces::msg::SetParametersResult{
                rcl_interfaces::msg::SetParametersResult result;
                result.successful = true;
                for (const auto & parameter : parameters) {
                    RCLCPP_INFO(this->get_logger(),"更新参数%s=%f",parameter.get_name().c_str(),parameter.as_double());
                    if(parameter.get_name()=="k_")
                    {
                        k_ = parameter.as_double();
                    }
                    if(parameter.get_name()=="max_speed_")
                    {
                        max_speed_ = parameter.as_double();
                    }
                }
                return result;
            }
        );

private:
这个共享指针是必不可少的
    OnSetParametersCallbackHandle::SharedPtr parameters_callback_handel_;

修改其他节点的参数

需要导入很多消息接口

因为这个参数的更新比较麻烦以后如果要写,就cv

#include "rclcpp/rclcpp.hpp"
#include <chrono>
#include "chapt4_interfaces/srv/partrol.hpp"
#include "rcl_interfaces/msg/parameter.hpp"
#include "rcl_interfaces/msg/parameter_value.hpp"
#include "rcl_interfaces/msg/parameter_type.hpp"
#include "rcl_interfaces/srv/set_parameters.hpp"
using SetP = rcl_interfaces::srv::SetParameters;
using Partrol = chapt4_interfaces::srv::Partrol;
using namespace std::chrono_literals;

class PartrolClient: public rclcpp::Node
{
public:
    PartrolClient()
    :Node("partrol_client")
    {
        srand(time(NULL));
        partrol_client_ = this->create_client<Partrol>("partrol");
        timer_ = this->create_wall_timer(10s,[&]()->void {
            //1检测服务端是否上线
            while(!partrol_client_->wait_for_service(1s))
            {
                if(!rclcpp::ok())
                {
                    RCLCPP_WARN(this->get_logger(),"等待服务的过程中被打断");
                    return;
                }
                RCLCPP_INFO(this->get_logger(),"等待服务端上线...");
            }
            //2准备request,创建一个共享指针(都使用的共享指针来管理)
            auto request = std::make_shared<Partrol::Request>();
            request->target_x = rand()%15;
            request->target_y = rand()%15;
            RCLCPP_INFO(this->get_logger(),"目标点准备完成x:%f,y:%f",request->target_x,request->target_y);
            //3发送请求,然后等待返回,返回的时候哦调用回调函数
            partrol_client_->async_send_request(request,
            [&](rclcpp::Client<Partrol>::SharedFuture result_future)->void{
                //这里有个参数result_future是发送请求后返回的一个future值
                auto response = result_future.get();
                if(response->result==Partrol::Response::SUCCESS)
                {
                    RCLCPP_INFO(this->get_logger(),"目标点处理成功");
                }
                if(response->result==Partrol::Response::FALL)
                {
                    RCLCPP_WARN(this->get_logger(),"目标点处理失败");
                }
            });
         });
    };
    //创建客户端发送请求,返回结果
    SetP::Response::SharedPtr call_set_parameter(const rcl_interfaces::msg::Parameter & param)
    {
        auto param_client_ = this->create_client<SetP>("/turtle_control/set_parameters");
        //1检测服务端是否上线
        while(!param_client_->wait_for_service(1s))
        {
            if(!rclcpp::ok())
            {
                RCLCPP_WARN(this->get_logger(),"等待服务的过程中被打断");
                return nullptr;
            }
            RCLCPP_INFO(this->get_logger(),"等待服务端上线...");
        }
        //2准备request,创建一个共享指针(都使用的共享指针来管理)
        auto request = std::make_shared<SetP::Request>();
        request->parameters.push_back(param);
        //3发送请求,然后等待返回,返回的时候哦调用回调函数
        auto future = param_client_->async_send_request(request);
        rclcpp::spin_until_future_complete(this->get_node_base_interface(),future);
        auto response = future.get();
        return response;      
    }

    //更新参数k_
    void update_server_param_k(double k)
    {
        //创建参数对象
        auto param = rcl_interfaces::msg::Parameter();
        param.name = "k_";
        //创建参数值
        auto param_value = rcl_interfaces::msg::ParameterValue();
        param_value.type = rcl_interfaces::msg::ParameterType::PARAMETER_DOUBLE;
        param_value.double_value = k;
        param.value = param_value;
        //请求更新参数并处理
        auto response = this->call_set_parameter(param);
        if(response==NULL)
        {
            RCLCPP_INFO(this->get_logger(),"参数更新失败");
            return ;
        }
        for(auto result:response->results)
        {
            if(result.successful==false)
            {
                RCLCPP_INFO(this->get_logger(),"参数更新失败,原因%s",result.reason.c_str());
            }
            else{
                RCLCPP_INFO(this->get_logger(),"参数更新成功");
            }
        }
    }

private:
    rclcpp::TimerBase::SharedPtr timer_;
    rclcpp::Client<Partrol>::SharedPtr partrol_client_;
};

int main(int argc,char* argv[])
{
    rclcpp::init(argc,argv);
    auto node = std::make_shared<PartrolClient>();
    node->update_server_param_k(4.0);
    rclcpp::spin(node);
    rclcpp::shutdown();
}

使用launch启动脚本

可以将多个节点同时启动

用python的方式来编写

generate_launch_description()

这个函数名字是定死的,返回值的类型也是定死的

而且是在包下面创建launch目录,添加后缀为.launch.py的文件(c++包和python包都是这样编写的,但是一个要修改cmakelist,一个要修改setup.py),在这个里面编写

返回launch.LaunchDescription([action动作])

import launch
import launch_ros

def generate_launch_description():
    action_turtle_node = launch_ros.actions.Node(
        package= 'turtlesim',
        executable = 'turtlesim_node',
        output= 'screen'#输出的地点,可以是屏幕,log文件,两者都输出
    )
    action_partrol_client = launch_ros.actions.Node(
        package= 'cy_cpp_service',
        executable = 'partrol_client',
        output= 'log'
    )
    action_turtle_control = launch_ros.actions.Node(
        package= 'cy_cpp_service',
        executable = 'turtle_control',
        output= 'both'
    )
    return launch.LaunchDescription([
        action_turtle_node,
        action_partrol_client,
        action_turtle_control
    ])

cmakelist

setup.py

使用launch传递参数

这里就不能用命令行指定参数了

1声明一个launch参数

2吧launch的参数传递给某个节点

import launch
import launch_ros

def generate_launch_description():
    #声明一个launch参数
    action_declare_arg_background_g = launch.actions.DeclareLaunchArgument('launch_arg_bg',default_value="150")
    #把launch参数手动的传递给某个节点
    action_turtle_node = launch_ros.actions.Node(
        package= 'turtlesim',
        executable = 'turtlesim_node',
        parameters=[{'background_g': launch.substitutions.LaunchConfiguration(
            'launch_arg_bg',default="150"
        )}],
        output= 'screen'#输出的地点,可以是屏幕,log文件,两者都输出
    )
    action_partrol_client = launch_ros.actions.Node(
        package= 'cy_cpp_service',
        executable = 'partrol_client',
        output= 'log'
    )
    action_turtle_control = launch_ros.actions.Node(
        package= 'cy_cpp_service',
        executable = 'turtle_control',
        output= 'both'
    )
    return launch.LaunchDescription([
        action_declare_arg_background_g,
        action_turtle_node,
        action_partrol_client,
        action_turtle_control
    ])

launch使用进阶

launch的运用在实际当中还是很多的

import launch
import launch.launch_description_sources
import launch_ros
from ament_index_python.packages import get_package_share_directory
def generate_launch_description():

    #声明参数,是否启动rqt
    action_declare_startup_rqt = launch.actions.DeclareLaunchArgument('startup_rqt',
    default_value="false")
    #参数声明和后要替换
    startup_rqt = launch.substitutions.LaunchConfiguration('startup_rqt',default="False")

    #1利用 IncludeLaunchDescription包含其他动作文件
    mutisim_launch_path = [get_package_share_directory("turtlesim"),"/launch","/multisim.launch.py"]
    action_include_launch = launch.actions.IncludeLaunchDescription(
    launch.launch_description_sources.PythonLaunchDescriptionSource(mutisim_launch_path)
    )

    #2利用ExecuteProcess 动作执行命令
    action_executeprocess = launch.actions.ExecuteProcess(
        #if startup_rqt
        # run:rqt
        condition = launch.conditions.IfCondition(startup_rqt),
        cmd=['rqt']
    )

    #3动作输出日志
    action_log_info = launch.actions.LogInfo(msg=str(mutisim_launch_path))

    #4组织动作成组,把多个动作放为一组
    action_group = launch.actions.GroupAction([
        #5动作定时器
        launch.actions.TimerAction(period=2.0,actions=[action_include_launch]),
        launch.actions.TimerAction(period=4.0,actions=[action_executeprocess])
    ])
    return launch.LaunchDescription([
        action_log_info,
        action_group
    ])

总结:

ROS2(Robot Operating System 2)与树莓派(Raspberry Pi) ROS2是一个为机器人提供硬件抽象、设备驱动、函数库、可视化工具、消息通信以及软件包管理等多种功能的开源操作系统。它支持分布式计算,允许多个节点(即进程)在局域网内自由通信,非常适合用于多机器人协作和复杂机器人系统的开发。树莓派是一款基于ARM架构的微型电脑主板,以SD卡为内存硬盘,卡片主板周围有1/2/4个USB接口和一个10/100以太网接口(A型没有网口),可连接键盘、鼠标和网线,同时拥有HDMI高清视频输出接口和微型USB接口(电源)。 阅读详情

相关推荐

基于ROS2和科大讯飞的文字转语音TTS入门教程

利用ROS2,实现listener和talker,当talker发送以‘topic’为主题的消息时,listener接收并处理消息内容,并调用科大讯飞的TTS模块,将某些文字转换成语音文件。在上一步中,可以直接通过选项安装vscode,也可以直接在ubuntu软件商店里安装,区别是软件商店里的版本更新。节点名称:my_publish (创建项目后某人创建的节点源码,后来才知道,可以不写这个,可以自行创建节点代码。本文使用的操作系统为vm虚拟机,安装的ubuntu22.04,请大家自行百度安装。

DL_62532的专栏 3599

ROS2 机器人入门到实战

摘要: ROS2(Robot Operating System 2)是一个开源的机器人中间件框架,继承了ROS1的优势,并提升了实时性、安全性和分布式系统支持。本文从ROS2的基础概念出发,逐步引导读者进行实际应用开发。首先介绍了如何在Ubuntu 22.04上搭建ROS2 Humble环境,包括安装ROS2、配置环境变量和工具。接着,详细讲解了如何创建ROS2工作空间和包,并展示了如何使用发布-订阅机制实现节点间的通信,包括Publisher和Subscriber节点的创建与运行。最后,文章还介绍了通过服

小蜗牛的珍贵百宝箱 5978

ROS2入门到精通—— 1-2 ROS2常用工具

本文介绍了ROS2常用的工具以及相关的使用方法:launch.py文件编写;ros2bag录包;rviz2数据可视化;gazebo生产数据,读完本文你会对ROS2常用工具有详细的了解

BigDavid123的博客 4195

ROS2入门教程

https://www.ncnynl.com/archives/201801/2250.html ROS2入门教程-基本概念 介绍ROS2的基本概念 1. 基本概念: ROS是一个用于在不同进程间匿名的发布、订阅、传递信息的中间件。 ROS2系统的核心部分是ROS网络(ROS Graph)。 ROS网络是指在ROS系统中不同的节点间相互通信的连接关系。 ROS Graph这里翻译成了ROS网络,因为我觉得Graph更加抽象,而网络的概念更容易帮助理解其内涵。 2. ROS网络(ROS Graph)概念简介:

AbiUni(YY熊) 1万+

【C++】ROS:ROS2环境配置与基础示例

ros2环境配置与基础使用

DevFrank的博客 6116

ROS四足机器人控制节点:quadruped_9g的设计与实现

本文还有配套的精品资源,点击获取 简介:该ROS节点名为“quadruped_9g”,旨在控制一个具有12个自由度的四足机器人。开发者通过URDF文件描述了机器人的结构和运动学特性,简化了运动控制问题。节点集成了逆运动学算法,允许计算关节角度以达到特定目标位置,这对于实现行走、奔跑等复杂运动至关重要。整个项目用C++编写,遵循ROS框架,提供了一个平台来学习如何设计和控制四...

weixin_42596246的博客 1336

Ros2手册(古月居教程)

通信域定义分组。在.bashrc文件中设置。设置同一Domain ID。标签用来定义一些常量,比如这样定义一个PI的常量名为“M_PI”,值为“3.14159”,在调用的时候,通过$加大括号,里边就可以使用定义好的常量了。针对原本移动机器人的URDF文件,我们就可以把底盘的质量、尺寸,轮子的质量、尺寸、安装位置,这些不会变化的数据,都通过常量定义,未来需要修改的时候也很方便,就不需要在模型文件中一行一行找了。机器人的轮子我们也做成宏定义,定义方式是通过这个。

weixin_43366181的博客 4556

uniapp 使用摄像头 live-pusher (APP)

uniapp 中开发 app 时使用live-pusher踩过的坑

随身笔记 1万+

四足机器人ROS2控制项目安装与配置指南

四足机器人ROS2控制项目安装与配置指南 1. 项目基础介绍 本项目是基于ROS2(Robot Operating System 2)的四足机器人控制项目,包含了适用于四足机器人的控制算法和仿真模型。项目的主要目的是为了提供一个开源的、基于ROS2的控制系统,用于四足机器人的仿真和实体机器人控制。 主要编程语言:C++、Python、CMake。 2. 项目使用的关键技术和框架 ROS2: 作为...

gitblog_00408的博客 878

关于使用CMT2300A FIFO缓存区间设置为64Byte的问题

帮助你解决在使用CMT2300A 时遇到的各种问题,若有需要,请及时与我交流

teleger的博客 1419

(保姆教程)Spyder 配置Tensorflow(2.5.0)和keras(2.4.3)

(保姆教程)Spyder 配置Tensorflow(2.5.0)和keras(2.4.3)

qq_43102225的博客 1万+

hexapod_ros:六足机器人ROS栈,助力机器人稳定行走

hexapod_ros:六足机器人ROS栈,助力机器人稳定行走 项目介绍 hexapod_ros 是一个为六足机器人设计的ROS(Robot Operating System)栈,适用于3自由度(3dof)或4自由度(4dof)的六足机器人。该项目由Kevin M. Ochs开发,并得到了Shubhankar Das、Renée Love、Konstantinos Chatzilygeroudis...

gitblog_00747的博客 1099

雷达回波仿真

文章目录前言一、基础理解1.慢时间维度和快时间维度2.脉冲压缩3.MTI4. 脉冲多普勒处理5.cacfar检测二、具体流程1.产生回波信息加入目标时延以及噪声2.解调回波得到基带信号3.脉冲压缩 MTI MTD cafar检测 前言 雷达算法仿真学习,主要流程包括生成目标回波,将基带信号加在载波上其次是加上目标信息和干扰信号后发射,解调回波信号得到基带信号然后MTI,MTD,最后再检测。 一、基础理解 1.慢时间维度和快时间维度 连续M个脉冲回波经过相干解调之后的基带数据形成的一个二维数据矩阵,.

qq_42961235的博客 1万+

ROS2 入门教程第一篇:配置 ROS2 环境

在上一篇文章里,我们已经按照官方 First Steps 梳理了 ROS2 的学习顺序:先理解基本概念,再安装 ROS 和 turtlesim,然后练习 nodes、topics、services、parameters、actions,接着学习日志、launch、rosbag,最后进入 client libraries 开始自己写节点。这篇文章正式进入 ROS2 入门教程的第一篇:配置 ROS2 环境。参考资料是 ROS2 Humble 官方文档中的。

weixin_64938938的博客 279

FPGA实战 —— 基于UART的多设备串口通信系统设计

本文深入探讨了基于FPGA的多设备UART串口通信系统设计。针对工业控制与物联网应用中一对多通信的需求,文章对比了共享总线式、多路独立式等硬件连接方案,并详细阐述了可配置UART IP核、波特率自适应算法以及多路数据仲裁与分发逻辑等核心设计要点。最后,通过一个四传感器采集节点的实战案例,展示了从硬件连接到Verilog代码实现的完整系统构建过程。

weixin_29172963的博客 229

Modkit

Nmod- 通过过滤器分类为具有指定碱基修饰的残基的调用数量。Ncanonical- 通过过滤器分类为未修饰的碱基的调用数量。确切的碱基必须通过修饰代码推断。例如,如果修饰代码是m(5mC),则未修饰的碱基是胞嘧啶。如果修饰代码是a,则未修饰的碱基是腺苷。Nother mod- 通过过滤器分类为修饰的调用数量,但修饰不同于列出的碱基(且相应的未修饰碱基相同)。例如,对于给定的胞嘧啶,可能有 3 个读取带有h调用,1 个带有未修饰调用,2 个带有m调用。在h的 bedMethyl 行中,N。

生物信息学 1261

Unitree GO2 ROS2 SDK终极实战指南:四足机器人控制与ROS2集成完整解决方案

Unitree GO2系列机器人凭借其卓越的运动性能和开放性,已成为机器人开发者和研究人员的首选平台。本文将深入解析基于ROS2的GO2 SDK技术架构,提供从部署配置到高级功能开发的完整实战指南,助力您快速掌握四足机器人控制与ROS2集成技巧。 ## 技术架构深度解析 🏗️ Unitree GO2 ROS2 SDK采用Clean Architecture设计理念,构建了层次分明的模块化架构

gitblog_01006的博客 927

爬虫不宕机!2026自愈式爬虫开发(Python+AI自动应对页面改版)

传统爬虫最大的痛点是"页面一改就宕机":前端布局调整、CSS选择器失效、数据结构变化,都会导致爬虫瘫痪,需要人工逐行修改代码。2026年,基于AI的自愈式爬虫成为行业标配:通过大模型自动识别页面结构变化、适配新的元素定位规则、修复数据提取逻辑,实现"页面改版不宕机,AI自动自愈"的企业级效果。本文从零讲解如何用Python+AI构建自愈式爬虫,聚焦页面改版自适应、故障自动修复、规则动态更新三大核心能力,附带可直接落地的生产级代码。问题场景解决方案LLM生成规则格式错误。

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 1210

Unitree GO2 ROS2开发实战:从零构建智能四足机器人

想象一下,当你需要让一台四足机器人自主巡逻办公区域、识别访客并引导路线时,传统的手动控制方式显然无法满足需求。这正是Unitree GO2 ROS2 SDK大显身手的场景——将先进的机器人控制技术与ROS2生态系统完美结合,为开发者提供从基础运动到智能决策的全套解决方案。 ## 🔧 环境部署:搭建机器人开发工作台 构建机器人开发环境就像为厨师准备厨房,需要精心配置各种工具和原料。首先创建一个

gitblog_00885的博客 706

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

ROS2入门教程—ros2doctor简介

ROS2入门教程—ros2doctor简介1 检查设置2 检查系统   当ROS 2安装程序未按预期运行时,可以使用ros2doctor工具检查其设置。ros2doctor检查ROS 2的所有方面,包括平台,版本,网络,环境,正在运行的系统等,并警告您可能的错误和问题原因。   ros2doctor仅在Eloquent及更高的版本中可以使用。   ros2doctor是ros2cli包的一部分。只要我们安装了ros2cli,我们就能够使用ros2doctor,通常我们安装ros2的时候就安装了这个包。 1

Roar冷颜的博客 2351

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

Unitree GO2 ROS2开发终极指南:5步掌握四足机器人编程

想要快速掌握Unitree GO2四足机器人的ROS2开发吗?这个完整的指南将带你从零开始,在短短5个步骤内学会如何为你的GO2机器人添加ROS2功能!无论你拥有的是AIR、PRO还是EDU版本,这个开源SDK都能让你的机器人获得完整的ROS2生态系统支持,包括实时控制、SLAM导航、物体检测等先进功能。🚀 ## 为什么选择Unitree GO2 ROS2 SDK? Unitree GO2

gitblog_00626的博客 997

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Go2 ROS2 SDK终极指南:解锁四足机器人5大核心功能

Go2 ROS2 SDK是一个革命性的开源项目,为Unitree GO2 AIR/PRO/EDU四足机器人提供完整的ROS2支持。这个强大的SDK让开发者能够通过Wi-Fi和以太网连接,实现实时机器人控制和数据采集,为四足机器人开发带来前所未有的便利。 ## 🚀 实时运动控制与状态同步 Go2 ROS2 SDK提供完整的关节状态实时同步功能。通过[go2_robot_sdk/go2_driv

gitblog_00839的博客 790

quadruped_ros2_control:四足机器人控制的强大工具

quadruped_ros2_control:四足机器人控制的强大工具 项目介绍 在现代机器人技术领域,四足机器人的研究和应用逐渐受到广泛关注。quadruped_ros2_control 是一个开源项目,专为四足机器人提供基于 ROS2 控制框架的控制器。它涵盖了从硬件接口到控制算法的全方位支持,使得开发者在仿真和实体机器人上都能实现高效、稳定的控制。 项目技术分析 quadruped_ros2...

gitblog_00383的博客 1222

ros入门教程--5.ROS2的节点

ROS2节点是系统的最小处理单元,具有模块化、独立性和通信能力的特点。它们通过话题、服务、动作和参数等机制实现高效数据交互,适用于自动驾驶、工业自动化、服务机器人等场景。节点支持生命周期管理,确保系统稳定性和灵活性。随着机器人技术的发展,ROS2节点将在更多领域推动智能系统的进步。

甜航一直在的博客 535

ROS2入门教程—创建ROS2工作空间

ROS2入门教程—创建ROS2工作空间1 设置环境变量2 创建一个新文件夹   ROS2中的工作空间类似我们常说的概念——“工程”,是我们在ROS中开发具体项目的空间,所有功能包的源码、配置、编译都在该空间下完成。我们可能会同时开发多个项目,就会产生多个工作空间,所以工作空间之间也有一个层次的问题,类似于优先级的概念,比如不同工作空间中有同名的功能包,那运行的时候是启动哪一个呢?ROS默认是启动最上层的工作空间( overlay),上层工作空间中的功能包会覆盖(override)下层工作空间(underla

Roar冷颜的博客 3075
上一篇: MaixCam二维云台检测人脸项目
下一篇: PID入门到精通(一篇足够)!!!!!
努力的小帅
博客等级 码龄2年 464粉丝 77原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值