0. 简介

现在越来越多的激光雷达方法如雨后春笋一般冒了出来,最近以FAST-LIO为代表的系列渐渐地被大众接受。而FAST-LIO2的工作越来越被大众所熟知与研究。最近作者也在研究与学习FAST-LIO2的相关知识,这里将会以长文的形式来介绍FAST-LIO2.0的学习心得。本文将通过代码的形式与论文结合,直观的来分析论文的核心算法部分。也同时作为自己的学习笔记来留给后来人学习。

1. 激光雷达

对于FAST-LIO2代码来说,主要的数据是通过preprocess与IMU_Processing拿到相应的传感器数据,并在主程序当中调用ESF与IKD-Tree完成优化。首先我们来看一下激光雷达部分。

1.1 preprocess.h

首先在最上面先定义了一系列enum的信息,通过enum选择不同的激光雷达类型,特征点特征等,并通过orgtype存放一些激光雷达点的一些其他属性。

// 枚举类型:表示支持的雷达类型
enum LID_TYPE
{AVIA = 1,VELO16,OUST64
}; //{1, 2, 3}// 枚举类型:表示特征点的类型
enum Feature
{Nor,        // 正常点Poss_Plane, // 可能的平面点Real_Plane, // 确定的平面点Edge_Jump,  // 有跨越的边Edge_Plane, // 边上的平面点Wire,       // 线段 这个也许当了无效点?也就是空间中的小线段?ZeroPoint   // 无效点 程序中未使用
};
// 枚举类型:位置标识
enum Surround
{Prev, // 前一个Next  // 后一个
};// 枚举类型:表示有跨越边的类型
enum E_jump
{Nr_nor,  // 正常Nr_zero, // 0Nr_180,  // 180Nr_inf,  // 无穷大 跳变较远?Nr_blind // 在盲区?
};
// orgtype类:用于存储激光雷达点的一些其他属性
struct orgtype
{double range; // 点云在xy平面离雷达中心的距离double dista; // 当前点与后一个点之间的距离//假设雷达原点为O 前一个点为M 当前点为A 后一个点为Ndouble angle[2];  // 这个是角OAM和角OAN的cos值double intersect; // 这个是角MAN的cos值E_jump edj[2];    // 前后两点的类型Feature ftype;    // 点类型// 构造函数orgtype(){range = 0;edj[Prev] = Nr_nor;edj[Next] = Nr_nor;ftype = Nor; //默认为正常点intersect = 2;}
};

其中LID_TYPE这些与下面的数据结构有关系。通过enum选择不同的激光雷达数据,来设置不同的数据结构。

