代码浅析DLIO(二)---预积分与单点去畸变

0. 简介

我们刚刚了解过DLIO的整个流程,我们发现相比于Point-LIO而言,这个方法更适合我们去学习理解,同时官方给出的结果来看DLIO的结果明显好于现在的主流方法,当然指的一提的是,这个DLIO是必须需要六轴IMU的,所以如果没有IMU的画,那只有DLO可以使用了。

在这里插入图片描述


1. initializeDLIO–初始化参数

这段代码非常简单,主要作用是等待IMU数据的接收和校准,如果条件不满足则直接返回,否则将dlio_initialized变量设置为true表示初始化完成。其中,first_imu_received和imu_calibrated是两个布尔类型的变量,用于判断是否接收到IMU数据和是否已经校准。如果条件不满足,函数直接返回,不进行后续的初始化操作。

void dlio::OdomNode::initializeDLIO() {// Wait for IMUif (!this->first_imu_received ||!this->imu_calibrated) { //如果没有接收到IMU数据或者IMU没有校准return;}this->dlio_initialized = true; //初始化完成std::cout << std::endl << " DLIO initialized!" << std::endl;
}

2. getScanFromROS–ROS数据包转换与激光类型判断

这个函数也是比较简单的函数,将ROS消息类型的点云数据转换为PCL点云类型并进行预处理。函数首先将ROS消息类型的点云数据转换为PCL点云类型,并去除无效点。然后函数使用crop滤波器对点云进行框选范围内的点的筛选。接着,函数通过检测点云数据的字段来自动确定传感器类型,如果无法确定则将deskew_标志设置为false。最后,函数将原始点云数据保存到original_scan变量中,并将点云数据的时间戳保存到scan_header_stamp变量中。

void dlio::OdomNode::getScanFromROS(const sensor_msgs::PointCloud2ConstPtr &pc) {pcl::PointCloud<PointType>::Ptr original_scan_(boost::make_shared<pcl::PointCloud<PointType>>());pcl::fromROSMsg(*pc, *original_scan_);// 去除无效点std::vector<int> idx;original_scan_->is_dense = false;pcl::removeNaNFromPointCloud(*original_scan_, *original_scan_, idx);// crop框选范围内的点this->crop.setInputCloud(original_scan_);this->crop.filter(*original_scan_);// 自动检测传感器类型this->sensor = dlio::SensorType::UNKNOWN;for (auto &field : pc->fields) {if (field.name == "t") {this->sensor = dlio::SensorType::OUSTER;break;} else if (field.name == "time") {this->sensor = dlio::SensorType::VELODYNE; // velodyne雷达break;}}if (this->sensor == dlio::SensorType::UNKNOWN) {this->deskew_ = false;}this->scan_header_stamp = pc->header.stamp;this->original_scan = original_scan_;
}

3. preprocessPoints—逐点去畸变与统一预积分

下面的代码对激光雷达点云数据进行预处理。首先,如果激光雷达可以逐点去畸变,将通过去畸变函数deskewPointcloud()进行处理,否则会使用统一预积分。如果是第一次处理扫描数据,需要等待IMU数据到达后才能进行处理。如果已经有了IMU数据,会通过IMU积分得到先验位姿,然后将原始点云转换到baselink坐标系下。接下来,如果开启了Voxel Grid Filter,则会对点云进行滤波处理,否则直接使用转换后的点云。最终,处理后的点云数据将作为当前激光雷达扫描的输入数据。

