Sparse ICP的使用(一)

一、代码下载以及修改

下载以及建立项目:

链接:palanglois/icpSparse: Implementation of the sparse icp algorithm (github.com)

如果github进不去,我这里下载好了:Sparseicp源码资源-CSDN文库 

下载好了之后,会有这些文件

 ccca7a2ffce74ac58162f5aa81ccab7c.png

首先visual studio项目,配置好PCL环境;ext文件夹包含了源码的依赖项Eigen和NanoFlann以及OptionParser,因为PCL自带Eigen所以ext文件夹的Eigen直接不要了,只要把NanoFlann和OptionParser的所有以cpp、h、hpp结尾的文件放入你的项目;lib文件夹则包含IcpOptimizer和ObjLoader,把只要把IcpOptimizer和ObjLoader的所有以cpp、h、hpp结尾的文件放入你的项目;接着把media放到你的项目路径下,media里面都是实验要用到的数据;最后把icpSparseDemo.h和main.cpp也放入项目即可;CMakeLists.txt不用放入项目,毕竟我是在windows平台,又不是在ubuntu。

6c203fd479f94ec281a7004a3827b043.png

修改 :

由于源码没有可视化代码,我这里修改了下,使得可以进行可视化,只要修改下面四个文件就行

ObjLoader.h

#ifndef OBJECT_LOADER_H
#define OBJECT_LOADER_H

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <sstream>
#include <algorithm>
#include <iterator>
#include <Eigen/Core>
#include <Eigen/Dense>
using namespace std;
using namespace Eigen;
class ObjectLoader
{
public:
	ObjectLoader();
	Eigen::Matrix<double, Eigen::Dynamic, 3> operator()(std::string filePath);
	void dumpToFile(Eigen::Matrix<double, Eigen::Dynamic, 3> vertice, Eigen::Matrix<double, Eigen::Dynamic, 3> normals, std::string filePath, string name);
private:
};

#endif

ObjLoader.cpp

#include "ObjLoader.h"



ObjectLoader::ObjectLoader()
{

}

/*This function loads the vertice contained in the 3d .obj file located in filePath
*/
Matrix<double, Dynamic, 3> ObjectLoader::operator()(string filePath)
{
    //Counting number of vertice in file
    ifstream fileCount(filePath.c_str());
    int nbVertice = 0;
    for (std::string line; std::getline(fileCount, line); )
    {
        if (line[0] == 'v' && line[1] == ' ')
            nbVertice++;
    }
    fileCount.close();

    //Filling the point cloud
    Eigen::Matrix<double, Eigen::Dynamic, 3> pointCloud;
    pointCloud.resize(nbVertice, 3);
    ifstream filein(filePath.c_str());
    int iterator = 0;
    for (std::string line; std::getline(filein, line); )
    {
        if (line[0] != 'v' || line[1] != ' ')
            continue;
        istringstream iss(line);
        vector<string> itemsInString{ istream_iterator<string>{iss},
                          istream_iterator<string>{} };
        if (itemsInString.size() != 4)
        {
            cout << "Problem when parsing vertex in file : " << filePath << endl;
            continue;
        }
        pointCloud(iterator, 0) = stod(itemsInString[1]);
        pointCloud(iterator, 1) = stod(itemsInString[2]);
        pointCloud(iterator, 2) = stod(itemsInString[3]);
        iterator++;
    }
    cout << "Point Cloud loaded with " << pointCloud.rows() << " vertice." << endl;
    filein.close();
    return pointCloud;
}

