Fastdds学习分享_xtpes_发布订阅模式及rpc模式

        在之前的博客中我们介绍了dds的大致功能,与组成结构。本篇博文主要介绍的是xtypes.分为理论和实际运用两部分.理论主要用于梳理hzy大佬的知识,对于某些一带而过的部分作出更为详细的阐释,并在之后通过实际案例便于理解。案例分为普通发布订阅模式与rpc模式。原博客地址:https://zhuanlan.zhihu.com/p/700132625

目录

xtypes是什么?

自定义类型相关的发送/接收接口

数据筛选

类型规范是不同DDS产品互联互通的基础

        静态模式

优势

劣势

类型描述

2.1. 类型描述

Nested

key

为什么 @key 重要?

id

optional

Extensibility

基础类型

interface

容器

使用流程

1.发布订阅模式

2.命令详细说明

3.RPC模式

这里先写一个demo

server

client

域ID

QoS

Transport

Timeout(RPC 调用超时)

Topic

Threading

FastDDS RPC 可配置参数总结


xtypes是什么?

        xtypes是 DDS(Data Distribution Service) 的一个扩展,提供了一种动态和静态数据类型管理机制.以数据为中心是DDS与其他消息中间件的一个重要的区别。它类似于ros的.msg文件但是更为强大。xtypes使得DDS表现的像能够理解业务数据一样。在hzy大佬的博客中总结了以下几点特性:

  • 自定义类型相关的发送/接收接口

  • 即提交给DDS和从DDS中获取的是主题关联的自定义数据结构对象。

  • 优势
    • 序列化/反序列化的工作从应用下沉到中间件,由中间件考虑端序/对齐/不同语言类型的转换;
    • 类型检查,在编译期即可检查出部分问题;
  • 劣势
    • 使用复杂,即便是简单的收发也需要IDL编译器编译支持代码;
  • 数据筛选

  • DDS提供类似于数据库的实时数据存储与查询的功能,包括:

    • 将主题数据按照key值组织,比如订阅端可以仅读取特定key值的数据;
    • 内容过滤,即订阅端可以配置只关心某个成员范围之间的值,DDS将自动过滤不属于这个范围的主题数据;
  • 类型规范是不同DDS产品互联互通的基础

    • 产品遵循相同的规范使得能够支持的数据类型互认;
    • 数据样本序列化方式规范使得A厂家的DDS产品序列化的数据可以由B厂家的DDS产品反序列化还原成相同类型的样本数据;

我们来理解一下是什么意思,首先自定义类型的发送/接收接口是什么意思?fastdds支持两种模式,静态模式需要对应的idl文件通过fastddsgen生成.hpp与xxxtypes.hpp文件。同时xtpes也支持动态类型来发送和接受数据此时无需idl文件.

        静态模式

        我们来简单看一下普通的静态模式的idl文件是如何编写的:

module state_and_error {// 错误码请求@extensibility(MUTABLE)struct ErrorCode {string code;                   // 错误码 (如 "1001", "1002")};// 错误码解析响应@extensibility(MUTABLE)struct ErrorCodeReply {string description;            // 错误码的解析描述string suggestion;             // 修复建议};// 错误处理接口interface ErrorHandle {ErrorCodeReply analyze_error(in ErrorCode error);};
}

        上面的module就类似于C++里面的namespace,里面还有个state碍于篇幅我就没放进来,看个原理就可以了。他这里面的消息单元就是用类似结构体的方式来进行编写的。extensibility这些后面会讲到,它用于支持数据扩展性。包括后面的interface,这些都会在后面的篇幅中讲到。这里看完了静态模式,我们来观看一下动态模式是如何编写的:

