LeGO LOAM坐标系问题的自我思考
LeGO LOAM坐标系问题的自我思考
- IMU坐标系
- LeGO LOAM代码分析
- 代码
- 对于IMU输出测量值的integration积分过程
- 欧拉角的旋转矩阵
- VeloToStartIMU()函数
- TransformToStartIMU(PointType *p)
IMU坐标系
在LeGO LOAM中IMU坐标系的形式采用前(x)-左(y)-上(z)的形式,IMU坐标系同时可以表示为载体坐标系(body frame),世界坐标系表示的形式采用左(x)-上(y)-前(z)的形式,世界坐标系也可以称为world系,世界坐标系作为一种固定的坐标系,而IMU坐标系作为一种非固定坐标系。
下图展示为两种坐标系的表示形式:
(下图展示的坐标系为IMU坐标系)
(下图展示的坐标系为世界坐标系)
坐标系在实体汽车上的表示:
LeGO LOAM代码分析
代码
void imuHandler(const sensor_msgs::Imu::ConstPtr& imuIn){double roll, pitch, yaw;tf::Quaternion orientation;tf::quaternionMsgToTF(imuIn->orientation, orientation);tf::Matrix3x3(orientation).getRPY(roll, pitch, yaw);float accX = imuIn->linear_acceleration.y - sin(roll) * cos(pitch) * 9.81;float accY = imuIn->linear_acceleration.z - cos(roll) * cos(pitch) * 9.81;float accZ = imuIn->linear_acceleration.x + sin(pitch) * 9.81;imuPointerLast = (imuPointerLast + 1) % imuQueLength;imuTime[imuPointerLast] = imuIn->header.stamp.toSec();imuRoll[imuPointerLast] = roll;imuPitch[imuPointerLast] = pitch;imuYaw[imuPointerLast] = yaw;imuAccX[imuPointerLast] = accX;imuAccY[imuPointerLast] = accY;imuAccZ[imuPointerLast] = accZ;imuAngularVeloX[imuPointerLast] = imuIn->angular_velocity.x;imuAngularVeloY[imuPointerLast] = imuIn->angular_velocity.y;imuAngularVeloZ[imuPointerLast] = imuIn->angular_velocity.z;AccumulateIMUShiftAndRotation();}
上述代码为消除IMU测量的加速度数据受到重力加速度的影响(重力加速度存在于world坐标系)
- 因为IMU坐标系与world坐标系是不对齐的,所以,下面这三行代码的用途如下:
#输出的为在IMU与world坐标系一致朝向的IMU坐标系下的新IMU坐标系下的加速度 测量数据值float accX = imuIn->linear_acceleration.y - sin(roll) * cos(pitch) * 9.81;float accY = imuIn->linear_acceleration.z - cos(roll) * cos(pitch) * 9.81;float accZ = imuIn->linear_acceleration.x + sin(pitch) * 9.81;
将重力加速度转到IMU坐标系,然后执行消除重力的影响:涉及的公式如下
同时因为IMU坐标系与world坐标系不对齐,所以第二部将X->y,Y->z,Z->x实则是考虑一个新的IMU坐标系,使得这个新的IMU坐标与world坐标系能够在朝向上一致!!!(但是需要区分的是此时新的IMU坐标系和world坐标系只是朝向一致,但是他们并不是同一个坐标系!!!)
- 但是对于imu坐标系表示为前x->左y->上z,新的IMU坐标系(IMU’)表示为左x->上y->前z,所以 最终得到的accX,accY,accZ分别经过imu到IMU’的坐标系的变换,(所以需要经历的变换为如下图所示:)

对于IMU输出测量值的integration积分过程
因为accX,accY,accZ对应的都是IMU表示的新的坐标系(IMU’)下的测量数据值,因为IMU到world坐标系下的旋转满足yaw-pitch-roll旋转,同时是内旋,满足右乘,所以最终满足的公式为:

// rollfloat x1 = cos(roll) * accX - sin(roll) * accY;float y1 = sin(roll) * accX + cos(roll) * accY;float z1 = accZ;//z

// pitchfloat x2 = x1;//xfloat y2 = cos(pitch) * y1 - sin(pitch) * z1;float z2 = sin(pitch) * y1 + cos(pitch) * z1;