// velodyne数据结构
namespace velodyne_ros
{struct EIGEN_ALIGN16 Point{PCL_ADD_POINT4D;                // 4D点坐标类型float intensity;                // 强度float time;                     // 时间uint16_t ring;                  // 点所属的圈数EIGEN_MAKE_ALIGNED_OPERATOR_NEW // 进行内存对齐};
} // namespace velodyne_ros// 注册velodyne_ros的Point类型
POINT_CLOUD_REGISTER_POINT_STRUCT(velodyne_ros::Point,(float, x, x)(float, y, y)(float, z, z)(float, intensity, intensity)(float, time, time)(uint16_t, ring, ring))// ouster数据结构
namespace ouster_ros
{struct EIGEN_ALIGN16 Point{PCL_ADD_POINT4D;                // 4D点坐标类型float intensity;                // 强度uint32_t t;                     // 时间uint16_t reflectivity;          // 反射率uint8_t ring;                   // 点所属的圈数uint16_t ambient;               // 没用到uint32_t range;                 // 距离EIGEN_MAKE_ALIGNED_OPERATOR_NEW // 进行内存对齐};
} // namespace ouster_ros// clang-format off
// 注册ouster的Point类型
POINT_CLOUD_REGISTER_POINT_STRUCT(ouster_ros::Point,(float, x, x)(float, y, y)(float, z, z)(float, intensity, intensity)// use std::uint32_t to avoid conflicting with pcl::uint32_t(std::uint32_t, t, t)(std::uint16_t, reflectivity, reflectivity)(std::uint8_t, ring, ring)(std::uint16_t, ambient, ambient)(std::uint32_t, range, range)
)

然后下面的这些就是Preprocess的主体函数了,主要作用是用于对激光雷达点云数据进行预处理。

// Preproscess类:用于对激光雷达点云数据进行预处理
class Preprocess
{public:
//   EIGEN_MAKE_ALIGNED_OPERATOR_NEWPreprocess();  // 构造函数~Preprocess(); // 析构函数void process(const livox_ros_driver::CustomMsg::ConstPtr &msg, PointCloudXYZI::Ptr &pcl_out);  // 对Livox自定义Msg格式的激光雷达数据进行处理void process(const sensor_msgs::PointCloud2::ConstPtr &msg, PointCloudXYZI::Ptr &pcl_out);     // 对ros的Msg格式的激光雷达数据进行处理void set(bool feat_en, int lid_type, double bld, int pfilt_num);// sensor_msgs::PointCloud2::ConstPtr pointcloud;PointCloudXYZI pl_full, pl_corn, pl_surf;  // 全部点、边缘点、平面点PointCloudXYZI pl_buff[128]; //maximum 128 line lidarvector<orgtype> typess[128]; //maximum 128 line lidarint lidar_type, point_filter_num, N_SCANS, SCAN_RATE;  // 雷达类型、采样间隔、扫描线数、扫描频率double blind;  // 最小距离阈值(盲区)bool feature_enabled, given_offset_time;  // 是否提取特征、是否进行时间偏移ros::Publisher pub_full, pub_surf, pub_corn;  // 发布全部点、发布平面点、发布边缘点private:void avia_handler(const livox_ros_driver::CustomMsg::ConstPtr &msg);  // 用于对Livox激光雷达数据进行处理void oust64_handler(const sensor_msgs::PointCloud2::ConstPtr &msg);   // 用于对ouster激光雷达数据进行处理void velodyne_handler(const sensor_msgs::PointCloud2::ConstPtr &msg); // 用于对velodyne激光雷达数据进行处理void give_feature(PointCloudXYZI &pl, vector<orgtype> &types);void pub_func(PointCloudXYZI &pl, const ros::Time &ct);int  plane_judge(const PointCloudXYZI &pl, vector<orgtype> &types, uint i, uint &i_nex, Eigen::Vector3d &curr_direct);bool small_plane(const PointCloudXYZI &pl, vector<orgtype> &types, uint i_cur, uint &i_nex, Eigen::Vector3d &curr_direct);  // 没有用到bool edge_jump_judge(const PointCloudXYZI &pl, vector<orgtype> &types, uint i, Surround nor_dir);int group_size;double disA, disB, inf_bound;double limit_maxmid, limit_midmin, limit_maxmin;double p2l_ratio;double jump_up_limit, jump_down_limit;double cos160;double edgea, edgeb;double smallp_intersect, smallp_ratio;double vx, vy, vz;
};

1.2 preprocess.cpp

上面是头文件,详细的代码在preprocess.cpp中,这里我们来详细介绍每一个函数,首先是构造函数和析构函数,里面主要存放一些雷达的参数

Preprocess::Preprocess(): feature_enabled(0), lidar_type(AVIA), blind(0.01), point_filter_num(1)
{inf_bound = 10;      // 有效点集合,大于10m则是盲区N_SCANS = 6;         //多线激光雷达的线数group_size = 8;      // 8个点为一组disA = 0.01;         // 点集合的距离阈值,判断是否为平面disB = 0.1;          // 点集合的距离阈值,判断是否为平面p2l_ratio = 225;     // 点到线的距离阈值,需要大于这个值才能判断组成面limit_maxmid = 6.25; // 中点到左侧的距离变化率范围limit_midmin = 6.25; // 中点到右侧的距离变化率范围limit_maxmin = 3.24; // 左侧到右侧的距离变化率范围jump_up_limit = 170.0;jump_down_limit = 8.0;cos160 = 160.0;edgea = 2;   //点与点距离超过两倍则认为遮挡edgeb = 0.1; //点与点距离超过0.1m则认为遮挡smallp_intersect = 172.5;smallp_ratio = 1.2;        //三个点如果角度大于172.5度,且比例小于1.2倍,则认为是平面given_offset_time = false; //是否提供时间偏移jump_up_limit = cos(jump_up_limit / 180 * M_PI);       //角度大于170度的点跳过,认为在jump_down_limit = cos(jump_down_limit / 180 * M_PI);   //角度小于8度的点跳过cos160 = cos(cos160 / 180 * M_PI);                     //夹角限制smallp_intersect = cos(smallp_intersect / 180 * M_PI); //三个点如果角度大于172.5度,且比例小于1.2倍,则认为是平面
}

下面一个函数是set函数,这里可以在laserMapping.cpp被使用,可是该函数并未被使用。

void Preprocess::set(bool feat_en, int lid_type, double bld, int pfilt_num)
{feature_enabled = feat_en;    //是否提取特征点lidar_type = lid_type;        //雷达的种类blind = bld;                  //最小距离阈值,即过滤掉0~blind范围内的点云point_filter_num = pfilt_num; //采样间隔,即每隔point_filter_num个点取1个点
}

下面的Preprocess预处理函数主要包含了不同激光雷达的处理,这里在laserMapping.cpp被调用,从而拿到处理后的点云,并初步完成了筛选。下面两个大同小异就不仔细说了

/*** @brief Livox激光雷达点云预处理函数** @param msg livox激光雷达点云数据,格式为livox_ros_driver::CustomMsg* @param pcl_out 输出处理后的点云数据,格式为pcl::PointCloud<pcl::PointXYZINormal>*/
void Preprocess::process(const livox_ros_driver::CustomMsg::ConstPtr &msg, PointCloudXYZI::Ptr &pcl_out)
{avia_handler(msg);*pcl_out = pl_surf;
}void Preprocess::process(const sensor_msgs::PointCloud2::ConstPtr &msg, PointCloudXYZI::Ptr &pcl_out)
{switch (lidar_type){case OUST64:oust64_handler(msg);break;case VELO16:velodyne_handler(msg);break;default:printf("Error LiDAR Type");break;}*pcl_out = pl_surf;
}

下面就是handle函数来预处理发出的点云数据。这里我们展示了Livox激光雷达点云数据进行预处理。这里的操作是拿到livox原始数据,然后通过线束和反射率重新拼接了

void Preprocess::avia_handler(const livox_ros_driver::CustomMsg::ConstPtr &msg)
{// 清除之前的点云缓存pl_surf.clear();             // 清除之前的平面点云缓存pl_corn.clear();             // 清除之前的角点云缓存pl_full.clear();             // 清除之前的全点云缓存double t1 = omp_get_wtime(); // 后面没用到int plsize = msg->point_num; // 一帧中的点云总个数// cout<<"plsie: "<<plsize<<endl;pl_corn.reserve(plsize); // 分配空间pl_surf.reserve(plsize); // 分配空间pl_full.resize(plsize);  // 分配空间for (int i = 0; i < N_SCANS; i++){pl_buff[i].clear();pl_buff[i].reserve(plsize); // 每一个scan保存的点云数量}uint valid_num = 0; // 有效的点云数// 特征提取(FastLIO2默认不进行特征提取)if (feature_enabled){// 分别对每个点云进行处理for (uint i = 1; i < plsize; i++){// 只取线数在0~N_SCANS内并且回波次序为0或者1的点云if ((msg->points[i].line < N_SCANS) && ((msg->points[i].tag & 0x30) == 0x10 || (msg->points[i].tag & 0x30) == 0x00)){pl_full[i].x = msg->points[i].x;                                    // 点云x轴坐标pl_full[i].y = msg->points[i].y;                                    // 点云y轴坐标pl_full[i].z = msg->points[i].z;                                    // 点云z轴坐标pl_full[i].intensity = msg->points[i].reflectivity;                 // 点云强度pl_full[i].curvature = msg->points[i].offset_time / float(1000000); // 使用曲率作为每个激光点的时间bool is_new = false;// 只有当当前点和上一点的间距足够大(>1e-7),才将当前点认为是有用的点,分别加入到对应line的pl_buff队列中if ((abs(pl_full[i].x - pl_full[i - 1].x) > 1e-7) || (abs(pl_full[i].y - pl_full[i - 1].y) > 1e-7) || (abs(pl_full[i].z - pl_full[i - 1].z) > 1e-7)){pl_buff[msg->points[i].line].push_back(pl_full[i]); // 将当前点加入到对应line的pl_buff队列中}}}static int count = 0;static double time = 0.0;count++;double t0 = omp_get_wtime();// 对每个line中的激光雷达分别进行处理for (int j = 0; j < N_SCANS; j++){// 如果该line中的点云过小,则继续处理下一条lineif (pl_buff[j].size() <= 5)continue;pcl::PointCloud<PointType> &pl = pl_buff[j];plsize = pl.size();vector<orgtype> &types = typess[j];types.clear();types.resize(plsize);plsize--;for (uint i = 0; i < plsize; i++){types[i].range = pl[i].x * pl[i].x + pl[i].y * pl[i].y; // 计算每个点到机器人本身的距离vx = pl[i].x - pl[i + 1].x;vy = pl[i].y - pl[i + 1].y;vz = pl[i].z - pl[i + 1].z;types[i].dista = vx * vx + vy * vy + vz * vz; // 计算两个间隔点的距离}//因为i最后一个点没有i+1了所以就单独求了一个range,没有distancetypes[plsize].range = pl[plsize].x * pl[plsize].x + pl[plsize].y * pl[plsize].y;give_feature(pl, types); //给特征// pl_surf += pl;}time += omp_get_wtime() - t0;printf("Feature extraction time: %lf \n", time / count);}else{// 分别对每个点云进行处理for (uint i = 1; i < plsize; i++){// 只取线数在0~N_SCANS内并且回波次序为0或者1的点云if ((msg->points[i].line < N_SCANS) && ((msg->points[i].tag & 0x30) == 0x10 || (msg->points[i].tag & 0x30) == 0x00)){valid_num++; // 有效的点云数// 等间隔降采样if (valid_num % point_filter_num == 0){pl_full[i].x = msg->points[i].x;                                    // 点云x轴坐标pl_full[i].y = msg->points[i].y;                                    // 点云y轴坐标pl_full[i].z = msg->points[i].z;                                    // 点云z轴坐标pl_full[i].intensity = msg->points[i].reflectivity;                 // 点云强度pl_full[i].curvature = msg->points[i].offset_time / float(1000000); // use curvature as time of each laser points// 只有当当前点和上一点的间距足够大(>1e-7),并且在最小距离阈值之外,才将当前点认为是有用的点,加入到pl_surf队列中if ((abs(pl_full[i].x - pl_full[i - 1].x) > 1e-7) || (abs(pl_full[i].y - pl_full[i - 1].y) > 1e-7) || (abs(pl_full[i].z - pl_full[i - 1].z) > 1e-7) && (pl_full[i].x * pl_full[i].x + pl_full[i].y * pl_full[i].y > blind)){pl_surf.push_back(pl_full[i]);}}}}}
}

在程序中我们可以看到give_feature(pl, types);这个函数主要是特征提取,下面我们对该函数进行学习和了解,对于每条line的点云提取特征

void Preprocess::give_feature(pcl::PointCloud<PointType> &pl, vector<orgtype> &types)
{int plsize = pl.size(); //单条线的点数int plsize2;if (plsize == 0){printf("something wrong\n");return;}uint head = 0;//不能在盲区 从这条线非盲区的点开始while (types[head].range < blind){head++;}// Surf// group_size默认等于8plsize2 = (plsize > group_size) ? (plsize - group_size) : 0; //判断当前点后面是否还有8个点 够的话就逐渐减少Eigen::Vector3d curr_direct(Eigen::Vector3d::Zero()); //当前平面的法向量Eigen::Vector3d last_direct(Eigen::Vector3d::Zero()); //上一个平面的法向量uint i_nex = 0, i2;  // i2为当前点的下一个点uint last_i = 0;     // last_i为上一个点的保存的索引uint last_i_nex = 0; // last_i_nex为上一个点的下一个点的索引int last_state = 0;  //为1代表上个状态为平面 否则为0//判断面点int plane_type;// 拿到8个点用于判断平面for (uint i = head; i < plsize2; i++){if (types[i].range < blind) // 在盲区范围内的点不做处理continue;{continue;}i2 = i; //更新i2plane_type = plane_judge(pl, types, i, i_nex, curr_direct); //求得平面,并返回类型0 1 2if (plane_type == 1) //返回1一般默认是平面{//设置确定的平面点和可能的平面点for (uint j = i; j <= i_nex; j++){if (j != i && j != i_nex){//把起始点和终止点之间的所有点设置为确定的平面点types[j].ftype = Real_Plane;}else{//把起始点和终止点设置为可能的平面点types[j].ftype = Poss_Plane;}}// if(last_state==1 && fabs(last_direct.sum())>0.5)//最开始last_state=0直接跳过//之后last_state=1//如果之前状态是平面则判断当前点是处于两平面边缘的点还是较为平坦的平面的点if (last_state == 1 && last_direct.norm() > 0.1){double mod = last_direct.transpose() * curr_direct;if (mod > -0.707 && mod < 0.707){//修改ftype,两个面法向量夹角在45度和135度之间 认为是两平面边缘上的点types[i].ftype = Edge_Plane;}else{//否则认为是真正的平面点types[i].ftype = Real_Plane;}}i = i_nex - 1;last_state = 1;}else // if(plane_type == 2){// plane_type=0或2的时候i = i_nex;last_state = 0; //设置为不是平面点}last_i = i2;               //更新last_ilast_i_nex = i_nex;        //更新last_i_nexlast_direct = curr_direct; //更新last_direct}//判断边缘点plsize2 = plsize > 3 ? plsize - 3 : 0; //如果剩下的点数小于3则不判断边缘点,否则计算哪些点是边缘点for (uint i = head + 3; i < plsize2; i++){//点不能在盲区 或者 点必须属于正常点和可能的平面点if (types[i].range < blind || types[i].ftype >= Real_Plane){continue;}//该点与前后点的距离不能挨的太近if (types[i - 1].dista < 1e-16 || types[i].dista < 1e-16){continue;}Eigen::Vector3d vec_a(pl[i].x, pl[i].y, pl[i].z); //当前点组成的向量Eigen::Vector3d vecs[2];for (int j = 0; j < 2; j++){int m = -1;if (j == 1){m = 1;}//若当前的前/后一个点在盲区内(4m)if (types[i + m].range < blind){if (types[i].range > inf_bound) //若其大于10m{types[i].edj[j] = Nr_inf; //赋予该点Nr_inf(跳变较远)}else{types[i].edj[j] = Nr_blind; //赋予该点Nr_blind(在盲区)}continue;}vecs[j] = Eigen::Vector3d(pl[i + m].x, pl[i + m].y, pl[i + m].z);vecs[j] = vecs[j] - vec_a; //前/后点指向当前点的向量//若雷达坐标系原点为O 当前点为A 前/后一点为M和N//则下面OA点乘MA/(|OA|*|MA|)//得到的是cos角OAM的大小types[i].intersect = vecs[Prev].dot(vecs[Next]) / vecs[Prev].norm() / vecs[Next].norm(); //角MAN的cos值//前一个点是正常点 && 下一个点在激光线上 && 当前点与后一个点的距离大于0.0225m && 当前点与后一个点的距离大于当前点与前一个点距离的四倍//这种边缘点像是7字形这种的边缘?if (types[i].angle[j] < jump_up_limit) // cos(170){types[i].edj[j] = Nr_180; // M在OA延长线上}else if (types[i].angle[j] > jump_down_limit) // cos(8){types[i].edj[j] = Nr_zero; // M在OA上}}types[i].intersect = vecs[Prev].dot(vecs[Next]) / vecs[Prev].norm() / vecs[Next].norm();if (types[i].edj[Prev] == Nr_nor && types[i].edj[Next] == Nr_zero && types[i].dista > 0.0225 && types[i].dista > 4 * types[i - 1].dista){if (types[i].intersect > cos160) //角MAN要小于160度 不然就平行于激光了{if (edge_jump_judge(pl, types, i, Prev)){types[i].ftype = Edge_Jump;}}}//与上面类似//前一个点在激光束上 && 后一个点正常 && 前一个点与当前点的距离大于0.0225m && 前一个点与当前点的距离大于当前点与后一个点距离的四倍else if (types[i].edj[Prev] == Nr_zero && types[i].edj[Next] == Nr_nor && types[i - 1].dista > 0.0225 && types[i - 1].dista > 4 * types[i].dista){if (types[i].intersect > cos160){if (edge_jump_judge(pl, types, i, Next)){types[i].ftype = Edge_Jump;}}}//前面的是正常点 && (当前点到中心距离>10m并且后点在盲区)else if (types[i].edj[Prev] == Nr_nor && types[i].edj[Next] == Nr_inf){if (edge_jump_judge(pl, types, i, Prev)){types[i].ftype = Edge_Jump;}}//(当前点到中心距离>10m并且前点在盲区) && 后面的是正常点else if (types[i].edj[Prev] == Nr_inf && types[i].edj[Next] == Nr_nor){if (edge_jump_judge(pl, types, i, Next)){types[i].ftype = Edge_Jump;}}//前后点都不是正常点else if (types[i].edj[Prev] > Nr_nor && types[i].edj[Next] > Nr_nor){if (types[i].ftype == Nor){types[i].ftype = Wire; //程序中应该没使用 当成空间中的小线段或者无用点了}}}plsize2 = plsize - 1;double ratio;//继续找平面点for (uint i = head + 1; i < plsize2; i++){//前面、当前、之后三个点都需要不在盲区内if (types[i].range < blind || types[i - 1].range < blind || types[i + 1].range < blind){continue;}//前面和当前 当前和之后的点与点之间距离都不能太近if (types[i - 1].dista < 1e-8 || types[i].dista < 1e-8){continue;}//还剩下来一些正常点继续找平面点if (types[i].ftype == Nor){//求点与点间距的比例 大间距/小间距if (types[i - 1].dista > types[i].dista){ratio = types[i - 1].dista / types[i].dista;}else{ratio = types[i].dista / types[i - 1].dista;}//如果夹角大于172.5度 && 间距比例<1.2if (types[i].intersect < smallp_intersect && ratio < smallp_ratio){//前后三个点认为是平面点if (types[i - 1].ftype == Nor){types[i - 1].ftype = Real_Plane;}if (types[i + 1].ftype == Nor){types[i + 1].ftype = Real_Plane;}types[i].ftype = Real_Plane;}}}//存储平面点int last_surface = -1;for (uint j = head; j < plsize; j++){//可能的平面点和确定的平面点if (types[j].ftype == Poss_Plane || types[j].ftype == Real_Plane){if (last_surface == -1){last_surface = j;}//通常连着好几个都是面点//必须在采样间隔上的平面点才使用(这里的是无差别滤波 从每次新找到面点开始每几个点才取一个)if (j == uint(last_surface + point_filter_num - 1)){PointType ap;ap.x = pl[j].x;ap.y = pl[j].y;ap.z = pl[j].z;ap.intensity = pl[j].intensity;ap.curvature = pl[j].curvature;pl_surf.push_back(ap);last_surface = -1;}}else{//跳变较大的边缘边的点 位于平面边缘的点if (types[j].ftype == Edge_Jump || types[j].ftype == Edge_Plane){pl_corn.push_back(pl[j]);}//假如上次找到的面点被无差别滤波掉了,而此时已经到了边缘if (last_surface != -1){PointType ap;//取上次面点到此次边缘线之间的所有点的重心当作一个面点存储进去for (uint k = last_surface; k < j; k++){ap.x += pl[k].x;ap.y += pl[k].y;ap.z += pl[k].z;ap.intensity += pl[k].intensity;ap.curvature += pl[k].curvature;}ap.x /= (j - last_surface);ap.y /= (j - last_surface);ap.z /= (j - last_surface);ap.intensity /= (j - last_surface);ap.curvature /= (j - last_surface);pl_surf.push_back(ap);}last_surface = -1;}}
}

在give_feature函数中存在了提取面特征与提取角特征的函数,下面我们来详细分析,面特征主要是主要是依据点与点之间的距离来拟合成一个向量,并通过乘积来计算出面特征的提取。这里的面特征提取和边缘特征提取和LOAM的类似,这里就用LOAM的图片来表示了。

…详情请参照古月居

FAST-LIO2代码解析(一)相关推荐