/* This function writes a .ply file at filePath that contains the vertice and normals as specified in the input variables
*/
void ObjectLoader::dumpToFile(Eigen::Matrix<double, Eigen::Dynamic, 3> vertice, Eigen::Matrix<double, Eigen::Dynamic, 3> normals, std::string filePath, string name)
{
    ofstream fileout(filePath.c_str());
    fileout << "ply" << endl
        << "format ascii 1.0" << endl
        << "element vertex " << vertice.rows() << endl
        << "property float x" << endl
        << "property float y" << endl
        << "property float z" << endl;
    //Writing header
    if (name == "result")
    {
        
        fileout << "property float nx" << endl
            << "property float ny" << endl
            << "property float nz" << endl;
            
    }
    fileout << "end_header" << endl;

    //Writing the vertice and normals
    for (int i = 0; i < vertice.rows(); i++)
    {
       
        if (name == "result")
        {
            fileout << vertice(i, 0) << " " << vertice(i, 1) << " " << vertice(i, 2) << " " << normals(i, 0) << " " << normals(i, 1) << " " << normals(i, 2) << endl;
        }
        else if (name == "source"|| name == "target")
        {
            fileout << vertice(i, 0) << " " << vertice(i, 1) << " " << vertice(i, 2) << endl;
        }

        
    }
        
        //fileout << vertice(i, 0) << " " << vertice(i, 1) << " " << vertice(i, 2) << " " << normals(i, 0) << " " << normals(i, 1) << " " << normals(i, 2) << endl;

}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

icpSparseDemo.h

#ifndef ICPSPARSEDEMO_H
#define ICPSPARSEDEMO_H

#include <iostream>
#include "ObjLoader.h"
#include "IcpOptimizer.h"
#include <pcl/io/ply_io.h>
#include <pcl/point_types.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <boost/thread/thread.hpp>
using namespace std;
using namespace Eigen;

class IcpSparseDemo
{
public:
    IcpSparseDemo()
    {

    }
    void view_show(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_target, pcl::PointCloud<pcl::PointXYZ>::Ptr result)
    {
        boost::shared_ptr<pcl::visualization::PCLVisualizer>viewer(new pcl::visualization::PCLVisualizer("PCL Viewer"));
        viewer->setBackgroundColor(0, 0, 0);
         对目标点云着色可视化 (red).
        pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ>target_color(cloud_target, 255, 0, 0);//红色
        viewer->addPointCloud<pcl::PointXYZ>(cloud_target, target_color, "target cloud");
        viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "target cloud");
        // 对配准点云着色可视化 (green).
        pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ>output_color(result, 0, 255, 0);//绿色
        viewer->addPointCloud<pcl::PointXYZ>(result, output_color, "output_color");
        viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "output_color");


        while (!viewer->wasStopped())
        {
            viewer->spinOnce(100);
            boost::this_thread::sleep(boost::posix_time::microseconds(1000));
        }
    }
    /*
    Run the demo
    */
    void run()
    {
        ///    First test with bunny    /
        cout << "Running demo for the bunny example" << endl;

        //Parameters
        size_t kNormals = 10;       //Number of nearest neighbours in order to estimate the normals
        int nbIterations = 25;      //Number of iterations for the algorithm
        int nbIterationsIn = 2;     //Number of iterations for the step 2 of the algorithm 
        double mu = 10.;            //Parameter for step 2.1
        int nbIterShrink = 3;       //Number of iterations for shrink step (2.1 also)
        double p = 0.5;             //We use the norm L_p
        bool verbose = false;       //Verbosity trigger
        IcpMethod method = pointToPlane; //Underlying ICP method

        //Finding the media directory
        string mediaDir = string("media/");
        mediaDir = mediaDir.substr(1, mediaDir.length() - 2);

        //Loading the point clouds
        ObjectLoader myLoader;
        Matrix<double, Dynamic, 3> pointCloudOne = myLoader(mediaDir + "bunny_side1.obj");
        Matrix<double, Dynamic, 3> pointCloudTwo = myLoader(mediaDir + "bunny_side2.obj");

        //Creating an IcpOptimizer in order to perform the sparse icp
        IcpOptimizer* myIcpOptimizer = new IcpOptimizer(pointCloudOne, pointCloudTwo, kNormals, nbIterations, nbIterationsIn, mu, nbIterShrink, p, method, verbose);

        //Perform ICP
        myIcpOptimizer->performSparceICP();
        PointCloud resultingCloud = myIcpOptimizer->getMovedPointCloud();
        myLoader.dumpToFile(resultingCloud, myIcpOptimizer->getMovedNormals(), mediaDir + "bunny_ICP_test.ply", "result");
        myIcpOptimizer->saveIter(mediaDir + "bunny_ICP_test.txt");
        cout << "Resulting point cloud is in media/bunny_ICP_test.ply" << endl;
        delete myIcpOptimizer;

        ///    Second test with bunny + 1000 noisy points per file    
        cout << "Running demo for the bunny + 1000 noisy points per files example" << endl;

        //Parameters
        kNormals = 10;         //Number of nearest neighbours in order to estimate the normals
        nbIterations = 25;     //Number of iterations for the algorithm
        nbIterationsIn = 2;    //Number of iterations for the step 2 of the algorithm 
        mu = 10.;              //Parameter for step 2.1
        nbIterShrink = 3;      //Number of iterations for shrink step (2.1 also)
        p = 0.5;               //We use the norm L_p
        verbose = false;       //Verbosity trigger
        method = pointToPlane; //Underlying ICP method

        //Loading the point clouds
        pointCloudOne = myLoader(mediaDir + "bunny_noised1.obj");
        pointCloudTwo = myLoader(mediaDir + "bunny_noised2.obj");

        //Creating an IcpOptimizer in order to perform the sparse icp
        IcpOptimizer* myIcpOptimizer2 = new IcpOptimizer(pointCloudOne, pointCloudTwo, kNormals, nbIterations, nbIterationsIn, mu, nbIterShrink, p, method, verbose);

        //Perform ICP
        myIcpOptimizer2->performSparceICP();
        resultingCloud = myIcpOptimizer2->getMovedPointCloud();
        myLoader.dumpToFile(resultingCloud, myIcpOptimizer2->getMovedNormals(), mediaDir + "bunny_noised_ICP.ply", "result");
        myIcpOptimizer2->saveIter(mediaDir + "bunny_noised_ICP.txt");
        cout << "Resulting point cloud is in media/bunny_noised_ICP.ply" << endl;
        delete myIcpOptimizer2;

    }