#include <fastdds/xtypes/dynamic_types/DynamicTypeBuilder.hpp>
#include <fastdds/xtypes/dynamic_types/DynamicData.hpp>// 1. 创建动态数据类型
DynamicTypeBuilder* builder = DynamicTypeBuilderFactory::get_instance()->create_struct_builder();
builder->add_member(0, "id", DynamicTypeBuilderFactory::get_instance()->create_int32_type());
builder->add_member(1, "name", DynamicTypeBuilderFactory::get_instance()->create_string_type());
DynamicType_ptr myType = builder->build();// 2. 创建 `DynamicData` 数据对象
DynamicData* myData = DynamicDataFactory::get_instance()->create_data(myType);
myData->set_int32_value(0, 42);
myData->set_string_value(1, "Example");// 3. 发送数据
dds_writer->write(myData);

        这个就相当于一个写在idl文件一个写在了程序里但是他们序列化都需要fastcdr支持。以下是静态和动态的一个对比表:

对比项静态 xtypes(IDL 编译)动态 xtypes(运行时创建)
定义方式通过 .idl 文件定义运行时动态定义
是否需要编译 IDL✅ 需要❌ 不需要
数据结构变化❌ 不能在运行时修改✅ 运行时可修改
类型检查✅ 编译期检查❌ 运行时检查
适合的应用场景实时性高、结构固定结构不固定、跨 DDS 版本兼容
序列化方式DDS CDR(默认高效)可用 JSON、CBOR、DDS CDR
性能更快(直接访问编译好的类型)稍慢(需要运行时解析类型)
ROS 2适配性✅ 是 ROS 2 默认方式❌ 目前 ROS 2 不支持动态 xtypes

        我这边建议使用静态模式,因为对于rpc模式来说,动态模式并不支持interface,并且他在传递性能上较动态模式更弱。但是如果你的数据结构是 “动态的” ,在运行时种类随时可能变化时动态模式也是较好的选择。但是有mutable其实也可以用静态的。

优势

  • 优势
    • 序列化/反序列化的工作从应用下沉到中间件,由中间件考虑端序/对齐/不同语言类型的转换;
    • 类型检查,在编译期即可检查出部分问题;

        这一部分是什么意思呢?在前面我们说了他的序列化是由fastcdr中间件完成的,对于我们程序编写就不用考虑序列化问题,但这也存在一个问题。比如说如果没有自定义序列化插件,将 Protobuf之类的序列化方式转换为 DDS 兼容的数据格式,那么他就不支持其他序列化协议。这种耦合有其好处也有其坏处。像ros1这种没有将序列化下沉到中间件而是用应用层来处理的,就可以通过sfinea机制来让他兼容protobuf.有好有坏吧。类型检查这些也不必多说,常规操作。

 劣势

  • 劣势
    • 使用复杂,即便是简单的收发也需要IDL编译器编译支持代码;

        这个怎么说呢,就是常用的静态 xtypes 使用复杂,即使只是简单的消息传输,也需要 IDL 编译。而且他编译器还挺搞的,dds版本很多有些编译器支持这种dds但是不支持其他dds。有些时候有些数据结构他最新的,自己版本的编译器又不支持。升级上去,可能自己的代码有些编译就会报错。建议用稳定的就行了别折腾了。

        下面的两种,在下文中会有提及,这里就不展开讲了。

类型描述

        

2.1. 类型描述

        类型描述定义开发语言无关的各种类型的语言以及结构,具体包含的类型参见上图,协议中规定DDS主题能够关联的数据类型只包括:结构体struct以及联合体union,其他类型则作为这两种聚合类型的成员。

        除了常规的类型/成员定义外,类型系统中还为类型或者成员添加了一些标签来提供额外的信息,常见的几个标签参见下表。

标签作用对象说明
Extensibility类型用于表明该类型的可扩展性,详见2.2.
Nested类型是否直接关联到DDS主题
key成员表明成员是否为键值
optional成员表明成员是否为可选
id成员指定成员的唯一ID
boundstring/sequence/map成员表明变长结构的长度上界,主要用于空间管理

        在我前面的例子中我们可以看到我只写了拓展性,因为这些其实都不是必填的,他们都是一些可选条件。如果我们要加上限制的话,我们可以这样写,看实际需要来写吧。

        