  1. Mina Kimchi SNARK 代码解析

    1. 引言 Mina系列博客有: Mina概览 Mina的支付流程 Mina的zkApp Mina中的Pasta(Pallas和Vesta)曲线 Mina中的Schnorr signature Min ...

  2. Compact Multi-Signatures for Smaller Blockchains代码解析

    1. 引言 Boneh等人2018年论文<Compact Multi-Signatures for Smaller Blockchains>,论文解读参见博客 Compact Multi- ...

  3. STARK中的FRI代码解析

    1. 引言 前序博客有: STARK入门知识 STARKs and STARK VM: Proofs of Computational Integrity Fast Reed-Solomon Inte ...

  4. 目标检测算法之常见评价指标的详细计算方法及代码解析

    前言 之前简单介绍过目标检测算法的一些评价标准,地址为目标检测算法之评价标准和常见数据集盘点.然而这篇文章仅仅只是从概念性的角度来阐述了常见的评价标准如Acc,Precision,Recall,AP等 ...

  5. ECCV2020|图像重建(超分辨率,图像恢复,去雨,去雾等)相关论文汇总(附论文链接/代码/解析)

    转载自https://zhuanlan.zhihu.com/p/180551773 原帖地址: ECCV2020|图像重建/底层视觉(超分辨率,图像恢复,去雨,去雾,去模糊,去噪等)相关论文汇总(附论 ...

