理解ROS2的动作

1. 创建一个动作

目标: 在ROS 2软件包中定义一个动作。

1.1 新建包

设置一个 workspace 并创建一个名为 action_tutorials_interfaces 的包:

mkdir -p ros2_ws/src #you can reuse existing workspace with this naming convention
cd ros2_ws/src
ros2 pkg create action_tutorials_interfaces

​1.2 任务

定义一个动作

动作在 .action 文件中定义,格式如下:

# Request
---
# Result
---
# Feedback

一个动作定义由三个消息定义组成,用 — 分隔。

一个 请求 消息是从动作客户端发送到动作服务器,用于启动一个新的目标。

一个 结果 消息是从动作服务器发送到动作客户端,表示一个目标已完成。

Feedback messages are periodically sent from an action server to an action client with updates about a goal.

一个动作的实例通常被称为一个目标。
假设我们想要定义一个用于计算 斐波那契数列 的新动作 “Fibonacci”。
在我们的ROS 2包action_tutorials_interfaces中创建一个名为action的目录:

cd action_tutorials_interfaces
mkdir action

action目录中创建一个名为Fibonacci.action的文件,并包含以下内容:

int32 order
---
int32[] sequence
---
int32[] partial_sequence

目标请求是我们想要计算的斐波那契序列的order,结果是最终的sequence,反馈是到目前为止计算出的partial_sequence

构建一个action

在我们的代码中使用新的Fibonacci action类型之前,我们必须将定义传递给rosidl代码生成流程。

这可以通过在action_tutorials_interfacesCMakeLists.txt文件中在ament_package()之前添加以下行来实现:

find_package(rosidl_default_generators REQUIRED)

rosidl_generate_interfaces(${PROJECT_NAME}
  "action/Fibonacci.action"
)

理解:在ROS 2 中,为了能够使用 rosidl_generate_interfaces 来自动生成消息、服务和动作的源代码,需要在 CMakeLists.txt 文件中添加 find_package(rosidl_default_generators REQUIRED) 这一行来告诉 CMake在构建过程中要找到并使用 rosidl_default_generators 工具。

rosidl_default_generators 是一个用于生成 ROS 2 消息、服务和动作的工具包。通过在CMakeLists.txt 文件中添加 find_package(rosidl_default_generators REQUIRED) 这一行,你可以确保在构建时正确地找到并使用该工具包来生成动作所需的源代码。

然后,通过 rosidl_generate_interfaces() 函数来指定需要生成的消息、服务和动作文件。在这个例子中,你指定了Fibonacci.action 动作文件,以便生成与该动作相关的源代码。

我们还需要将所需的依赖项添加到我们的 package.xml 文件中:

<buildtool_depend>rosidl_default_generators</buildtool_depend>

<depend>action_msgs</depend>

<member_of_group>rosidl_interface_packages</member_of_group>

注意,我们需要依赖于 action_msgs,因为动作定义包括附加元数据(例如目标 ID)。

现在,我们应该能够构建包含 Fibonacci 动作定义的软件包:

# Change to the root of the workspace
cd ~/ros2_ws
# Build
colcon build

我们完成了!

按照惯例,动作类型将以其包名称和单词 action 作为前缀。因此,当我们想引用我们的新动作时,它将具有完整的名称 action_tutorials_interfaces/action/Fibonacci

我们可以使用命令行工具检查我们的动作是否成功构建:

# Source our workspace
# On Windows: call install/setup.bat
. install/setup.bash
# Check that our action definition exists
ros2 interface show action_tutorials_interfaces/action/Fibonacci

在这里插入图片描述

2. 编写一个动作服务器和客户端(Python)

编写一个动作服务器和客户端(C++)

目标: 在Python中实现一个动作服务器和客户端。

2.1 背景

在ROS 2中,动作是一种异步通信形式。动作客户端动作服务器发送目标请求。动作服务器动作客户端发送目标反馈和结果。