module state_and_error {@extensibility(APPENDABLE)struct State {@key int32 status;  // `status` 作为唯一标识@id(1) double current_x;@id(2) double current_y;@id(3) double current_theta;@optional double linear_velocity;  // 这个成员是可选的@optional double angular_velocity;  // 这个成员也是可选的@bound(255) string feedback_message;  // 限制字符串最大长度为 255};
};

下面来详细讲一下,这些标签。

Nested

        他是一个类型标签(Annotation),它用于指示该类型是否可以直接用作 DDS 主题(Topic),或者它是否只能作为其他数据类型的成员如果一个struct或union被标记为 @Nested,它不能直接作为 DDS 主题(Topic)发布或订阅,只能作为其他 struct 的成员来使用了。如果不加@Nested,默认情况下struct可以直接作为 DDS 主题使用。以下是代码案例:

struct Position {double x;double y;double z;
};@Nested
struct State {int32 status;Position pos; // `@Nested` 使 `State` 只能作为 `struct` 的成员
};

key

        在 DDS 里,DDS 通过@key识别数据实例(Instance),@key 相同的数据会被认为是同一个对象,可以更新,不是新的消息。如果你不加@key,DDS 认为你的数据是无状态的消息流(类似于 UDP 广播),而如果你加了@key,DDS 就会把数据当作唯一标识的实例(类似数据库的主键)这句话怎么理解呢?当没有加@key的时候:

struct SensorData {int32 id;double temperature;
};

        DDS 认为所有SensorData消息是“独立的消息流”,不会追踪id是否重复。每个消息就像 UDP 广播,没有“实例管理”机制,接收方无法分辨两个数据是否属于同一个传感器。

加@key:

struct SensorData {@key int32 id;  // 传感器的唯一标识double temperature;
};

DDS 现在认为id相同的数据是同一个“实例”,它会:

  1. 缓存最后一次收到的 id = 1 的数据(类似数据库的 UPDATE)。也就是说如果 State 结构体有 @key id,那么 DDS 会按 id 分别存储不同的实例。如果 DDS 订阅者(Subscriber)已经收到 id = 1 的数据,再次收到 id = 1 的新数据时,DDS 只会 更新 id = 1 的数据,不会新增新的条目
  2. 自动删除旧数据(可以配置数据历史策略)。DDS 允许你配置“数据历史策略”(History QoS),决定保留多少条历史记录。如果配置KEEP_LAST(1) DDS 只会保存每个id的最新数据,旧数据会自动被删除。如果配置KEEP_ALL  DDS 会保留所有历史数据,不删除。
  3. 允许 QueryCondition 进行实例查询,比如“只订阅 id = 2 的数据”。

这里展示一下怎么配置只保留最新的

DataReaderQos qos;
qos.history().kind = KEEP_LAST_HISTORY_QOS;
qos.history().depth = 1;  // 只保留最新的一条数据
reader->set_qos(qos);
为什么 @key 重要?

        如果你加了@key,DDS 知道哪些数据属于同一个实例,可以做增量更新,而不是简单的消息广播。这句话就是说

  • 如果你加了@key,DDS 就会按照 key(通常是 id)来管理数据。
  • @key 让 DDS 认为 id 相同的数据是同一个对象的“状态更新”,可以进行增量更新(类似数据库的UPDATA)。

        如果你不加@key,每个消息都是“独立的”,无法做基于 ID 的筛选、历史记录管理或 QoS 策略。但是如果@key类型相同,其他类型不同,如果拓展性没有设置mutable那么就会报错。

id