void dlio::OdomNode::preprocessPoints() {// 取消原始数据类型扫描if (this->deskew_) { //如果可以去畸变。也就是不为dlio::SensorType::UNKNOWNthis->deskewPointcloud(); //去畸变if (!this->first_valid_scan) {return;}} else {this->scan_stamp = this->scan_header_stamp.toSec();// 在IMU数据到达之前不要处理扫描数据if (!this->first_valid_scan) { // tips:这个和下面的deskewPointcloud函数中判断类似if (this->imu_buffer.empty() ||this->scan_stamp <= this->imu_buffer.back().stamp) {return;}this->first_valid_scan = true;this->T_prior = this->T; // 假设第一次扫描没有运动} else {// 第二个及以后的扫描的IMU先验std::vector<Eigen::Matrix4f, Eigen::aligned_allocator<Eigen::Matrix4f>>frames;frames = this->integrateImu(this->prev_scan_stamp, this->lidarPose.q, this->lidarPose.p,this->geo.prev_vel.cast<float>(), {this->scan_stamp}); // IMU积分if (frames.size() > 0) {this->T_prior = frames.back();} else {this->T_prior = this->T;}}pcl::PointCloud<PointType>::Ptr deskewed_scan_(boost::make_shared<pcl::PointCloud<PointType>>()); //创建一个点云指针pcl::transformPointCloud(*this->original_scan, *deskewed_scan_,this->T_prior *this->extrinsics.baselink2lidar_T); //将原始点云转换到baselink坐标系下this->deskewed_scan = deskewed_scan_;this->deskew_status = false;}// Voxel Grid Filterif (this->vf_use_) {pcl::PointCloud<PointType>::Ptr current_scan_(boost::make_shared<pcl::PointCloud<PointType>>(*this->deskewed_scan));           //创建一个点云指针this->voxel.setInputCloud(current_scan_); //设置输入点云this->voxel.filter(*current_scan_);       //滤波this->current_scan = current_scan_;} else {this->current_scan = this->deskewed_scan;}
}

4. deskewPointcloud–雷达逐点预积分

这个函数的作用是去除点云数据的畸变,使得点云数据的时间戳相对于一个参考时间戳。该函数的输入是一个原始的点云数据,输出是一个去畸变的点云数据。

首先,该函数创建了一个pcl::PointCloud::Ptr类型的指针deskewed_scan_,用于存储去畸变后的点云数据。然后,该函数按照时间戳对点进行排序并构建时间戳列表。具体实现是通过定义比较函数point_time_cmp和不等于函数point_time_neq,将点按照时间戳排序。如果传感器类型是OUSTER,则时间戳存储在点的t字段中;否则,时间戳存储在点的time字段中。extract_point_time函数用于从点中提取时间戳。

接下来,该函数将点按照时间戳的顺序复制到deskewed_scan_中。此处使用std::partial_sort_copy函数,将原始点云按照时间戳排序后存储在deskewed_scan_中。

随后,该函数调用了一个名为points_unique_timestamps的函数。这个函数的作用是从deskewed_scan_点云数据中提取出时间戳不相同的点,并将它们存储在一个名为points_unique_timestamps的变量中。这个函数使用了Boost库中的adaptors,首先使用indexed()将点序号和点本身组合成一个pair,然后使用adjacent_filtered()过滤掉时间戳相邻的相同点,最终得到时间戳不相同的点。

接下来,该函数从点中提取时间戳并将它们放入一个独立的列表中。unique_time_indices存储了每个时间戳的点云在deskewed_scan_中的起始和终止索引。median_pt_index为时间戳列表的中位数。

然后,该函数检查IMU数据是否已经到达,如果没有到达,则不处理扫描数据。如果传感器类型为OUSTER,则将原始点云转换到baselink坐标系下。然后,将T_prior设置为T,将原始点云转换到baselink坐标系下,并将deskewed_scan_设为原始点云。

如果IMU数据已经到达,则使用IMU先验和去斜校正。该函数调用了integrateImu函数,该函数使用IMU数据对lidar的运动进行积分,传入timestamps这个vector来获取每个点时间的IMU积分。如果积分成功,则时间戳的数量应该等于timestamps.size()。

