ros -slam - microros- PID控制器实现

news/2024/7/4 6:07:58/文章来源:https://www.cnblogs.com/ai-ldj/p/18279700

上一节我们通过编码器完成了对机器人单个轮子的速度测量,完成了电机速度闭环控制的重要一步-反馈。

 有了反馈,接着我们需要设计一个控制器来帮助我们实现这个需求,这个控制器的输入是当前的速度和目标速度,输出是应该给到电机的PWM占空比。

一、PID控制器介绍
PID控制器是一种广泛应用于工业控制、自动化控制等领域的控制算法,其名称来源于“比例-积分-微分”三个控制器参数,即Proportional(比例)、Integral(积分)、Derivative(微分)。
PID控制器的基本原理是通过测量目标系统的反馈信号和期望输出信号之间的误差,根据一定的数学模型计算出控制信号,使目标系统能够稳定地达到期望输出。具体来说,PID控制器的计算公式为:

其中,Kp、Ki和Kd分别表示比例系数、积分系数和微分系数,Error表示目标系统的误差,Integral(Error)表示误差的积分,Derivative(Error)表示误差的微分。
在PID控制器中,比例系数、积分系数和微分系数的选取是关键,需要根据具体的控制需求进行调整。比例系数主要影响系统的响应速度和稳定性,积分系数主要影响系统的稳态误差,而微分系数主要影响系统的抗干扰性能。
说了理论你可能不是很理解,没关系,写完代码和调参后你感受就会变的深刻起来。

二、新建工程搭建框架
2.1 新建工程
example27_pid_controller

 修改platformio.ini配置,添加开源库和microros配置

[env:featheresp32]
platform = espressif32
board = featheresp32
framework = arduino
board_microros_transport = wifi
board_microros_distro = humble
lib_deps = https://gitee.com/ohhuo/micro_ros_platformio.githttps://github.com/fishros/Esp32McpwmMotor.githttps://github.com/fishros/Esp32PcntEncoder.git

2.2 添加PidController
在lib下新建PidController文件夹,并在PidController下新建PidController.h和PidController.cpp

最终目录结构

.
├── include
│   └── README
├── lib
│   ├── PidController
│   │   ├── PidController.cpp
│   │   └── PidController.h
│   └── README
├── platformio.ini
├── src
│   └── main.cpp
└── test└── README5 directories, 7 files

2.3 复制并修改代码
将之前遥控车的代码复制粘贴到当前的main函数中,同时

  • 添加PidController.h的头文件
  • 删除原有的控制逻辑
  • 添加电机速度测量函数
  • 修改为双核通信
  • 添加了一些注释

最终代码如下

#include <Arduino.h>
#include <micro_ros_platformio.h>    // 包含用于 ESP32 的 micro-ROS PlatformIO 库
#include <WiFi.h>                    // 包含 ESP32 的 WiFi 库
#include <rcl/rcl.h>                 // 包含 ROS 客户端库 (RCL)
#include <rclc/rclc.h>               // 包含用于 C 的 ROS 客户端库 (RCLC)
#include <rclc/executor.h>           // 包含 RCLC 执行程序库,用于执行订阅和发布
#include <geometry_msgs/msg/twist.h> // 包含 ROS2 geometry_msgs/Twist 消息类型
#include <Esp32PcntEncoder.h>        // 包含用于计数电机编码器脉冲的 ESP32 PCNT 编码器库
#include <Esp32McpwmMotor.h>         // 包含使用 ESP32 的 MCPWM 硬件模块控制 DC 电机的 ESP32 MCPWM 电机库
#include <PidController.h>           // 包含 PID 控制器库,用于实现 PID 控制