        用于mutable可扩展性模式,确保新旧版本字段顺序不同也能正确解析数据。不会影响实例管理。如果不加@id,DDS 解析数据时只能按字段顺序匹配,无法正确解析字段新增、删除或重排的情况。这句话怎么理解呢?因为拓展性的mutable允许添加新的数据,那么就需要@id确保新旧版本的数据结构,即使字段顺序不同,DDS 仍然可以正确解析,而不会误解数据格式如果不加 @id,DDS 只能按照字段的顺序解析数据,这意味着:如果字段的顺序改变,旧版本可能解析错字段,导致数据错误。如果字段被删除或新增,旧版本可能会崩溃或丢弃数据。这样,即使新版本的数据结构发生了变化,旧版本仍然可以解析它能识别的字段,不会因字段顺序变化而导致错误!

举个例子

//旧数据
@extensibility(MUTABLE)
struct State {int32 status;double x;double y;
};
//新数据
@extensibility(MUTABLE)
struct State {int32 status;double y;  // ⚠️ 位置发生变化!double x;  // ⚠️ 位置发生变化!
};

这样就会出问题,但是如果加了@id呢?

@extensibility(MUTABLE)
struct State {@id(1) int32 status;@id(2) double x;@id(3) double y;
};@extensibility(MUTABLE)
struct State {@id(1) int32 status;@id(3) double y;  // 位置变化了,但 `@id(3)` 让 DDS 知道它是 `y`@id(2) double x;  // 位置变化了,但 `@id(2)` 让 DDS 知道它是 `x`
};

这样就没问题了

optional

        他是在旧版本里面使用的,但是现在有拓展性的mutable,就没那么重要了。但是如果某个字段在新版本中可能为空,但旧版本的解析器不允许null值,optional让新系统的发布者可以选择是否发送该字段,避免影响旧系统。optional允许你在不影响旧版本的情况下逐步添加新功能。也就是说大部分时间是没用的。

Extensibility

        这一部分hzy大佬讲的非常详细,引用他的原文即可。需要了解更多dds知识的可以去上面博客去看看原博客,写的很不错。但是注意大佬写的是DDS规范,规范是一个宽泛的概念,各版本的dds具体实现可能略有不同。

DDS可扩展性分为3种,详见下表,为什么取名叫“类型演进”,因为基于APPENDABLE/MUTABLE可扩展性类型,原有系统无需做任何的代码、配置的修改,即可与新的系统(使用迭代后的新的数据类型)进行数据交互。

可扩展性说明
FINAL不可扩展,类型结构必须完全一致才能相互交换数据,用于保护已有系统。
APPENDABLE可追加,这种类型是默认的类型,新的类型是基于老的类型在后面添加成员得到,这种模式下新老数据结构关联的主题能够相互交换数据。
MUTABLE可随意变换,新的类型可将老的类型重新排序组合以及添加新的成员得到,这种模式下新老数据结构关联的主题能够相互交换数据。

FINAL可扩展性示意图

上图中下面蓝色部分代表已有运行系统,上面的橙色部分代表新建的系统,新建的发布/订阅应用将位置信息从原有的2个坐标修改为3个坐标,此时由于原有系统设置为FINAL的保护状态,新的应用无法集成到老的系统中去。

APPENDABLE可扩展性示意图

上图中下面蓝色部分代表已有运行系统,上面的橙色部分代表新建的系统,新建的发布/订阅应用将位置信息从原有的2个坐标修改为3个坐标,此时由于类型系统设置为APPENDABLE可扩展状态,老的应用不修改任何的配置以及代码,即可把新的发布/订阅应用集成到原有的系统中,老的订阅者(右下)将接收到新的发布者发布的数据,其中多出的z成员将被忽略,而新的订阅者应用(左上)将接收到老的发布端者发布的数据,其中缺少的z成员将赋予默认的值。

MUABLE可扩展性示意图

上图中下面蓝色部分代表已有运行系统,上面的橙色部分代表新建的系统,新建的发布/订阅应用将位置信息将原有的x、y坐标打乱并在中间插入一个新的成员z,此时由于类型系统设置为MUTABLE可扩展状态,老的应用不修改任何的配置以及代码,即可把新的发布/订阅应用集成到原有的系统中,老的订阅者(右下)将接收到新的发布者发布的数据,其中多出的z成员将被忽略,而新的订阅者应用(左上)将接收到老的发布端者发布的数据,其中缺少的z成员将赋予默认的值。

介绍到这里可能会产生一个疑问:既然能够支持MUTABLE类型,那所有的类型都设计成可变的类型,系统的可扩展性不就可以得到保证吗,为什么还需要支持前面两个类型?答案总结在下面的这张不同类型的优劣势中,不同类型可扩展性实现的关键技术在数据序列化中介绍。

可扩展性优势劣势
FINAL1、首先是安全,类似于Java里面把一个类声明为final禁止其他类型继承扩展;2、固定结构下数据序列化/反序列化效率高无可扩展性
MUTABLE具备很好的可扩展性结构可变带来底层序列化/反序列化需要携带更多的额外信息,导致效率变低
APPENDABLE1、具备一定的可扩展性;2、接近于固定结构序列化/反序列化效率高可扩展性有限

基础类型

idl和C++用的基本类型差不多:

类型描述示例
boolean布尔值(truefalseboolean is_active;
char单个字符(ASCII)char letter;
octet8-bit 无符号整数octet small_value;
int88-bit 有符号整数int8 small_number;
uint88-bit 无符号整数uint8 small_number;
int1616-bit 有符号整数int16 medium_number;
uint1616-bit 无符号整数uint16 medium_number;
int3232-bit 有符号整数int32 large_number;
uint3232-bit 无符号整数uint32 large_number;
int6464-bit 有符号整数int64 very_large_number;
uint6464-bit 无符号整数uint64 very_large_number;
float32-bit 单精度浮点数float temperature;
double64-bit 双精度浮点数double precise_value;

interface

        interface用于 DDS RPC(远程过程调用),类似 ROS 的 Service。后面会详细介绍

容器

FastDDS 的 xtypes 支持容器类型(Collection Types),包括:

  • sequence<T>(可变长度序列)
  • array<T, N>(固定大小数组)
  • map<K, V, N>(键值对映射,部分 DDS 实现支持)

    例子如下:

struct SensorReadings {sequence<float, 10> temperatures;  // 最多存储 10 个温度值
};
SensorReadings data;
data.temperatures().resize(5); // 运行时调整大小struct Position {array<float, 3> coordinates;  // 3D 坐标 (x, y, z)
};
Position pos;
pos.coordinates()[0] = 1.0;
pos.coordinates()[1] = 2.0;
pos.coordinates()[2] = 3.0;struct SensorMapping {map<string<10>, float, 5> sensor_data;  // 最多存储 5 个传感器数据
};
SensorMapping mapping;
mapping.sensor_data()["temperature"] = 36.5;
mapping.sensor_data()["humidity"] = 45.0;

使用流程

        

1.发布订阅模式

