KinectFusion中的ICP算法

news2024/9/20 10:51:32

投影数据关联-求匹配点

利用算法projective data association对前一帧和当前帧的(Vertex、Normal)进行匹配,算法如下:

  1. 在当前帧 i 的深度图像上的每一个像素 U并行计算;
  2. 对于深度值大于0的像素,求该像素点对应的顶点在上一帧所处的相机坐标系下的位置Vi-1:方法是用上一帧的相机位姿Tg,k-1的逆矩阵左乘上一帧中该点对应的像素点的全局坐标Vi-1(g);
  3. 将Vi-1投影到像素平面得到p;
  4. 对于p属于当前帧范围内的点(说明该顶点在上一帧中也在相机视口范围内),用上一帧的位姿Tg,k-1左乘该点 Vi§(注:这是本帧中像素p对应的Vertex) 将其投影到全局坐标中得V;
  5. 同上得全局坐标下法向量 N;
  6. 如果V和 Vi-1(g)的距离小于阈值,且 N 和 Ni-1(g) 的夹角小于阈值,则找到匹配点对。

Point-to-Plane ICP

使用点到平面(point-plane)误差度量的迭代最近点 (ICP) 算法已被证明比使用点到点(point-point)误差度量的算法收敛得更快。在 ICP 算法的每次迭代中,产生最小点到平面误差的相对位姿变化通常使用标准的非线性最小二乘法来解决。例如 Levenberg-Marquardt 方法。当使用点到平面误差度量时,最小化的对象是每个源点与其对应目标点的切平面之间的平方距离之和。在这里插入图片描述
详细介绍见点到面的ICP算法。

Frame.h

#pragma once


#include <Eigen/Dense>
#include <opencv2/opencv.hpp>


#define MINF -std::numeric_limits<float>::infinity()


class Frame
{
public:
    Frame(cv::Mat depthMap, cv::Mat colorMap, Eigen::Matrix3f& depthIntrinsics, Eigen::Matrix4f& depthExtrinsics);
    Eigen::Vector3f getVertexGlobal(size_t idx) const;
    Eigen::Vector3f getNormalGlobal(size_t idx) const;
    std::vector<Eigen::Vector3f>& getVertexMapGlobal();
    std::vector<Eigen::Vector3f>& getNormalMapGlobal();
    std::vector<Eigen::Vector3f>& getVertexMap();
    std::vector<Eigen::Vector3f>& getNormalMap();
    int getFrameHeight();
    int getFrameWidth();
    bool containsImgPoint(Eigen::Vector2i);
    Eigen::Vector3f projectPointIntoFrame(const Eigen::Vector3f& point);
    Eigen::Vector2i projectOntoImgPlane(const Eigen::Vector3f& point);

private:
	void computeVertexMap(cv::Mat depthMap, const Eigen::Matrix3f& depthIntrinsics);
    void computeNormalMap(cv::Mat depthMap);
    std::vector<Eigen::Vector3f> transformPoints(const std::vector<Eigen::Vector3f>& points, const Eigen::Matrix4f& transformation);
    std::vector<Eigen::Vector3f> rotatePoints(const std::vector<Eigen::Vector3f>& points, const Eigen::Matrix3f& rotation);

    int depthWidth;
    int depthHeight;
	cv::Mat depthMap;
	cv::Mat colorMap;
    std::shared_ptr<std::vector<Eigen::Vector3f>> mVertices;
    std::shared_ptr<std::vector<Eigen::Vector3f>> mNormals;
    std::shared_ptr<std::vector<Eigen::Vector3f>> mVerticesGlobal;
    std::shared_ptr<std::vector<Eigen::Vector3f>> mNormalsGlobal;
    Eigen::Matrix4f extrinsicMatrix;
    Eigen::Matrix3f intrinsicMatrix;
};

Frame.cpp

#include "Frame.h"