Esp32PcntEncoder encoders[2];      // 创建一个长度为 2 的 ESP32 PCNT 编码器数组
rclc_executor_t executor;          // 创建一个 RCLC 执行程序对象,用于处理订阅和发布
rclc_support_t support;            // 创建一个 RCLC 支持对象,用于管理 ROS2 上下文和节点
rcl_allocator_t allocator;         // 创建一个 RCL 分配器对象,用于分配内存
rcl_node_t node;                   // 创建一个 RCL 节点对象,用于此基于 ESP32 的机器人小车
rcl_subscription_t subscriber;     // 创建一个 RCL 订阅对象,用于订阅 ROS2 消息
geometry_msgs__msg__Twist sub_msg; // 创建一个 ROS2 geometry_msgs/Twist 消息对象
Esp32McpwmMotor motor;             // 创建一个 ESP32 MCPWM 电机对象,用于控制 DC 电机
float out_motor_speed[2];          // 创建一个长度为 2 的浮点数数组,用于保存输出电机速度
float current_speeds[2];           // 创建一个长度为 2 的浮点数数组,用于保存当前电机速度void twist_callback(const void *msg_in)
{const geometry_msgs__msg__Twist *twist_msg = (const geometry_msgs__msg__Twist *)msg_in;float linear_x = twist_msg->linear.x;   // 获取 Twist 消息的线性 x 分量float angular_z = twist_msg->angular.z; // 获取 Twist 消息的角度 z 分量if (linear_x == 0 && angular_z == 0)    // 如果 Twist 消息没有速度命令
    {motor.updateMotorSpeed(0, 0); // 停止第一个电机motor.updateMotorSpeed(1, 0); // 停止第二个电机return;                       // 退出函数
    }
}// 这个函数是一个后台任务,负责设置和处理与 micro-ROS 代理的通信。
void microros_task(void *param)
{// 设置 micro-ROS 代理的 IP 地址。
  IPAddress agent_ip;agent_ip.fromString("192.168.2.105");// 使用 WiFi 网络和代理 IP 设置 micro-ROS 传输层。set_microros_wifi_transports("fishbot", "12345678", agent_ip, 8888);// 等待 2 秒,以便网络连接得到建立。delay(2000);// 设置 micro-ROS 支持结构、节点和订阅。allocator = rcl_get_default_allocator();rclc_support_init(&support, 0, NULL, &allocator);rclc_node_init_default(&node, "esp32_car", "", &support);rclc_subscription_init_default(&subscriber,&node,ROSIDL_GET_MSG_TYPE_SUPPORT(geometry_msgs, msg, Twist),"/cmd_vel");// 设置 micro-ROS 执行器,并将订阅添加到其中。rclc_executor_init(&executor, &support.context, 1, &allocator);rclc_executor_add_subscription(&executor, &subscriber, &sub_msg, &twist_callback, ON_NEW_DATA);// 循环运行 micro-ROS 执行器以处理传入的消息。while (true){delay(100);rclc_executor_spin_some(&executor, RCL_MS_TO_NS(100));}
}// 这个函数根据编码器读数更新两个轮子速度。
void update_speed()
{// 初始化静态变量以存储上一次更新时间和编码器读数。static uint64_t last_update_time = millis();static int64_t last_ticks[2];// 获取自上次更新以来的经过时间。uint64_t dt = millis() - last_update_time;if (dt == 0)return;// 获取当前的编码器读数并计算当前的速度。int32_t pt[2];pt[0] = encoders[0].getTicks() - last_ticks[0];pt[1] = encoders[1].getTicks() - last_ticks[1];current_speeds[0] = float(pt[0] * 0.1051566) / dt * 1000;current_speeds[1] = float(pt[1] * 0.1051566) / dt * 1000;// 更新上一次更新时间和编码器读数。last_update_time = millis();last_ticks[0] = encoders[0].getTicks();last_ticks[1] = encoders[1].getTicks();
}void setup()
{// 初始化串口通信,波特率为115200Serial.begin(115200);// 将两个电机分别连接到引脚22、23和12、13上motor.attachMotor(0, 22, 23);motor.attachMotor(1, 12, 13);// 在引脚32、33和26、25上初始化两个编码器encoders[0].init(0, 32, 33);encoders[1].init(1, 26, 25);// 在核心0上创建一个名为"microros_task"的任务,栈大小为10240xTaskCreatePinnedToCore(microros_task, "microros_task", 10240, NULL, 1, NULL, 0);
}void loop()
{// 更新电机速度
  update_speed();// 更新电机0和电机1的速度值motor.updateMotorSpeed(0, out_motor_speed[0]);motor.updateMotorSpeed(1, out_motor_speed[1]);// 延迟10毫秒delay(10);
}