        我们先写idl文件,然后进入fastddsgen文件夹。

运行命令

./fastddsgen -language C++ path/to/xxx.idl -d path/to/output/

2.命令详细说明

参数作用示例
-language C++指定生成 C++ 代码(默认是 C++)./fastddsgen -language C++ xxx.idl
-d <output_path>指定输出目录./fastddsgen -d /home/user/generated_code xxx.idl
-replace覆盖旧文件,重新生成代码./fastddsgen -replace xxx.idl
-example <OS>生成完整示例(可选 Linux, Windows, Mac)./fastddsgen -example Linux xxx.idl
-help显示帮助信息./fastddsgen -help

        他会生成一系列代码。在写发布者订阅者的时候,需要.hpp文件与xxxPubSubTypes.hpp.首先需要注册类型,这里就是注册给cdr序列化协议的。

TypeSupport type_support(new Destination::Destination_sitePubSubType());
participant->register_type(type_support); 

之后就可以定义数据结构了

Destination::Destination_site data;
data.x(0);
data.y(0);

3.RPC模式

这里先写一个demo

module robot_control {interface RobotService {string get_status();boolean move_to(in double x, in double y, out string response_msg);};
};

然后我们用

fastddsgen -example C++ robot_service.idl -d /home/user/generated_code/

注意一下,有些版本他是用fastrpcgen来编译idl,需要注意一下。

他会生成

robot_controlRobotServiceProxy.hpp   // RPC 客户端(Proxy)
robot_controlRobotServiceServer.hpp  // RPC 服务端(Server)
robot_controlRobotServiceImpl.hpp    // 需要用户实现的服务逻辑
robot_controlRobotService.cxx        // FastDDS RPC 底层实现
robot_controlRobotServicePubSubTypes.hpp  // 数据类型支持

server

#include "robot_controlRobotServiceServer.hpp"class RobotServiceImpl : public robot_control::RobotServiceServer
{
public:// 实现 get_status() 方法void get_status(::eprosima::fastdds::dds::StringType& _return) override{_return = "Robot is running";  // 返回状态信息}// 实现 move_to() 方法,返回是否移动成功bool move_to(double x, double y, ::eprosima::fastdds::dds::StringType& response_msg) override{std::cout << "Moving to: (" << x << ", " << y << ")" << std::endl;if (x >= 0 && y >= 0) // 只允许正坐标{response_msg = "Move successful!";return true; // 移动成功}else{response_msg = "Invalid target position.";return false; // 移动失败}}
};int main()
{RobotServiceImpl robot_service;if (robot_service.run()){std::cout << "RPC Server is running..." << std::endl;while (true) { } // 保持运行}return 0;
}

        在 FastDDS RPC 生成的 C++ 代码中,IDL 里定义的返回值 在生成的 C++ 代码中会 被转换为void,并使用 out 参数_return 传递结果。这是 FastDDS RPC 代码生成的特性,用于避免额外的拷贝,提高性能。

client

#include "robot_controlRobotServiceProxy.hpp"int main()
{robot_control::RobotServiceProxy client;if (client.run()){std::cout << "Connected to RPC Server!" << std::endl;// 远程调用 get_status()eprosima::fastdds::dds::StringType status;client.get_status(status);std::cout << "Robot Status: " << status << std::endl;// 远程调用 move_to(),获取返回值eprosima::fastdds::dds::StringType response_msg;bool result = client.move_to(10.5, 20.8, response_msg);std::cout << "Move Result: " << (result ? "Success" : "Failure") << std::endl;std::cout << "Server Response: " << response_msg << std::endl;client.stop();}return 0;
}

        在RPC模式下你无需创建主题,域参与者,qos之类的。fastddsrpc内部都会帮你搞定,你只要拥有相同的头文件即可。

普通 DDS 需要手动做的事情FastDDS RPC 自动管理
创建 DomainParticipant✅ FastDDS 自动创建
定义 Topic✅ FastDDS 自动创建
创建 PublisherSubscriber✅ FastDDS 自动创建
管理 RequestReply 的序列化✅ FastDDS 自动管理
匹配 ClientServerDomain ID✅ FastDDS 内部处理

        但与自动管理并不代表你不能设置,比如:

域ID

client.set_domain_id(5);  // 修改 Domain ID
server.set_domain_id(5);

QoS

FastDDS 允许你设置 QoS,控制 RPC 的可靠性、历史记录等。例如:

  • RELIABLE_RELIABILITY_QOS(可靠传输,确保请求不丢失)
  • KEEP_LAST_HISTORY_QOS(保留最近的 N 条历史记录)
  • TRANSIENT_LOCAL_DURABILITY_QOS(即使 Server 断开,Client 仍然能获取数据)
eprosima::fastdds::dds::QoSSettings qos;
qos.reliability(eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS);
qos.history(eprosima::fastdds::dds::KEEP_LAST_HISTORY_QOS);
client.set_qos(qos);

Transport

        默认情况下,FastDDS 使用 UDP 进行通信。如果你想强制使用 TCP,可以这样配置:

eprosima::fastdds::dds::TransportConfig transport;
transport.use_tcp(true);
client.set_transport(transport);

Timeout(RPC 调用超时)

        如果Client调用Server超时(Server可能崩溃或网络异常),默认 FastDDS 不会一直等待,可以设置超时时间:

client.set_timeout(std::chrono::milliseconds(5000));  // 5 秒超时

如果 5 秒内 Server没有响应,RPC 调用会失败并返回错误

Topic

client.set_topic_name("MyCustomTopic");

如果你想同时运行多个不同的 RPC 服务,可以用不同的Topic进行隔离

Threading

eprosima::fastdds::dds::ThreadSettings threads;
threads.use_separate_thread(true);  // 每个 RPC 请求使用单独线程
client.set_threading(threads);

        默认情况下,FastDDS 使用单线程模式,你可以改为多线程,提高吞吐量。如果你的 RPC 请求处理速度较慢,建议开启多线程模式,以支持高并发调用。

FastDDS RPC 可配置参数总结

参数作用示例
Domain ID指定 RPC 运行的 DDS 领域client.set_domain_id(5);
QoS设置可靠性、持久性client.set_qos(qos);
Transport指定 TCP/UDP 传输client.set_transport(transport);
Timeout设置调用超时client.set_timeout(std::chrono::milliseconds(5000));
Topic手动指定 Topic 名称client.set_topic_name("MyCustomTopic");
Threading设定是否使用多线程client.set_threading(threads);

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Three.js 后期处理(Post-Processing)详解

目录 前言 一、什么是后期处理&#xff1f; 二、Three.js 后期处理的工作流程 2.1 创建 EffectComposer 2.2 添加渲染通道&#xff08;Render Pass&#xff09; 2.3 应用最终渲染 三、后期处理实现示例 3.1 基础代码 四、常见的后期处理效果 4.1 辉光效果&#xf…

计算机视觉-边缘检测

一、边缘 1.1 边缘的类型 ①实体上的边缘 ②深度上的边缘 ③符号的边缘 ④阴影产生的边缘 不同任务关注的边缘不一样 1.2 提取边缘 突变-求导&#xff08;求导也是一种卷积&#xff09; 近似&#xff0c;1&#xff08;右边的一个值-自己可以用卷积做&#xff09; 该点f(x,y)…

基于SpringBoot的美食烹饪互动平台的设计与实现(源码+SQL脚本+LW+部署讲解等)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

通信方式、点对点通信、集合通信

文章目录 传统组网互联大模型组网互联&#xff1a;超高带宽、超低延迟、超高可靠性☆☆☆ AI计算集群互联方式&#xff1a;Die间、片间、集群间Die间&#xff1a;SoC架构转向 Chilplet 异构&#xff08;多Die&#xff09;、UCIe标准IO Die & Base Die节点内 NPU 间互联&…

git:恢复纯版本库

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github&#xff1a;codetoys&#xff0c;所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的&#xff0c;可以在任何平台上使用。 源码指引&#xff1a;github源…

npm知识

npm 是什么 npm 为你和你的团队打开了连接整个 JavaScript 天才世界的一扇大门。它是世界上最大的软件注册表&#xff0c;每星期大约有 30 亿次的下载量&#xff0c;包含超过 600000 个包&#xff08;package&#xff09;&#xff08;即&#xff0c;代码模块&#xff09;。来自…

【Java】位图 布隆过滤器

位图 初识位图 位图, 实际上就是将二进制位作为哈希表的一个个哈希桶的数据结构, 由于二进制位只能表示 0 和 1, 因此通常用于表示数据是否存在. 如下图所示, 这个位图就用于标识 0 ~ 14 中有什么数字存在 可以看到, 我们这里相当于是把下标作为了 key-value 的一员. 但是这…

python学opencv|读取图像(五十六)使用cv2.GaussianBlur()函数实现图像像素高斯滤波处理

【1】引言 前序学习了均值滤波和中值滤波&#xff0c;对图像的滤波处理有了基础认知&#xff0c;相关文章链接为&#xff1a; python学opencv|读取图像&#xff08;五十四&#xff09;使用cv2.blur()函数实现图像像素均值处理-CSDN博客 python学opencv|读取图像&#xff08;…

如何使用 DeepSeek 和 Dexscreener 构建免费的 AI 加密交易机器人?

我使用DeepSeek AI和Dexscreener API构建的一个简单的 AI 加密交易机器人实现了这一目标。在本文中&#xff0c;我将逐步指导您如何构建像我一样的机器人。 DeepSeek 最近发布了R1&#xff0c;这是一种先进的 AI 模型。您可以将其视为 ChatGPT 的免费开源版本&#xff0c;但增加…

【Linux系统】信号:再谈OS与内核区、信号捕捉、重入函数与 volatile

再谈操作系统与内核区 1、浅谈虚拟机和操作系统映射于地址空间的作用 我们调用任何函数&#xff08;无论是库函数还是系统调用&#xff09;&#xff0c;都是在各自进程的地址空间中执行的。无论操作系统如何切换进程&#xff0c;它都能确保访问同一个操作系统实例。换句话说&am…

蓝桥与力扣刷题(141 环形链表)

题目&#xff1a;给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整数 pos 来表示链表尾连接到链表中的…

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】2.29 NumPy+Scikit-learn(sklearn):机器学习基石揭秘

2.29 NumPyScikit-learn&#xff1a;机器学习基石揭秘 目录 #mermaid-svg-46l4lBcsNWrqVkRd {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-46l4lBcsNWrqVkRd .error-icon{fill:#552222;}#mermaid-svg-46l4lBcsNWr…

VSCode设置内容字体大小

1、打开VSCode软件&#xff0c;点击左下角的“图标”&#xff0c;选择“Setting”。 在命令面板中的Font Size处选择适合自己的字体大小。 2、对比Font Size值为14与20下的字体大小。

防火墙安全策略配置实验

一.实验拓扑&#xff1a; 二.实验需求&#xff1a; 1.vlan 2 属于办公区&#xff1b; vlan 3 属于生产区 2.办公区PC在工作日时间&#xff08;早8晚6&#xff09;可以正常访问OA server&#xff0c;其他时间不允许 3.办公区PC可以在任意时间访问Web server 4.生产区PC可以…

Redis入门概述

1.1、Redis是什么 Redis&#xff1a;官网 高性能带有数据结构的Key-Value内存数据库 Remote Dictionary Server&#xff08;远程字典服务器&#xff09;是完全开源的&#xff0c;使用ANSIC语言编写遵守BSD协议&#xff0c;例如String、Hash、List、Set、SortedSet等等。数据…

【C++篇】哈希表

目录 一&#xff0c;哈希概念 1.1&#xff0c;直接定址法 1.2&#xff0c;哈希冲突 1.3&#xff0c;负载因子 二&#xff0c;哈希函数 2.1&#xff0c;除法散列法 /除留余数法 2.2&#xff0c;乘法散列法 2.3&#xff0c;全域散列法 三&#xff0c;处理哈希冲突 3.1&…

基于RTOS的STM32游戏机

1.游戏机的主要功能 所有游戏都来着B站JL单片机博主开源 这款游戏机具备存档与继续游戏功能&#xff0c;允许玩家在任何时候退出当前游戏并保存进度&#xff0c;以便日后随时并继续之前的冒险。不仅如此&#xff0c;游戏机还支持多任务处理&#xff0c;玩家可以在退出当前游戏…

优选算法的灵动之章:双指针专题(一)

个人主页&#xff1a;手握风云 专栏&#xff1a;算法 目录 一、双指针算法思想 二、算法题精讲 2.1. 查找总价格为目标值的两个商品 2.2. 盛最多水的容器 ​编辑 2.3. 移动零 2.4. 有效的三角形个数 一、双指针算法思想 双指针算法主要用于处理数组、链表等线性数据结构…

ROS应用之SwarmSim在ROS 中的协同路径规划

SwarmSim 在 ROS 中的协同路径规划 前言 在多机器人系统&#xff08;Multi-Robot Systems, MRS&#xff09;中&#xff0c;SwarmSim 是一个常用的模拟工具&#xff0c;可以对多机器人进行仿真以实现复杂任务的协同。除了任务分配逻辑以外&#xff0c;SwarmSim 在协同路径规划方…

MVC、MVP和MVVM模式

MVC模式中&#xff0c;视图和模型之间直接交互&#xff0c;而MVP模式下&#xff0c;视图与模型通过Presenter进行通信&#xff0c;MVVM则采用双向绑定&#xff0c;减少手动同步视图和模型的工作。每种模式都有其优缺点&#xff0c;适合不同规模和类型的项目。 ### MVVM 与 MVP…