LeGO LOAM坐标系问题的自我思考

news2025/2/2 9:46:09

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坐标系)

  1. 因为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坐标系只是朝向一致,但是他们并不是同一个坐标系!!!)
在这里插入图片描述

  1. 但是对于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旋转,同时是内旋,满足右乘,所以最终满足的公式为:
在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

//yaw
        accX = cos(yaw) * x2 + sin(yaw) * z2;
        accY = y2;//y
        accZ = -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 z
        float x1 = cos(roll) * accX - sin(roll) * accY;
        float y1 = sin(roll) * accX + cos(roll) * accY;
        float z1 = accZ;//z
//pitch x
        float x2 = x1;//x
        float y2 = cos(pitch) * y1 - sin(pitch) * z1;
        float z2 = sin(pitch) * y1 + cos(pitch) * z1;
//yaw y
        accX = cos(yaw) * x2 + sin(yaw) * z2;
        accY = y2;//y
        accZ = -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 data
                float 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 time

                if (timeScanCur + pointTime > imuTime[imuPointerFront]) {//no imu data
                    imuRollCur = imuRoll[imuPointerFront];//imu data coverage
                    imuPitchCur = 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 x
                    float 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 initialization
                    imuRollStart = 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 time
        imuVeloFromStartYCur = 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;
    }

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

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

相关文章

vim交换文件的作用

1.数据恢复&#xff1a;因为vim异常的退出&#xff0c;使用交换文件可以恢复之前的修改内容。 2.防止多人同时编辑&#xff1a;vim检测到交换文件的存在,会给出提示&#xff0c;以避免一个文件同时被多人编辑。 &#xff08;vim交换文件的工作原理&#xff1a;vim交换文件的工作…

PHP实现混合加密方式,提高加密的安全性(代码解密)

代码1&#xff1a; <?php // 需要加密的内容 $plaintext 授权服务器拒绝连接;// 1. AES加密部分 $aesKey openssl_random_pseudo_bytes(32); // 生成256位AES密钥 $iv openssl_random_pseudo_bytes(16); // 生成128位IV// AES加密&#xff08;CBC模式&#xff09…

五. 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()配置…

(9) 上:学习与验证 linux 里的 epoll 对象里的 EPOLLIN、 EPOLLHUP 与 EPOLLRDHUP 的不同

&#xff08;1&#xff09;经过之前的学习。俺认为结论是这样的&#xff0c;因为三次握手到四次挥手&#xff0c;到 RST 报文&#xff0c;都是 tcp 连接上收到了报文&#xff0c;这都属于读事件。所以&#xff1a; EPOLLIN : 包含了读事件&#xff0c; FIN 报文的正常四次挥手、…

因果推断与机器学习—用机器学习解决因果推断问题

Judea Pearl 将当前备受瞩目的机器学习研究戏谑地称为“仅限于曲线拟合”,然而,曲线拟合的实现绝非易事。机器学习模型在图像识别、语音识别、自然语言处理、蛋白质分子结构预测以及搜索推荐等多个领域均展现出显著的应用效果。 在因果推断任务中,在完成因果效应识别之后,需…

2025全自动企业站群镜像管理系统 | 支持繁简转换拼音插入

2025全自动企业站群镜像管理系统 | 支持繁简转换拼音插入 在全球化的今天&#xff0c;企业面临着管理多站点的挑战&#xff0c;尤其是跨语言和地理位置的站点。为此&#xff0c;我们设计了一套基于PHP的全自动企业站群镜像管理系统&#xff0c;它不仅能够自动化站点的管理&…

基于阿里云百炼大模型Sensevoice-1的语音识别与文本保存工具开发

基于阿里云百炼大模型Sensevoice-1的语音识别与文本保存工具开发 摘要 随着人工智能技术的不断发展&#xff0c;语音识别在会议记录、语音笔记等场景中得到了广泛应用。本文介绍了一个基于Python和阿里云百炼大模型的语音识别与文本保存工具的开发过程。该工具能够高效地识别东…

GIS与相关专业软件汇总

闲来无事突然想整理一下看看 GIS及相关领域 究竟有多少软件或者工具包等。 我询问了几个AI工具并汇总了一个软件汇总&#xff0c;不搜不知道&#xff0c;一搜吓一跳&#xff0c;搜索出来了大量的软件&#xff0c;大部分软件或者工具包都没有见过&#xff0c;不知大家还有没有要…

飞书项目流程入门指导手册