Frame::Frame(cv::Mat depthMap, cv::Mat colorMap, Eigen::Matrix3f& depthIntrinsics, Eigen::Matrix4f& depthExtrinsics): depthMap(depthMap), colorMap(colorMap) 
{
	depthWidth = depthMap.cols;
	depthHeight = depthMap.rows;
    computeVertexMap(depthMap, depthIntrinsics);
    computeNormalMap(depthMap);

    intrinsicMatrix = depthIntrinsics;
	extrinsicMatrix = depthExtrinsics;
	Eigen::Matrix4f extrinsicMatrixInv = extrinsicMatrix.inverse();
	const auto rotation = extrinsicMatrixInv.block(0, 0, 3, 3);
	mVerticesGlobal = std::make_shared<std::vector<Eigen::Vector3f>>(transformPoints(*mVertices, extrinsicMatrixInv));
	mNormalsGlobal = std::make_shared<std::vector<Eigen::Vector3f>>(rotatePoints(*mNormals, rotation));
}

Eigen::Vector3f Frame::getVertexGlobal(size_t idx) const { return mVerticesGlobal->at(idx); }

Eigen::Vector3f Frame::getNormalGlobal(size_t idx) const { return mNormalsGlobal->at(idx); }

bool Frame::containsImgPoint(Eigen::Vector2i imgPoint) 
{
    return imgPoint[0] >= 0 && imgPoint[0] < depthWidth && imgPoint[1] >= 0 && imgPoint[1] < depthHeight;
}

int Frame::getFrameHeight() { return depthHeight; }
int Frame::getFrameWidth() { return depthWidth; }

std::vector<Eigen::Vector3f>& Frame::getVertexMap() { return *mVertices; }
std::vector<Eigen::Vector3f>& Frame::getNormalMap() { return *mNormals; }
std::vector<Eigen::Vector3f>& Frame::getVertexMapGlobal() { return *mVerticesGlobal; }
std::vector<Eigen::Vector3f>& Frame::getNormalMapGlobal() { return *mNormalsGlobal; }

Eigen::Vector3f Frame::projectPointIntoFrame(const Eigen::Vector3f& point) 
{
    const auto rotation = extrinsicMatrix.block(0, 0, 3, 3);
    const auto translation = extrinsicMatrix.block(0, 3, 3, 1);
    return rotation * point + translation;
}

Eigen::Vector2i Frame::projectOntoImgPlane(const Eigen::Vector3f& point)
{
    Eigen::Vector3f projected = intrinsicMatrix * point;
    if (projected[2] == 0) return Eigen::Vector2i(MINF, MINF);
    projected /= projected[2];
    return Eigen::Vector2i((int)round(projected.x()), (int)round(projected.y()));
}

std::vector<Eigen::Vector3f> Frame::transformPoints(const std::vector<Eigen::Vector3f>& points, const Eigen::Matrix4f& transformation) 
{
    const Eigen::Matrix3f rotation = transformation.block(0, 0, 3, 3);
    const Eigen::Vector3f translation = transformation.block(0, 3, 3, 1);
    std::vector<Eigen::Vector3f> transformed(points.size());
    for (size_t idx = 0; idx < points.size(); ++idx)
	{
        if (points[idx].allFinite())
            transformed[idx] = rotation * points[idx] + translation;
        else
            transformed[idx] = Eigen::Vector3f(MINF, MINF, MINF);
    }
    return transformed;
}

std::vector<Eigen::Vector3f> Frame::rotatePoints(const std::vector<Eigen::Vector3f>& points, const Eigen::Matrix3f& rotation) 
{
    std::vector<Eigen::Vector3f> transformed(points.size());
    for (size_t idx = 0; idx < points.size(); ++idx)
	{
        if (points[idx].allFinite())
            transformed[idx] = rotation * points[idx];
        else
            transformed[idx] = Eigen::Vector3f(MINF, MINF, MINF);
    }
    return transformed;
}

void Frame::computeVertexMap(cv::Mat depthMap, const Eigen::Matrix3f& depthIntrinsics)
{
	float fx = depthIntrinsics(0, 0);
	float fy = depthIntrinsics(1, 1);
	float cx = depthIntrinsics(0, 2);
	float cy = depthIntrinsics(1, 2);

	mVertices = std::make_shared<std::vector<Eigen::Vector3f>>(std::vector<Eigen::Vector3f>());
	mVertices->reserve(depthMap.rows * depthMap.cols);
	for (size_t i = 0; i < depthMap.rows; i++)
	{
		for (size_t j = 0; j < depthMap.cols; j++)
		{
			if (depthMap.at<ushort>(i, j) == 0)
				mVertices->emplace_back(Eigen::Vector3f(MINF, MINF, MINF));
			else
			{
				float depth = (float)depthMap.at<ushort>(i, j) / 5000.0f;
				mVertices->emplace_back(Eigen::Vector3f((j - cx) / fx * depth, (i - cy) / fy * depth, depth));
			}	
		}
	}
}