三、PID控制器代码实现
接着我们来编写PidController.h和PidController.cpp。

#ifndef __PIDCONTROLLER_H__ // 如果没有定义__PIDCONTROLLER_H__
#define __PIDCONTROLLER_H__ // 定义__PIDCONTROLLER_H__class PidController
{ // 定义一个PID控制器类
public:PidController() = default;                   // 默认构造函数PidController(float kp, float ki, float kd); // 构造函数,传入kp、ki、kdpublic:float target_;      // 目标值float out_mix_;     // 输出下限float out_max_;     // 输出上限float kp_;          // 比例系数float ki_;          // 积分系数float kd_;          // 微分系数float last_output_; // 上一次输出值// pidfloat error_sum_;           // 误差累积和float derror_;              // 误差变化率float error_pre_;           // 上上次误差float error_last_;          // 上一次误差float intergral_up_ = 2500; // 积分上限public:float update(float control);                   // 更新输出值void reset();                                  // 重置PID控制器void update_pid(float kp, float ki, float kd); // 更新PID系数void update_target(float target);              // 更新目标值void out_limit(float out_mix, float out_max);  // 输出限制
};#endif // __PIDCONTROLLER_H__ // 结束条件

定义PidController,提供五个函数。

  • update(control): 传入当前控制量control并返回PID控制器的输出值
  • reset(): 将PID控制器的状态重置为初始状态
  • update_pid(kp, ki, kd): 更新PID控制系数
  • update_target(target): 更新目标值
  • out_limit(out_mix, out_max): 输出限制

3.2 PidController.cpp

#include "PidController.h"
#include "Arduino.h"PidController::PidController(float kp, float ki, float kd)
{reset(); // 初始化控制器update_pid(kp, ki, kd); // 更新PID参数
}float PidController::update(float control)
{// 计算误差及其变化率float error = target_ - control; // 计算误差derror_ = error_last_ - error; // 计算误差变化率error_last_ = error;// 计算积分项并进行积分限制error_sum_ += error;if (error_sum_ > intergral_up_)error_sum_ = intergral_up_;if (error_sum_ < -1 * intergral_up_)error_sum_ = -1 * intergral_up_;// 计算控制输出值float output = kp_ * error + ki_ * error_sum_ + kd_ * derror_;// 控制输出限幅if (output > out_max_)output = out_max_;if (output < out_mix_)output = out_mix_;// 保存上一次的控制输出值last_output_ = output;return output;
}void PidController::update_target(float target)
{target_ = target; // 更新控制目标值
}void PidController::update_pid(float kp, float ki, float kd)
{reset(); // 重置控制器状态kp_ = kp; // 更新比例项系数ki_ = ki; // 更新积分项系数kd_ = kd; // 更新微分项系数
}void PidController::reset()
{// 重置控制器状态last_output_ = 0.0f; // 上一次的控制输出值target_ = 0.0f; // 控制目标值out_mix_ = 0.0f; // 控制输出最小值out_max_ = 0.0f; // 控制输出最大值kp_ = 0.0f; // 比例项系数ki_ = 0.0f; // 积分项系数kd_ = 0.0f; // 微分项系数error_sum_ = 0.0f; // 误差累计值derror_ = 0.0f; // 误差变化率error_last_ = 0.0f; // 上一次的误差值
}void PidController::out_limit(float out_mix, float out_max)
{out_mix_ = out_mix; // 控制输出最小值out_max_ = out_max; // 控制输出最大值
}

上面这段代码是用于实现一个PID控制器的C++代码。PID控制器是一种常用的控制器,它的输入是控制系统的误差信号,输出是控制器的控制量。PID控制器由比例项、积分项和微分项三个部分组成,这三个部分的系数可以通过调节来实现控制器的性能优化。

