OpenCV开发笔记(七十六):相机标定(一):识别棋盘并绘制角点

若该文为原创文章,转载请注明原文出处
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/136535848
各位读者,知识无穷而人力有穷,要么改需求,要么找专业人士,要么自己研究
红胖子(红模仿)的博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结合等等)持续更新中…(点击传送门)

OpenCV开发专栏(点击传送门)

上一篇:《OpenCV开发笔记(七十五):相机标定矫正中使用remap重映射进行畸变矫正》
下一篇:《OpenCV开发笔记(七十七):相机标定(二):通过棋盘标定计算相机内参矩阵矫正畸变摄像头图像》


前言

  知道图像畸变矫映射的原理之后,那么如何得到相机的内参是矫正的第一步,内参决定了内参矩阵(中心点、焦距等),用内参矩阵才能计算出投影矩阵,从而将原本畸变的图像矫正为平面投影图像。
  本篇描述了相机成形的原理,并绘制出识别的角点。


Demo

  请添加图片描述

  请添加图片描述

  请添加图片描述


相机成形的原理

小孔成像原理

  在这里插入图片描述

  得到矩阵计算原理:
  在这里插入图片描述

  得到计算过程:
  在这里插入图片描述


相机的畸变

  相机的畸变是指相机镜头对物体所成的像相对于物体本身而言的失真程度,它是光学透镜的固有特性。畸变产生的原因主要是透镜的边缘部分和中心部分的放大倍率不一样。
畸变分为以下几类:

  • 径向畸变
  • 切向畸变
  • 薄棱镜畸变
      通常情况下,径向畸变的影响要远远大于其他畸变。畸变是不可消除的,但在实际的应用中,可以通过一些软件来进行畸变的补偿,如OpenCV、MATLAB等。

径向畸变

  主要由透镜不同部位放大倍率不同造成,它又分为枕形畸变和桶形畸变两种。枕形畸变,也称为鞍形形变,视野中边缘区域的放大率远大于光轴中心区域的放大率,常用在远摄镜头中。桶形畸变则与枕形畸变相反,视野中光轴中心区域的放大率远大于边缘区域的放大率,常出现在广角镜头和鱼眼镜头中
  在这里插入图片描述

切向畸变

  主要由透镜安装与成像平面不平行造成,类似于透视原理,如近大远小、圆变椭圆等。
  在这里插入图片描述

薄棱镜畸变

  由透镜设计缺陷和加工安装误差造成,又称为线性畸变。其影响较小,一般忽略不计。


棋牌识别步骤

步骤一:标定采集的数据图像

  采集一张棋盘图片,要确认他是可以被识别的。
  在这里插入图片描述

  读取图像,这里由于图片较大,我们重设大小为原来宽高的1/2:

    // 使用图片
    std::string srcFilePath = "D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/chessboard.png";
//    std::string srcFilePath = "D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/24.jpg";
    cv::Mat srcMat = cv::imread(srcFilePath);
    int chessboardColCornerCount = 6;
    int chessboardRowCornerCount = 9;
    // 步骤一:读取文件
//    cv::imshow("1", srcMat);
//    cv::waitKey(0);
    // 步骤二:缩放,太大了缩放下(可省略)
    cv::resize(srcMat, srcMat, cv::Size(srcMat.cols / 2, srcMat.rows / 2));
    cv::Mat srcMat2 = srcMat.clone();
    cv::Mat srcMat3 = srcMat.clone();
//    cv::imshow("2", srcMat);
//    cv::waitKey(0);

步骤二:图像处理,提取角点,并绘制出来

  先灰度化,然后输入预制的纵向横向角数量,使用棋盘角点函数提取角点

    // 步骤三:灰度化
    cv::Mat grayMat;
    cv::cvtColor(srcMat, grayMat, cv::COLOR_BGR2GRAY);
    cv::imshow("3", grayMat);
