使用单目相机前后帧特征点匹配进行3D深度估计的方法

在计算机视觉和机器人领域,三维空间感知是实现环境理解和交互的核心技术之一。特别是在资源受限的场合,使用针孔模型的单目相机进行深度估计成为了一种既经济又实用的解决方案。单目深度估计技术依赖于从连续视频帧中提取和匹配特征点,以估计场景中物体的三维位置。本文将探讨几种基于单目相机前后帧特征点匹配的3D深度估计方法。通过详细分析特征点匹配及其对深度恢复的贡献,我们旨在提供一种既精确又实用的深度估计技术,适用于从自动驾驶到增强现实等多种应用场景。

一、通过多视角立体几何 (Multi-view Stereo, MVS)的方法    

1. 原理

        特征点三角化是VSLAM中的一个基础问题,用于根据特征点在多个相机中的投影来恢复其在3D坐标中的位置。其的基本思想是:在某个相机中观测到的特征点可以通过相机位姿和观测向量来确定一条从相机中心出发的观测射线。当存在多个相机位姿观测时,会生成多条观测射线。理想情况下,这些观测射线会在空间中的一个点相交。通过求解所有观测射线的交点,可以确定特征点在3D空间中的位置。

        然而,在实际应用中,由于存在噪声,观测射线往往不会精确交于一点。为了确定特征点的坐标,有以下几种思路:

  1. 直观方法:寻找一个与所有观测射线距离都很近的三维点,将其作为特征点。

  2. 重投影误差最小化:认为误差源于二维图像观测,因此将特征点投影到每个相机平面,最小化所有二维投影点与对应观测点之间的距离(特征点的线性三角化(Triangulation)方法)。

  3. 在线滤波:三角化常常是在线进行的,即边获取观测边估计特征点。利用滤波器估计特征点的概率分布(通常为高斯分布),旧观测信息隐式存储在概率分布中。当新观测到来时,用其更新特征点的概率分布,节省计算量。这是滤波方法的基本思想。

        设3D特征点w_{\boldsymbol{p}_i}M个相机观测到,已知:相机姿态$\{_w^{c_j}R,{}^wt_{c_j}\}_{j=1,...,M}$,相机内参K,特征点在各相机的图像观测$\{z_{ij}\}_{j=1,...,M}$,求:特征点的3D坐标w_{\boldsymbol{p}_i}

$\left\{\{_{w}^{​{c_{j}}}R,{}^{w}t_{​{c_{j}}},z_{ij}\}_{j=1,...,M}\text{,}K\right\}\to{}^{w}p_{i}$

不考虑误差的情况有如下投影关系:

$s\begin{bmatrix}u_{ij}\\v_{ij}\\1\end{bmatrix}=K[_w^{c_j}R|-_w^{c_j}R^wt_{c_j}]\begin{bmatrix}x_i\\y_i\\z_i\\1\end{bmatrix}$

通过多视角立体几何 (Multi-view Stereo, MVS)的这类方法中,重点介绍思路2:重投影误差最小化。

设特征点齐次坐标$\mathbf{\bar{x}}=(x,y,z,1)$,在第j个相机的图像观测点齐次坐标为$\bar{\mathbf{z}}_{j}=(u_{j},v_{j},1)$投影矩阵为$P_j=K[R_j|t_j]$,投影模型如下:

$s\bar{\mathbf{z}}_j=s\begin{bmatrix}u_j\\v_j\\1\end{bmatrix}=K[R_j|t_j]\begin{bmatrix}x\\y\\z\\1\end{bmatrix}=P_j\bar{\mathbf{x}}=\begin{bmatrix}P_j^{(1)}\\P_j^{(2)}\\P_j^{(3)}\end{bmatrix}\bar{\mathbf{x}}$

\bar{\mathbf{z}}_j\times\bar{\mathbf{z}}_j=0可得\lfloor\bar{\mathbf{z}}_{j_\times}\rfloor P_j\bar{\mathbf{x}}=0,将其展开:

\begin{bmatrix}0&-1&v_j\\1&0&-u_j\\-v_j&u_j&0\end{bmatrix}\begin{bmatrix}P_j^{(1)}\\P_j^{(2)}\\P_j^{(3)}\end{bmatrix}\bar{\mathbf{x}}=\begin{bmatrix}-P_j^{(2)}+v_jP_j^{(3)}\\P_j^{(1)}-u_jP_j^{(3)}\\-v_jP_j^{(1)}+u_jP_j^{(2)}\end{bmatrix}\bar{\mathbf{x}}=\begin{bmatrix}0\\0\\0\end{bmatrix}

        所以只有两个线性无关的方程,每个相机观测有两个线性方程,将M个相机观测的约束方程合并得到2M个线性方程:

\underbrace{\left[\begin{array}{c} -P_1^{(2)} + v_1 P_1^{(3)} \\ P_1^{(1)} - u_1 P_1^{(3)} \\ -P_2^{(2)} + v_2 P_2^{(3)} \\ P_2^{(1)} - u_2 P_2^{(3)} \\ \vdots \\ -P_M^{(2)} + v_M P_M^{(3)} \\ P_M^{(1)} - u_M P_M^{(3)} \end{array}\right]}_{\mathbf{H}} \bar{\mathbf{x}} = 0

这里可以使用SVD求解,齐次坐标\bar{\mathrm{x}}即为 H的最小奇异值的奇异向量。

2. 代码

 代码来自:

orbslam2_learn/linear_triangular at master · yepeichu123/orbslam2_learn · GitHubicon-default.png?t=N7T8https://github.com/yepeichu123/orbslam2_learn/tree/master/linear_triangular

这里给代码添加了中文注释。

main.cpp