以下是代码中各部分的注释:

  • PidController::PidController(float kp, float ki, float kd):PID控制器的构造函数,用于初始化控制器状态并更新PID参数。
  • void PidController::update_target(float target):用于更新控制器的目标值。
  • void PidController::update_pid(float kp, float ki, float kd):用于更新控制器的PID参数。
  • void PidController::out_limit(float out_mix, float out_max):用于限制控制器的控制输出范围。
  • float PidController::update(float control):控制器的核心函数,用于根据当前控制量计算出下一时刻的控制量。具体实现包括以下步骤:
    • 计算误差及其变化率;
    • 计算积分项并进行积分限制;
    • 计算控制输出值,并进行输出限幅;
    •   保存上一次的控制输出值。
  • void PidController::reset():用于重置控制器的状态。包括重置PID参数、目标值、控制输出范围等状态变量。

在代码实现中,float代表浮点数类型,在C++中用于表示实数。kp_、ki_、kd_分别代表PID控制器中的比例项系数、积分项系数、微分项系数。target_代表控制器的目标值,out_mix_和out_max_用于限制控制器的控制输出范围。error_sum_代表误差累计值,derror_代表误差变化率,error_last_代表上一次的误差值。last_output_保存上一次的控制输出值。

四、修改主程序

#include <Arduino.h>
#include <micro_ros_platformio.h>    // 包含用于 ESP32 的 micro-ROS PlatformIO 库
#include <WiFi.h>                    // 包含 ESP32 的 WiFi 库
#include <rcl/rcl.h>                 // 包含 ROS 客户端库 (RCL)
#include <rclc/rclc.h>               // 包含用于 C 的 ROS 客户端库 (RCLC)
#include <rclc/executor.h>           // 包含 RCLC 执行程序库,用于执行订阅和发布
#include <geometry_msgs/msg/twist.h> // 包含 ROS2 geometry_msgs/Twist 消息类型
#include <Esp32PcntEncoder.h>        // 包含用于计数电机编码器脉冲的 ESP32 PCNT 编码器库
#include <Esp32McpwmMotor.h>         // 包含使用 ESP32 的 MCPWM 硬件模块控制 DC 电机的 ESP32 MCPWM 电机库
#include <PidController.h>           // 包含 PID 控制器库,用于实现 PID 控制