//    cv::waitKey(0);
    // 步骤四:检测角点
    std::vector<cv::Point2f> vectorPoint2fCorners;
    bool patternWasFound = false;
    patternWasFound = cv::findChessboardCorners(grayMat,
                                                cv::Size(chessboardColCornerCount, chessboardRowCornerCount),
                                                vectorPoint2fCorners,
                                                cv::CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_FAST_CHECK | cv::CALIB_CB_NORMALIZE_IMAGE);
    /*
    enum { CALIB_CB_ADAPTIVE_THRESH = 1,    // 使用自适应阈值将图像转化成二值图像
           CALIB_CB_NORMALIZE_IMAGE = 2,    // 归一化图像灰度系数(用直方图均衡化或者自适应阈值)
           CALIB_CB_FILTER_QUADS    = 4,    // 在轮廓提取阶段,使用附加条件排除错误的假设
           CALIB_CB_FAST_CHECK      = 8     // 快速检测
         };
    */
    cvui::printf(srcMat, 0, 0, 1.0, 0xFF0000, "found = %s", patternWasFound ? "true" : "false");
    cvui::printf(srcMat, 0, 24, 1.0, 0xFF0000, "count = %d", vectorPoint2fCorners.size());
    qDebug() << __FILE__ << __LINE__ << vectorPoint2fCorners.size();
    // 步骤五:绘制棋盘点
    cv::drawChessboardCorners(srcMat2,
                              cv::Size(chessboardColCornerCount, chessboardRowCornerCount),
                              vectorPoint2fCorners,
                              patternWasFound);

步骤三:进行亚像素角点计算,进一步提取图片准确性

// 步骤六:进一步提取亚像素角点
    cv::TermCriteria criteria(CV_TERMCRIT_EPS | CV_TERMCRIT_ITER,   // 类型
                              30,                                   // 参数二: 最大次数
                              0.001);                               // 参数三:迭代终止阈值
    /*
    #define CV_TERMCRIT_ITER    1                   // 终止条件为: 达到最大迭代次数终止
    #define CV_TERMCRIT_NUMBER  CV_TERMCRIT_ITER    //
    #define CV_TERMCRIT_EPS     2                   // 终止条件为: 迭代到阈值终止
    */
    qDebug() << __FILE__ << __LINE__ << vectorPoint2fCorners.size();
    cv::cornerSubPix(grayMat,
                     vectorPoint2fCorners,
                     cv::Size(11, 11),
                     cv::Size(-1, -1),
                     criteria);

函数原型

findChessboardCorners:识别预制棋盘角点数量的棋盘

  OpenCV 中用于检测图像中棋盘角点的函数。

bool cv::findChessboardCorners(InputArray image,
                           Size patternSize,
                           OutputArray corners,
                           int flags=CALIB_CB_ADAPTIVE_THRESH+CALIB_CB_NORMALIZE_IMAGE)

  参数解释:

  • image:输入的图像,通常是一个灰度图像,因为角点检测在灰度空间中进行更为准确。
  • patternSize:棋盘的内角点数量,例如一个 8x6 的棋盘会有 48 个内角点,所以 patternSize 会是 Size(8, 6)。
  • corners:检测到的角点输出数组。
  • flags:不同的标志,用于指定角点检测的不同方法。可以是以下的一个或多个标志的组合:
    CALIB_CB_ADAPTIVE_THRESH:使用自适应阈值将图像转换为二值图像,而不是使用固定的全局阈值。
    CALIB_CB_NORMALIZE_IMAGE:在寻找角点之前,先对图像进行归一化,以提高鲁棒性。
    CALIB_CB_FAST_CHECK:仅检查角点候选者中的少量点,用于快速检测,但可能不如标准方法准确。

  函数返回值是一个布尔值,如果找到足够的角点以形成一个棋盘模式,则返回 true;否则返回 false。
  findChessboardCorners 函数通常用于相机标定,通过检测棋盘角点来确定图像与真实世界之间的对应关系。一旦角点被检测到,就可以使用这些点来估计相机的内参(如焦距、主点)和外参(如旋转和平移矩阵)。

drawChessboardCorners:绘制棋盘角点

  OpenCV中的一个函数,用于在检测到的棋盘角点周围绘制方框。这对于相机标定、图像对齐等应用非常有用。