void dlio::OdomNode::deskewPointcloud() {pcl::PointCloud<PointType>::Ptr deskewed_scan_(boost::make_shared<pcl::PointCloud<PointType>>());deskewed_scan_->points.resize(this->original_scan->points.size()); //设置点云大小// 各个点的时间戳应该相对于此时间double sweep_ref_time = this->scan_header_stamp.toSec();// 按时间戳对点进行排序并构建时间戳列表std::function<bool(const PointType &, const PointType &)>point_time_cmp; //比较函数std::function<bool(boost::range::index_value<PointType &, long>,boost::range::index_value<PointType &, long>)>point_time_neq; //不等于函数std::function<double(boost::range::index_value<PointType &, long>)>extract_point_time; //提取时间if (this->sensor == dlio::SensorType::OUSTER) {point_time_cmp = [](const PointType &p1, const PointType &p2) {return p1.t < p2.t;}; //定义内容point_time_neq = [](boost::range::index_value<PointType &, long> p1,boost::range::index_value<PointType &, long> p2) {return p1.value().t != p2.value().t;};extract_point_time =[&sweep_ref_time](boost::range::index_value<PointType &, long> pt) {return sweep_ref_time + pt.value().t * 1e-9f;};} else {point_time_cmp = [](const PointType &p1, const PointType &p2) {return p1.time < p2.time;};point_time_neq = [](boost::range::index_value<PointType &, long> p1,boost::range::index_value<PointType &, long> p2) {return p1.value().time != p2.value().time;};extract_point_time =[&sweep_ref_time](boost::range::index_value<PointType &, long> pt) {return sweep_ref_time + pt.value().time;};}// 按照时间戳的顺序将点复制到deskewed_scan_中std::partial_sort_copy(this->original_scan->points.begin(),this->original_scan->points.end(),deskewed_scan_->points.begin(),deskewed_scan_->points.end(), point_time_cmp);// 这个函数的作用是从deskewed_scan_点云数据中提取出时间戳不相同的点,并将它们存储在一个名为points_unique_timestamps的变量中。// 这个函数使用了Boost库中的adaptors,首先使用indexed()将点序号和点本身组合成一个pair,然后使用adjacent_filtered()过滤掉时间戳相邻的相同点,最终得到时间戳不相同的点。auto points_unique_timestamps =deskewed_scan_->points | boost::adaptors::indexed() |boost::adaptors::adjacent_filtered(point_time_neq);// 从点中提取时间戳并将它们放入一个独立的列表中std::vector<double> timestamps;std::vector<int> unique_time_indices;for (auto it = points_unique_timestamps.begin();it != points_unique_timestamps.end(); it++) {timestamps.push_back(extract_point_time(*it));unique_time_indices.push_back(it->index());}unique_time_indices.push_back(deskewed_scan_->points.size()); //最后一个存点的个数int median_pt_index = timestamps.size() / 2;this->scan_stamp =timestamps[median_pt_index]; // 将this->scan_stamp设置为中位点的时间戳//在IMU数据到达之前不要处理扫描数据if (!this->first_valid_scan) {if (this->imu_buffer.empty() ||this->scan_stamp <= this->imu_buffer.back().stamp) {return;}this->first_valid_scan = true;this->T_prior = this->T; // 假设第一次扫描时没有运动pcl::transformPointCloud(*deskewed_scan_, *deskewed_scan_,this->T_prior * this->extrinsics.baselink2lidar_T);this->deskewed_scan = deskewed_scan_;this->deskew_status = true;return;}//从第二次扫描开始,使用IMU先验和去斜校正std::vector<Eigen::Matrix4f, Eigen::aligned_allocator<Eigen::Matrix4f>>frames;frames = this->integrateImu(this->prev_scan_stamp, this->lidarPose.q, this->lidarPose.p,this->geo.prev_vel.cast<float>(),timestamps); // IMU积分,传入timestamps这个vector来获取每个点时间的IMU积分this->deskew_size =frames.size(); // 如果积分成功,则时间戳的数量应该等于timestamps.size()// 如果扫描开始和结束之间没有帧,则可能意味着存在同步问题if (frames.size() != timestamps.size()) {ROS_FATAL("Bad time sync between LiDAR and IMU!");this->T_prior = this->T; //那直接将T_prior设置为Tpcl::transformPointCloud(*deskewed_scan_, *deskewed_scan_,this->T_prior *this->extrinsics.baselink2lidar_T); //将原始点云转换到baselink坐标系下this->deskewed_scan = deskewed_scan_;this->deskew_status = false;return;}// 将先验更新为扫描中间时间的估计姿态(对应于this->scan_stamp)this->T_prior = frames[median_pt_index];#pragma omp parallel for num_threads(this->num_threads_) //并行计算for (int i = 0; i < timestamps.size(); i++) {Eigen::Matrix4f T =frames[i] * this->extrinsics.baselink2lidar_T; //设置变换矩阵// transform point to world framefor (int k = unique_time_indices[i]; k < unique_time_indices[i + 1]; k++) {auto &pt = deskewed_scan_->points[k]; //取出点云中的点pt.getVector4fMap()[3] = 1.; //将点云中的点转换为齐次坐标pt.getVector4fMap() =T * pt.getVector4fMap(); //将点云中的点转换到baselink坐标系下}}this->deskewed_scan = deskewed_scan_;this->deskew_status = true;
}