Esp32PcntEncoder encoders[2];      // 创建一个长度为 2 的 ESP32 PCNT 编码器数组
rclc_executor_t executor;          // 创建一个 RCLC 执行程序对象,用于处理订阅和发布
rclc_support_t support;            // 创建一个 RCLC 支持对象,用于管理 ROS2 上下文和节点
rcl_allocator_t allocator;         // 创建一个 RCL 分配器对象,用于分配内存
rcl_node_t node;                   // 创建一个 RCL 节点对象,用于此基于 ESP32 的机器人小车
rcl_subscription_t subscriber;     // 创建一个 RCL 订阅对象,用于订阅 ROS2 消息
geometry_msgs__msg__Twist sub_msg; // 创建一个 ROS2 geometry_msgs/Twist 消息对象
Esp32McpwmMotor motor;             // 创建一个 ESP32 MCPWM 电机对象,用于控制 DC 电机
float out_motor_speed[2];          // 创建一个长度为 2 的浮点数数组,用于保存输出电机速度
float current_speeds[2];           // 创建一个长度为 2 的浮点数数组,用于保存当前电机速度
PidController pid_controller[2];   // 创建PidController的两个对象void twist_callback(const void *msg_in)
{const geometry_msgs__msg__Twist *twist_msg = (const geometry_msgs__msg__Twist *)msg_in;float linear_x = twist_msg->linear.x;   // 获取 Twist 消息的线性 x 分量float angular_z = twist_msg->angular.z; // 获取 Twist 消息的角度 z 分量if (linear_x == 0 && angular_z == 0)    // 如果 Twist 消息没有速度命令
  {pid_controller[0].update_target(0); // 更新控制器的目标值pid_controller[1].update_target(0);motor.updateMotorSpeed(0, 0); // 停止第一个电机motor.updateMotorSpeed(1, 0); // 停止第二个电机return;                       // 退出函数
  }// 根据线速度和角速度控制两个电机的转速if (linear_x != 0){pid_controller[0].update_target(linear_x * 1000);  // 使用mm/s作为targetpid_controller[1].update_target(linear_x * 1000);}
}// 这个函数是一个后台任务,负责设置和处理与 micro-ROS 代理的通信。
void microros_task(void *param)
{// 设置 micro-ROS 代理的 IP 地址。
  IPAddress agent_ip;agent_ip.fromString("192.168.2.105");// 使用 WiFi 网络和代理 IP 设置 micro-ROS 传输层。set_microros_wifi_transports("fishbot", "12345678", agent_ip, 8888);// 等待 2 秒,以便网络连接得到建立。delay(2000);// 设置 micro-ROS 支持结构、节点和订阅。allocator = rcl_get_default_allocator();rclc_support_init(&support, 0, NULL, &allocator);rclc_node_init_default(&node, "esp32_car", "", &support);rclc_subscription_init_default(&subscriber,&node,ROSIDL_GET_MSG_TYPE_SUPPORT(geometry_msgs, msg, Twist),"/cmd_vel");// 设置 micro-ROS 执行器,并将订阅添加到其中。rclc_executor_init(&executor, &support.context, 1, &allocator);rclc_executor_add_subscription(&executor, &subscriber, &sub_msg, &twist_callback, ON_NEW_DATA);// 循环运行 micro-ROS 执行器以处理传入的消息。while (true){delay(100);rclc_executor_spin_some(&executor, RCL_MS_TO_NS(100));}
}// 这个函数根据编码器读数更新两个轮子速度。
void update_speed()
{// 初始化静态变量以存储上一次更新时间和编码器读数。static uint64_t last_update_time = millis();static int64_t last_ticks[2];// 获取自上次更新以来的经过时间。uint64_t dt = millis() - last_update_time;if (dt == 0)return;// 获取当前的编码器读数并计算当前的速度。int32_t pt[2];pt[0] = encoders[0].getTicks() - last_ticks[0];pt[1] = encoders[1].getTicks() - last_ticks[1];current_speeds[0] = float(pt[0] * 0.1051566) / dt * 1000;current_speeds[1] = float(pt[1] * 0.1051566) / dt * 1000;// 更新上一次更新时间和编码器读数。last_update_time = millis();last_ticks[0] = encoders[0].getTicks();last_ticks[1] = encoders[1].getTicks();
}void setup()
{// 初始化串口通信,波特率为115200Serial.begin(115200);// 将两个电机分别连接到引脚22、23和12、13上motor.attachMotor(0, 22, 23);motor.attachMotor(1, 12, 13);// 在引脚32、33和26、25上初始化两个编码器encoders[0].init(0, 32, 33);encoders[1].init(1, 26, 25);// 初始化PID控制器的kp、ki和kdpid_controller[0].update_pid(0.625, 0.125, 0.0);pid_controller[1].update_pid(0.625, 0.125, 0.0);// 初始化PID控制器的最大输入输出,MPCNT大小范围在正负100之间pid_controller[0].out_limit(-100, 100);pid_controller[1].out_limit(-100, 100);// 在核心0上创建一个名为"microros_task"的任务,栈大小为10240xTaskCreatePinnedToCore(microros_task, "microros_task", 10240, NULL, 1, NULL, 0);
}void loop()
{// 更新电机速度
  update_speed();// 计算最新的电机输出值out_motor_speed[0] = pid_controller[0].update(current_speeds[0]);out_motor_speed[1] = pid_controller[1].update(current_speeds[1]);// 更新电机0和电机1的速度值motor.updateMotorSpeed(0, out_motor_speed[0]);motor.updateMotorSpeed(1, out_motor_speed[1]);// 延迟10毫秒delay(10);
}

添加PidController控制器到main函数中,关于Pid控制器的kp、ki和kd的设置,这里小鱼直接使用了比较合适的0.625和0.125,对于KD并没有设置,接下来我们下载代码进去并修改下PID进行测试。
需要注意:你要修改网络参数为你的当前环境的网络参数。

五、下载测试
下载代码,运行agent,点击RST按键。

sudo docker run -it --rm -v /dev:/dev -v /dev/shm:/dev/shm --privileged --net=host microros/micro-ros-agent:$ROS_DISTRO udp4 --port 8888 -v6

看到连接建立表示通信成功,接着用ros2 topic list

ros2 topic list

 看到/cmd_vel表示正常,接着我们使用teleop_twist_keyboard进行键盘控制

ros2 run teleop_twist_keyboard teleop_twist_keyboard