private:
};

#endif
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

main.cpp 

#ifdef _HAS_STD_BYTE
#undef _HAS_STD_BYTE
#endif
#define _HAS_STD_BYTE 0
#include <iostream>
#include "ObjLoader.h"
#include "IcpOptimizer.h"
#include "option_parser.h"
#include "icpSparseDemo.h"
#include <io.h>
#include <direct.h>

int main(int argc, char* argv[])
{
    //Create parsing options
    op::OptionParser opt;
    opt.add_option("-h", "--help", "show option help");
    opt.add_option("-d", "--demo", "demo mode (uses integrated media)");
    opt.add_option("-i1", "--input_1", "Path to first input obj file (REQUIRED)", "media/bunny_side1.obj");
    opt.add_option("-i2", "--input_2", "Path to second input obj file (REQUIRED)", "media/bunny_side2.obj");
    opt.add_option("-o", "--output", "Path to the output directory (REQUIRED)", "media/myout_bunny");
    opt.add_option("-n", "--name", "Name of the output file", "output");
    opt.add_option("-k", "--k_normals", "knn parameter for normals computation", "10");//10
    opt.add_option("-n1", "--n_iterations_1", "Nb of iterations for the algorithm", "25");//25
    opt.add_option("-n2", "--n_iterations_2", "Nb of iterations for the algorithm's step 2", "2");//2
    opt.add_option("-mu", "--mu", "Parameter for step 2.1", "10");//10
    opt.add_option("-ns", "--n_iterations_shrink", "Number of iterations for shrink step (2.1)", "3");//3
    //opt.add_option("-p", "--p_norm", "Use of norm L_p", "0.6");//0.6是适合兔子的参数
    opt.add_option("-p", "--p_norm", "Use of norm L_p", "2");//2是适合牛和猪的参数
    opt.add_option("-po", "--point_to_point", "Use point to point variant", "true");
    opt.add_option("-pl", "--point_to_plane", "Use point to plane variant");
    opt.add_option("-v", "--verbose", "Verbosity trigger");

    //Parsing options
    bool correctParsing = opt.parse_options(argc, argv);
    if (!correctParsing)
        return EXIT_FAILURE;

    //Parameters
    const string first_path = opt["-i1"];
    const string second_path = opt["-i2"];
    string output_path = opt["-o"];
    size_t kNormals = op::str2int(opt["-k"]);
    const int nbIterations = op::str2int(opt["-n1"]);
    const int nbIterationsIn = op::str2int(opt["-n2"]);
    const double mu = op::str2double(opt["-mu"]);
    const int nbIterShrink = op::str2int(opt["-ns"]);
    const double p = op::str2double(opt["-p"]);
    const bool verbose = op::str2bool(opt["-v"]);
    const bool demoMode = op::str2bool(opt["-d"]);
    const bool hasHelp = op::str2bool(opt["-h"]);

    const bool isPointToPoint = op::str2bool(opt["-po"]);
    const bool isPointToPlane = op::str2bool(opt["-pl"]);

    //Making checks
    IcpSparseDemo demo;
    if (demoMode)
    {
        //IcpSparseDemo demo;
        demo.run();
        return 0;
    }

    if (hasHelp)
    {
        opt.show_help();
        return 0;
    }

    if (first_path == "")
    {
        cerr << "Please specify the path of the first object file." << endl;
        opt.show_help();
        return EXIT_FAILURE;
    }

    if (second_path == "")
    {
        cerr << "Please specify the path of the second object file." << endl;
        opt.show_help();
        return EXIT_FAILURE;
    }

    if (output_path == "")
    {
        cerr << "Please specify the path of the output directory." << endl;
        opt.show_help();
        return EXIT_FAILURE;
    }
    if (_access(output_path.c_str(), 0) == -1)	//如果文件夹不存在
        _mkdir(output_path.c_str());
    if (output_path[output_path.size() - 1] != '/')
        output_path.append("/");
    string output_pc_path = output_path + opt["-n"] + ".ply";
    string output_iter_path = output_path + opt["-n"] + ".txt";
    string target_pc_path = output_path + opt["-n"] + "Target" + ".ply";
    string source_pc_path = output_path + opt["-n"] + "Source" + ".ply";
    if (isPointToPlane && isPointToPoint)
    {
        cerr << "Please choose only one ICP method !" << endl;
        opt.show_help();
        return EXIT_FAILURE;
    }

    IcpMethod method = pointToPoint;

    if (isPointToPlane)
        method = pointToPlane;
    else if (isPointToPoint)
        method = pointToPoint;
    else
    {
        cerr << "Please choose at least one ICP method (point to point or point to plane)." << endl;
        opt.show_help();
        return EXIT_FAILURE;
    }

    //Loading the point clouds
    ObjectLoader myLoader;
    Matrix<double, Dynamic, 3> pointCloudOne = myLoader(first_path);
    Matrix<double, Dynamic, 3> pointCloudTwo = myLoader(second_path);

    //Creating an IcpOptimizer in order to perform the sparse icp
    IcpOptimizer myIcpOptimizer(pointCloudOne, pointCloudTwo, kNormals, nbIterations, nbIterationsIn, mu, nbIterShrink, p, method, verbose);

    //Perform ICP
    int hasIcpFailed = myIcpOptimizer.performSparceICP();
    if (hasIcpFailed)
    {
        cerr << "Failed to load the point clouds. Check the paths." << endl;
        return EXIT_FAILURE;
    }
    PointCloud resultingCloud = myIcpOptimizer.getMovedPointCloud();

    //Save the resulting point cloud and iterations report
    myLoader.dumpToFile(resultingCloud, myIcpOptimizer.getMovedNormals(), output_pc_path, "result");
    myIcpOptimizer.saveIter(output_iter_path);
    myLoader.dumpToFile(pointCloudTwo, myIcpOptimizer.getMovedNormals(), target_pc_path, "target");
    myLoader.dumpToFile(pointCloudOne, myIcpOptimizer.getMovedNormals(), source_pc_path, "source");

    RigidTransfo resultingTransfo = myIcpOptimizer.getComputedTransfo();
    cout << "Computed Rotation : " << endl << resultingTransfo.first << endl << "Computed Translation : " << endl << resultingTransfo.second << endl;
    
    pcl::PointCloud<pcl::PointXYZ>::Ptr result(new pcl::PointCloud<pcl::PointXYZ>);
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_target(new pcl::PointCloud<pcl::PointXYZ>);
    if (pcl::io::loadPLYFile<pcl::PointXYZ>(output_pc_path, *result) == -1)
    {
        PCL_ERROR("加载点云失败\n");
    }
    if (pcl::io::loadPLYFile<pcl::PointXYZ>(source_pc_path, *cloud) == -1)
    {
        PCL_ERROR("加载点云失败\n");
    }
    if (pcl::io::loadPLYFile<pcl::PointXYZ>(target_pc_path, *cloud_target) == -1)
    {
        PCL_ERROR("加载点云失败\n");
    }
    //配准前
    demo.view_show(cloud_target, cloud);
    //配准后
    demo.view_show(cloud_target, result);

    return 0;
}


wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

使用:

main.cpp前面两行分别是用来设置输入点云和目标点云;第三行表示配准后的结果点云放置的文件夹位置,如果你用的是源码这个文件夹要提前建好,如果用的是我修改过的代码就不需要了,如果你想换一组点云配准,你最好把文件夹名字换个别的,不然会把之前点云配准结果覆盖掉;

第四行则是用来给点云命名的,就比如这行填的是output,下面的文件都是以output开头;这里我解释下为什么有四个文件,因为本人修改了源码,如果按源码来的话,只有两个文件,即output.ply和output.txt

671fa34c10854e7385f965b99c8ef17c.png1710f7745ce34b819f58eaa0c1902fbc.png 

main.cpp这两行决定配准是用点对点还是点对面的模式进行配准,选哪一个,就在哪个后面加上“true”,源码这里是一个“true”都没有,编译可以通过,但是根本不会帮你配准,所以这里一定要改

 1a236a7acb6a469ead89d265b772524f.png

 icpSparseDemo.h这里要修改文件路径,改成你自己的media存放的路径,我是放在我的项目文件夹里了,直接用相对路径

631c8fee0d2943aea773d2b8a6ce89d0.png

这几步改完之后,无论是源码或者我修改后的代码都可以运行 

结果:

配准前

66888d9f90f44e2dab31e6be7a3d29ca.png

 配准后,配准效果不错

