使用单目相机前后帧特征点匹配进行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 );
    // 构造矩阵 A
    ConstructMatrixA( 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 = 0
     cv::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 的 SVD
    cv::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博客

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

dbeaver 链接 Oceanbase 数据库,dbeaver安装数据库驱动

新增驱动 提前到Oceanbase官网下载好驱动 1、点击数据库 -> 驱动管理器 -> 新建 2、添加驱动文件 联接数据库 1、选择你添加的驱动 2、测试

CST电磁仿真软件远场变更和结果相关【从入门到精通】

1、使用阵列系数计算阵列远场结果 对单一天线进行 仿真分析后&#xff0c;查看反映阵列系数的远场结果&#xff01; Navigation Tree > Farfields > Selection > Farfield Plot > Array Factor 下面介绍一下&#xff0c;对单一天线进行仿真后&#xff0c;轻松计…

小白必看:新手学编程必会的100个代码

前言 我记得刚开始接触编程的时候&#xff0c;觉得太难了。 也很好奇&#xff0c;写代码的那些人也太厉害了吧&#xff1f;全是英文的&#xff0c;他们的英文水平一定很好吧&#xff1f; 他们是怎么记住这么多代码格式的&#xff1f;而且错了一个标点符号&#xff0c;整个程…

线性模型之岭回归的用法

实战&#xff1a;使用岭回归模型 完整代码&#xff1a; import numpy as np import matplotlib.pyplot as plt from sklearn.linear_model import LinearRegression from sklearn.datasets import make_regression from sklearn.model_selection import train_test_split fro…

C语言收尾 预处理相关知识

一. 预处理详解 1.1 预定义符号 FILE //进行编译的源文件LINE //文件当前的行号DATE //文件被编译的日期TIME //文件被编译的时间FUNCTION //文件当前所在的函数STDC //如果编译器遵循ANSI C标准&#xff0c;其值为1&#xff0c;否则未定义 这些预定义符号都是语言内置的 我们…

【教学类-55-04】20240515图层顺序挑战(四格长条纸加黑色边框、4*4、7张,不重复5400张,16坐标点颜色哈希值去重、保留7色)

背景需求&#xff1a; 前文实现了7张色彩纸条加上黑色边框的需求。 【教学类-55-02】20240512图层顺序挑战&#xff08;四格长条纸加黑色边框、4*4、7张 、43200张去掉非7色有23040张&#xff0c;哈希算法快速去重剩余1221张&#xff09;-CSDN博客文章浏览阅读1k次&#xff0…

反序列化漏洞【1】

1.不安全的反序列化漏洞介绍 序列化&#xff1a;将对象转换成字符串&#xff0c;目的是方便传输&#xff0c;关键词serialize a代表数组&#xff0c;数组里有三个元素&#xff0c;第一个元素下标为0&#xff0c;长度为7&#xff0c;内容为porsche&#xff1b;第二个元素下标为1…

工作达人的小秘密

在快节奏的工作环境中&#xff0c;想要提升效率&#xff0c;保持头脑清晰&#xff1f;别急&#xff0c;我这就为你揭秘我的几大法宝&#xff0c;让我们一起探索它们如何助你事半功倍&#xff01; 1️⃣【亿可达】 它是一款自动化工具&#xff0c;相当于国内版免费的zaiper。它…

新手也能看懂的前端单元测试框架:Vitest

单元测试的概念及作用 1.什么是单元测试&#xff1f; 单元测试是测试中的一个重要环节&#xff0c;它针对软件中的最小可测试单元进行验证&#xff0c;通常是指对代码中的单个函数、方法或模块进行测试。 单元测试旨在确定特定部分代码的行为是否符合预期&#xff0c;通过针…

LearnOpenGL(十八)之面剔除

一、面剔除 对于一个3D立方体&#xff0c;无论我们从哪个方向&#xff0c;最多只能同时看到3个面。如果我们能够以某种方式丢弃另外几个看不见的面&#xff0c;我们就能省下超过50%的片段着色器执行数&#xff01; 这正是面剔除(Face Culling)所做的。OpenGL能够检查所有面向…

在Linux系统上使用nmcli命令配置各种网络(有线、无线、vlan、vxlan、路由、网桥等)

前言&#xff1a;原文在我的博客网站中&#xff0c;持续更新数通、系统方面的知识&#xff0c;欢迎来访&#xff01; 在Linux系统上使用nmcli命令配置各种网络&#xff08;有线、无线、vlan、vxlan等&#xff09;https://myweb.myskillstree.cn/123.html 更新于2024/5/13&…

定时发圈操作介绍

1、登陆已有的账号&#xff0c;点击到"朋友圈"功能 2、选择要发圈的微信号&#xff0c;编辑发圈的文案内容 3、自定义想要的时间点 4、点击"立即发送" 5、可进行跟圈

AquaCrop模型运行及结果分析、代码解析;气象、土壤、作物和管理措施等数据的准备和输入;农业水资源管理

目录 专题一 模型原理与数据要求 专题二 模型数据准备 专题三 模型运行及结果分析 专题四 参数分析 专题五 源代码分析 更多应用 AquaCrop是由世界粮食及农业组织&#xff08;FAO&#xff09;开发的一个先进模型&#xff0c;旨在研究和优化农作物的水分生产效率。这个模型…

Invalid bound statement (not found) 六种解决方法

前五种参考博文&#xff1a; Invalid bound statement (not found) 五种解决方法-CSDN博客 第六种&#xff1a; 在启动类上加上MapperScan&#xff0c;指定扫描包

【刷题篇】二分查找(二)

文章目录 1、山脉数组的峰顶索引2、寻找峰值3、寻找旋转排序数组中的最小值4、LCR 点名 1、山脉数组的峰顶索引 符合下列属性的数组 arr 称为 山脉数组 &#xff1a; arr.length > 3 存在 i&#xff08;0 < i < arr.length - 1&#xff09;使得&#xff1a; arr[0] &l…

线性模型快速入门

使用matplotlib画一条直线 import numpy as np import matplotlib.pyplot as pltx np.linspace(-5, 5, 100) y 0.5*x 3plt.plot(x, y, c"orange") plt.title("Straight Line") plt.show()线性模型的直线表示 import numpy as np import matplotlib.py…

我与C++的爱恋:string类的常见接口函数

​ ​ &#x1f525;个人主页&#xff1a;guoguoqiang. &#x1f525;专栏&#xff1a;我与C的爱恋 朋友们大家好啊&#xff0c;本节我们来到STL内容的第一部分&#xff1a;string类接口函数的介绍 ​ ​ 1.string类的认识 给大家分享一个c文档 https://legacy.cplusplus.…

详细教程!VMware Workstation Pro16 安装 + 创建 win7 虚拟机!

嚯嚯嚯&#xff0c;很多宝子都想拥有自己不同的操作系统环境&#xff0c;用于学习或项目搭建。买服务器费钱&#xff0c;虚拟机则成为了一个很好的选择。本文详细介绍VMware Workstation Pro 16安装及win7虚拟机创建&#xff0c;保姆级教程奉上&#xff01; 一、准备工作 VMw…

【Android】重写onClick方法时,显示Method does not override method from its supperclass

问题 重写onClick方法时&#xff0c;显示Method does not override method from its supperclass 解决 在类上加implements View.OnClickListener

SC8908电机驱动芯片替代AN41908

SC8908 描述 五路H桥静音驱动电机驱动芯片&#xff0c;闭环直流电机光圈调节&#xff0c;支持霍尔位置检测&#xff0c; 2个步进电机。步进电机驱动带256微步细分。 主要特性 • 步进驱动H桥每路250mA最大驱动电流 • 光圈直流驱动H桥每路150mA最大驱动电流 • 单独…