飞书项目流程入门指导手册 参考资料准备工作新建空间国际化配置新建工作项字段管理新建字段对接标识授权角色 流程管理基础说明流程节点配置流程节点的布局配置页面上布局按钮布局配置 流程节点驳回流程图展示自动化字段修改 局限性 参考资料 飞书官方参考文档&#xff1a;飞书…

Android学习制作app(ESP8266-01S连接-简单制作)

一、理论 部分理论见arduino学习-CSDN博客和Android Studio安装配置_android studio gradle 配置-CSDN博客 以下直接上代码和效果视频&#xff0c;esp01S的收发硬件代码目前没有分享&#xff0c;但是可以通过另一个手机网络调试助手进行模拟。也可以直接根据我的代码进行改动…

如何使用SliverList组件

文章目录 1 概念介绍2 使用方法3 示例代码 我们在上一章回中介绍了沉浸式状态栏相关的内容&#xff0c;本章回中将介绍SliverList组件.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1 概念介绍 我们在这里介绍的SliverList组件是一种列表类组件&#xff0c;类似我们之前介…

单细胞分析基础-第一节 数据质控、降维聚类

scRNA_pipeline\1.Seurat 生物技能树 可进官网查询 添加链接描述 分析流程 准备:R包安装 options("repos"="https://mirrors.ustc.edu.cn/CRAN/") if(!require("BiocManager")) install.packages("BiocManager",update = F,ask =…

HTML<hgroup>标签

例子&#xff1a; 使用hgroup元素标记标题和段落是相关的&#xff1a; <hgroup> <h2>Norway</h2> <p>The land with the midnight sun.</p> </hgroup> 定义和用法&#xff1a; 标签<hgroup>用于包围标题和一个或多个<p&g…

【已解决】黑马点评项目Redis版本替换过程的数据迁移

黑马点评项目Redis版本替换过程的数据迁移 【哭哭哭】附近商户中需要用到的GEO功能只在Redis 6.2以上版本生效 如果用的是老版本&#xff0c;美食/KTV的主页能正常返回&#xff0c;但无法显示内容 上次好不容易升到了5.0以上版本&#xff0c;现在又用不了了 Redis 6.2的windo…

mybatis辅助配置

驼峰映射 sql里面定义字段通常是使用下划线定义 比如dept_id 而我们的后端属性通常就是驼峰命名 deptId 所以这两匹配进行自动赋值就比较麻烦 可以使用 select dept_id as deptId 来解决&#xff08;起别名&#xff09; 也可以用mybatis的辅助配置解决 第三种就是推荐的在spr…

基于YOLO11的肺结节检测系统

基于YOLO11的肺结节检测系统 (价格90) LUNA16数据集 数据一共 1186张 按照8&#xff1a;1&#xff1a;1随机划分训练集&#xff08;948张&#xff09;、验证集&#xff08;118张&#xff09;与测试集&#xff08;120张&#xff09; 包含 nodule 肺结节 1种…

C#面向对象(继承)

1.什么是继承 在 C# 编程语言中&#xff0c;继承是一个核心概念&#xff0c;它允许一个类&#xff08;称为派生类&#xff09;继承另一个类&#xff08;称为基类&#xff09;的成员&#xff0c;如方法、属性和其他成员。继承机制使得代码重用成为可能&#xff0c;简化了应用程…

Qt事件处理:理解处理器、过滤器与事件系统

1. 事件 事件 是一个描述应用程序中、发生的某些事情的对象。 在 Qt 中&#xff0c;所有事件都继承自 QEvent &#xff0c;并且每个事件都有特定的标识符&#xff0c;如&#xff1a;Qt::MouseButtonPress 代表鼠标按下事件。 每个事件对象包含该事件的所有相关信息&#xff…

为大模型提供webui界面的利器:Open WebUI 完全本地离线部署deepseek r1

为大模型提供webui界面的利器&#xff1a;Open WebUI Open WebUI的官网&#xff1a;&#x1f3e1; Home | Open WebUI 开源代码&#xff1a;WeTab 新标签页 Open WebUI是一个可扩展、功能丰富、用户友好的自托管AI平台&#xff0c;旨在完全离线运行。它支持各种LLM运行程序&am…

17.1 图像操作

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 17.1.1 Image类 Image类为源自 Bitmap 和 Metafile 的类提供功能的抽象基类。 Image的属性大多数是只读的&#xff1a; FrameDim…