void Frame::computeNormalMap(cv::Mat depthMap)
{
    mNormals = std::make_shared<std::vector<Eigen::Vector3f>>(std::vector<Eigen::Vector3f>());
    mNormals->reserve(depthMap.rows * depthMap.cols);
    for (size_t i = 0; i < depthMap.rows; i++)
	{
        for (size_t j = 0; j < depthMap.cols; j++)
		{
            size_t idx = i * depthMap.cols + j;
            if (i == 0 || j == 0 || i == depthMap.rows - 1 || j == depthMap.cols - 1)
                mNormals->emplace_back(Eigen::Vector3f(MINF, MINF, MINF));
            else
			{
                Eigen::Vector3f du = mVertices->at(idx + 1) - mVertices->at(idx - 1);
                Eigen::Vector3f dv = mVertices->at(idx + depthMap.cols) - mVertices->at(idx - depthMap.cols);
                if (du.allFinite() && dv.allFinite()) 
                    mNormals->emplace_back(du.cross(dv).normalized());
                else 
                    mNormals->emplace_back(Eigen::Vector3f(MINF, MINF, MINF));
            }
        }
    }
}

ICP.h

#pragma once


#include <Eigen/Dense>
#include "Frame.h"


class ICP 
{
 public:
  ICP(Frame &_prevFrame, Frame &_curFrame, const double distanceThreshold, const double normalThreshold);
  Eigen::Matrix4f estimatePose(Eigen::Matrix4f& pose, int iterations = 10 );
  std::vector<std::pair<size_t, size_t>> findIndicesOfCorrespondingPoints(const Eigen::Matrix4f &pose);

 private:
  Frame &prevFrame;
  Frame &curFrame;
  const double distanceThreshold;
  const double normalThreshold;
};

ICP.cpp

#include "ICP.h"


ICP::ICP(Frame& _prevFrame, Frame& _curFrame, const double distanceThreshold, const double normalThreshold): 
	prevFrame(_prevFrame), curFrame(_curFrame), distanceThreshold(distanceThreshold), normalThreshold(normalThreshold) {}

Eigen::Matrix4f ICP::estimatePose(Eigen::Matrix4f& pose, int iterations) 
{
	for (size_t iteration = 0; iteration < iterations; iteration++)
	{
		const std::vector<std::pair<size_t, size_t>> correspondenceIds = findIndicesOfCorrespondingPoints(pose);
		std::cout <<"iteration: "<< iteration <<  "\t corresponding points: " << correspondenceIds.size() << std::endl;

		Eigen::Matrix3f rotationEP = pose.block(0, 0, 3, 3);
		Eigen::Vector3f translationEP = pose.block(0, 3, 3, 1);

		Eigen::MatrixXf A = Eigen::MatrixXf::Zero(correspondenceIds.size(), 6);
		Eigen::VectorXf b = Eigen::VectorXf::Zero(correspondenceIds.size());

		for (size_t i = 0; i < correspondenceIds.size(); i++)
		{
			const auto& x = rotationEP * curFrame.getVertexGlobal(correspondenceIds[i].second) + translationEP;
			const auto& y = prevFrame.getVertexGlobal(correspondenceIds[i].first);
			const auto& n = prevFrame.getNormalGlobal(correspondenceIds[i].first);

			A(i, 0) = n(2) * x(1) - n(1) * x(2);
			A(i, 1) = n(0) * x(2) - n(2) * x(0);
			A(i, 2) = n(1) * x(0) - n(0) * x(1);
			A(i, 3) = n(0);
			A(i, 4) = n(1);
			A(i, 5) = n(2);
			b(i) = n(0) * y(0) + n(1) * y(1) + n(2) * y(2) - n(0) * x(0) - n(1) * x(1) - n(2) * x(2);
		}

		Eigen::VectorXf x = A.bdcSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(b);

		float alpha = x(0), beta = x(1), gamma = x(2);
		Eigen::Vector3f translation = x.tail(3);
		Eigen::Matrix3f rotation = Eigen::AngleAxisf(alpha, Eigen::Vector3f::UnitX()).toRotationMatrix() *
			Eigen::AngleAxisf(beta, Eigen::Vector3f::UnitY()).toRotationMatrix() *
			Eigen::AngleAxisf(gamma, Eigen::Vector3f::UnitZ()).toRotationMatrix();

		Eigen::Matrix4f curentPose = Eigen::Matrix4f::Identity();
		curentPose.block<3, 3>(0, 0) = rotation;
		curentPose.block<3, 1>(0, 3) = translation;
		pose = curentPose * pose;
	}
	return pose;
}