#include "linear_triangular.h"
// c++
#include <iostream>
#include <vector>
// opencv
#include <opencv2/calib3d/calib3d.hpp>
// pcl
#include <pcl-1.8/pcl/io/pcd_io.h>
#include <pcl-1.8/pcl/point_types.h>
#include <pcl-1.8/pcl/point_cloud.h>
#include <pcl-1.8/pcl/common/impl/io.hpp>
#include <pcl-1.8/pcl/visualization/cloud_viewer.h>using namespace cv;
using namespace std;
using namespace pcl;// 从图像中提取 ORB 特征并计算描述子
void featureExtraction( const Mat& img, vector<KeyPoint>& kpt, Mat& desp );// 通过暴力匹配方法进行特征匹配
// 根据匹配得分的阈值选择一些良好的匹配对
void featureMatching( const Mat& rDesp, const Mat& cDesp, vector<DMatch>& matches,const vector<KeyPoint>& rKpt, const vector<KeyPoint>& cKpt,vector<Point2d>& goodRKpt, vector<Point2d>& goodCKpt );// 通过对极几何(Epipolar geometry)估计运动
bool motionEstimation( const vector<Point2d>& goodRKpt, const vector<Point2d>& goodCKpt,const Mat& K, Mat& Rcr, Mat& tcr );// 比较我们的方法和 OpenCV 的三角化误差
void triangularByOpencv( const vector<Point2d>& RKpt, const vector<Point2d>& CKpt, const Mat& Trw, const Mat& Tcw, const Mat& K,vector<Point2d>& nRKpt, vector<Point2d>& nCKpt,vector<Point3d>& Pw3dCV );int main( int argc, char** argv )
{// 确保我们有正确的输入数据if( argc < 3 ){cout << "Please enter ./linear_triangular currImg refImg" << endl;return -1;}// 从输入路径读取图像Mat currImg = imread( argv[1], 1 );Mat refImg = imread( argv[2], 1 );// 根据数据集设置相机参数Mat K = ( Mat_<double>(3,3) << 517.3, 0, 318.6, 0, 516.5, 255.3, 0, 0, 1 );// 检测 ORB 特征并计算 ORB 描述子vector<KeyPoint> ckpt, rkpt;Mat cdesp, rdesp;featureExtraction( currImg, ckpt, cdesp );featureExtraction( refImg, rkpt, rdesp );cout << "finish feature extration!" << endl;// 特征匹配vector<DMatch> matches;vector<Point2d> goodCKpt, goodRKpt;featureMatching( rdesp, cdesp, matches, rkpt, ckpt, goodRKpt, goodCKpt );cout << "finish feature matching!" << endl;// 运动估计Mat Rcr, tcr;if( !motionEstimation(goodRKpt, goodCKpt, K, Rcr, tcr) ){return -1;}cout << "finish motion estimation!" << endl;// 构造变换矩阵Mat Trw = Mat::eye(4,4,CV_64F);Mat Tcr = (Mat_<double>(3,4) << Rcr.at<double>(0,0), Rcr.at<double>(0,1), Rcr.at<double>(0,2), tcr.at<double>(0),Rcr.at<double>(1,0), Rcr.at<double>(1,1), Rcr.at<double>(1,2), tcr.at<double>(1),Rcr.at<double>(2,0), Rcr.at<double>(2,1), Rcr.at<double>(2,2), tcr.at<double>(2),0, 0, 0, 1);// 传播变换Mat Tcw = Tcr * Trw;Trw = Trw.rowRange(0,3).clone();Tcw = Tcw.rowRange(0,3).clone();                             cout << "Tcw = " << Tcw << endl;cout << "Trw = " << Trw << endl;cout << "现在我们通过线性三角化来三角化匹配对。" << endl;// 根据我们的博客开始三角化vector<Point3d> Pw3d;vector<Point2d> Pr2d;XIAOC::LinearTriangular myTriangular( K, Trw, Tcw );for( int i = 0; i < matches.size(); ++i ){Point3d pw;bool result = myTriangular.TriangularPoint( goodRKpt[i], goodCKpt[i], pw );// 检查三角化是否成功if( !result ){continue;}//cout << "pw in world coordinate is " << pw << endl;// 保存世界坐标系下的 3D 点Pw3d.push_back( pw );Pr2d.push_back( goodRKpt[i] );}// 根据 OpenCV 进行三角化vector<Point3d> Pw3dCV;vector<Point2d> nRKpt, nCKpt;triangularByOpencv( goodRKpt, goodCKpt, Trw, Tcw, K, nRKpt, nCKpt, Pw3dCV);// 重投影误差double errorOurs = 0, errorOpencv = 0;for( int i = 0; i < Pw3dCV.size(); ++i ){// 我们的三角化结果Point3d Pw = Pw3d[i];Point2d ppc = Point2d( Pw.x/Pw.z,Pw.y/Pw.z );// OpenCV 的三角化结果Point3d Pcv = Pw3dCV[i];Point2d ppcv = Point2d( Pcv.x/Pcv.z, Pcv.y/Pcv.z );// 参考归一化平面上的特征坐标Point2d ppr = nRKpt[i];errorOurs += norm( ppc - ppr );errorOpencv += norm( ppcv - ppr );}cout << "我们的总误差是 " << errorOurs << endl;cout << "OpenCV 的总误差是 " << errorOpencv << endl;cout << "我们的平均误差是 " << errorOurs / Pw3d.size() << endl;cout << "OpenCV 的平均误差是 " << errorOpencv / Pw3dCV.size() << endl;// 显示关键点Mat currOut;drawKeypoints( currImg, ckpt, currOut, Scalar::all(-1), DrawMatchesFlags::DRAW_RICH_KEYPOINTS );imshow( "CurrOut", currOut );waitKey(0);// 显示良好的匹配结果Mat goodMatchOut;drawMatches( refImg, rkpt, currImg, ckpt, matches, goodMatchOut );imshow( "goodMatchOut", goodMatchOut );waitKey(0);// 将 3D 点转换为点云进行显示PointCloud<PointXYZRGB>::Ptr cloud( new PointCloud<PointXYZRGB> );for( int i = 0; i < Pw3d.size(); ++i ){   PointXYZRGB point;Point3d p = Pw3d[i];Point2d pixel = Pr2d[i];point.x = p.x;point.y = p.y;point.z = p.z;point.r = refImg.at<Vec3b>(pixel.y,pixel.x)[0];point.g = refImg.at<Vec3b>(pixel.y,pixel.x)[1];point.b = refImg.at<Vec3b>(pixel.y,pixel.x)[2];cloud->push_back( point );}cout << "点云的数量是 " << cloud->size() << endl;visualization::CloudViewer viewer( "Viewer" );viewer.showCloud( cloud );while( !viewer.wasStopped() ){// loop loop loop~~~}cout << "成功!" << endl;return 0;
}// 特征提取并计算描述子
void featureExtraction( const Mat& img, vector<KeyPoint>& kpt, Mat& desp )
{// 设置要提取的特征数量Ptr<FeatureDetector> detector = ORB::create( 10000 );Ptr<DescriptorExtractor> descriptor = ORB::create();detector->detect( img, kpt );descriptor->compute( img, kpt, desp );
}// 通过暴力匹配方法进行特征匹配并找到良好的匹配
void featureMatching( const Mat& rDesp, const Mat& cDesp, vector<DMatch>& matches, const vector<KeyPoint>& rKpt, const vector<KeyPoint>& cKpt,vector<Point2d>& goodRKpt, vector<Point2d>& goodCKpt )
{// 通过暴力匹配方法进行粗匹配,以汉明距离为度量Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create( "BruteForce-Hamming" );vector<DMatch> initMatches;matcher->match( rDesp, cDesp, initMatches );cout << "初始匹配完成!" << endl;// 计算最大距离和最小距离double min_dist = min_element( initMatches.begin(), initMatches.end(), [](const DMatch& m1, const DMatch& m2) {return m1.distance<m2.distance;} )->distance;double max_dist = max_element( initMatches.begin(), initMatches.end(), [](const DMatch& m1, const DMatch& m2) {return m1.distance<m2.distance;} )->distance;// 找到良好的匹配并记录相应的像素坐标for( int i = 0; i < initMatches.size(); ++i ){if( initMatches[i].distance <= max(min_dist*2, 30.0) ){matches.push_back( initMatches[i] );goodRKpt.push_back( rKpt[initMatches[i].queryIdx].pt );goodCKpt.push_back( cKpt[initMatches[i].trainIdx].pt );}}
}// 估计当前帧和参考帧之间的运动
bool motionEstimation( const vector<Point2d>& goodRKpt, const vector<Point2d>& goodCKpt,const Mat& K, Mat& Rcr, Mat& tcr )
{// 根据对极几何计算本质矩阵Mat E = findEssentialMat( goodRKpt, goodCKpt, K, RANSAC );// 从本质矩阵恢复姿态int inliers = recoverPose( E, goodRKpt, goodCKpt, K, Rcr, tcr );// 确保我们有足够的内点进行三角化return inliers > 100;
}// 比较我们的方法和 OpenCV 的三角化误差
void triangularByOpencv( const vector<Point2d>& RKpt, const vector<Point2d>& CKpt, const Mat& Trw, const Mat& Tcw, const Mat& K, vector<Point2d>& nRKpt, vector<Point2d>& nCKpt,vector<Point3d>& Pw3dCV )
{Mat pts_4d;// 将特征点反投影到归一化平面for( int i = 0; i < RKpt.size(); ++i ){Point2d pr( (RKpt[i].x-K.at<double>(0,2))/K.at<double>(0,0),(RKpt[i].y-K.at<double>(1,2))/K.at<double>(1,1) );Point2d pc( (CKpt[i].x-K.at<double>(0,2))/K.at<double>(0,0),(CKpt[i].y-K.at<double>(1,2))/K.at<double>(1,1) );nRKpt.push_back( pr );nCKpt.push_back( pc );                }// 三角化点triangulatePoints( Trw, Tcw, nRKpt, nCKpt, pts_4d );// 从齐次坐标中恢复位置for( int i = 0; i < pts_4d.cols; ++i ){Mat x = pts_4d.col(i);x /= x.at<double>(3,0);Point3d pcv( x.at<double>(0,0), x.at<double>(1,0), x.at<double>(2,0));Pw3dCV.push_back( pcv );}  cout << "pw3d 的大小 =  " << Pw3dCV.size() << endl;
}