void cv::drawChessboardCorners(InputOutputArray image,
                            Size patternSize,
                            InputArray corners,
                            bool patternWasFound)

  参数解释:

  • image:输入的图像,通常是一个彩色图像,函数会在这个图像上绘制角点。
  • patternSize:棋盘的内角点数量,例如一个 8x6 的棋盘会有 48 个内角点,所以 patternSize 会是 Size(8, 6)。
  • corners:检测到的角点,通常是通过 findChessboardCorners 函数得到的。
  • patternWasFound:一个布尔值,表示是否找到了足够的角点来形成一个棋盘模式。如果为 true,则函数会在角点周围绘制彩色的方框;如果为 false,则只会绘制白色的方框。
    这个函数通常与 findChessboardCorners 结合使用,以检测图像中的棋盘角点,并在检测到的角点周围绘制方框。这对于视觉校准和相机标定等任务非常有用。

TermCriteria:迭代终止模板类

  TermCriteria是OpenCV中用于指定迭代算法终止条件的模板类。它取代了之前的CvTermCriteria,并且在许多OpenCV算法中作为迭代求解的结构被使用。

struct TermCriteria {  
    enum { COUNT=1, MAX_ITER=COUNT, EPS=2 };  
    TermCriteria();  
    TermCriteria(int type, int maxCount, double epsilon);  
    TermCriteria(const CvTermCriteria& criteria);  
};

  构造时需要三个参数:

  • 类型(type):它决定了迭代终止的条件。类型可以是CV_TERMCRIT_ITER、CV_TERMCRIT_EPS或CV_TERMCRIT_ITER+CV_TERMCRIT_EPS。在C++中,这些宏对应的版本分别为TermCriteria::COUNT、TermCriteria::EPS。
    CV_TERMCRIT_ITER或TermCriteria::COUNT:表示迭代终止条件为达到最大迭代次数;
    CV_TERMCRIT_EPS或TermCriteria::EPS:表示迭代到特定的阈值就终止;
    CV_TERMCRIT_ITER+CV_TERMCRIT_EPS:则表示两者都作为迭代终止条件。
  • 迭代的最大次数(maxCount):这是算法可以执行的最大迭代次数。
  • 特定的阈值(epsilon):当满足这个精确度时,迭代算法会停止。

cornerSubPix:亚像素角点提取

  OpenCV中用于精确化角点位置,其函数原型如下:

void cv::cornerSubPix(InputArray image,
                   InputOutputArray corners,
                   Size winSize,
                   Size zeroZone,
                   TermCriteria criteria);

  参数解释:

  • image:输入图像的像素矩阵,最好是8位灰度图像,这样检测效率会更高。
  • corners:初始的角点坐标向量,同时作为亚像素坐标位置的输出,因此需要是浮点型数据。
  • winSize:搜索窗口的大小,它表示的是搜索窗口的一半尺寸。
  • zeroZone:死区的一半尺寸,死区是搜索窗口内不对中央位置做求和运算的区域。这是为了避免自相关矩阵出现某些可能的奇异性。
  • criteria:角点搜索的停止条件,通常包括迭代次数、角点位置变化量或角点误差变化量等。

  cornerSubPix函数用于在初步提取的角点信息上进一步提取亚像素信息,从而提高相机标定的精度。在相机标定、目标跟踪和三维重建等应用中,精确的角点位置是非常重要的,因此cornerSubPix函数在这些领域有广泛的应用。


Demo源码