你需要使用action_tutorials_interfaces包和前面教程中定义的Fibonacci.action接口

2.2 任务

编写动作服务器

在您的主目录ros2_ws/src中新建一个文件,我们将其命名为fibonacci_action_server.py,然后添加以下代码:

import rclpy
from rclpy.action import ActionServer
from rclpy.node import Node

from action_tutorials_interfaces.action import Fibonacci


class FibonacciActionServer(Node):

    def __init__(self):
        super().__init__('fibonacci_action_server')
        self._action_server = ActionServer(
            self,
            Fibonacci,
            'fibonacci',
            self.execute_callback)

    def execute_callback(self, goal_handle):
        self.get_logger().info('Executing goal...')
        result = Fibonacci.Result()
        return result


def main(args=None):
    rclpy.init(args=args)

    fibonacci_action_server = FibonacciActionServer()

    rclpy.spin(fibonacci_action_server)


if __name__ == '__main__':
    main()

第8行定义了一个名为FibonacciActionServer的类,它是Node的子类。通过调用Node构造函数来初始化该类,并将节点命名为fibonacci_action_server

        super().__init__('fibonacci_action_server')

在构造函数中,我们还实例化了一个新的动作服务器:

        self._action_server = ActionServer(
            self,
            Fibonacci,
            'fibonacci',
            self.execute_callback)

一个操作服务器需要四个参数:

  1. 要添加操作客户端的ROS 2节点对象:self
  2. 动作消息类型:Fibonacci(在第5行导入)。
  3. 动作服务器的名称:fibonacci
  4. 用于执行已接受的目标的回调函数:self.execute_callback。该回调函数必须返回该操作类型的结果消息。

该方法是一个回调函数,它会在接收到动作目标时被调用。在这个示例中,它只是打印一条日志,并创建一个空的 Fibonacci 动作结果,然后返回。

我们还在类中定义了一个execute_callback方法:

    def execute_callback(self, goal_handle):
        self.get_logger().info('Executing goal...')
        result = Fibonacci.Result()
        return result

这是一旦接受目标就会被调用来执行目标的方法。

让我们尝试运行我们的动作服务器:

python3 fibonacci_action_server.py

在另一个终端中,我们可以使用命令行界面发送一个目标:

ros2 action send_goal fibonacci action_tutorials_interfaces/action/Fibonacci "{order: 5}"

这个命令的意思是向名为 fibonacci 的动作服务器发送一个动作目标,该动作目标包含一个名为 order 的参数,其值为 5。

具体来说,命令的各部分含义如下:

  • ros2 action send_goal:这是一个 ROS 2 命令,用于向动作服务器发送动作目标。
  • fibonacci:动作服务器的名称,这里是指向名为 fibonacci 的动作服务器发送动作目标。
  • action_tutorials_interfaces/action/Fibonacci:动作的消息类型,指定了动作的类型为
  • Fibonacci。 “{order: 5}”:动作目标的参数,其中 order 是动作的参数名称,5 是 order 参数的值。

在正在运行动作服务器的终端中,您应该看到一个记录的消息“正在执行目标…”,然后是一个警告,说明目标状态未设置。默认情况下,如果在执行回调中未设置目标处理状态,则假定为“中止”状态。