std::vector<std::pair<size_t, size_t>> ICP::findIndicesOfCorrespondingPoints(const Eigen::Matrix4f & pose) 
{
	Eigen::Matrix4f estimatedPose = pose;
	std::vector<std::pair<size_t, size_t>> indicesOfCorrespondingPoints;

	std::vector<Eigen::Vector3f> prevFrameVertexMapGlobal = prevFrame.getVertexMapGlobal();
	std::vector<Eigen::Vector3f> prevFrameNormalMapGlobal = prevFrame.getNormalMapGlobal();

	std::vector<Eigen::Vector3f> curFrameVertexMapGlobal = curFrame.getVertexMapGlobal();
	std::vector<Eigen::Vector3f> curFrameNormalMapGlobal = curFrame.getNormalMapGlobal();

	const auto rotation = estimatedPose.block(0, 0, 3, 3);
	const auto translation = estimatedPose.block(0, 3, 3, 1);

	const auto estimatedPoseInv = estimatedPose.inverse();
	const auto rotationInv = estimatedPoseInv.block(0, 0, 3, 3);
	const auto translationInv = estimatedPoseInv.block(0, 3, 3, 1);

	for (size_t idx = 0; idx < prevFrameVertexMapGlobal.size(); idx++)
	{
		Eigen::Vector3f prevPointGlobal = prevFrameVertexMapGlobal[idx];
		Eigen::Vector3f prevNormalGlobal = prevFrameNormalMapGlobal[idx];
		if (prevPointGlobal.allFinite() && prevNormalGlobal.allFinite())
		{
			Eigen::Vector3f prevPointCurCamera = rotationInv * prevPointGlobal + translationInv;
			Eigen::Vector3f prevNormalCurCamera = rotationInv * prevFrameNormalMapGlobal[idx];

			// project point from global camera system into camera system of the current frame
			const Eigen::Vector3f prevPointCurFrame = curFrame.projectPointIntoFrame(prevPointCurCamera);
			// project point from camera system of the previous frame onto the image plane of the current frame
			const Eigen::Vector2i prevPointImgCoordCurFrame = curFrame.projectOntoImgPlane(prevPointCurFrame);

			if (curFrame.containsImgPoint(prevPointImgCoordCurFrame))
			{
				size_t curIdx = prevPointImgCoordCurFrame[1] * curFrame.getFrameWidth() + prevPointImgCoordCurFrame[0];
				Eigen::Vector3f curFramePointGlobal = rotation * curFrameVertexMapGlobal[curIdx] + translation;
				Eigen::Vector3f curFrameNormalGlobal = rotation * curFrameNormalMapGlobal[curIdx];

				if (curFramePointGlobal.allFinite() &&  curFrameNormalGlobal.allFinite() && (curFramePointGlobal - prevPointGlobal).norm() < distanceThreshold &&
					std::abs(curFrameNormalGlobal.dot(prevNormalGlobal)) / curFrameNormalGlobal.norm() / prevNormalGlobal.norm() < normalThreshold)
					indicesOfCorrespondingPoints.push_back(std::make_pair(idx, curIdx));
			}
		}
	}
	return indicesOfCorrespondingPoints;
}

main.cpp

#include "ICP.h"
#include "Frame.h"


#define DISTANCE_THRESHOLD 0.05
#define ANGLE_THRESHOLD 1.05
#define ICP_ITERATIONS 20