void OpenCVManager::testFindChessboardCorners()
{
#define FindChessboardCornersUseCamera 1
#if !FindChessboardCornersUseCamera
    // 使用图片
    std::string srcFilePath = "D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/chessboard.png";
//    std::string srcFilePath = "D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/24.jpg";
    cv::Mat srcMat = cv::imread(srcFilePath);
#else
    // 使用摄像头
    cv::VideoCapture capture;
    // 插入USB摄像头默认为0
    if(!capture.open(0))
    {
        qDebug() << __FILE__ << __LINE__  << "Failed to open camera: 0";
    }else{
        qDebug() << __FILE__ << __LINE__  << "Succeed to open camera: 0";
    }
    while(true)
    {
        cv::Mat srcMat;
        capture >> srcMat;
#endif
    int chessboardColCornerCount = 6;
    int chessboardRowCornerCount = 9;
    // 步骤一:读取文件
//    cv::imshow("1", srcMat);
//    cv::waitKey(0);
    // 步骤二:缩放,太大了缩放下(可省略)
    cv::resize(srcMat, srcMat, cv::Size(srcMat.cols / 2, srcMat.rows / 2));
    cv::Mat srcMat2 = srcMat.clone();
    cv::Mat srcMat3 = srcMat.clone();
//    cv::imshow("2", srcMat);
//    cv::waitKey(0);
    // 步骤三:灰度化
    cv::Mat grayMat;
    cv::cvtColor(srcMat, grayMat, cv::COLOR_BGR2GRAY);
    cv::imshow("3", grayMat);
//    cv::waitKey(0);
    // 步骤四:检测角点
    std::vector<cv::Point2f> vectorPoint2fCorners;
    bool patternWasFound = false;
    patternWasFound = cv::findChessboardCorners(grayMat,
                                                cv::Size(chessboardColCornerCount, chessboardRowCornerCount),
                                                vectorPoint2fCorners,
                                                cv::CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_FAST_CHECK | cv::CALIB_CB_NORMALIZE_IMAGE);
    /*
    enum { CALIB_CB_ADAPTIVE_THRESH = 1,    // 使用自适应阈值将图像转化成二值图像
           CALIB_CB_NORMALIZE_IMAGE = 2,    // 归一化图像灰度系数(用直方图均衡化或者自适应阈值)
           CALIB_CB_FILTER_QUADS    = 4,    // 在轮廓提取阶段,使用附加条件排除错误的假设
           CALIB_CB_FAST_CHECK      = 8     // 快速检测
         };
    */
    cvui::printf(srcMat, 0, 0, 1.0, 0xFF0000, "found = %s", patternWasFound ? "true" : "false");
    cvui::printf(srcMat, 0, 24, 1.0, 0xFF0000, "count = %d", vectorPoint2fCorners.size());
    qDebug() << __FILE__ << __LINE__ << vectorPoint2fCorners.size();
    // 步骤五:绘制棋盘点
    cv::drawChessboardCorners(srcMat2,
                              cv::Size(chessboardColCornerCount, chessboardRowCornerCount),
                              vectorPoint2fCorners,
                              patternWasFound);
#if FindChessboardCornersUseCamera
    cv::imshow("0", srcMat);
    cv::imshow("4", srcMat2);
    if(!patternWasFound)
    {
        cv::imshow("5", srcMat3);
        cv::waitKey(1);
        continue;
    }
#endif
    // 步骤六:进一步提取亚像素角点
    cv::TermCriteria criteria(CV_TERMCRIT_EPS | CV_TERMCRIT_ITER,   // 类型
                              30,                                   // 参数二: 最大次数
                              0.001);                               // 参数三:迭代终止阈值
    /*
    #define CV_TERMCRIT_ITER    1                   // 终止条件为: 达到最大迭代次数终止
    #define CV_TERMCRIT_NUMBER  CV_TERMCRIT_ITER    //
    #define CV_TERMCRIT_EPS     2                   // 终止条件为: 迭代到阈值终止
    */
    qDebug() << __FILE__ << __LINE__ << vectorPoint2fCorners.size();
    cv::cornerSubPix(grayMat,
                     vectorPoint2fCorners,
                     cv::Size(11, 11),
                     cv::Size(-1, -1),
                     criteria);
    // 步骤七:绘制棋盘点
    cv::drawChessboardCorners(srcMat3,
                              cv::Size(chessboardColCornerCount, chessboardRowCornerCount),
                              vectorPoint2fCorners,
                              patternWasFound);
    cv::imshow("5", srcMat3);
//    cv::waitKey(0);

#if FindChessboardCornersUseCamera
    cv::waitKey(1);
    }
//    cv::imshow(_windowTitle.toStdString(), dstMat);
#else

    cv::waitKey(0);
#endif

}

对应工程模板v1.67.0

  在这里插入图片描述


入坑

入坑一:无法检测出角点

问题

  检测角点失败
  在这里插入图片描述

原因

  输入棋牌横向竖向角点的数量入函数,而不是输入行数和列数。

解决

  输入正确的横向纵向角点数量即可。
  在这里插入图片描述

入坑二:检测亚像素角点崩溃

问题

  检测亚像素角点函数崩溃
  在这里插入图片描述

原因

  输入要是灰度mat

解决

  将灰度图输入即可。


上一篇:《OpenCV开发笔记(七十五):相机标定矫正中使用remap重映射进行畸变矫正》
下一篇:《OpenCV开发笔记(七十七):相机标定(二):通过棋盘标定计算相机内参矩阵矫正畸变摄像头图像》