  6. FSP:Flow of Solution Procedure (CVPR 2017) 原理与代码解析

    paper:A Gift From Knowledge Distillation: Fast Optimization, Network Minimization and Transfer Learn ...

  7. matrix_multiply代码解析

    matrix_multiply代码解析 关于matrix_multiply 程序执行代码里两个矩阵的乘法,并将相乘结果打印在屏幕上. 示例的主要目的是展现怎么实现一个自定义CPU计算任务. 参考:ht ...

  8. CornerNet代码解析——损失函数

    CornerNet代码解析--损失函数 文章目录 CornerNet代码解析--损失函数 前言 总体损失 1.Heatmap的损失 2.Embedding的损失 3.Offset的损失 前言 今天要解 ...

  9. 视觉SLAM开源算法ORB-SLAM3 原理与代码解析

    来源:深蓝学院,文稿整理者:何常鑫,审核&修改:刘国庆 本文总结于上交感知与导航研究所科研助理--刘国庆关于[视觉SLAM开源算法ORB-SLAM3 原理与代码解析]的公开课. ORB-SLA ...

  10. java获取object属性值_java反射获取一个object属性值代码解析

    有些时候你明明知道这个object里面是什么,但是因为种种原因,你不能将它转化成一个对象,只是想单纯地提取出这个object里的一些东西,这个时候就需要用反射了. 假如你这个类是这样的: privat ...

最新文章