int main()
{
	Eigen::Matrix3f depthIntrinsics;
	depthIntrinsics << 525, 0, 319.5, 0, 525, 239.5, 0, 0, 1;
	Eigen::Matrix4f depthExtrinsics = Eigen::Matrix4f::Identity();
	Eigen::Matrix4f init_pose = Eigen::Matrix4f::Identity(4, 4);
	cv::Mat colorMap, depthMap;

	colorMap = cv::imread("./color/1305031102.175304.png", 1);
	depthMap = cv::imread("./depth/1305031102.160407.png", -1);
	Frame prevFrame(depthMap, colorMap, depthIntrinsics, depthExtrinsics);

	colorMap = cv::imread("./color/1305031102.211214.png", 1);
	depthMap = cv::imread("./depth/1305031102.194330.png", -1);
	Frame curFrame(depthMap, colorMap, depthIntrinsics, depthExtrinsics);

	ICP icp(prevFrame, curFrame, DISTANCE_THRESHOLD, ANGLE_THRESHOLD);
	std::cout << icp.estimatePose(init_pose, ICP_ITERATIONS) << std::endl;

	return 0;
}

运行结果
在这里插入图片描述

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

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

相关文章

从破解虫脑到攻克人脑:一条“永生之路”的新赛道?

从破解虫脑到攻克人脑&#xff1a;一条“永生之路”的新赛道&#xff1f; 首张果蝇大脑连接组&#xff1a;耗费十余年&#xff0c;重建三千神经元&#xff0c;超50万突触&#xff01; 论文地址 果蝇幼虫大脑的连接组。 所有脑神经元的形态学都经过了突触分辨率的电子显微镜成像…

聊天机器人开发实战--(微信小程序+SpringCloud+Pytorch+Flask)【后端部分】

文章目录 前言架构SpringCloud服务构建后台搭建Python服务调用 Python算法服务app 总结 前言 趁着五一有时间&#xff0c;先把大三下个学期的期末作业做了&#xff0c;把微信小程序和Java开发的一起做了。顺便把机器学习的也一起做了。所以的话&#xff0c;我们完整项目的技术…

如何用ChatGPT做书籍、报告、文件的读取与互动式问答?故事人物活起来

该场景对应的关键词库&#xff08;15个&#xff09;&#xff1a; 书籍、报告、文件、详细信息、查询、主题、作者、出版日期、出版社、问题、方面、原则、方法、概括、主要观点、解释。 注意&#xff1a; ChatGPT的知识库截止于2021年9月&#xff0c;对于更新的数据或最新出版…

系统化思维:大数中心原理与限制性选择原理。

系统化思维&#xff1a;大数中心原理与限制性选择原理TOC 许多人的思考特点都是混乱而复杂的&#xff0c;只有受过严格训练的人才能做到系统化思维。这里将讨论系统化思维的基础考量。 大数中心原理&#xff1a;大数中心原理是客观而真实的普遍存在&#xff0c;应用在思维上就…

ImageJ实践——拟合矩形选区探究(bounding rectangle),左侧优先法则

在上一篇ImageJ实践中ImageJ实践——测量大小/长短&#xff08;以细胞为例&#xff09;&#xff0c;我勾选了Set Measurements中的Bounding rectangle以测量细胞的长和宽&#xff08;实际上是拟合矩形的长短边&#xff09;&#xff0c;文末我也提出了自己的疑惑&#xff1a;拟合…

【GORM框架】模型定义超详解,确定不来看看?

博主简介&#xff1a;努力学习的大一在校计算机专业学生&#xff0c;热爱学习和创作。目前在学习和分享&#xff1a;数据结构、Go&#xff0c;Java等相关知识。博主主页&#xff1a; 是瑶瑶子啦所属专栏: GORM框架学习 近期目标&#xff1a;写好专栏的每一篇文章 目录 一、GORM…

Ansible自动化运维工具---Playbook

Ansible自动化运维工具--playbook 一、playbook1、playbook简介2、playbook应用场景3、yaml基本语法规则4、yaml支持数据结构 二、Inventory中的变量1、inventor变量参数 三、playbook实例1、编写httpd的playbook2、tasks列表和action3、条件测试4、迭代5、with_items模块6、te…

5.4.1树的存储结构 5.4.2树和森林的遍历

回忆一下树的逻辑结构&#xff1a; 双亲表示法&#xff08;顺序存储&#xff09; 如果增加一个结点M&#xff0c;L。毋须按照逻辑上的次序存储。 如果是删除元素&#xff1a; 方案一&#xff1a;比如说删除元素为G,设置其双亲结点为-1。 方案二&#xff1a; 把尾部的结点提上…

真题详解(对象)-软件设计(六十四)