若该文为原创文章,转载请注明原文出处
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/136535848

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

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

相关文章

爬虫练习:获取某网站的房价信息

一、相关网站 二、相关代码 import requests from lxml import etree import csv with open(房天下数据.csv, w, newline, encodingutf-8) as csvfile:fieldnames [名称, 地点,价格,总价,联系电话]writer csv.DictWriter(csvfile, fieldnamesfieldnames)writer.writeheader…

MySQL临时表创建出错(OS errno 13 - Permission denied)

一个客户向我抱怨&#xff1a;在MySQL查询小表没有问题&#xff0c;查询大表出错&#xff0c;下面是他发给我的出错的部分截屏&#xff08;客户的表名被我隐藏了&#xff09;。 这里的给出的信息已经比较明显了&#xff0c;是向/tmp目录中创建临时表失败&#xff08;临时表的路…

在用Java写算法的时候如何加快读写速度

对于解决该方法我们一般如下操作&#xff0c;不需要知道为什么&#xff0c;有模板&#xff08;个人观点&#xff09; 使用BufferedReader代替Scanner&#xff1a;Scanner类在读取大量输入时性能较差&#xff0c;而BufferedReader具有更高的读取速度。可以使用BufferedReader的r…

B端系统:漂亮就行。扯淡,漂亮仅占五分之一!

Hi&#xff0c;我是贝格前端工场&#xff0c;接触N多B端系统&#xff0c;也优化升级过N多。在这个过程中&#xff0c;仅仅美观是不够的&#xff0c;所以我拓展出来的B端系统五度评价指标&#xff0c;本篇着重讲易用性指标&#xff0c;欢迎老铁们评论点赞转发&#xff0c;有需求…

安卓studio安装

安卓studio安装 2024.3.11官网的版本&#xff08;有些翻墙步骤下载东西也解决了&#xff09; 这次写的略有草率&#xff0c;后面会更新布局的&#xff0c;因为截图量太大了&#xff0c;有需要的小伙伴可以试着接受一下哈哈哈哈 !(https://gitee.com/jiuzheyangbawjf/img/raw/ma…

Node.Js编码注意事项

Node.js 中不能使用 BOM 和 DOM 的 API&#xff0c;可以使用 console 和定时器 APINode.js 中的顶级对象为 global&#xff0c;也可以用 globalThis 访问顶级对象 浏览器端js的组成 Node.js中的JavaScript组成 相比较之下发现只有console与定时器是两个API所共有的&#xff…

【CLIP综述】CLIP在医学影像中的应用(二)

原文传递&#xff1a;CLIP in Medical Imaging: A Comprehensive Survey 其他综述篇&#xff1a;   【SAM综述】医学图像分割的分割一切模型&#xff1a;当前应用和未来方向   【CLIP综述】CLIP在医学影像中的应用&#xff08;一&#xff09; 4、基于CLIP的应用&#xff08…

OD_2024_C卷_200分_10、部门人力分配【JAVA】【二分法 + 双指针】

说明 输入数据两行&#xff0c;第一行输入数据3表示开发时间要求&#xff0c;第二行输入数据表示需求工作量大小&#xff0c;输出数据一行&#xff0c;表示部门人力需求。当选择人力为6时&#xff0c;2个需求量为3的工作可以在1个月里完成&#xff0c;其他2个工作各需要1个月完…

​​​​​​​ARCGIS API for Python进行城市区域提取

ArcGIS API for Python主要用于Web端的扩展和开发&#xff0c;提供简单易用、功能强大的Python库&#xff0c;以及大数据分析能力&#xff0c;可轻松实现实时数据、栅格数据、空间数据等多源数据的接入和GIS分析、可视化&#xff0c;同时提供对平台的空间数据管理和组织管理功能…

Visual C++ 2005 可以生成清单信息了

在 Visual C 2005 中&#xff0c;我们可以通过 #pragma 指令来生成你想要的清单信息&#xff0c;这项功能可以简化新版本通用控件的使用。 例如&#xff0c;下面的代码可以指示链接器&#xff0c;将通用控件的新版本(6.0.0.0)链接到应用程序中。 // do not use – see discussi…

01 数据结构引入 和 顺序表