liner_triangular.cpp


#include "linear_triangular.h"
#include <iostream>// 构造函数:设置相机参数和变换矩阵
XIAOC::LinearTriangular::LinearTriangular(const cv::Mat& K, const cv::Mat& Trw, const cv::Mat& Tcw):mK_(K), mTrw_(Trw), mTcw_(Tcw)
{}// 从两个视图的特征点三角化出 3D 点
// 输入:2D pr 和 2D pc,分别是参考帧和当前帧中的像素坐标
// 输出:3D Pw,即世界坐标系下的 3D 坐标
bool XIAOC::LinearTriangular::TriangularPoint(const cv::Point2d& pr, const cv::Point2d& pc, cv::Point3d& Pw )
{// 反投影到归一化平面UnprojectPixel( pr, pc );// 构造矩阵 AConstructMatrixA( mPrn_, mPcn_, mTrw_, mTcw_ );// 获取 3D 位置if( CompBySVD( mA_, mPw_ ) ){Pw = mPw_;return true;}return false;
}// 将像素点反投影到归一化平面
void XIAOC::LinearTriangular::UnprojectPixel(const cv::Point2d& pr, const cv::Point2d& pc)
{// X = (u-cx)/fx;// Y = (v-cy)/fy;mPrn_.x = (pr.x - mK_.at<double>(0,2))/mK_.at<double>(0,0);mPrn_.y = (pr.y - mK_.at<double>(1,2))/mK_.at<double>(1,1);mPrn_.z  = 1;mPcn_.x = (pc.x - mK_.at<double>(0,2))/mK_.at<double>(0,0);mPcn_.y = (pc.y - mK_.at<double>(1,2))/mK_.at<double>(1,1);mPcn_.z = 1;
}// 根据我们的博客构造矩阵 A
void XIAOC::LinearTriangular::ConstructMatrixA(const cv::Point3d& Prn, const cv::Point3d& Pcn, const cv::Mat& Trw, const cv::Mat& Tcw )
{// ORBSLAM 方法/* cv::Mat A(4,4,CV_64F);A.row(0) = Prn.x*Trw.row(2)-Trw.row(0);A.row(1) = Prn.y*Trw.row(2)-Trw.row(1);A.row(2) = Pcn.x*Tcw.row(2)-Tcw.row(0);A.row(3) = Pcn.y*Tcw.row(2)-Tcw.row(1);A.copyTo( mA_ );*/// 我博客中的原始方法cv::Mat PrnX = (cv::Mat_<double>(3,3) << 0, -Prn.z, Prn.y, Prn.z, 0, -Prn.x,-Prn.y, Prn.x, 0);cv::Mat PcnX = (cv::Mat_<double>(3,3) << 0, -Pcn.z, Pcn.y, Pcn.z, 0, -Pcn.x,-Pcn.y, Pcn.x, 0);// A = [prX*Trw; pcX*Tcw ]// APw = 0cv::Mat B = PrnX * Trw;cv::Mat C = PcnX * Tcw;cv::vconcat( B, C, mA_ );}//  通过计算 SVD 求解问题
bool XIAOC::LinearTriangular::CompBySVD(const cv::Mat& A, cv::Point3d& Pw )
{if( A.empty() ){return false;}// 计算矩阵 A 的 SVDcv::Mat w, u, vt;cv::SVD::compute( A, w, u, vt, cv::SVD::MODIFY_A|cv::SVD::FULL_UV );// 归一化cv::Mat Pw3d;Pw3d = vt.row(3).t();if( Pw3d.at<double>(3) == 0 ){return false;}Pw3d = Pw3d.rowRange(0,3)/Pw3d.at<double>(3);// 保存位置的值Pw.x = Pw3d.at<double>(0);Pw.y = Pw3d.at<double>(1);Pw.z = Pw3d.at<double>(2);return true;
}// 检查两个视图之间特征点的角度
bool XIAOC::LinearTriangular::CheckCrossAngle( const cv::Point3d& Prn, const cv::Point3d& Pcn, const cv::Mat& Trw, const cv::Mat& Tcw )
{// 待办:通过角度检查是否适合三角化
}// 检查我们三角化得到的点的深度是否正确
bool XIAOC::LinearTriangular::CheckDepth( const cv::Point3d& Pw3d )
{// 待办:通过深度检查是否适合接受
}

CMakeList.txt 

cmake_minimum_required( VERSION 2.8 )
project( linear_triangular )set( CMAKE_BUILD_TYPE "Release" )
set( CMAKE_CXX_FLAGS "-std=c++11" )
set( EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin )
set( LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib )find_package( OpenCV 3.0 REQUIRED )
include_directories( ${OpenCV_INCLUDE_DIRS} )find_package( PCL REQUIRED )
include_directories( ${PCL_INCLUDE_DIRS} )
link_directories( ${PCL_LIBRARY_DIRS} )
add_definitions( ${PCL_DEFINITIONS} )include_directories( ${PROJECT_SOURCE_DIR}/include )
#add_subdirectory( ${PROJECT_SOURCE_DIR} )add_executable( linear_triangular src/linear_triangular.cpp src/main.cpp ) 
target_link_libraries( linear_triangular ${OpenCV_LIBS} ${PCL_LIBRARIES} ) 

3. 结果 

二、通过端到端深度学习方法估计单目深度---DPT

      Ranftl et al. (2020) - 提出了一种基于Transformer的深度估计方法,称为Vision Transformer for Dense Prediction (DPT)。该方法利用了Transformer强大的全局建模能力,通过self-attention机制学习像素之间的长距离依赖关系。同时,他们还设计了一种多尺度融合策略,以结合不同层次的特征信息。DPT在多个数据集上都取得了最先进的性能。

1. 原理

        根据论文 "Vision Transformers for Dense Prediction" 的内容,Dense Prediction Transformers (DPT) 的主要原理可以总结如下:DPT在编码器-解码器架构中利用视觉transformer (ViT)作为骨干网络,用于深度估计和语义分割等密集预测任务。与逐步下采样特征图的卷积骨干网络不同,ViT骨干网络通过对分块/令牌进行操作,在整个过程中保持恒定的空间分辨率。在每个阶段,ViT通过自注意力机制具有全局感受野。这允许在高分辨率下捕获长距离依赖关系。来自不同ViT阶段的令牌被重组为多尺度的类图像特征。然后,卷积解码器融合这些特征并对其进行上采样,以获得最终的密集预测。通过在每个阶段保持高分辨率并具有全局感受野,与全卷积网络相比,DPT能够提供细粒度和全局连贯的预测。当有大量训练数据可用时,DPT在先前技术的基础上大幅改进,在具有挑战性的数据集上设置了新的最先进水平。
        总之,DPT的创新之处在于使用了ViT骨干,该骨干在每个阶段以全局上下文处理高分辨率特征,然后由卷积解码器将其合并为细粒度预测,从而在密集预测任务上显著提高了性能。
不是搞深度学习的我也看不懂啊,读者自行分别吧。这个坑后面再填

2. 代码

代码连接: https://github.com/intel-isl/DPTicon-default.png?t=N7T8https://github.com/intel-isl/DPT论文连接:

https://arxiv.org/pdf/2103.13413icon-default.png?t=N7T8https://arxiv.org/pdf/2103.13413

3. 结果


 

参考文章:

 VSLAM中的特征点三角化 - 知乎 (zhihu.com)

SLAM--三角化求解3D空间点_jacobisvd(eigen::computefullv).matrixv()-CSDN博客

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

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

相关文章

理解JavaScript递归

什么是递归 程序调用自身的编程技巧称为递归&#xff08;recursion&#xff09; 递归的基本思想是将一个复杂的问题分解成更小、更易于管理的子问题&#xff0c;这些子问题与原始问题相似&#xff0c;但规模更小。 递归的要素 基本情况&#xff08;Base Case&#xff09;&…

2024年第十届中西部外语翻译大赛

2024年第十届中西部外语翻译大赛 竞赛信息 “由中西部翻译协会共同体指导发起&#xff0c;各省市译协共建学术指导委员会&#xff0c;2024年第十届中西部外语翻译大赛由中西部翻译协会共同体秘书处&#xff08;武汉公仪网络科技有限公司&#xff09;承办。” - 获奖证书样图 -…

开发板连接电机,烧坏芯片的原因、解决

当使用开发板、核心板&#xff0c;连接电机驱动板&#xff0c;控制电机的转动&#xff0c;会很容易烧芯片。 极少数是通电就烧坏&#xff0c;有些是调试了一段时间才烧&#xff0c;也有些是稳定运行好些日子突然烧了...... 百度搜索&#xff1a;“STM32 电机 烧坏”&#xff…

python文件操作常用方法(读写txt、xlsx、CSV、和json文件)

引言 用代码做分析的时候经常需要保存中间成果&#xff0c;保存文件格式各不相同&#xff0c;在这里好好总结一下关于python文件操作的方法和注意事项 Python 提供了丰富的文件操作功能&#xff0c;允许我们创建、读取、更新和删除文件。允许程序与外部世界进行交互。 文章目录…

【C++】从零开始构建二叉搜索树

送给大家一句话&#xff1a; 我们始终有选择的自由。选错了&#xff0c;只要真诚的反思&#xff0c;真诚面对&#xff0c;也随时有机会修正错误和选择。 – 《奇迹男孩(电影)》 &#x1f4bb;&#x1f4bb;&#x1f4bb;&#x1f4bb;&#x1f4bb;&#x1f4bb;&#x1f4bb;…

数据资源入表难在哪?今晚带你一一弄懂(文末有福利)

​本周&#xff0c;我们即将开启数据要素系列直播《星光对话》的第四期&#xff0c;将由讲师-星光数智首席数据架构师 魏战松&#xff0c;于今晚19:00带来《数据资源入表和运营路径》的主题分享。 精彩内容提前知&#xff1a; 1、入表流程及各阶段参与方 2、入表难点和注意事项…

Android中使用Palette让你的页面UI优雅起来

文章目录 1. 什么是Palette2. 引入Palette3. 使用 Palette3.1 同步方式3.2 异步方式3.3 获取色调值 4. 举例4.1 布局文件 activity_palette_list.xml ⬇️4.2 Activity&#xff1a;PaletteListActivity⬇️4.3 列表Adapter&#xff1a;PaletteListAdapter ⬇️4.4 列表item布局…

「Python绘图」绘制同心圆

python 绘制同心圆 一、预期结果 二、核心代码 import turtle print("开始绘制同心圆") # 创建Turtle对象 pen turtle.Turtle() pen.shape("turtle") # 移动画笔到居中位置 pen.pensize(2) #设置外花边的大小 # 设置填充颜色 pen.fillcolor("green&…

java 并发线程应用

java 并发线程相关 线程状态 新建(NEW): 创建后尚未启动。可运行(RUNABLE): 正在 Java 虚拟机中运行。但是在操作系统层面,它可能处于运行状态,也可能等待资源调度(例如处理器资源),资源调度完成就进入运行状态。所以该状态的可运行是指可以被运行,具体有没有运行要看底层…

【C++算法】堆相关经典算法题

1.最后一块石头的重量 其实就是一个模拟的过程&#xff1a;每次从石堆中拿出最大的元素以及次大的元素&#xff0c;然后将它们粉碎&#xff1b;如果还有剩余&#xff0c;就将剩余的石头继续放在原始的石堆里面重复上面的操作&#xff0c;直到石堆里面只剩下一个元素&#xff0c…

[C/C++] -- 装饰器模式

装饰器模式是一种结构型设计模式&#xff0c;它允许在不改变原始对象的基础上动态地扩展其功能。这种模式通过将对象包装在装饰器类的对象中来实现&#xff0c;每个装饰器对象都包含一个原始对象&#xff0c;并可以在调用原始对象的方法之前或之后执行一些额外的操作。 装饰器…

自学C语言能达到什么境界呢?

C 语言是一门广泛应用于系统软件、嵌入式系统、游戏开发等领域的编程语言。那么&#xff0c;通过自学 C 语言&#xff0c;能够达到什么样的境界呢&#xff1f; 就像学习小提琴一样&#xff0c;仅凭自学也可以达到一定的水平&#xff0c;能够自娱自乐&#xff0c;在亲友聚会时表…

Xed编辑器开发第一期:使用Rust从0到1写一个文本编辑器

这是一个使用Rust实现的轻量化文本编辑器。学过Rust的都知道&#xff0c;Rust 从入门到实践中间还隔着好几个Go语言的难度&#xff0c;因此&#xff0c;如果你也正在学习Rust,那么恭喜你&#xff0c;这个项目被你捡到了。本项目内容较多&#xff0c;大概会分三期左右陆续发布&a…

NAS导航面板Sun-Panel

什么是 Sun-Panel &#xff1f; Sun-Panel 是一个服务器、NAS 导航面板、Homepage、浏览器首页。 软件主要特点&#xff1a; &#x1f349; 界面简洁&#xff0c;功能强大&#xff0c;资源消耗低&#x1f34a; 简单易用&#xff0c;可视化操作&#xff0c;零代码使用&#x1f…

python怎么安装matplotlib

1、登陆官方网址“https://pypi.org/project/matplotlib/#description”&#xff0c;下载安装包。 2、选择合适的安装包&#xff0c;下载下来。 3、将安装包放置到python交互命令窗口的当前目录下。 4、打开windows的命令行窗口&#xff0c;通过"pip install"这个命令…

新质生产力之工业互联网产业链

随着全球经济的数字化转型&#xff0c;新基建的概念逐渐成为推动工业发展的关键动力。在这一转型过程中&#xff0c;工业互联网作为新基建的核心组成部分&#xff0c;正逐渐塑造着未来工业的面貌。那么工业互联网产业链是如何构成的&#xff0c;以及它如何成为推动工业4.0和智能…

CRMEB开源打通版/标准版v4电商商城系统小程序发布之后无法生成海报问题

小程序产品分销二维码生成不了 开发者工具可以生成海报&#xff0c;但是发布之后无法生成 1.在开发者工具中&#xff0c;将不校验合法域名关闭 2.点击生成海报&#xff0c;查看console 3.将域名填写到微信公众平台小程序的download合法域名中 网址微信公众平台

react18【系列实用教程】memo —— 缓存组件 (2024最新版)

memo 的语法 如上图所示&#xff0c;在react中&#xff0c;当父组件重新渲染时&#xff0c;子组件也会重新渲染&#xff0c;即便子组件无任何变化&#xff0c;通过 memo 可以实现对组件的缓存&#xff0c;即当子组件无变化时&#xff0c;不再重新渲染子组件&#xff0c;核心代码…

【深度学习】Diffusion扩散模型的逆扩散问题

1、前言 上一篇&#xff0c;我们讲了Diffusion这个模型的原理推导。但在推导中&#xff0c;仍然遗留了一些问题。本文将解决那些问题 参考论文&#xff1a; ①Variational Diffusion Models (arxiv.org) ②Tutorial on Diffusion Models for Imaging and Vision (arxiv.org…