真题详解(DNS)-软件设计&#xff08;六十三)https://blog.csdn.net/ke1ying/article/details/130448106 TCP和UCP都提供了_____能力。 端口寻址 解析&#xff1a; UDP是不可靠无连接协议&#xff0c;没有连接管理&#xff0c;没有流量控制&#xff0c;没有重试。 面向对象…

MySQL 常用命令

#--------------------------- #----cmd命令行连接MySql--------- cd C:\Program Files\MySQL\MySQL Server 5.5\bin # 启动mysql服务器 net start mysql # 关闭mysql服务器 net stop mysql # 进入mysql命令行 mysql -h localhost -u root -p 或mysql -u root -p #---------…

推荐算法实战项目:AutoRec模型原理以及案例实战(附完整 Python 代码)

本文要介绍的AutoRec模型是由澳大利亚国立大学在2015年提出的&#xff0c;它将自编码器(AutoEncoder)的思想与协同过滤(Collaborative Filter)的思想结合起来&#xff0c;提出了一种单隐层的简单神经网络推荐模型。 可以说这个模型的提出&#xff0c;拉开了使用深度学习解决推…

LVS - DR 模式集群搭建

VIPRIPweb1192.168.88.136httpdweb2192.168.88.139httpdLVS192.168.88.110192.168.88.144(DIP) 省略最后的共享存储&#xff0c;webserver内容不一致&#xff08;方便查看负载的效果&#xff09;&#xff0c;关闭防火墙&#xff0c;关闭se 1、配置web服务 web1和web2相同 y…

【文件描述符|重定向|缓冲区】

1 C语言文件操作的回顾 这块博主在讲解C语言时就已经做了很详细的讲解&#xff0c;这里就不详细讲了&#xff0c;直接给出代码。 写操作&#xff1a; #include<stdio.h> #include<stdlib.h> #include<errno.h> #define LOG "log.txt" …

【STM32CubeMX】外部中断

前言 本文记录下我学习STM32CubeMX时的流程&#xff0c;方便以后回忆。本章记录外部中断。 步骤 该实验步骤以&#xff0c;配置PA1为外部中断下降沿触发事件&#xff0c;在触发事件后点亮板载PC13LED灯 时钟配置和生成文件配置之类的&#xff0c;其它文章讲过了&#xff0c;这…

MySQL高级篇——性能分析工具

导航&#xff1a; 【黑马Java笔记踩坑汇总】JavaSEJavaWebSSMSpringBoot瑞吉外卖SpringCloud黑马旅游谷粒商城学成在线设计模式牛客面试题 目录 1. 数据库服务器的优化步骤 2. 查看系统性能参数 2.1 SHOW STATUS LIKE 参数 2.2 查看SQL的查询成本 3. 定位执行慢的 SQL&am…

【Java虚拟机】JVM垃圾回收机制和常见回收算法原理

1.垃圾回收机制 &#xff08;1&#xff09;什么是垃圾回收机制&#xff08;Garbage Collection&#xff0c; 简称GC) 指自动管理动态分配的内存空间的机制&#xff0c;自动回收不再使用的内存&#xff0c;以避免内存泄漏和内存溢出的问题最早是在1960年代提出的&#xff0c;程…

20230501-win10-制作U盘启动盘-firpe

20230501-win10-制作U盘启动盘-firpe 一、软件环境 zh-cn_windows_10_consumer_editions_version_22h2_updated_march_2023_x64_dvd_1e27e10b.isofirpe 1.8.2标签&#xff1a;firpe win10分栏&#xff1a;WINDOWS 二、硬件环境 8G或以上的U盘一个FX86笔记本一台 三、官方下…

2。硬件基础知识

介绍嵌入式软件开发所需要了解的硬件基础知识&#xff0c;与软件相结合学习 一 电阻 阻值&#xff1a;直标法&#xff0c;或色标法&#xff08;碳膜电阻上的横线&#xff09; 类型&#xff1a;线性&#xff0c;非线性&#xff08;压敏电阻、热敏电阻&#xff09; 基本参数&a…

HQL - 查询首次下单后第二天连续下单的用户比率

水善利万物而不争&#xff0c;处众人之所恶&#xff0c;故几于道&#x1f4a6; 题目&#xff1a; 从订单信息表(order_info)中查询首次下单后第二天仍然下单的用户占所有下单用户的比例&#xff0c;结果保留一位小数&#xff0c;使用百分数显示&#xff0c; 解题&#xff1a; …