  1. 移动开发工程师面试题集:Android iOS
  2. Enterprise Vault 10.0.4 FOR Exchange2013 部署之二 -安装过程
  3. 每秒8.8亿次请求!让数据存得起,看得见 - 云原生多模数据库Lindorm 2020双十一总结
  4. traceroute命令的用法实例
  5. echart freemarker 模板_SpringBoot集成Freemarker模板生成Echarts图片(三)
  6. [转载] Bitmap的秘密
  7. ajax获取301,PHP获取301重定向页面跳转后真实URL地址
  8. idea2020显示内存占用_【解决讨论】关于macbook pro 16使用 idea2020.1风扇狂转的问题(很吵)...
  9. Tensorflow使用教训:被tf.image.resize浪费的60天
  10. 拓端tecdat|R语言中的岭回归、套索回归、主成分回归:线性模型选择和正则化
  11. verifycode.php,php验证码|php图片验证码|php image.verifyCode.class.php
  12. 解决There is no getter for property named ‘organization_id‘ in的报错
  13. ppt制作的一些要点
  14. 在eclipse上使用Maven创建简单项目
  15. [book]《心流:最优体验心理学》
  16. sdut 4408 这真的是签到题
  17. linux硬盘和分区,Linux硬盘和分区
  18. 深入理解vue slot插槽
  19. java double 乘以100,将double乘以100,然后转换为long表示错误的值
  20. 这样学Redis,才能技高一筹!

热门文章

  1. 看表情读情绪:AI“察言观色”背后的表情识别数据
  2. MySQL--管理数据库表相关操作
  3. 磨金石教育摄影技能干货分享|优秀作品欣赏—技巧十足的艺术摄影
  4. 婚恋交友网站比相亲更靠谱!幸福婚姻算法了解一下
  5. 解决mac登录APPLE ID无限重复弹窗提示登录
  6. 微信小程序,画布中,根据需要展示的图片比例,获取能截取原图中的最大图片尺寸,并且不变形展示
  7. Hololens2,隐藏着微软真正的野心!
  8. PowerManagerService分析-updatePowerStateLocked方法
  9. 用手机远程控制扫描仪
  10. EasyCVR通过进程启动无报错,但是自动退出该如何解决?