把速度修改为0.10左右,接着把小车放到地上,点击键盘上的i,记时10s之后点击k或者控制让机器人停下来,接着看看机器人行走距离是不是1m。

 测试结果

六、PID调节实验
请自行修改PID参数进行测试,注意结合理论进行。

参数整定找最佳,从小到大顺序查; 先是比例后积分,最后再把微分加; 曲线振荡很频繁,比例度盘要放大; 曲线漂浮绕大湾,比例度盘往小扳; 曲线偏离回复慢,积分时间往下降; 曲线波动周期长,积分时间再加长; 曲线振荡频率快,先把微分降下来; 动差大来波动慢。微分时间应加长; 理想曲线两个波,前高后低四比一; 一看二调多分析,调节质量不会低;

七、总结
本节我们完成了PID控制器对两个电机速度的控制,但是仅限于前进和后退,如果想实现角速度的控制,我们还要结合两轮差速运动学模型才行。

 

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

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

相关文章

vmdk to vhdx 虚拟磁盘格式转换qemu-img

qemu-img是创建、转换、修改磁盘映像的工具,我们可以用它非常方便的转换虚拟磁盘格式,比如在vmdk、vhdx、qcow2、vdi之间相互转换,它在流行的Linux、macOS、Windows平台上都发布有对应的版本。 本文介绍的是Windows版本,它支持下图中所示的格式。转换格式需要使用它的conve…

NAML论文阅读笔记

Neural News Recommendation with Attentive Multi-View Learning论文阅读笔记 这篇也是比较老但是比较经典的文章,来读一下 Abstract ​ 现有的新闻推荐方法通常基于单一的新闻信息(如标题)来学习这些表征,这可能是不够的。在本文中,我们提出了一种神经新闻推荐方法,它…

ros2 - slam - 使用编码器测量轮子最大速度

新建example26_max_speed_measurement 添加依赖[env:featheresp32] ; 这是一个环境配置标签,指定了代码将运行的硬件平台和框架 platform = espressif32 ; 指定了使用的平台为Espressif 32 board = featheresp32 ; 指定使用的硬件板为Feather ESP32 framework = arduino ;…

入门Salesforce:必须掌握的20+基础专业术语!

Salesforce的发展令人印象深刻。在过去的20年中,Salesforce创建了一个由管理员、开发人员、顾问和用户组成的生态系统,不断颠覆创新CRM,促进平等和多样性。 作为初学者,探索Salesforce领域就像学习一门新语言。Salesforce中有着大量术语,从潜在客户、自定义对象到仪表板、…

ros2 - slam - 直流电机-编码器脉冲测量与校准

这一节我们编写代码来尝试下是否能够读取到电机上编码器的脉冲数,并通过实验测试出小车的输出轴转速和编码器脉冲的比值。 一、新建工程并导入开源库新建example25_encoder添加依赖[env:featheresp32] ; 这是一个环境配置标签,指定了代码将运行的硬件平台和框架 platform = …

在开发环境中使用 RawCap 和 Wireshark 排查本地回路地址

如何使用 RawCap 和 Wireshark 排查本地网络请求中的404错误 开发微服务应用时,正确配置网络请求的转发至关重要。本文将通过一个具体示例来展示如何使用 RawCap 和 Wireshark 来监控和分析本地回路请求,并排查导致 HTTP 404 错误的可能原因。 背景 在本例中,用户的浏览器请…

AI冲击创意岗位,OpenAI技术官语出惊人:一些岗位或许本不该存在哈哈,这些段子有意思,太幽默了,篇篇精彩

现在老美在人工智能领域对我们卡脖子了,但人工智能就像一把达摩克利斯之剑,正威胁着越来越多的创意工作岗位。 OpenAI 的首席技术官米拉・穆拉蒂 (Mira Murati) 上个月在达特茅斯大学 Thayer 工程学院的采访中表示:“一些创意岗位可能会消失。但也许它们本来就不应该存在 —…

性能测试:主流压测工具介绍

简介 性能压测工具是用于模拟大量用户访问、负载和压力条件的软件工具,以评估系统、应用程序或服务在高负载条件下的性能表现和稳定性。 这些工具通常用于软件开发、测试、部署前的准备以及生产环境中的性能监测和优化。性能压测工具的主要功能包括模拟用户行为、生成大量的请…