d9c1abd1b79542a18b492406698d9df7.png

配准前,这里我特意用鼠标转了个角度,方便看清

6b56035c5bd64a4a9d76254a1b072f02.png 

配准后,配准效果还行

99a53e94fcd04af19abd7675892e404c.png 

配准前

fc823f83d2be43c08e885efada23e20e.png 

配准后,感觉效果不太好,可能还得调整参数

3a5ab1534b7b4b488c7002b5a2998a68.png 

 

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

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

相关文章

【关于python变量类型学习笔记】

python的变量类型 在创建变量时会在内存中开辟一个空间&#xff0c;变量是存储在内存中的值。 根据变量的数据类型&#xff0c;解释器会分配指定内存&#xff0c;并决定什么数据可以被存储在内存中。 变量可以指定不同的数据类型&#xff0c;这些变量可以存储整数&#xff0c;…

Canvas绘制

Canvas绘制 一、介绍效果图 二、画圆1 写一个页面2 画一个圆&#xff08;点&#xff09;3 效果 三 画直线1 写一个页面2 画直线3 效果 四 用直线连接两个点1 写一个页面2 连线3 效果 五 画随机点1 写一个页面2 随机点3 效果 六 画随机点并连线1 写一个页面2 画点连线3 效果 七 …

项目成本和收益管理,用易趋就够了,项目价值可量化