4. integrateImu—IMU积分与点云去畸变入口

这个代码主要用来对IMU数据进行积分,得到每个点的IMU积分。首先,函数接受了一些参数,包括上一帧扫描的时间戳、上一帧估算的姿态、位置和速度,以及当前帧对应点(帧)扫描的时间戳。然后,函数会检查时间戳是否为空或者时间戳大于第一个时间戳,如果是,则返回一个空向量。接着,函数会从IMU数据中提取出时间戳不相同的点,如果IMU测量不足,则返回一个空向量。

接下来,函数会通过反向整合,找到第一个IMU样本的姿态,并计算两个IMU样本之间的时间间隔。然后,函数会根据公式4克罗内克积,推算出开始时间对应的姿态。接着,函数会计算第一和第二个IMU样本之间的平均角速度,并根据公式4计算出第二个惯性测量单元样本的方向。然后,函数会计算第一个和第二个IMU样本之间的加速度,并减去重力加速度。接着,函数会计算在前两个IMU采样之间的jerk,并根据公式4计算出第一个IMU样本的速度和位置。最后,函数会调用integrateImuInternal函数,对IMU数据进行积分,并返回一个包含每个点IMU积分的向量。

/*** @brief* 这个函数主要是用来处理IMU数据的,主要是对IMU数据进行积分,得到每个点的IMU积分** @param start_time 上一帧扫描的时间戳* @param q_init 上一帧估算的姿态* @param p_init 上一帧估算的位置* @param v_init 上一帧估算的速度* @param sorted_timestamps 当前帧对应点(帧)扫描的时间戳* @return std::vector<Eigen::Matrix4f,* Eigen::aligned_allocator<Eigen::Matrix4f>>*/
std::vector<Eigen::Matrix4f, Eigen::aligned_allocator<Eigen::Matrix4f>>
dlio::OdomNode::integrateImu(double start_time, Eigen::Quaternionf q_init,Eigen::Vector3f p_init, Eigen::Vector3f v_init,const std::vector<double> &sorted_timestamps) {const std::vector<Eigen::Matrix4f, Eigen::aligned_allocator<Eigen::Matrix4f>>empty;if (sorted_timestamps.empty() || start_time > sorted_timestamps.front()) {// 如果时间戳为空或者时间戳大于第一个时间戳,认为无效的输入,返回空向量return empty;}boost::circular_buffer<ImuMeas>::reverse_iterator begin_imu_it;boost::circular_buffer<ImuMeas>::reverse_iterator end_imu_it;if (this->imuMeasFromTimeRange(start_time, sorted_timestamps.back(),begin_imu_it, end_imu_it) == false) {//从IMU数据中提取出时间戳不相同的点,IMU测量不足,返回空向量。return empty;}// 反向整合以找到第一个IMU样本的姿态const ImuMeas &f1 = *begin_imu_it;const ImuMeas &f2 = *(begin_imu_it + 1);// 两个IMU样本之间的时间double dt = f2.dt;// 第一个IMU样本和开始时间之间的时间间隔double idt = start_time - f1.stamp;//第一和第二个IMU样本之间的角加速度Eigen::Vector3f alpha_dt = f2.ang_vel - f1.ang_vel;Eigen::Vector3f alpha = alpha_dt / dt;// 首个IMU样本和起始时间之间的平均角速度(反向)Eigen::Vector3f omega_i = -(f1.ang_vel + 0.5 * alpha * idt);// 将q_init设置为第一个IMU样本的方向q_init = Eigen::Quaternionf(q_init.w() - 0.5 *(q_init.x() * omega_i[0] + q_init.y() * omega_i[1] +q_init.z() * omega_i[2]) *idt,q_init.x() + 0.5 *(q_init.w() * omega_i[0] - q_init.z() * omega_i[1] +q_init.y() * omega_i[2]) *idt,q_init.y() + 0.5 *(q_init.z() * omega_i[0] + q_init.w() * omega_i[1] -q_init.x() * omega_i[2]) *idt,q_init.z() +0.5 *(q_init.x() * omega_i[1] - q_init.y() * omega_i[0] +q_init.w() * omega_i[2]) *idt); //根据第一个IMU推算出开始时间,对应公式4克罗内克积,省略了后面一项q_init.normalize();// 第一和第二个IMU样本之间的平均角速度Eigen::Vector3f omega = f1.ang_vel + 0.5 * alpha_dt;// 第二个惯性测量单元样本的方向Eigen::Quaternionf q2(q_init.w() - 0.5 *(q_init.x() * omega[0] + q_init.y() * omega[1] +q_init.z() * omega[2]) *dt,q_init.x() + 0.5 *(q_init.w() * omega[0] - q_init.z() * omega[1] +q_init.y() * omega[2]) *dt,q_init.y() + 0.5 *(q_init.z() * omega[0] + q_init.w() * omega[1] -q_init.x() * omega[2]) *dt,q_init.z() + 0.5 *(q_init.x() * omega[1] - q_init.y() * omega[0] +q_init.w() * omega[2]) *dt);q2.normalize();// 首个IMU样本的加速度Eigen::Vector3f a1 =q_init._transformVector(f1.lin_accel); //根据线加速度求出f1时刻的加速度a1[2] -= this->gravity_;                   //减去重力加速度// 第二个IMU样本的加速度Eigen::Vector3f a2 = q2._transformVector(f2.lin_accel);a2[2] -= this->gravity_;// 在前两个IMU采样之间的jerkEigen::Vector3f j = (a2 - a1) / dt;// 将 v_init 设置为第一个IMU样本的速度(从 start_time 开始向后倒退),公式4v_init -= a1 * idt + 0.5 * j * idt * idt;//将p_init设置为第一个IMU样本的位置(从start_time往回走,公式4p_init -=v_init * idt + 0.5 * a1 * idt * idt + (1 / 6.) * j * idt * idt * idt;return this->integrateImuInternal(q_init, p_init, v_init, sorted_timestamps,begin_imu_it, end_imu_it);
}

5. imuMeasFromTimeRange–根据时间范围获取IMU测量值

下面的代码根据给定的时间范围获取IMU测量值。它接受起始时间和结束时间,以及一个IMU缓冲区的起始和结束迭代器。该函数首先检查IMU缓冲区是否为空或第一个IMU数据的时间戳是否小于结束时间。如果是,则等待最新的IMU数据。然后,函数使用迭代器找到最后一个时间戳小于结束时间的IMU数据,以及第一个时间戳大于等于开始时间的IMU数据。最后,该函数将找到的IMU数据存储在反向迭代器中,并返回true。如果找不到IMU数据,则返回false。

/*** @brief 根据时间范围获取IMU测量值** @param start_time 需要的IMU测量值的开始时间* @param end_time 需要的IMU测量值的结束时间* @param begin_imu_it 对应的imu_buffer的起始索引* @param end_imu_it 对应的imu_buffer的结束索引* @return true* @return false*/
bool dlio::OdomNode::imuMeasFromTimeRange(double start_time, double end_time,boost::circular_buffer<ImuMeas>::reverse_iterator &begin_imu_it,boost::circular_buffer<ImuMeas>::reverse_iterator &end_imu_it) {//如果IMU缓冲区为空或者IMU缓冲区的第一个IMU数据的时间戳小于end_timeif (this->imu_buffer.empty() || this->imu_buffer.front().stamp < end_time) {// 等待最新的IMU数据std::unique_lock<decltype(this->mtx_imu)> lock(this->mtx_imu); //互斥锁this->cv_imu_stamp.wait(lock, [this, &end_time] {return this->imu_buffer.front().stamp >= end_time; //等待最新的IMU数据}); //等待最新的IMU数据}auto imu_it =this->imu_buffer.begin(); // IMU数据的迭代器,直到IMU数据要在end_time之后auto last_imu_it = imu_it; //设置最新的imu作为最后的imuimu_it++;while (imu_it != this->imu_buffer.end() && imu_it->stamp >= end_time) {last_imu_it = imu_it; //不断迭代拿到之前时间的IMU数据imu_it++;}while (imu_it != this->imu_buffer.end() && imu_it->stamp >= start_time) {imu_it++; //不断迭代拿到之前时间的IMU数据,并拿到imu_it和last_imu_it之间的IMU数据}if (imu_it == this->imu_buffer.end()) {// 测量不足,返回false,因为没有值在end_time之后return false;}imu_it++;// 设置反向迭代器(以正向时间迭代)end_imu_it = boost::circular_buffer<ImuMeas>::reverse_iterator(last_imu_it);begin_imu_it = boost::circular_buffer<ImuMeas>::reverse_iterator(imu_it);return true;
}

6. integrateImuInternal–单点去畸变

下面码实现了一个IMU积分方法,可以根据所有的点以及IMU时间完成计算,并对其进行插值,以便在任何时间点都可以获得姿态。该方法接受初始姿态、位置和速度、所有点的时间戳以及IMU测量值和时间戳等参数,并返回一个表示IMU测量值对应的位姿变换矩阵的向量。

在代码中,首先对初始姿态、位置和速度进行初始化,并计算出初始加速度。然后遍历IMU测量值和时间戳,根据公式4计算四元数,并更新加速度、角加速度和平均角速度等参数。随后,根据给定的时间戳进行插值,计算出对应的四元数和位置,并将其转换为位姿变换矩阵的形式,存储在一个向量中。最后,更新位置和速度,循环直到遍历完所有的IMU测量值和时间戳。

该方法的主要思路是根据IMU测量值和时间戳计算出对应的姿态和位置信息,并进行插值,以便在任何时间点都可以获得姿态。该方法的优点是可以提高定位的精度和鲁棒性,尤其是在存在运动模糊或者缺失数据等情况下。但是该方法也存在一些缺点,例如需要较高的计算资源和较高的计算精度,同时还需要对IMU测量误差进行校准和补偿,否则可能会导致积分误差的累积。

…详情请参照古月居

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

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

相关文章

【ZYNQ】SD 卡读写及文件扫描实验

SD 卡控制器&#xff08;SD/SDIO Controller&#xff09; ZYNQ 中的 SD 卡控制器符合 SD2.0 协议规范&#xff0c;接口兼容 eMMC、MMC3.31、SDIO2.0、SD2.0、SPI&#xff0c;支持 SDHC、SDHS 器件。SD 卡控制器支持 SDMA&#xff08;单操作 DMA&#xff09;、ADMA1&#xff08…

数据结构-顺序表

文章目录 线性表概念顺序表静态顺序表动态顺序表 总结 线性表概念 线性表是最基本、最简单、也是最常用的一种数据结构&#xff0c;常见的线性表:顺序表、链表、栈、队列、字符串…线性表&#xff08;linear> list&#xff09;是数据结构的一种&#xff0c;一个线性表是n个具…

蚂蚁庄园小课堂答题今日答案最新

蚂蚁庄园小课堂答题今日答案最新 温馨提醒&#xff1a;由于本文章会停留在一个固定的更新时间上&#xff0c;包含当前日期最新的支付宝蚂蚁庄园小课堂答题今日答案。如果您看到这篇文章已成为过去时&#xff0c;请按下面的方法进入查看天天保持更新的最新今日答案&#xff1b; …

Linux 网络通信

(一)套接字Socket概念 Socket 中文意思是“插座”&#xff0c;在 Linux 环境下&#xff0c;用于表示进程 x 间网络通信的特殊文件 类型。本质为内核借助缓冲区形成的伪文件。 既然是文件&#xff0c;那么理所当然的&#xff0c;我们可以使用文件描述符引用套接字。Linux 系统…

Windows11安装后跳过联网登录

Windows11安装后跳过联网登录 实验设备&#xff1a; VMware17Pro虚拟机中使用Windows11镜像安装Windows11操作系统&#xff0c;并且在虚拟机中测试跳过联网登录。 步骤 说明&#xff1a;物理卸载网卡&#xff08;在虚拟机上禁用网卡&#xff09;没用 思路&#xff1a; sh…

8.统一异常处理 + 统一记录日志

目录 1.统一异常处理 2.统一记录日志 1.统一异常处理 在 HomeController 类中添加请求方法&#xff08;服务器发生异常之后需要统一处理异常&#xff0c;记录日志&#xff0c;然后转到 500 页面&#xff0c;需要人工处理重定向到 500 页面&#xff0c;提前把 500 页面请求访问…

经典神经网络——AlexNet模型论文详解及代码复现

一、背景 AlexNet是在2012年由Alex Krizhevsky等人提出的&#xff0c;该网络在2012年的ImageNet大赛上夺得了冠军&#xff0c;并且错误率比第二名高了很多。Alexnet共有8层结构&#xff0c;前5层为卷积层&#xff0c;后三层为全连接层。 论文地址&#xff1a;ImageNet Classif…

ModuleNotFoundError: No module named ‘mdtex2html‘ module已经安装还是报错,怎么办?

用streamlit运行ChatGLM/basic_model/web_demo.py的时候&#xff0c;出现了module not found&#xff1a; ModuleNotFoundError: No module named mdtex2html Traceback: File "/home/haiyue/.local/lib/python3.10/site-packages/streamlit/runtime/scriptrunner/script…

【阿里云】图像识别 智能分类识别 增加网络控制功能点(三)

一、增加网络控制功能 实现需求TCP 心跳机制解决Soket异常断开问题 二、Linux内核提供了通过sysctl命令查看和配置TCP KeepAlive参数的方法。 查看当前系统的TCP KeepAlive参数修改TCP KeepAlive参数 三、C语言实现TCP KeepAlive功能 四、setsockopt用于设置套接字选项的系…

Qt4利用MVC开发曲线数据编辑器

目录 1 需求 2 开发流程 1 搭建框架 2 构造函数 3 打开工程 4 实现应用程序参数加载 5 QCustomPlot和TableView的联动 6 数据的可视化修改 7 列表点击事件事先键盘控制 8 表格实现复制&#xff0c;粘贴&#xff0c;删除等一系列功能 9 曲线实现自适应范围和统一范围…

【JMeter】运行方式

第一种&#xff1a; 使用GUI 操作&#xff1a; 在JMeter界面菜单导航上点击运行按钮 一般用作创建TestPlan和调试脚本增加java堆空间来满足测试环境 第二种&#xff1a;使用CLI(Command Line) 性能测试一般请求量比较大&#xff0c;为了节省资源 CLI参数用法&#xff1a; 字段…

Flask Echarts 实现历史图形查询

Flask前后端数据动态交互涉及用户界面与服务器之间的灵活数据传递。用户界面使用ECharts图形库实时渲染数据。它提供了丰富多彩、交互性强的图表和地图&#xff0c;能够在网页上直观、生动地展示数据。ECharts支持各种常见的图表类型&#xff0c;包括折线图、柱状图、饼图、散点…

[Spring] 字节一面~Spring 如何解决循环依赖问题 以及 @resource 与 @autowire 同时存在时谁生效

文章目录 Spring 如何解决循环依赖问题resource 与 autowire 同时存在时谁生效 Spring 如何解决循环依赖问题 Spring在实例化一个bean的时候&#xff0c;是首先递归实例化其所依赖的所有bean&#xff0c;直到某个bean没有依赖其他bean&#xff0c;此时就会将该实例返回&#x…

【JavaWeb】Servlet

Servlet 文章目录 Servlet一、简介二、开发流程三、生命周期四、ServletConfig和ServletContext五、HttpServletRequest常见API六、HttpServletResponse常见API七、请求转发和响应重定向7.1 概述7.2 请求转发7.3 响应重定向 八、请求与响应乱码问题8.1 GET与POST请求乱码8.2 响…

内网穿透的应用-Jupyter Notbook+cpolar内网穿透实现公共互联网访问使用数据分析工作

文章目录 1.前言2.Jupyter Notebook的安装2.1 Jupyter Notebook下载安装2.2 Jupyter Notebook的配置2.3 Cpolar下载安装 3.Cpolar端口设置3.1 Cpolar云端设置3.2.Cpolar本地设置 4.公网访问测试5.结语 1.前言 在数据分析工作中&#xff0c;使用最多的无疑就是各种函数、图表、…

五、Lua流程控制与函数

一、流程控制 &#xff08;一&#xff09;含义 Lua 编程语言流程控制语句通过程序设定一个或多个条件语句来设定。在条件为 true 时执行指定程序代码&#xff0c;在条件为 false 时执行其他指定代码。 &#xff08;二&#xff09;原型 if (成立) then执行体1else执行体2 end…

字符串入门算法题!

概述 字符串和数组一样算是比较简单的题目&#xff0c;正适合打算法基础&#xff0c;一定要认真对待&#xff01;&#xff01;&#xff01; 字符串类型的算法问题可以分为简单、中等和困难的难度级别&#xff0c;基础类型一些基本的字符串处理问题&#xff0c;如字符串的拼接…

自动化部署 扩容openGauss —— Ansible for openGauss

前言 大家好&#xff0c;今天我们为大家推荐一套基于Ansible开发的&#xff0c;自动化部署及扩容openGauss的脚本工具&#xff1a;Ansible for openGauss&#xff08;以下简称 AFO&#xff09;。 通过AFO&#xff0c;我们只需简单修改一些配置文件&#xff0c;即可快速部署多种…

数智赋能 锦江汽车携手苏州金龙打造高质量盛会服务

作为一家老牌客运公司&#xff0c;成立于1956年的上海锦江汽车服务有限公司&#xff08;以下简称锦江汽车&#xff09;&#xff0c;拥有1200多辆大巴和5000多辆轿车&#xff0c;是上海乃至长三角地区规模最大的专业旅游客运公司。面对客运市场的持续萎缩&#xff0c;锦江汽车坚…

王道数据结构课后代码题p19 第14题请设计一个尽可能高效的算法,计算并输出所有可能的三元组(a,b,c) 中的最小距离。(c语言代码实现)

本题其实就是找a到c的最小值 有讲解p19 第14题 c语言实现王道数据结构课后代码题_哔哩哔哩_bilibili 下方有图&#xff1a; 本题代码如下 int abs(int a)//计算绝对值 {if (a < 0)return -a;elsereturn a; } int min(int a, int b, int c)//a是否为三个数中的最小值 {if …