阅读引言&#xff1a; 从本文开始给大家带来我在复习过程中写的数据结构的代码&#xff0c; 分享给需要的同学 一、数据结构引入 1.数据结构解决什么问题 数据结构可以将杂乱无章的数据管理起来&#xff0c; 提高数据的访问效率 计算机处理的对象&#xff08;数据&#xff09…

2022 年广西职业院校技能大赛高职组《云计算》赛项赛卷

#需要资源或有问题的&#xff0c;可私博主&#xff01;&#xff01;&#xff01; #需要资源或有问题的&#xff0c;可私博主&#xff01;&#xff01;&#xff01; #需要资源或有问题的&#xff0c;可私博主&#xff01;&#xff01;&#xff01; 某企业拟使用 OpenStack 搭建一…

YOLOv8独家改进:backbone改进 | 最新大卷积核CNN架构UniRepLKNet,ImageNet 88% | CVPR2024

💡💡💡本文独家改进:大核卷积一统多种模态!RepLK正统续作UniRepLKNet,代替YOLOv8 Backbone 改进结构图如下: 收录 YOLOv8原创自研 https://blog.csdn.net/m0_63774211/category_12511737.html?spm=1001.2014.3001.5482 💡💡💡全网独家首发创新(原创),适…

基于智慧灯杆的智慧城市解决方案(2)

功能规划 智慧照明功能 智慧路灯的基本功能仍然是道路照明, 因此对照明功能的智慧化提升是最基本的一项要求。 对道路照明管理进行智慧化提升, 实施智慧照明, 必然将成为智慧城市中道路照明发展的主要方向之一。 智慧照明是集计算机网络技术、 通信技术、 控制技术、 数据…

WordPress高端后台美化WP Adminify Pro优化版

后台UI美化WP Adminify Pro修改自定义插件&#xff0c;适合建站公司和个人使用&#xff0c;非常高大上&#xff0c;下载地址&#xff1a;WP Adminify Pro优化版 修复记录&#xff1a; 1、修复已知BUG 2、修复手机版兼容问题 3、修复打开速度&#xff0c;原版打开速度太慢 4…

华为数通方向HCIP-DataCom H12-821题库(多选题:121-140)

第121题 以下哪些事件会导致IS-IS产生一个新的LSP? A、引入的IP路由发送变化 B、周期性更新 C、接口开销发生了变化 D、邻接Up或Down 【参考答案】ABCD 【答案解析】 第122题 以下哪些协议既支持网络配置管理又支持网络监控管理? A、Telemetry B、NETCONF C、SNMP D、LLDP …

STM32CubeIDE基础学习-STM32CubeIDE软件偏好设置

STM32CubeIDE基础学习-STM32CubeIDE软件偏好设置 文章目录 STM32CubeIDE基础学习-STM32CubeIDE软件偏好设置前言第1章 设置字体颜色第2章 设置字体大小第3章 设置代码区背景颜色总结 前言 编程软件环境最好就设置一个自己喜欢的界面进行显示&#xff0c;这样看起来会比较舒服些…

重生奇迹MU觉醒弓箭手技能有哪些

1、普攻&#xff1a;向前方射出箭矢&#xff0c;造成一定的物理输出。 2、冰封箭&#xff1a;弓箭手射出一发冰冻的箭矢&#xff0c;造成一定的范围伤害。 3、精灵祝福&#xff1a;可以召唤一只守护精灵&#xff0c;为自己加血治疗。 4、多重箭&#xff1a;弓手射出扇形范围…

c++基础知识之父类地址和基类地址一定相同?

背景 hi&#xff0c;粉丝朋友们&#xff1a; 大家好&#xff01;这两天在分析智能指针Refbase相关内容时候&#xff0c;debug打印相关记录一直有个颠覆我观念的问题一直让我无比纠结。 本质原因可能还是java代码的思维去理解c导致的。 情况如下&#xff1a; java代码 public …

基于PHP的餐厅管理系统APP设计与实现

目 录 摘 要 I Abstract II 引 言 1 1 相关技术 3 1.1 MVC 3 1.2 ThinkPHP 3 1.3 MySQL数据库 3 1.4 uni-app 4 1.5 本章小结 4 2 系统分析 5 2.1 功能需求 5 2.2 用例分析 7 2.3 非功能需求 8 2.4 本章小结 8 3 系统设计 9 3.1 系统总体设计 9 3.2 系统详细设计 10 3.3 本章小…