最近看到一个吐槽贴&#xff0c;项目经理小刘说&#xff0c;“去年很多项目都成功交付了&#xff0c;为啥项目奖金还是这么少呢&#xff1f;一问领导是由于项目的绩效没有达成&#xff0c;尤其是很多项目的成本都超支了。”总结来说&#xff0c;这主要是由于没有达成项目预期的…

理论学习-ARM-内核

ARM内核 函数的调用加载、存储计算中断异常线程的切换 为了提高学习效率&#xff0c;我们要提前想好学习策略。 首先&#xff0c;使用频率越高的知识点&#xff0c;越要首先学习。假使&#xff0c;我们学习了一个知识点&#xff0c;能覆盖工作中80%的工作量&#xff0c;那是不是…

MySQL数据库进阶第三篇(MySQL性能优化)

文章目录 一、插入数据优化二、主键优化三、order by优化四、group by优化五、limit优化六、count优化七、update优化&#xff08;避免行锁升级为表锁&#xff09; 这篇博客详细探讨了MySQL数据库的多项优化技巧。包括如何进行数据插入优化&#xff0c;采用批量插入和MySQL的lo…

四非保研之旅

大家好&#xff0c;我是工藤学编程&#xff0c;虽有万分感概&#xff0c;但是话不多说&#xff0c;先直接进入正题&#xff0c;抒情环节最后再说&#xff0c;哈哈哈 写在开头 我的分享是来给大家涨信心的&#xff0c;网上的大佬们都太强了&#xff0c;大家拿我涨涨信心&#…

在linux环境如何使用Anaconda安装指定的python版本

首先我们可以查看一下服务器现有的环境 conda info --envs 发现没有我需要的版本&#xff0c;那么可以用如下命令 conda create --name py36 python3.6 我这里安装了python 3.6的版本 再次输入 conda info --envs 可以通过以下命令激活刚刚创建的环境 conda activate py36…

Docker中如何删除某个镜像

1. 停止使用镜像的容器 首先&#xff0c;您需要停止所有正在使用该镜像的容器。您可以使用 docker stop 命令来停止容器&#xff1a; docker stop 11184993a106如果有多个容器使用该镜像&#xff0c;您需要对每个容器都执行停止命令。您可以通过 docker ps -a | grep core-ba…

C语言------------指针笔试题目深度剖析

1. #include <stdio.h> int main() { int a[5] { 1, 2, 3, 4, 5 }; int *ptr (int *)(&a 1); printf( "%d,%d", *(a 1), *(ptr - 1)); return 0; } 首先要明白这个强制类型转换&#xff0c;即int(*)[5]类型转换成int(*)类型&#xff1b; *&#xff…

联发科将展示6G环境运算和次世代卫星宽带 | 百能云芯

联发科技术有限公司&#xff08;MediaTek&#xff09;近日宣布&#xff0c;将在2024年世界移动通信大会&#xff08;MWC&#xff09;上展示其在移动通信技术领域的最新成就&#xff0c;包括6G环境运算、Pre-6G卫星宽带以及智能手机生成式人工智能&#xff08;AI&#xff09;应用…

相机图像质量研究(40)常见问题总结:显示器对成像的影响--画面泛白