//yawaccX = cos(yaw) * x2 + sin(yaw) * z2;accY = y2;//yaccZ = -sin(yaw) * x2 + cos(yaw) * z2;

IMU坐标系转为机器人坐标系(左X(pitch)->上Y(yaw)->前Z(roll)),需要按照转换后的坐标系进行欧拉角的旋转yaw->pitch->roll的内旋右乘->表示为先✖roll(Z)->再✖pitch(x)->再✖yaw(Y)
void AccumulateIMUShiftAndRotation(){float roll = imuRoll[imuPointerLast];float pitch = imuPitch[imuPointerLast];float yaw = imuYaw[imuPointerLast];float accX = imuAccX[imuPointerLast];float accY = imuAccY[imuPointerLast];float accZ = imuAccZ[imuPointerLast];
//将新的坐标系IMU'数据通过YAW->PITCH->ROLL转到world坐标系
// roll zfloat x1 = cos(roll) * accX - sin(roll) * accY;float y1 = sin(roll) * accX + cos(roll) * accY;float z1 = accZ;//z
//pitch xfloat x2 = x1;//xfloat y2 = cos(pitch) * y1 - sin(pitch) * z1;float z2 = sin(pitch) * y1 + cos(pitch) * z1;
//yaw yaccX = cos(yaw) * x2 + sin(yaw) * z2;accY = y2;//yaccZ = -sin(yaw) * x2 + cos(yaw) * z2;int imuPointerBack = (imuPointerLast + imuQueLength - 1) % imuQueLength;double timeDiff = imuTime[imuPointerLast] - imuTime[imuPointerBack];if (timeDiff < scanPeriod) {
// 最终得到的 Shift | Velo | AngularRotation均表示为world坐标系下的数值imuShiftX[imuPointerLast] = imuShiftX[imuPointerBack] + imuVeloX[imuPointerBack] * timeDiff + accX * timeDiff * timeDiff / 2;imuShiftY[imuPointerLast] = imuShiftY[imuPointerBack] + imuVeloY[imuPointerBack] * timeDiff + accY * timeDiff * timeDiff / 2;imuShiftZ[imuPointerLast] = imuShiftZ[imuPointerBack] + imuVeloZ[imuPointerBack] * timeDiff + accZ * timeDiff * timeDiff / 2;imuVeloX[imuPointerLast] = imuVeloX[imuPointerBack] + accX * timeDiff;imuVeloY[imuPointerLast] = imuVeloY[imuPointerBack] + accY * timeDiff;imuVeloZ[imuPointerLast] = imuVeloZ[imuPointerBack] + accZ * timeDiff;imuAngularRotationX[imuPointerLast] = imuAngularRotationX[imuPointerBack] + imuAngularVeloX[imuPointerBack] * timeDiff;imuAngularRotationY[imuPointerLast] = imuAngularRotationY[imuPointerBack] + imuAngularVeloY[imuPointerBack] * timeDiff;imuAngularRotationZ[imuPointerLast] = imuAngularRotationZ[imuPointerBack] + imuAngularVeloZ[imuPointerBack] * timeDiff;}}
其最终得到的速度,偏移以及旋转的积分数据值表示的为在world坐标系下的数据
欧拉角的旋转矩阵
在adjustDistortion对点云数据执行运动去畸变的过程是这样的,首先将lidar点云同样与IMU数据统一的跟world坐标系同指向的新的lidar’坐标系(用来与world坐标系的朝向对齐):执行如下代码的变换:
point.x = segmentedCloud->points[i].y;point.y = segmentedCloud->points[i].z;point.z = segmentedCloud->points[i].x;
然后,找到针对此刻点云对应的IMU数据信息,如果是第一帧,则不需要旋转到IMU对应的初始时刻 但是对于在scanperiod的时间间隔内,lidar360度旋转,非第一帧对应的初始时刻的IMU数据,则需要转换到初始时刻的IMU数据坐标系下也就是涉及的两个函数VeloToStartIMU()函数和TransformToStartIMU(point)函数
void adjustDistortion(){bool halfPassed = false;int cloudSize = segmentedCloud->points.size();PointType point;for (int i = 0; i < cloudSize; i++) {point.x = segmentedCloud->points[i].y;point.y = segmentedCloud->points[i].z;point.z = segmentedCloud->points[i].x;float ori = -atan2(point.x, point.z);if (!halfPassed) {if (ori < segInfo.startOrientation - M_PI / 2)ori += 2 * M_PI;else if (ori > segInfo.startOrientation + M_PI * 3 / 2)ori -= 2 * M_PI;if (ori - segInfo.startOrientation > M_PI)halfPassed = true;}else {ori += 2 * M_PI;if (ori < segInfo.endOrientation - M_PI * 3 / 2)ori += 2 * M_PI;else if (ori > segInfo.endOrientation + M_PI / 2)ori -= 2 * M_PI;}float relTime = (ori - segInfo.startOrientation) / segInfo.orientationDiff;point.intensity = int(segmentedCloud->points[i].intensity) + scanPeriod * relTime;if (imuPointerLast >= 0) {//have imu datafloat pointTime = relTime * scanPeriod;imuPointerFront = imuPointerLastIteration;while (imuPointerFront != imuPointerLast) {if (timeScanCur + pointTime < imuTime[imuPointerFront]) {break;}imuPointerFront = (imuPointerFront + 1) % imuQueLength;}//find the imuPointerFront (imu time ) > current scan timeif (timeScanCur + pointTime > imuTime[imuPointerFront]) {//no imu dataimuRollCur = imuRoll[imuPointerFront];//imu data coverageimuPitchCur = imuPitch[imuPointerFront];imuYawCur = imuYaw[imuPointerFront];imuVeloXCur = imuVeloX[imuPointerFront];imuVeloYCur = imuVeloY[imuPointerFront];imuVeloZCur = imuVeloZ[imuPointerFront];imuShiftXCur = imuShiftX[imuPointerFront];imuShiftYCur = imuShiftY[imuPointerFront];imuShiftZCur = imuShiftZ[imuPointerFront]; } else {int imuPointerBack = (imuPointerFront + imuQueLength - 1) % imuQueLength;//0-1 % imuQueLength xfloat ratioFront = (timeScanCur + pointTime - imuTime[imuPointerBack]) / (imuTime[imuPointerFront] - imuTime[imuPointerBack]);float ratioBack = (imuTime[imuPointerFront] - timeScanCur - pointTime) / (imuTime[imuPointerFront] - imuTime[imuPointerBack]);imuRollCur = imuRoll[imuPointerFront] * ratioFront + imuRoll[imuPointerBack] * ratioBack;imuPitchCur = imuPitch[imuPointerFront] * ratioFront + imuPitch[imuPointerBack] * ratioBack;if (imuYaw[imuPointerFront] - imuYaw[imuPointerBack] > M_PI) {imuYawCur = imuYaw[imuPointerFront] * ratioFront + (imuYaw[imuPointerBack] + 2 * M_PI) * ratioBack;} else if (imuYaw[imuPointerFront] - imuYaw[imuPointerBack] < -M_PI) {imuYawCur = imuYaw[imuPointerFront] * ratioFront + (imuYaw[imuPointerBack] - 2 * M_PI) * ratioBack;} else {imuYawCur = imuYaw[imuPointerFront] * ratioFront + imuYaw[imuPointerBack] * ratioBack;}imuVeloXCur = imuVeloX[imuPointerFront] * ratioFront + imuVeloX[imuPointerBack] * ratioBack;imuVeloYCur = imuVeloY[imuPointerFront] * ratioFront + imuVeloY[imuPointerBack] * ratioBack;imuVeloZCur = imuVeloZ[imuPointerFront] * ratioFront + imuVeloZ[imuPointerBack] * ratioBack;imuShiftXCur = imuShiftX[imuPointerFront] * ratioFront + imuShiftX[imuPointerBack] * ratioBack;imuShiftYCur = imuShiftY[imuPointerFront] * ratioFront + imuShiftY[imuPointerBack] * ratioBack;imuShiftZCur = imuShiftZ[imuPointerFront] * ratioFront + imuShiftZ[imuPointerBack] * ratioBack;}if (i == 0) {//first point cloud initializationimuRollStart = imuRollCur;imuPitchStart = imuPitchCur;imuYawStart = imuYawCur;imuVeloXStart = imuVeloXCur;imuVeloYStart = imuVeloYCur;imuVeloZStart = imuVeloZCur;imuShiftXStart = imuShiftXCur;imuShiftYStart = imuShiftYCur;imuShiftZStart = imuShiftZCur;if (timeScanCur + pointTime > imuTime[imuPointerFront]) {imuAngularRotationXCur = imuAngularRotationX[imuPointerFront];imuAngularRotationYCur = imuAngularRotationY[imuPointerFront];imuAngularRotationZCur = imuAngularRotationZ[imuPointerFront];}else{int imuPointerBack = (imuPointerFront + imuQueLength - 1) % imuQueLength;float ratioFront = (timeScanCur + pointTime - imuTime[imuPointerBack]) / (imuTime[imuPointerFront] - imuTime[imuPointerBack]);float ratioBack = (imuTime[imuPointerFront] - timeScanCur - pointTime) / (imuTime[imuPointerFront] - imuTime[imuPointerBack]);imuAngularRotationXCur = imuAngularRotationX[imuPointerFront] * ratioFront + imuAngularRotationX[imuPointerBack] * ratioBack;imuAngularRotationYCur = imuAngularRotationY[imuPointerFront] * ratioFront + imuAngularRotationY[imuPointerBack] * ratioBack;imuAngularRotationZCur = imuAngularRotationZ[imuPointerFront] * ratioFront + imuAngularRotationZ[imuPointerBack] * ratioBack;}imuAngularFromStartX = imuAngularRotationXCur - imuAngularRotationXLast;imuAngularFromStartY = imuAngularRotationYCur - imuAngularRotationYLast;imuAngularFromStartZ = imuAngularRotationZCur - imuAngularRotationZLast;imuAngularRotationXLast = imuAngularRotationXCur;imuAngularRotationYLast = imuAngularRotationYCur;imuAngularRotationZLast = imuAngularRotationZCur;updateImuRollPitchYawStartSinCos();} else {VeloToStartIMU();TransformToStartIMU(&point);}}segmentedCloud->points[i] = point;}imuPointerLastIteration = imuPointerLast;}
VeloToStartIMU()函数
==因为对应的velocity数据是在world坐标系下的,所以imuVeloFromStartXCur存在的坐标系也是world坐标系下的,因此,如果要转到start坐标系,需要先转移到IMU坐标系,然后在转移到start imu的坐标系 ==
**world坐标系到IMU坐标系的变换满足roll (Z)->pitch(X) ->yaw(Y) ,得到的变换如下图所示: **

void VeloToStartIMU(){imuVeloFromStartXCur = imuVeloXCur - imuVeloXStart;//current time to start timeimuVeloFromStartYCur = imuVeloYCur - imuVeloYStart;imuVeloFromStartZCur = imuVeloZCur - imuVeloZStart;
//新的坐标系Y为yaw ->yaw的转置float x1 = cosImuYawStart * imuVeloFromStartXCur - sinImuYawStart * imuVeloFromStartZCur;float y1 = imuVeloFromStartYCur;float z1 = sinImuYawStart * imuVeloFromStartXCur + cosImuYawStart * imuVeloFromStartZCur;
//新的坐标系X为pitch->X的转置float x2 = x1;float y2 = cosImuPitchStart * y1 + sinImuPitchStart * z1;float z2 = -sinImuPitchStart * y1 + cosImuPitchStart * z1;
//新的坐标系Z为roll ->Z的转置imuVeloFromStartXCur = cosImuRollStart * x2 + sinImuRollStart * y2;imuVeloFromStartYCur = -sinImuRollStart * x2 + cosImuRollStart * y2;imuVeloFromStartZCur = z2;}
TransformToStartIMU(PointType *p)
函数首先通过yaw(Y)->pitch(X)->roll(Z)的右乘转到world坐标系,然后再进行world 到 start imu的转换(YawT)->(PitchT)->(RollT)因为是固定坐标系的旋转所以执行的乘法为左乘(Y->X->Z)最终得到变换的结果
void TransformToStartIMU(PointType *p){float x1 = cos(imuRollCur) * p->x - sin(imuRollCur) * p->y;float y1 = sin(imuRollCur) * p->x + cos(imuRollCur) * p->y;float z1 = p->z;float x2 = x1;float y2 = cos(imuPitchCur) * y1 - sin(imuPitchCur) * z1;float z2 = sin(imuPitchCur) * y1 + cos(imuPitchCur) * z1;float x3 = cos(imuYawCur) * x2 + sin(imuYawCur) * z2;float y3 = y2;float z3 = -sin(imuYawCur) * x2 + cos(imuYawCur) * z2;float x4 = cosImuYawStart * x3 - sinImuYawStart * z3;float y4 = y3;float z4 = sinImuYawStart * x3 + cosImuYawStart * z3;float x5 = x4;float y5 = cosImuPitchStart * y4 + sinImuPitchStart * z4;float z5 = -sinImuPitchStart * y4 + cosImuPitchStart * z4;p->x = cosImuRollStart * x5 + sinImuRollStart * y5 + imuShiftFromStartXCur;p->y = -sinImuRollStart * x5 + cosImuRollStart * y5 + imuShiftFromStartYCur;p->z = z5 + imuShiftFromStartZCur;}
相关文章:
LeGO LOAM坐标系问题的自我思考
LeGO LOAM坐标系问题的自我思考 IMU坐标系LeGO LOAM代码分析代码 对于IMU输出测量值的integration积分过程欧拉角的旋转矩阵VeloToStartIMU()函数TransformToStartIMU(PointType *p) IMU坐标系 在LeGO LOAM中IMU坐标系的形式采用前(x)-左(y)-上(z)的形式,IMU坐标系…...
vim交换文件的作用
1.数据恢复:因为vim异常的退出,使用交换文件可以恢复之前的修改内容。 2.防止多人同时编辑:vim检测到交换文件的存在,会给出提示,以避免一个文件同时被多人编辑。 (vim交换文件的工作原理:vim交换文件的工作…...
PHP实现混合加密方式,提高加密的安全性(代码解密)
代码1: <?php // 需要加密的内容 $plaintext 授权服务器拒绝连接;// 1. AES加密部分 $aesKey openssl_random_pseudo_bytes(32); // 生成256位AES密钥 $iv openssl_random_pseudo_bytes(16); // 生成128位IV// AES加密(CBC模式)…...
五. Redis 配置内容(详细配置说明)
五. Redis 配置内容(详细配置说明) 文章目录 五. Redis 配置内容(详细配置说明)1. Units 单位配置2. INCLUDES (包含)配置3. NETWORK (网络)配置3.1 bind(配置访问内容)3.2 protected-mode (保护模式)3.3 port(端口)配置3.4 timeout(客户端超时时间)配置3.5 tcp-keepalive()配置…...
安卓通过网络获取位置的方法
一 方法介绍 1. 基本权限设置 首先需要在 AndroidManifest.xml 中添加必要权限: xml <uses-permission android:name"android.permission.INTERNET" /> <uses-permission android:name"android.permission.ACCESS_NETWORK_STATE" /&g…...
python leetcode 笔记
只为记录一些python相关的特殊写法 无穷大,无穷小,NAN float(inf), float(-inf), float(nan) 判断字符的类型 isdigit(x) isspace(x) 字符串拼接 /.join([a,b,c]) # a/b/c 格式转换,字符转整形 ord(a) # 97 chr(97) # a 进制转…...
使用HttpClient和HttpRequest发送HTTP请求
项目中经常会用到向第三方系统发送请求来传递数据或者获得信息,一般用的比较多的为HttpClient 和 HttpRequest,这里简要总结一下 HttpClient 和 HttpRequest 的用法 一、HttpClient 1. 发送get请求 public static String get(String url, Map<Stri…...
(9) 上:学习与验证 linux 里的 epoll 对象里的 EPOLLIN、 EPOLLHUP 与 EPOLLRDHUP 的不同
(1)经过之前的学习。俺认为结论是这样的,因为三次握手到四次挥手,到 RST 报文,都是 tcp 连接上收到了报文,这都属于读事件。所以: EPOLLIN : 包含了读事件, FIN 报文的正常四次挥手、…...
C#属性和字段(访问修饰符)
不同点逻辑性/灵活性存储性访问性使用范围安全性属性(Property)源于字段,对字段的扩展,逻辑字段并不占用实际的内存可以被其他类访问对接收的数据范围做限定,外部使用增加了数据的安全性字段(Field)不经过逻辑处理占用内存的空间及位置大部分字段不能直接被访问内存使用不安全 …...
【C语言指针】指针和函数
文章目录 一、前言二、指针函数2.1 概念2.2 定义2.3 具体例子 三、函数指针3.1 概念3.2 定义3.3 具体例子3.4 回调函数3.4.1 概念3.4.2 例子13.4.3 例子2 四、函数指针数组4.1 概念4.2 定义4.3 具体例子 五、函数指针数组的指针5.1 概念5.2 定义5.3 具体例子 一、前言 关于指针…...
因果推断与机器学习—用机器学习解决因果推断问题
Judea Pearl 将当前备受瞩目的机器学习研究戏谑地称为“仅限于曲线拟合”,然而,曲线拟合的实现绝非易事。机器学习模型在图像识别、语音识别、自然语言处理、蛋白质分子结构预测以及搜索推荐等多个领域均展现出显著的应用效果。 在因果推断任务中,在完成因果效应识别之后,需…...
2025全自动企业站群镜像管理系统 | 支持繁简转换拼音插入
2025全自动企业站群镜像管理系统 | 支持繁简转换拼音插入 在全球化的今天,企业面临着管理多站点的挑战,尤其是跨语言和地理位置的站点。为此,我们设计了一套基于PHP的全自动企业站群镜像管理系统,它不仅能够自动化站点的管理&…...
基于阿里云百炼大模型Sensevoice-1的语音识别与文本保存工具开发
基于阿里云百炼大模型Sensevoice-1的语音识别与文本保存工具开发 摘要 随着人工智能技术的不断发展,语音识别在会议记录、语音笔记等场景中得到了广泛应用。本文介绍了一个基于Python和阿里云百炼大模型的语音识别与文本保存工具的开发过程。该工具能够高效地识别东…...
日志2025.2.1
日志2025.2.1 1.做了敌人状态机 public class EnermyStateMachine { public EnermyState currentState { get; private set; } public void InitializeState(EnermyState startState) { currentState startState; currentState.Enter(); } public void Change…...
GIS与相关专业软件汇总
闲来无事突然想整理一下看看 GIS及相关领域 究竟有多少软件或者工具包等。 我询问了几个AI工具并汇总了一个软件汇总,不搜不知道,一搜吓一跳,搜索出来了大量的软件,大部分软件或者工具包都没有见过,不知大家还有没有要…...
webrtc编译需要常用环境变量以及相关名词解释
set vs2022_installD:\\vs2022 set GYP_MSVS_OVERRIDE_PATHD:\\vs2022 set GYP_GENERATORSmsvs-ninja,ninja set WINDOWSSDKDIRD:\\Windows Kits\10 set DEPOT_TOOLS_WIN_TOOLCHAIN0 set GYP_MSVS_VERSION2022 这些环境变量是为了编译 WebRTC 时让 GYP/Depot Tools 正确找到 V…...
飞书项目流程入门指导手册
飞书项目流程入门指导手册 参考资料准备工作新建空间国际化配置新建工作项字段管理新建字段对接标识授权角色 流程管理基础说明流程节点配置流程节点的布局配置页面上布局按钮布局配置 流程节点驳回流程图展示自动化字段修改 局限性 参考资料 飞书官方参考文档:飞书…...
Android学习制作app(ESP8266-01S连接-简单制作)
一、理论 部分理论见arduino学习-CSDN博客和Android Studio安装配置_android studio gradle 配置-CSDN博客 以下直接上代码和效果视频,esp01S的收发硬件代码目前没有分享,但是可以通过另一个手机网络调试助手进行模拟。也可以直接根据我的代码进行改动…...
docker直接运行arm下的docker
运行环境是树莓派A 处理器是 arm32v6 安装了docker,运行lamp 编译安装php的时候发现要按天来算,于是用电脑vm下的Ubuntu系统运行arm的docker 然后打包到a直接导入运行就可以了 第一种方法 sudo apt install qemu-user-static 导入直接运行就可以了…...
PYH与MAC的桥梁MII/MIIM
在学习车载互联网时,看到了一句话,Processor通过DMA直接存储访问与MAC之间进行数据的交互,MAC通过MII介质无关接口与PHY之间进行数据的交互。常见的以太网硬件结构是,将MAC集成进Processor芯片,将PHY留在Processor片外…...
如何使用SliverList组件
文章目录 1 概念介绍2 使用方法3 示例代码 我们在上一章回中介绍了沉浸式状态栏相关的内容,本章回中将介绍SliverList组件.闲话休提,让我们一起Talk Flutter吧。 1 概念介绍 我们在这里介绍的SliverList组件是一种列表类组件,类似我们之前介…...
SQL UCASE() 函数详解
SQL UCASE() 函数详解 在SQL中,UCASE() 函数是一个非常有用的字符串处理函数,它可以将字符串中的所有小写字母转换为大写字母。本文将详细介绍UCASE() 函数的用法、语法、示例以及其在实际应用中的优势。 一、UCASE() 函数简介 UCASE() 函数是SQL标准…...
单细胞分析基础-第一节 数据质控、降维聚类
scRNA_pipeline\1.Seurat 生物技能树 可进官网查询 添加链接描述 分析流程 准备:R包安装 options("repos"="https://mirrors.ustc.edu.cn/CRAN/") if(!require("BiocManager")) install.packages("BiocManager",update = F,ask =…...
Vue 与 Electron 结合开发桌面应用
1. 引言 1.1 什么是 Electron? Electron 是一个使用 JavaScript、HTML 和 CSS 构建跨平台桌面应用程序的框架。它结合了 Chromium 渲染引擎和 Node.js 运行时,使得开发者可以使用 Web 技术创建原生桌面应用。1.2 为什么选择 Vue.js 和 Electron? Vue.js 是一个渐进式 JavaSc…...
Golang 应用的 Docker 部署方式介绍及使用详解
本文将介绍如何使用 Docker 部署一个基于 Go 语言的后台服务应用 godco,并介绍如何配置 MongoDB 数据库容器的连接,确保应用能够成功启动并连接到容器方式部署的mongoDB数据库。 前提条件 1.已安装 Docker/Podman 2.已安装 MongoDB 数据库容器ÿ…...
DeepSeek的提示词使用说明
一、DeepSeek概述 DeepSeek是一款基于先进推理技术的大型语言模型,能够根据用户提供的简洁提示词生成高质量、精准的内容。在实际应用中,DeepSeek不仅能够帮助用户完成各类文案撰写、报告分析、市场研究等工作,还能够生成结构化的内容&#…...
python小知识-typing注解你的程序
python小知识-typing注解你的程序 1. Typing的简介 typing 是 Python 的一个标准库,它提供了类型注解的支持,但并不会强制类型检查。类型注解在 Python 3.5 中引入,并在后续版本中得到了增强和扩展。typing 库允许开发者为变量、函数参数和…...
HTML<hgroup>标签
例子: 使用hgroup元素标记标题和段落是相关的: <hgroup> <h2>Norway</h2> <p>The land with the midnight sun.</p> </hgroup> 定义和用法: 标签<hgroup>用于包围标题和一个或多个<p&g…...
【已解决】黑马点评项目Redis版本替换过程的数据迁移
黑马点评项目Redis版本替换过程的数据迁移 【哭哭哭】附近商户中需要用到的GEO功能只在Redis 6.2以上版本生效 如果用的是老版本,美食/KTV的主页能正常返回,但无法显示内容 上次好不容易升到了5.0以上版本,现在又用不了了 Redis 6.2的windo…...
mybatis辅助配置
驼峰映射 sql里面定义字段通常是使用下划线定义 比如dept_id 而我们的后端属性通常就是驼峰命名 deptId 所以这两匹配进行自动赋值就比较麻烦 可以使用 select dept_id as deptId 来解决(起别名) 也可以用mybatis的辅助配置解决 第三种就是推荐的在spr…...







