ORB-SLAM2源码学习:Initializer.cc⑧: Initializer::CheckRT检验三角化结果
前言
ORB-SLAM2源码学习:Initializer.cc⑦: Initializer::Triangulate特征点对的三角化_cv::svd::compute-CSDN博客
经过上面的三角化我们成功得到了三维点,但是经过三角化成功的三维点并不一定是有效的,需要筛选才能作为初始化地图点。
1.函数声明
int Initializer::CheckRT(const cv::Mat &R, const cv::Mat &t, const vector<cv::KeyPoint> &vKeys1, const vector<cv::KeyPoint> &vKeys2,const vector<Match> &vMatches12, vector<bool> &vbMatchesInliers,const cv::Mat &K, vector<cv::Point3f> &vP3D, float th2, vector<bool> &vbGood, float ¶llax)
2.函数定义
我们把相机1的光轴中心作为世界坐标系的原点,从相机1到相机2的位姿:

则从相机2到相机1的位姿:

对应关系:


则相机2的光轴中心O2在相机1的坐标系下的坐标为:

计算相机2 的光轴中心O2 在相机1 坐标系下的坐标是为了求解三维点分别在两个坐标系下和光轴中心的夹角。如果我们要求向量夹角,那么前提是这些向量都在同一个坐标系下。我们看相机1坐标系,此时O1是相机1 的光轴中心,也是相机1 坐标系原点, P3d 是相机1坐标系(世界坐标系)下的三维点,这就必须得到O2在相机1 坐标系下的坐标,也就是我们前面推导的过程。
计算夹角是为了判断三维点的有效性, 因为初始化地图点( 三角化得到的三维点)特别重要,后续跟踪都是以此为基础的,所以在确定三维点时要非常小心。确定一个合格的三维点需要通过以下条件:
1.三维点的3 个坐标都必须是有限的实数。
2.三维点深度值必须为正。
3.三维点和两帧图像光轴中心夹角需要满足一定的条件。夹角越大,视差越大, 三角化结果越准确。
4. 三维点的重投影误差小于设定的阈值。
经过上面条件的筛选,最后剩下的三维点才是合格的三维点。我们会记录当前位姿对应的合格三维点数目和视差。
具体的流程
1.计算前的参数准备与声明
// 对给出的特征点对及其R t , 通过三角化检查解的有效性,也称为 cheirality check// Calibration parameters//从相机内参数矩阵获取相机的校正参数const float fx = K.at<float>(0,0);const float fy = K.at<float>(1,1);const float cx = K.at<float>(0,2);const float cy = K.at<float>(1,2);//特征点是否是good点的标记,这里的特征点指的是参考帧中的特征点vbGood = vector<bool>(vKeys1.size(),false);//重设存储空间坐标的点的大小vP3D.resize(vKeys1.size());//存储计算出来的每对特征点的视差vector<float> vCosParallax;vCosParallax.reserve(vKeys1.size());
2.构建投影矩阵计算相机光心2在世界坐标系下的坐标
这里与前边推导的过程一致。
// Camera 1 Projection Matrix K[I|0]// Step 1:计算相机的投影矩阵 // 投影矩阵P是一个 3x4 的矩阵,可以将空间中的一个点投影到平面上,获得其平面坐标,这里均指的是齐次坐标。// 对于第一个相机是 P1=K*[I|0]// 以第一个相机的光心作为世界坐标系, 定义相机的投影矩阵cv::Mat P1(3,4, //矩阵的大小是3x4CV_32F, //数据类型是浮点数cv::Scalar(0)); //初始的数值是0//将整个K矩阵拷贝到P1矩阵的左侧3x3矩阵,因为 K*I = KK.copyTo(P1.rowRange(0,3).colRange(0,3));// 第一个相机的光心设置为世界坐标系下的原点cv::Mat O1 = cv::Mat::zeros(3,1,CV_32F);// Camera 2 Projection Matrix K[R|t]// 计算第二个相机的投影矩阵 P2=K*[R|t]cv::Mat P2(3,4,CV_32F);R.copyTo(P2.rowRange(0,3).colRange(0,3));t.copyTo(P2.rowRange(0,3).col(3));//最终结果是K*[R|t]P2 = K*P2;// 第二个相机的光心在世界坐标系下的坐标cv::Mat O2 = -R.t()*t;
3.开始遍历所有的特征点对
//在遍历开始前,先将good点计数设置为0int nGood=0;// 开始遍历所有的特征点对for(size_t i=0, iend=vMatches12.size();i<iend;i++){// 跳过outliersif(!vbMatchesInliers[i])continue;....}
3.1调用Triangulate函数进行三角化
// Step 2 获取特征点对,调用Triangulate() 函数进行三角化,得到三角化测量之后的3D点坐标// kp1和kp2是匹配好的有效特征点const cv::KeyPoint &kp1 = vKeys1[vMatches12[i].first];const cv::KeyPoint &kp2 = vKeys2[vMatches12[i].second];//存储三维点的的坐标cv::Mat p3dC1;// 利用三角法恢复三维点p3dC1Triangulate(kp1,kp2, //特征点P1,P2, //投影矩阵p3dC1); //输出,三角化测量之后特征点的空间坐标
3.2合格的三维点的条件1的判断
// Step 3 第一关:检查三角化的三维点坐标是否合法(非无穷值)// 只要三角测量的结果中有一个是无穷大的就说明三角化失败,跳过对当前点的处理,进行下一对特征点的遍历 if(!isfinite(p3dC1.at<float>(0)) || !isfinite(p3dC1.at<float>(1)) || !isfinite(p3dC1.at<float>(2))){//其实这里就算是不这样写也没问题,因为默认的匹配点对就不是good点vbGood[vMatches12[i].first]=false;//继续对下一对匹配点的处理continue;}
3.3合格的三维点的条件2和3的判断
// Check parallax// Step 4 第二关:通过三维点深度值正负、两相机光心视差角大小来检查是否合法 //得到向量PO1cv::Mat normal1 = p3dC1 - O1;//求取模长,其实就是距离float dist1 = cv::norm(normal1);//同理构造向量PO2cv::Mat normal2 = p3dC1 - O2;//求模长float dist2 = cv::norm(normal2);//根据公式:a.*b=|a||b|cos_theta 可以推导出来下面的式子float cosParallax = normal1.dot(normal2)/(dist1*dist2);// Check depth in front of first camera (only if enough parallax, as "infinite" points can easily go to negative depth)// 如果深度值为负值,为非法三维点跳过该匹配点对// ?视差比较小时,重投影误差比较大。这里0.99998 对应的角度为0.36°,这里不应该是 cosParallax>0.99998 吗?// ?因为后面判断vbGood 点时的条件也是 cosParallax<0.99998 // !可能导致初始化不稳定if(p3dC1.at<float>(2)<=0 && cosParallax<0.99998)continue;// Check depth in front of second camera (only if enough parallax, as "infinite" points can easily go to negative depth)// 讲空间点p3dC1变换到第2个相机坐标系下变为p3dC2cv::Mat p3dC2 = R*p3dC1+t; //判断过程和上面的相同if(p3dC2.at<float>(2)<=0 && cosParallax<0.99998)continue;
3.4计算重投影误差
// Step 5 第三关:计算空间点在参考帧和当前帧上的重投影误差,如果大于阈值则舍弃// Check reprojection error in first image// 计算3D点在第一个图像上的投影误差//投影到参考帧图像上的点的坐标x,yfloat im1x, im1y;//这个使能空间点的z坐标的倒数float invZ1 = 1.0/p3dC1.at<float>(2);//投影到参考帧图像上。因为参考帧下的相机坐标系和世界坐标系重合,因此这里就直接进行投影就可以了im1x = fx*p3dC1.at<float>(0)*invZ1+cx;im1y = fy*p3dC1.at<float>(1)*invZ1+cy;//参考帧上的重投影误差,这个的确就是按照定义来的float squareError1 = (im1x-kp1.pt.x)*(im1x-kp1.pt.x)+(im1y-kp1.pt.y)*(im1y-kp1.pt.y);// 重投影误差太大,跳过淘汰if(squareError1>th2)continue;// Check reprojection error in second image// 计算3D点在第二个图像上的投影误差,计算过程和第一个图像类似float im2x, im2y;// 注意这里的p3dC2已经是第二个相机坐标系下的三维点了float invZ2 = 1.0/p3dC2.at<float>(2);im2x = fx*p3dC2.at<float>(0)*invZ2+cx;im2y = fy*p3dC2.at<float>(1)*invZ2+cy;// 计算重投影误差float squareError2 = (im2x-kp2.pt.x)*(im2x-kp2.pt.x)+(im2y-kp2.pt.y)*(im2y-kp2.pt.y);// 重投影误差太大,跳过淘汰if(squareError2>th2)continue;
3.5统计经过检验的三维点信息
// Step 6 统计经过检验的3D点个数,记录3D点视差角 // 如果运行到这里就说明当前遍历的这个特征点对靠谱,经过了重重检验,说明是一个合格的点,称之为good点 vCosParallax.push_back(cosParallax);//存储这个三角化测量后的3D点在世界坐标系下的坐标vP3D[vMatches12[i].first] = cv::Point3f(p3dC1.at<float>(0),p3dC1.at<float>(1),p3dC1.at<float>(2));//good点计数++nGood++;//判断视差角,只有视差角稍稍大一丢丢的才会给打good点标记//? bug 我觉得这个写的位置不太对。你的good点计数都++了然后才判断,不是会让good点标志和good点计数不一样吗if(cosParallax<0.99998)vbGood[vMatches12[i].first]=true;
4.得到最小的视差角
如果满足要求的三维点数大于50就获取其中最小的视差角如果不满足数量要求就将视差角设置为0。
// Step 7 得到3D点中较小的视差角,并且转换成为角度制表示if(nGood>0){// 从小到大排序,注意vCosParallax值越大,视差越小sort(vCosParallax.begin(),vCosParallax.end());// !排序后并没有取最小的视差角,而是取一个较小的视差角// 作者的做法:如果经过检验过后的有效3D点小于50个,那么就取最后那个最小的视差角(cos值最大)// 如果大于50个,就取排名第50个的较小的视差角即可,为了避免3D点太多时出现太小的视差角 size_t idx = min(50,int(vCosParallax.size()-1));//将这个选中的角弧度制转换为角度制parallax = acos(vCosParallax[idx])*180/CV_PI;}else//如果没有good点那么这个就直接设置为0了parallax=0;//返回good点计数return nGood;
完整的代码分析
/*用位姿来对特征匹配点三角化,从中筛选中合格的三维点R 旋转矩阵Rt 平移矩阵tvKeys1 参考帧特征点vKeys2 当前帧特征点vMatches12 两帧特征点的匹配关系vbMatchesInliers 特征点对内点标记K 相机内参矩阵vP3D 三角化测量之后的特征点的空间坐标th2 重投影误差的阈值vbGood 标记成功三角化点?parallax 计算出来的比较大的视差角(注意不是最大,具体看后面代码)return int*/
int Initializer::CheckRT(const cv::Mat &R, const cv::Mat &t, const vector<cv::KeyPoint> &vKeys1, const vector<cv::KeyPoint> &vKeys2,const vector<Match> &vMatches12, vector<bool> &vbMatchesInliers,const cv::Mat &K, vector<cv::Point3f> &vP3D, float th2, vector<bool> &vbGood, float ¶llax)
{ // 对给出的特征点对及其R t , 通过三角化检查解的有效性,也称为 cheirality check// Calibration parameters//从相机内参数矩阵获取相机的校正参数const float fx = K.at<float>(0,0);const float fy = K.at<float>(1,1);const float cx = K.at<float>(0,2);const float cy = K.at<float>(1,2);//特征点是否是good点的标记,这里的特征点指的是参考帧中的特征点vbGood = vector<bool>(vKeys1.size(),false);//重设存储空间坐标的点的大小vP3D.resize(vKeys1.size());//存储计算出来的每对特征点的视差vector<float> vCosParallax;vCosParallax.reserve(vKeys1.size());// Camera 1 Projection Matrix K[I|0]// Step 1:计算相机的投影矩阵 // 投影矩阵P是一个 3x4 的矩阵,可以将空间中的一个点投影到平面上,获得其平面坐标,这里均指的是齐次坐标。// 对于第一个相机是 P1=K*[I|0]// 以第一个相机的光心作为世界坐标系, 定义相机的投影矩阵cv::Mat P1(3,4, //矩阵的大小是3x4CV_32F, //数据类型是浮点数cv::Scalar(0)); //初始的数值是0//将整个K矩阵拷贝到P1矩阵的左侧3x3矩阵,因为 K*I = KK.copyTo(P1.rowRange(0,3).colRange(0,3));// 第一个相机的光心设置为世界坐标系下的原点cv::Mat O1 = cv::Mat::zeros(3,1,CV_32F);// Camera 2 Projection Matrix K[R|t]// 计算第二个相机的投影矩阵 P2=K*[R|t]cv::Mat P2(3,4,CV_32F);R.copyTo(P2.rowRange(0,3).colRange(0,3));t.copyTo(P2.rowRange(0,3).col(3));//最终结果是K*[R|t]P2 = K*P2;// 第二个相机的光心在世界坐标系下的坐标cv::Mat O2 = -R.t()*t;//在遍历开始前,先将good点计数设置为0int nGood=0;// 开始遍历所有的特征点对for(size_t i=0, iend=vMatches12.size();i<iend;i++){// 跳过outliersif(!vbMatchesInliers[i])continue;// Step 2 获取特征点对,调用Triangulate() 函数进行三角化,得到三角化测量之后的3D点坐标// kp1和kp2是匹配好的有效特征点const cv::KeyPoint &kp1 = vKeys1[vMatches12[i].first];const cv::KeyPoint &kp2 = vKeys2[vMatches12[i].second];//存储三维点的的坐标cv::Mat p3dC1;// 利用三角法恢复三维点p3dC1Triangulate(kp1,kp2, //特征点P1,P2, //投影矩阵p3dC1); //输出,三角化测量之后特征点的空间坐标 // Step 3 第一关:检查三角化的三维点坐标是否合法(非无穷值)// 只要三角测量的结果中有一个是无穷大的就说明三角化失败,跳过对当前点的处理,进行下一对特征点的遍历 if(!isfinite(p3dC1.at<float>(0)) || !isfinite(p3dC1.at<float>(1)) || !isfinite(p3dC1.at<float>(2))){//其实这里就算是不这样写也没问题,因为默认的匹配点对就不是good点vbGood[vMatches12[i].first]=false;//继续对下一对匹配点的处理continue;}// Check parallax// Step 4 第二关:通过三维点深度值正负、两相机光心视差角大小来检查是否合法 //得到向量PO1cv::Mat normal1 = p3dC1 - O1;//求取模长,其实就是距离float dist1 = cv::norm(normal1);//同理构造向量PO2cv::Mat normal2 = p3dC1 - O2;//求模长float dist2 = cv::norm(normal2);//根据公式:a.*b=|a||b|cos_theta 可以推导出来下面的式子float cosParallax = normal1.dot(normal2)/(dist1*dist2);// Check depth in front of first camera (only if enough parallax, as "infinite" points can easily go to negative depth)// 如果深度值为负值,为非法三维点跳过该匹配点对// ?视差比较小时,重投影误差比较大。这里0.99998 对应的角度为0.36°,这里不应该是 cosParallax>0.99998 吗?// ?因为后面判断vbGood 点时的条件也是 cosParallax<0.99998 // !可能导致初始化不稳定if(p3dC1.at<float>(2)<=0 && cosParallax<0.99998)continue;// Check depth in front of second camera (only if enough parallax, as "infinite" points can easily go to negative depth)// 讲空间点p3dC1变换到第2个相机坐标系下变为p3dC2cv::Mat p3dC2 = R*p3dC1+t; //判断过程和上面的相同if(p3dC2.at<float>(2)<=0 && cosParallax<0.99998)continue;// Step 5 第三关:计算空间点在参考帧和当前帧上的重投影误差,如果大于阈值则舍弃// Check reprojection error in first image// 计算3D点在第一个图像上的投影误差//投影到参考帧图像上的点的坐标x,yfloat im1x, im1y;//这个使能空间点的z坐标的倒数float invZ1 = 1.0/p3dC1.at<float>(2);//投影到参考帧图像上。因为参考帧下的相机坐标系和世界坐标系重合,因此这里就直接进行投影就可以了im1x = fx*p3dC1.at<float>(0)*invZ1+cx;im1y = fy*p3dC1.at<float>(1)*invZ1+cy;//参考帧上的重投影误差,这个的确就是按照定义来的float squareError1 = (im1x-kp1.pt.x)*(im1x-kp1.pt.x)+(im1y-kp1.pt.y)*(im1y-kp1.pt.y);// 重投影误差太大,跳过淘汰if(squareError1>th2)continue;// Check reprojection error in second image// 计算3D点在第二个图像上的投影误差,计算过程和第一个图像类似float im2x, im2y;// 注意这里的p3dC2已经是第二个相机坐标系下的三维点了float invZ2 = 1.0/p3dC2.at<float>(2);im2x = fx*p3dC2.at<float>(0)*invZ2+cx;im2y = fy*p3dC2.at<float>(1)*invZ2+cy;// 计算重投影误差float squareError2 = (im2x-kp2.pt.x)*(im2x-kp2.pt.x)+(im2y-kp2.pt.y)*(im2y-kp2.pt.y);// 重投影误差太大,跳过淘汰if(squareError2>th2)continue;// Step 6 统计经过检验的3D点个数,记录3D点视差角 // 如果运行到这里就说明当前遍历的这个特征点对靠谱,经过了重重检验,说明是一个合格的点,称之为good点 vCosParallax.push_back(cosParallax);//存储这个三角化测量后的3D点在世界坐标系下的坐标vP3D[vMatches12[i].first] = cv::Point3f(p3dC1.at<float>(0),p3dC1.at<float>(1),p3dC1.at<float>(2));//good点计数++nGood++;//判断视差角,只有视差角稍稍大一丢丢的才会给打good点标记//? bug 我觉得这个写的位置不太对。你的good点计数都++了然后才判断,不是会让good点标志和good点计数不一样吗if(cosParallax<0.99998)vbGood[vMatches12[i].first]=true;}// Step 7 得到3D点中较小的视差角,并且转换成为角度制表示if(nGood>0){// 从小到大排序,注意vCosParallax值越大,视差越小sort(vCosParallax.begin(),vCosParallax.end());// !排序后并没有取最小的视差角,而是取一个较小的视差角// 作者的做法:如果经过检验过后的有效3D点小于50个,那么就取最后那个最小的视差角(cos值最大)// 如果大于50个,就取排名第50个的较小的视差角即可,为了避免3D点太多时出现太小的视差角 size_t idx = min(50,int(vCosParallax.size()-1));//将这个选中的角弧度制转换为角度制parallax = acos(vCosParallax[idx])*180/CV_PI;}else//如果没有good点那么这个就直接设置为0了parallax=0;//返回good点计数return nGood;
}
结束语
以上就是我学习到的内容,如果对您有帮助请多多支持我,如果哪里有问题欢迎大家在评论区积极讨论,我看到会及时回复。
相关文章:
ORB-SLAM2源码学习:Initializer.cc⑧: Initializer::CheckRT检验三角化结果
前言 ORB-SLAM2源码学习:Initializer.cc⑦: Initializer::Triangulate特征点对的三角化_cv::svd::compute-CSDN博客 经过上面的三角化我们成功得到了三维点,但是经过三角化成功的三维点并不一定是有效的,需要筛选才能作为初始化地图点。 …...
【ArcGIS微课1000例】0141:提取多波段影像中的单个波段
文章目录 一、波段提取函数二、加载单波段导出问题描述:如下图所示,img格式的时序NDVI数据有24个波段。现在需要提取某一个波段,该怎样操作? 一、波段提取函数 首先加载多波段数据。点击【窗口】→【影像分析】。 选择需要处理的多波段影像,点击下方的【添加函数】。 在多…...
【测试人生】变更风险观测的流程逻辑设计
在线上服务变更过程中,我们希望可以通过一套实时观测机制去监测线上服务的风险,从而能够确保线上稳定性,在出问题是可以及时回滚变更。今天这篇文章,就简单讲一下变更风险观测的流程逻辑需要怎么设计。 首先需要明确变更观测的相…...
一文大白话讲清楚webpack基本使用——17——Tree Shaking
文章目录 一文大白话讲清楚webpack基本使用——17——Tree Shaking1. 建议按文章顺序从头看,一看到底,豁然开朗2. 啥叫Tree Shaking3. 什么是死代码,怎么来的3. Tree Shaking的流程3.1 标记3.2 利用Terser摇起来 4. 具体使用方式4.1 适用前提…...
ChatGPT从数据分析到内容写作建议相关的46个提示词分享!
在当今快节奏的学术环境中,研究人员面临着海量的信息和复杂的研究任务。幸运的是,随着人工智能技术的发展,像ChatGPT这样的先进工具为科研人员提供了强大的支持。今天就让我们一起探索如何利用ChatGPT提升研究效率进一步优化研究流程。 ChatG…...
PyCharm配置Python环境
1、打开PyCharm项目 可以从File-->Open-->选择你的项目路径-->OK,或者直接点击Open,找到项目路径-->OK,如图所示(点击Ok后可能有下面的弹窗,选择“Trust Project”即可,然后选择“New Window”打开项目) …...
c#配置config文件
1,引用命名空间 Configuration 及配置信息...
RDMA 工作原理 | 支持 RDMA 的网络协议
注:本文为 “RDMA” 相关文章合辑。 英文引文机翻未校。 图片清晰度受引文所限。 Introduction to Remote Direct Memory Access (RDMA) Written by: Dotan Barak on March 31, 2014.on February 13, 2015. What is RDMA? 什么是 RDMA? Direct me…...
01-硬件入门学习/嵌入式教程-CH340C使用教程
前言 CH340C广泛应用于DIY项目和嵌入式开发中,用于USB数据转换和串口通信。本文将详细介绍CH340C的基本功能、引脚接线及使用方法。 CH340C简介 CH340C是一款USB转TTL电平转换器,可以将电脑的USB数据转换成串口数据,方便与单片机ÿ…...
STM32——LCD
一、引脚配置 查看引脚 将上述引脚都设置为GPIO_Output 二、导入驱动文件 将 LCD 驱动的 Inc 以及 Src 中的 fonts.h,lcd.h 和 lcd.c 导入到自己工程的驱动文件中。 当然,后面 lcd 的驱动学习可以和 IMX6U 一块学。 三、LCD函数 void LCD_Clear(u16 Color); 功能…...
破解浏览器渲染“死锁”:CSS与JS如何影响页面加载速度?
破解浏览器渲染“死锁”:CSS与JS如何影响页面加载速度? 在这个快速发展的Web世界里,性能是开发者们永恒的追求。当你打开一个网页,可能会注意到一些页面加载特别慢,甚至产生短暂的“白屏”,你有没有想过&a…...
操作系统(Linux Kernel 0.11Linux Kernel 0.12)解读整理——内核初始化(main init)之内存的划分
前言 MMU:内存管理单元(Memory Management Unit)完成的工作就是虚拟地址到物理地址的转换,可以让系统中的多个程序跑在自己独立的虚拟地址空间中,相互不会影响。程序可以对底层的物理内存一无所知,物理地址可以是不连续的&#x…...
.NET MAUI进行UDP通信(二)
上篇文章有写过一个简单的demo,本次对项目进行进一步的扩展,添加tabbar功能。 1.修改AppShell.xaml文件,如下所示: <?xml version"1.0" encoding"UTF-8" ?> <Shellx:Class"mauiDemo.AppShel…...
社区养老服务平台的设计与实现(代码+数据库+LW)
摘 要 互联网发展至今,无论是其理论还是技术都已经成熟,而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播,搭配信息管理工具可以很好地为人们提供服务。针对信息管理混乱,出错率高,信息安全性差&#…...
生信软件管家——conda vs pip
pip vs conda: 安装过python包的人自然两种管理软件都用过, Pip install和Conda install在Python环境中用于安装第三方库和软件包,但它们在多个方面存在显著的区别 总的来说: pip是包管理软件,conda既是包管理软件&…...
项目文章 | PNAS 斑马鱼转录因子ChIP-seq助力解析GATA6突变相关的肝脏疾病机制
近日,西南大学阮华/黄红辉团队联合重庆大学邱菊辉/王贵学团队在PNAS发表了题为“An animal model recapitulates human hepatic diseases associated with GATA6 mutations”的研究论文。该研究构建了一个gata6敲除斑马鱼模型,它重现了gata6突变患者的大…...
JavaScript系列(44)--微服务架构实现详解
JavaScript微服务架构实现详解 🏗️ 今天,让我们来学习如何在JavaScript中实现微服务架构。微服务架构是一种将应用程序构建为一组小型服务的方法,每个服务运行在自己的进程中,并通过轻量级机制通信。 微服务基础概念 …...
Vue组件开发-使用 html2canvas 和 jspdf 库实现PDF文件导出 设置页面大小及方向
在 Vue 项目中实现导出 PDF 文件、调整文件页面大小和页面方向的功能,使用 html2canvas 将 HTML 内容转换为图片,再使用 jspdf 把图片添加到 PDF 文件中。以下是详细的实现步骤和代码示例: 步骤 1:安装依赖 首先,在项…...
Java-并发编程-特性-可见性-synchronized如何保证可见性?
synchronized 能保证可见性吗? 在Java并发编程中,synchronized 关键字不仅用于实现互斥访问,还能够保证内存可见性。理解这一点需要了解Java内存模型(Java Memory Model,JMM)以及happens-before࿰…...
iOS 权限管理:同时请求相机和麦克风权限的最佳实践
引言 在开发视频类应用时,我们常常会遇到需要同时请求相机和麦克风权限的场景。比如,在用户发布视频动态时,相机用于捕捉画面,麦克风用于录制声音;又或者在直播功能中,只有获得这两项权限,用户…...
【深入理解FFMPEG】命令行阅读笔记
这里写自定义目录标题 第三章 FFmpeg工具使用基础3.1 ffmpeg常用命令3.1.13.1.3 转码流程 3.2 ffprobe 常用命令3.2.1 ffprobe常用参数3.2.2 ffprobe 使用示例 3.3 ffplay常用命令3.3.1 ffplay常用参数3.3.2 ffplay高级参数3.3.4 ffplay快捷键 第4章 封装与解封装4.1 视频文件转…...
数据结构:二叉树—面试题(二)
1、二叉树的最近公共祖先 习题链接https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/description/ 描述: 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点…...
【C++高并发服务器WebServer】-6:信号
本文目录 信号的概念1.1 core文件1.2 kill命令1.3 alarm函数1.4 setitimer调用1.5 signal捕捉信号1.6 信号集1.7 内核实现信号捕捉的过程1.8 sigaction1.9 sigchld 信号的概念 信号是 Linux 进程间通信的最古老的方式之一,是事件发生时对进程的通知机制,…...
《探秘人工智能:从基础到未来变革》
在当今科技飞速发展的时代,人工智能(AI)无疑是最具影响力和变革性的技术之一。从手机里智能语音助手到自动驾驶汽车,从智能医疗诊断到智能金融服务,人工智能已经渗透到我们生活和工作的方方面面,悄然改变着…...
【数据分享】1929-2024年全球站点的逐月平均能见度(Shp\Excel\免费获取)
气象数据是在各项研究中都经常使用的数据,气象指标包括气温、风速、降水、湿度等指标!说到气象数据,最详细的气象数据是具体到气象监测站点的数据! 有关气象指标的监测站点数据,之前我们分享过1929-2024年全球气象站点…...
【PyTorch】3.张量类型转换
个人主页:Icomi 在深度学习蓬勃发展的当下,PyTorch 是不可或缺的工具。它作为强大的深度学习框架,为构建和训练神经网络提供了高效且灵活的平台。神经网络作为人工智能的核心技术,能够处理复杂的数据模式。通过 PyTorch࿰…...
不解释快上车
聊一聊 最近有小伙伴问我有小红书图片和短视频下载的软件吗,我心想,下载那上面的图片和视频做什么?也许是自己没有这方面的需求,不了解。 不过话又说回来,有些很多下载器可能作者没有持续的维护,所以可能…...
C++红黑树详解
文章目录 红黑树概念规则为什么最长路径不超过最短路径的二倍?红黑树的时间复杂度红黑树的结构插入叔叔节点情况的讨论只变色(叔叔存在且为红)抽象的情况变色单旋(叔叔不存在或叔叔存在且为黑)变色双旋(叔叔不存在或叔叔存在且为黑…...
csapp2.4节——浮点数
目录 二进制小数 十进制小数转二进制小数 IEEE浮点表示 规格化表示 非规格化表示 特殊值 舍入 浮点运算 二进制小数 类比十进制中的小数,可定义出二进制小数 例如1010.0101 小数点后的权重从-1开始递减。 十进制小数转二进制小数 整数部分使用辗转相除…...
神经网络|(一)加权平均法,感知机和神经元
【1】引言 从这篇文章开始,将记述对神经网络知识的探索。相关文章都是学习过程中的感悟和理解,如有雷同或者南辕北辙的表述,请大家多多包涵。 【2】加权平均法 在数学课本和数理统计课本中,我们总会遇到求一组数据平均值的做法…...