系列文章目录 相机图像质量研究(1)Camera成像流程介绍 相机图像质量研究(2)ISP专用平台调优介绍 相机图像质量研究(3)图像质量测试介绍 相机图像质量研究(4)常见问题总结&#xff1a;光学结构对成像的影响--焦距 相机图像质量研究(5)常见问题总结&#xff1a;光学结构对成…

C++学习Day08之类模板中的成员函数分文件编写问题及解决

目录 一、程序及输出1.1 .h文件cpp1.2 包含hpp 二、分析与总结 一、程序及输出 1.1 .h文件cpp person.h #pragma once #define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std;template<class T1, class T2> class Person { public:Person(T1…

判断一个dll/exe是32位还是64位

通过记事本判断&#xff08;可判断C或者C#&#xff09; 64位、将dll用记事本打开&#xff0c;可以看到一堆乱码&#xff0c;但是找到乱码行的第一个PE&#xff0c;如果后面是d?则为64位 32位、将dll用记事本打开&#xff0c;可以看到一堆乱码&#xff0c;但是找到乱码行的第…

三防加固平板在房地产行业的应用|亿道三防onerugged

近期&#xff0c;有一款引人注目的解决方案——亿道三防onerugged平板电脑&#xff0c;它以其出色的性能和多功能的设计&#xff0c;为房地产行业带来了全新的应用体验。 首先&#xff0c;亿道三防onerugged平板电脑的NFC功能在小区业主身份验证中发挥着重要作用。传统的身份验…

Excel SUMPRODUCT函数用法(乘积求和,分组排序)

SUMPRODUCT函数是Excel中功能比较强大的一个函数&#xff0c;可以实现sum,count等函数的功能&#xff0c;也可以实现一些基础函数无法直接实现的功能&#xff0c;常用来进行分类汇总&#xff0c;分组排序等 SUMPRODUCT 函数基础 SUMPRODUCT函数先计算多个数组的元素之间的乘积…

【RL】Policy Gradient Methods(策略梯度方法)

Lecture 9: Policy Gradient Methods Basic idea of policy gradient 之前&#xff0c;policy是用表格表示的&#xff1a; 所有state的action概率都存储在表 π ( a ∣ s ) \pi(a|s) π(a∣s)中。 表的每个条目都由state和action索引。 因此可以直接访问或更改表中的值。 …

药物检测设备行业分析:市场年均复合增长速度为14.04%

在制药行业中&#xff0c;质量检验检测过程尤为重要。因为药品质量关系到人们的身体健康&#xff0c;如何控制好药品的质量安全&#xff0c;做好药品生产管理过程中的质量风险管理工作&#xff0c;是药品生产企业面临的重要问题。 为保证做好药品质量、安全方面的控制&#xff…

☀️将大华摄像头画面接入Unity 【1】配置硬件和初始化摄像头

一、硬件准备 目前的设想是后期采用网口供电的形式把画面传出来&#xff0c;所以这边我除了大华摄像头还准备了POE供电交换机&#xff0c;为了方便索性都用大华的了&#xff0c;然后全都连接电脑主机即可。 二、软件准备 这边初始化摄像头需要用到大华的Configtool软件&#…

ipad作为扩展屏的最简单方式(无需数据线)

ipad和win都下载安装toDesk&#xff0c;并且都处于同一局域网下 连接ipad&#xff0c;在ipad中输入win设备的设备密码和临时密码&#xff0c;连接上后可以看到ipad会是win屏幕的镜像&#xff0c;此时退出连接&#xff0c;准备以扩展模式再次连接。 注意&#xff0c;如果直接从…

#gStore-weekly | gMaster功能详解之数据库管理

gMaster提供了数据库管理功能。该功能可以对集群中的数据库进行集中管理&#xff0c;可以查看各个数据库详细信息。能够方便的对数据库进行新建、构建、导出、备份、还原、删除操作。 登录gMaster&#xff0c;点击左侧菜单【数据库】下的【数据库管理】&#xff0c;进入数据库…
最新文章