springboot实现异步调用demo

异步操作的简单实现springboot实现异步调用 异步调用特点 异步调用在开发程序中被广泛应用,在异步任务中,主线程不需要阻塞等待异步任务的完成,而是可以继续处理其他请求。 异步调用的特点如下:非阻塞:主线程在调用异步方法后不会等待其完成,而是立即返回。 后台处理:耗…

智慧工地AI行为监控系统

智慧工地AI行为监控系统主要体现在在工地原有的监控系统上面利用智慧工地AI行为监控系统,对分布在工地各处的监控画面传回的视频流进行实时检测。AI行为监控系统可以保障工地施工人员安全,确保工人施工过程及行为符合工地要求规范。这样既保障了工地人员的安全,也同时保证施…

视频监控智能分析技术

视频监控智能分析技术又叫智能视频分析技术,该技术诞生于机器视觉及边缘计算。视频监控智能分析技术是当下在人们生活中应用范围很广的技术之一。智能视频分析技术能够现场监控传回的视频流中抓取图片及人体状态动作信息,建立图片与规则建立映射关系。SuiJi视频监控智能分析技…

TypeScript中,如何利用数组生成一个联合类型

本文由 ChatMoney团队出品 在开发中我们常常会遇到这样一个问题,代码如下: const arr = ["a","b","c","d","e","f","g","h","i","j","k","l",&qu…

图神经网络版本的Kolmogorov Arnold(KAN)代码实现和效果对比

Kolmogorov Arnold Networks (KAN)最近作为MLP的替代而流行起来,KANs使用Kolmogorov-Arnold表示定理的属性,该定理允许神经网络的激活函数在边缘上执行,这使得激活函数“可学习”并改进它们。 目前我们看到有很多使用KAN替代MLP的实验,但是目前来说对于图神经网络来说还没有…

修改yum安装的php的启动用户

在使用yum安装php时,默认情况下,安装完成后php的启动用户是apache 1、编辑php-fpm的配置文件。在大多数Linux系统中,php-fpm的配置文件路径为/etc/php-fpm.d/www.conf。 2、找到配置文件中的`user`和`group`项。默认情况下,`user`的值是apache,`group`的值是apache,即php…

Vue预渲染prerender-spa-plugin+vue-meta-info

前言: 公司现有一个新需求需要在原有的vue2项目上面进行预渲染,进行SEO的优化,在网上进行了一系列的搜索,发现两种简单易上手的方案(1.使用prerender-spa-plugin+vue-meta-info进行打包构建;2.使用nuxt.js在开发过程中就渲染呈现出来) 因是在原有项目基础上进行,所以采…

禅道的使用:如何搭建Ubuntu环境安装禅道

一、禅道安装部署的环境要求 1.禅道安装部署环境推荐使用 Linux + Apache + PHP7.0以上版本+ MySQL5.5以上版本/MariaDB的组合。Nginx其次,不推荐IIS + PHP组合。 2.禅道需要使用PHP的这些扩展:pdo、pdo_mysql、json、filte、openssl、mbstring、zlib、curl、gd、iconv,企业…

vue3.4+最新属性变化

Attribute绑定 新增简写方法 <!-- 与 :id="id" 相同 --> <div :id></div><!-- 这也同样有效 --> <div v-bind:id></div>动态参数 <a v-bind:[demoName]="url"></a>//简写 <a :[demoName]="url&qu…

【Modelsim常见问题】Error: (vsim-3170) Could not find

这个错误发生的原因通常是测试的tb文件模块名和文件名不一致导致,参考如下:

易基因:番茄果实采后处理成熟诱导的DNA甲基化和转录组综合分析 | 作物研究

番茄果实成熟是一个复杂的生理过程,涉及关键基因的去甲基化,改变其转录水平,从而触发一系列生理事件。然而果实采摘后的处理方法,如冷藏、化学处理等,虽然能延长番茄的货架寿命,但通常可能会降低果实品质,目前尚不清楚这些过程是如何改变的。因此,研究采后处理诱导的番…