我们可以使用方法` succeed() 来表示目标已成功:

    def execute_callback(self, goal_handle):
        self.get_logger().info('Executing goal...')

        goal_handle.succeed()

        result = Fibonacci.Result()
        return result

现在,如果重新启动动作服务器并发送另一个目标,您应该看到目标以SUCCEEDED状态完成。

现在让我们实际计算并返回请求的斐波那契数列:

    def execute_callback(self, goal_handle):
        self.get_logger().info('Executing goal...')


        sequence = [0, 1]



        for i in range(1, goal_handle.request.order):

            sequence.append(sequence[i] + sequence[i-1])


        goal_handle.succeed()

        result = Fibonacci.Result()

        result.sequence = sequence

        return result

计算序列后,我们在返回之前将其分配给结果消息字段。

然后重新启动动作服务器并发送另一个目标。您应该看到目标以正确的结果序列完成。

发布反馈

动作的一个好处是在目标执行期间向动作客户端提供反馈。我们可以通过调用目标处理器的 publish_feedback() 方法,使我们的动作服务器为动作客户端发布反馈。

我们将替换 sequence 变量,并使用反馈消息来存储序列。在 for 循环中每次更新反馈消息后,我们都会发布反馈消息并休眠以产生戏剧效果:

import time


import rclpy
from rclpy.action import ActionServer
from rclpy.node import Node

from action_tutorials_interfaces.action import Fibonacci


class FibonacciActionServer(Node):

    def __init__(self):
        super().__init__('fibonacci_action_server')
        self._action_server = ActionServer(
            self,
            Fibonacci,
            'fibonacci',
            self.execute_callback)

    def execute_callback(self, goal_handle):
        self.get_logger().info('Executing goal...')


        feedback_msg = Fibonacci.Feedback()		# 创建一个名为 feedback_msg 的 Fibonacci.Feedback 对象

        feedback_msg.partial_sequence = [0, 1]		# 初始化 partial_sequence 数组为 [0, 1]


        for i in range(1, goal_handle.request.order):

            feedback_msg.partial_sequence.append(

                feedback_msg.partial_sequence[i] + feedback_msg.partial_sequence[i-1])

            self.get_logger().info('Feedback: {0}'.format(feedback_msg.partial_sequence))

            goal_handle.publish_feedback(feedback_msg)

            time.sleep(1)

		# 在这个循环中,计算 Fibonacci 数列的下一个值,并将它添加到 partial_sequence 数组中。然后,将更新后的 	
		# partial_sequence 发布为反馈,以便客户端知道当前的计算进度,并且在每次循环之后,程序会暂停 1 秒钟,以模拟计算的过程

        goal_handle.succeed()		# 调用 goal_handle.succeed() 来通知客户端目标已经成功执行

        result = Fibonacci.Result()		# 创建一个名为 result 的 Fibonacci.Result 对象

        result.sequence = feedback_msg.partial_sequence		# 将最终的 Fibonacci 数列赋值给 result.sequence

        return result


def main(args=None):
    rclpy.init(args=args)

    fibonacci_action_server = FibonacciActionServer()

    rclpy.spin(fibonacci_action_server)


if __name__ == '__main__':
    main()

编写一个动作客户端

我们还将将动作客户端限定在单个文件范围内。打开一个新文件,我们称之为fibonacci_action_client.py,然后添加以下样板代码:

import rclpy
from rclpy.action import ActionClient
from rclpy.node import Node

from action_tutorials_interfaces.action import Fibonacci


class FibonacciActionClient(Node):

    def __init__(self):
        super().__init__('fibonacci_action_client')
        self._action_client = ActionClient(self, Fibonacci, 'fibonacci')

    def send_goal(self, order):		# send_goal 方法用于向服务器发送目标。它接受一个参数 order,表示要计算 Fibonacci 数列的长度
        goal_msg = Fibonacci.Goal()		# 首先,创建一个 Fibonacci.Goal 对象,并将 order 赋值给 goal_msg.order
        goal_msg.order = order

        self._action_client.wait_for_server()	# 调用 _action_client.wait_for_server() 方法等待服务器准备就绪

        return self._action_client.send_goal_async(goal_msg)		# 异步地发送目标


def main(args=None):
    rclpy.init(args=args)

    action_client = FibonacciActionClient()

    future = action_client.send_goal(10)

    rclpy.spin_until_future_complete(action_client, future)


if __name__ == '__main__':
    main()

我们定义了一个名为FibonacciActionClient的类,它是Node的子类。通过调用Node构造函数来初始化该类,将我们的节点命名为fibonacci_action_client

        super().__init__('fibonacci_action_client')

在类的构造函数中,我们还使用之前教程中的自定义动作定义来创建一个动作客户端:

        self._action_client = ActionClient(self, Fibonacci, 'fibonacci')

我们通过传递三个参数来创建一个ActionClient

要添加动作客户端的 ROS 2 节点:self

动作的类型:Fibonacci

动作的名称:fibonacci

我们的动作客户端将能够与具有相同名称和类型的动作服务器进行通信。

我们还在FibonacciActionClient类中定义了一个方法send_goal

    def send_goal(self, order):
        goal_msg = Fibonacci.Goal()
        goal_msg.order = order

        self._action_client.wait_for_server()

        return self._action_client.send_goal_async(goal_msg)

该方法等待动作服务器可用,然后向服务器发送一个目标。它返回一个未来对象,我们可以稍后等待该对象。

在类定义之后,我们定义了一个函数main(),它初始化ROS 2并创建FibonacciActionClient节点的一个实例。然后它发送一个目标并等待该目标完成。

最后,在我们的Python程序入口点调用main()

让我们通过首先运行之前构建的动作服务器来测试我们的动作客户端:

python3 fibonacci_action_server.py

在另一个终端中运行动作客户端:

python3 fibonacci_action_client.py

当动作服务器成功执行目标时,您应该会看到由其打印的消息:
在这里插入图片描述

获取结果

那么我们可以发送一个目标,但我们如何知道何时完成呢?我们可以通过以下几个步骤获取结果信息。首先,我们需要为发送的目标获取一个目标句柄。然后,我们可以使用目标句柄来请求结果。

以下是这个示例的完整代码:

import rclpy
from rclpy.action import ActionClient
from rclpy.node import Node

from action_tutorials_interfaces.action import Fibonacci


class FibonacciActionClient(Node):

    def __init__(self):
        super().__init__('fibonacci_action_client')
        self._action_client = ActionClient(self, Fibonacci, 'fibonacci')

    def send_goal(self, order):
        goal_msg = Fibonacci.Goal()
        goal_msg.order = order

        self._action_client.wait_for_server()

        self._send_goal_future = self._action_client.send_goal_async(goal_msg)		# 调用 send_goal_async 方法发送目标给服务器

        self._send_goal_future.add_done_callback(self.goal_response_callback)		# 注册一个回调函数 goal_response_callback,以处理服务器对目标的响应

    def goal_response_callback(self, future):		# 发送目标后的回调函数。当服务器接受目标时,它打印一条日志表示目标被接受,并调用 get_result_async 方法以异步方式获取服务器的结果。
        goal_handle = future.result()
        if not goal_handle.accepted:
            self.get_logger().info('Goal rejected :(')
            return

        self.get_logger().info('Goal accepted :)')

        self._get_result_future = goal_handle.get_result_async()
        self._get_result_future.add_done_callback(self.get_result_callback)

    def get_result_callback(self, future):		# 获取结果后的回调函数。当收到结果时,它打印结果,并调用 rclpy.shutdown() 关闭 ROS 2 节点。
        result = future.result().result
        self.get_logger().info('Result: {0}'.format(result.sequence))
        rclpy.shutdown()


def main(args=None):
    rclpy.init(args=args)

    action_client = FibonacciActionClient()

    action_client.send_goal(10)

    rclpy.spin(action_client)


if __name__ == '__main__':
    main()

ActionClient.send_goal_async() 方法返回一个对目标句柄的 future。首先,我们为 future 完成时注册一个回调函数:

        self._send_goal_future.add_done_callback(self.goal_response_callback)

请注意,当一个动作服务器接受或拒绝目标请求时,future 将会完成。让我们更详细地查看 goal_response_callback。我们可以检查目标是否被拒绝,并在此处提前返回,因为我们知道将不会有结果:

    def goal_response_callback(self, future):
        goal_handle = future.result()
        if not goal_handle.accepted:
            self.get_logger().info('Goal rejected :(')
            return

        self.get_logger().info('Goal accepted :)')

现在我们已经获得了一个目标句柄,我们可以使用它来使用 get_result_async() 方法请求结果。与发送目标类似,我们将得到一个 future,该 future 将在结果准备好时完成。让我们注册一个与目标响应时类似的回调函数:

        self._get_result_future = goal_handle.get_result_async()
        self._get_result_future.add_done_callback(self.get_result_callback)

在回调函数中,我们记录结果序列并优雅地关闭 ROS 2 以完成退出:

    def get_result_callback(self, future):
        result = future.result().result
        self.get_logger().info('Result: {0}'.format(result.sequence))
        rclpy.shutdown()

在一个独立的终端中运行动作服务器后,可以尝试运行我们的斐波那契动作客户端!

python3 fibonacci_action_client.py

你应该会看到记录的消息,显示目标已被接受和最终结果。

获取反馈

我们的动作客户端可以发送目标。很好!但是如果我们能够从动作服务器获取一些关于发送的目标的反馈信息就更好了。

以下是这个示例的完整代码:

import rclpy
from rclpy.action import ActionClient
from rclpy.node import Node

from action_tutorials_interfaces.action import Fibonacci


class FibonacciActionClient(Node):

    def __init__(self):
        super().__init__('fibonacci_action_client')
        self._action_client = ActionClient(self, Fibonacci, 'fibonacci')

    def send_goal(self, order):
        goal_msg = Fibonacci.Goal()
        goal_msg.order = order

        self._action_client.wait_for_server()

        self._send_goal_future = self._action_client.send_goal_async(goal_msg, feedback_callback=self.feedback_callback)

        self._send_goal_future.add_done_callback(self.goal_response_callback)

    def goal_response_callback(self, future):
        goal_handle = future.result()
        if not goal_handle.accepted:
            self.get_logger().info('Goal rejected :(')
            return

        self.get_logger().info('Goal accepted :)')

        self._get_result_future = goal_handle.get_result_async()
        self._get_result_future.add_done_callback(self.get_result_callback)

    def get_result_callback(self, future):
        result = future.result().result
        self.get_logger().info('Result: {0}'.format(result.sequence))
        rclpy.shutdown()

    def feedback_callback(self, feedback_msg):
        feedback = feedback_msg.feedback
        self.get_logger().info('Received feedback: {0}'.format(feedback.partial_sequence))


def main(args=None):
    rclpy.init(args=args)

    action_client = FibonacciActionClient()

    action_client.send_goal(10)

    rclpy.spin(action_client)


if __name__ == '__main__':
    main()

这是用于反馈消息的回调函数:

    def feedback_callback(self, feedback_msg):
        feedback = feedback_msg.feedback
        self.get_logger().info('Received feedback: {0}'.format(feedback.partial_sequence))

在回调函数中,我们获取消息的反馈部分并将partial_sequence字段打印到屏幕上。

我们需要在动作客户端中注册回调函数。当我们发送一个目标时,可以通过将回调函数附加到动作客户端来实现:

        self._send_goal_future = self._action_client.send_goal_async(goal_msg, feedback_callback=self.feedback_callback)

一切准备就绪。如果我们运行我们的动作客户端,你应该会在屏幕上看到打印出的反馈信息。

参考:http://fishros.org/doc/ros2/humble/Tutorials/Intermediate/Writing-an-Action-Server-Client/Py.html#

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/581110.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HTTPS证书申请:相关流程及注意事项

HTTPS证书&#xff08;即HTTPS证书、服务器证书&#xff09;是实现网络通信安全的重要技术产品&#xff0c;它为网站提供HTTPS加密和服务器身份验证的功能。HTTPS证书申请有那些流程&#xff1f;如何快速完成HTTPS证书申请&#xff1f;有哪些注意事项&#xff1f;本文将以沃通H…

Meta Llama 3 性能提升与推理服务部署

利用 NVIDIA TensorRT-LLM 和 NVIDIA Triton 推理服务器提升 Meta Llama 3 性能 我们很高兴地宣布 NVIDIA TensorRT-LLM 支持 Meta Llama 3 系列模型&#xff0c;从而加速和优化您的 LLM 推理性能。 您可以通过浏览器用户界面立即试用 Llama 3 8B 和 Llama 3 70B&#xff08;该…

Android优化RecyclerView图片展示:Glide成堆加载批量Bitmap在RecyclerView成片绘制Canvas,Kotlin(b)

Android优化RecyclerView图片展示&#xff1a;Glide成堆加载批量Bitmap在RecyclerView成片绘制Canvas&#xff0c;Kotlin&#xff08;b&#xff09; 对 Android GridLayoutManager Glide批量加载Bitmap绘制Canvas画在RecyclerView&#xff0c;Kotlin&#xff08;a&#xff09;-…

【哔哩哔哩笔试题汇总】2024-04-28-哔哩哔哩春招笔试题-三语言题解(CPP/Python/Java)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新b站近期的春秋招笔试题汇总&#xff5e; &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f497…

基于Hadoop的网上购物行为分析设计与实现

2.8 数据分析及可视化 2.8.1 店铺销售情况分析 通过这里可以看出&#xff0c;该店家的数据用户访问量比较的大&#xff0c;有接近6W多条数据&#xff0c;但是通过对用户进行透视分析发现只有981位用户&#xff0c;其次就是对于用户购买次数进行分析&#xff0c;发现数据只有27…

2017年全国职业院校技能大赛高职组“信息安全管理与评估”样题

培训、环境、资料、考证 公众号&#xff1a;Geek极安云科 网络安全群&#xff1a;624032112 网络系统管理群&#xff1a;223627079 网络建设与运维群&#xff1a;870959784 移动应用开发群&#xff1a;548238632 极安云科专注于技能提升&#xff0c;赋能 2024年广东省高校的技…

2.Neo4j的搭建启动

Graph Database 图数据库 版本对应关系 官网都是高版本&#xff0c;推荐使用下载地址可以找到社区老版本&#xff1a; https://we-yun.com/doc/neo4j/ neo4j.bat 启动脚本 cypher-shell.bat 执行CQL语句的。 import文件夹可以放入excel,csv等数据文件&#xff0c;导入到…

Transformer - Layer Normalization

Transformer - Layer Normalization flyfish y x − E [ x ] V a r [ x ] ϵ ∗ γ β y \frac{x - \mathrm{E}[x]}{ \sqrt{\mathrm{Var}[x] \epsilon}} * \gamma \beta yVar[x]ϵ ​x−E[x]​∗γβ 论文 Layer Normalization import numpy as np import torch import…

交直流充电桩检测的基础知识

交直流充电桩检测是电动汽车充电设施的重要组成部分&#xff0c;其目的是确保充电桩的正常运行&#xff0c;保障电动汽车的安全充电。以下是关于交直流充电桩检测的一些基础知识。 我们需要了解什么是交直流充电桩&#xff0c;简单来说&#xff0c;交直流充电桩是一种为电动汽车…

Centos7 RPM包离线安装Nginx

查看是否安装nginx #使用命令 rpm -qa|grep 列出需要卸载的软件包 rpm -qa | grep nginx 卸载nginx #使用rpm -e 加包名删除 rpm -e nginx-release-centos-7-0.el7.ngx.noarch nginx-1.14.1-1.el7_4.ngx.x86_64 rpm -e nginx 安装nginx 其他版本步骤一样 下载rpm包In…

BTCOIN的革命之路:通过SocialFi重塑全球金融生态系统

BTCOIN的革命之路&#xff1a;通过SocialFi重塑全球金融生态系统 今日&#xff0c;BTCOIN宣布发布WEB3.0论坛引发业内现象级关注&#xff1a;作为一个倡导WEB3.0理念的数字金融平台&#xff0c;在数字货币的波澜壮阔中&#xff0c;BTCOIN以其独特的生态定位和战略愿景&#xff…

进程控制7 - exec函数族

区别1 &#xff1a;参数1—>可执行文件名 区别2 &#xff1a;参数表的传递 区别3 &#xff1a;环境表的传递 详细举例说明&#xff1a; 下面这个demo使用execl函数&#xff0c;传入path也就是execlnewpro的路径&#xff08;这里也可以写绝对路径&#xff09;&#xff0c;…

线上社交app的搭建,圈子社交系统,小程序+app+H5三端,源码交付,支持二开!

在科技飞速发展的大背景下&#xff0c;年轻人社交不再局限于面对面&#xff0c;线上社交app已深入各大年轻人的手机中。相比于传统交友方式&#xff0c;线上社交app为用户提供了更加新奇的交友体验。同时&#xff0c;它还可以吸引更多的朋友&#xff0c;提高用户的整体交友体验…

Python 操作PDF图片 – 添加、替换、删除PDF中的图片

PDF文件中的图片可以丰富文档内容&#xff0c;提升用户的阅读体验。除了在PDF中添加图片外&#xff0c;有时也需要替换或删除其中的图片&#xff0c;以改进视觉效果或更新信息。文本将提供以下三个示例&#xff0c;介绍如何使用Python 操作PDF文件中的图片&#xff1a; 目录 …

python_django农产品物流信息服务系统6m344

Python 中存在众多的 Web 开发框架&#xff1a;Flask、Django、Tornado、Webpy、Web2py、Bottle、Pyramid、Zope2 等。近几年较为流行的&#xff0c;大概也就是 Flask 和 Django 了 Flask 是一个轻量级的 Web 框架&#xff0c;使用 Python 语言编写&#xff0c;较其他同类型框…

C++智能指针详解

目录 一. 智能指针初识 1.1 什么是智能指针 1.2 智能指针历史历程 1.3 为什么需要智能指针 1.3.1 内存泄漏 1.3.2 防止内存泄漏 1.3.3 异常的重新捕获 二. 智能指针的原理与使用 2.1 智能指针的原理 2.2 智能指针的使用 2.3 智能指针的拷贝问题…

视频抽帧转图片,opencv和ffmpeg效果测评

最近在做一个项目&#xff0c;需要从视频中抽帧转图片&#xff0c;于是对opencv和ffmpeg效果进行了测评。 文章目录 1. open cv2. ffmpeg3.抽帧效果对比 1. open cv open cv 视频抽图片的教程&#xff0c;推荐以下链接&#xff0c;抽的帧数可以自行调节&#xff01; 用pythono…

CSS伪类大全!4大类伪类详解

你好&#xff0c;我是云桃桃。 一个希望帮助更多朋友快速入门 WEB 前端的程序媛。 云桃桃-大专生&#xff0c;一枚程序媛&#xff0c;感谢关注。回复 “前端基础题”&#xff0c;可免费获得前端基础 100 题汇总&#xff0c;回复 “前端工具”&#xff0c;可获取 Web 开发工具合…

[C++基础学习]----01-C++数据类型详解

前言 C是一种静态类型的编程语言&#xff0c;它提供了丰富的数据类型来存储和操作数据。这些数据类型为C程序员提供了丰富的选择&#xff0c;可以根据具体需求来选择最合适的类型来存储和操作数据。下面详细解释一些常见的C数据类型&#xff0c;包括其原理和使用方法&#xff1…

VBA技术资料MF146:发出多次Beep提示声

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。“VBA语言専攻”提供的教程一共九套&#xff0c;分为初级、中级、高级三大部分&#xff0c;教程是对VBA的系统讲解&#…