apritag 定位记录 C++ opencv 3.4.5

news2024/9/23 3:19:47

参考:2021-06-23 基于AprilTag的位姿估计,原理,完整代码(相机坐标系、世界坐标系) - 简书

Apriltag使用之二:方位估计(定位)_arczee的博客-CSDN博客_apriltag位姿估计

1.AprilTag概述

AprilTag是一种视觉基准系统,类似的视觉基准标签还有:ArUco、ARTag、TopoTag等。而AprilTag因其对旋转、光照、模糊具有良好的鲁棒性,因此被广泛应用于目标跟踪、室内定位、增强现实等领域。本文主要展示使用AprilTag标签,分别获取AprilTag标签在相机坐标系下和世界坐标系下位置。

生成AprilTag不同家族标签图像教程

2.AprilTag 相关资料

AprilTag论文三部曲:

  1. AprilTag: A robust and flexible visual fiducial system. --2011

  2. AprilTag 2: Efficient and robust fiducial detection. --2016

  3. Flexible Layouts for Fiducial Tags. --2019(AprilTag3)

一些应用AprilTag的论文:

  1. UAV Autonomous Landing Technology Based on AprilTags Vision Positioning Algorithm.
  2. Pose Estimation for Multicopters Based on Monocular Vision and AprilTag.
  3. Image Digital Zoom Based Single Target Apriltag Recognition Algorithm in Large Scale Changes on the Distance.

上述资料打包下载地址:https://cloud.189.cn/web/share?code=JRJRJbNfe2ay(访问码:7l96)

AprilTag论文源码地址:https://github.com/AprilRobotics/apriltag

3.AprilTag 原理

检测标签

  1. 检测线

  2. 检测矩形(基于深度优先搜索

  3. 单应性变换与外参估计

  4. 外参估计

  5. 建立两种阈值模型(超过该阈值即认为”1“,少于该阈值即认为是”0“),一种基于“黑色”像素强度的空间变化,一种基于“白色“像素强度的空间变化模型:


  6. 该模型有四个参数,能用最小二乘回归求解。最后阈值大小由两种模型预测的平均值取得。根据不同黑白色块解码标签得到不同的码型,将解码得到的码型与储存在本地的码型库相对比,排除其它码型的干扰,判断是否为正确的标签。

解算位姿

现在我们已知了由世界坐标系至相机坐标系的旋转矩阵和平移矩阵,现在可以根据这两个矩阵,估计相机在世界坐标系下的位置相机坐标系下标签的位置

1. 相机在世界坐标系下的位置

根据相机坐标系与世界坐标系之间的关系可知:

因为相机在相机坐标系下的坐标为,因此可得:

4.为ApriTag论文源码 opencv_demo.cc 添加位姿估计的代码

运行环境

Ubuntu 18.04

Opencv 3.14

IDE: Clion

推荐在Linux环境下,先安装基于C++的OpenCV lib,再编译执行下列代码。

/*调试经验:
1.相机位姿的精度主要取决于tag四个角点的检测像素精度。根据以前测试经验,存在角点误检,导致位姿误差大。主要有两种情况:
一、运动(特别是转弯)过程中照片有点模糊
二、相机与tag存在较大的倾角(30度以外误差比较大)
三、距离越远,误差越大,2m以外谨慎使用。
可能还要其他情况导致误检。
另外相机的内参也会影响计算的位姿,一定要标定好相机内参(重投影误差<0.15),做好畸变校正。
故需要加入严格的判断,最好让相机是正对mark。
*/
#include "tag_station_detection.hpp"
CREATE_LOGex(TagStationDetectionTask);

TagStationDetection::TagStationDetection()
{
    td_ = apriltag_detector_create();
    tf_ = tag36h11_create();
    // apriltag_detector_add_family(td_, tf_);
    apriltag_detector_add_family_bits(td_, tf_, 3);
    td_->quad_decimate = 1.0; // Decimate input image by factor //降低图片分辨率,经实验该参数可大幅提升检测时间且不影响检测结果
    td_->quad_sigma = 0;      // No blur //高斯降噪,如果噪声较大的图片,可使用该参数
    td_->nthreads = 2;        // 2 treads provided //使用几个线程来运行
    td_->debug = 0;           // No debuging output
    td_->refine_edges = 1;
    info_.tagsize = APRILTAG_DETECTED_SIZE;
    cv::Mat K, D;
    if (HIGH_RESOLUTION)
    {
        K = (cv::Mat_<double>(3, 3) << 471.33715, 0.0, 437.19435, 0.0, 472.0701, 275.1862, 0.0, 0.0, 1.0);
        D = (cv::Mat_<double>(5, 1) << -0.305921, 0.060122, -0.000640, 0.001716, 0.000000);
    }
    else
    {
        // K = (cv::Mat_<double>(3, 3) << 343.029387, 0.0, 320.552669, 0.0, 342.238068, 200.402539, 0.0, 0.0, 1.0);
        // D = (cv::Mat_<double>(5, 1) << -0.343152, 0.095372, -0.003143, -0.001278, 0.000000);

        K = (cv::Mat_<double>(3, 3) << 319.1774094207188, 0.0, 315.9695951747343, 0.0, 319.5137994758127, 199.07306955419622, 0.0, 0.0, 1.0);
        D = (cv::Mat_<double>(5, 1) << -0.3204769598908819, 0.07196729379479753, -0.0019994529282904004, 0.0008377275431221735, 0.0);
    }

    cv::Size img_size(img_width_, img_height_);
    initUndistortRectifyMap(K, D, cv::Mat(), K, img_size, CV_16SC2, map1_, map2_);//图片去畸变

    cv::Mat new_camera_matric = cv::getOptimalNewCameraMatrix(K, D, img_size, 1);
    cv::initUndistortRectifyMap(K, D, cv::Mat(), new_camera_matric, img_size, CV_16SC2, map1_, map2_);
    if (TAG_IMG_UNDISTORT)
        K = new_camera_matric;

    info_.fx = K.at<double>(0, 0);
    info_.fy = K.at<double>(1, 1);
    info_.cx = K.at<double>(0, 2);
    info_.cy = K.at<double>(1, 2);

    Eigen::Vector3d ea_cam_slope(0, 0, -37.0 / 180 * CV_PI);
    // Eigen::Vector3d ea_cam_slope(0, 0, 0.0 / 180 * CV_PI);
    //绕不同轴进行旋转 旋转相机 ZYX 相机变换
    rot_cam_slope_ = Eigen::AngleAxisd(ea_cam_slope[0], Eigen::Vector3d::UnitZ()) * //默认为单位矩阵,绕z轴旋转0度,结果为单位矩阵
                     Eigen::AngleAxisd(ea_cam_slope[1], Eigen::Vector3d::UnitY()) *  //绕y轴旋转0弧度,结果不变
                     Eigen::AngleAxisd(ea_cam_slope[2], Eigen::Vector3d::UnitX());  //绕x轴旋转-37/180×pi弧度 绕x轴右乘
    
    //绕z轴旋转90度,绕y轴旋转-180 绕x轴旋转90,在z方向向上平移 0.1m
    Eigen::Quaterniond quat_b_c(0.5, -0.5, 0.5, -0.5); //w,x,y,z,四元数4×1 90 -180 90 -0.5i+0.5j-0.5k+0.5
    Eigen::Matrix3d rot_mat_b_c;
    rot_mat_b_c = quat_b_c.toRotationMatrix();//四元数转矩阵
    Eigen::Translation3d trans_b_c(0.0, 0.0, 0.1);
    Tb_c_ = trans_b_c * rot_mat_b_c;

    //绕z轴旋转0度,绕y轴旋转-90度,绕x轴旋转90度,平移0
    Eigen::Quaterniond quat_t_tt(0.5, 0.5, -0.5, 0.5); // w,x,y,z
    Eigen::Matrix3d rot_mat_t_tt = quat_t_tt.toRotationMatrix();
    Eigen::Translation3d trans_t_tt(0, 0, 0);
    Tt_tt_ = trans_t_tt * rot_mat_t_tt;

    //不进行旋转,在x负方向平移-0.21米
    Eigen::Quaterniond quat_car(1, 0, 0, 0); // w,x,y,z
    Eigen::Matrix3d rot_car = quat_car.toRotationMatrix();
    Eigen::Translation3d trans_car(-0.21, 0, 0);
    T_car_ = trans_car * rot_car;
}

TagStationDetection::~TagStationDetection()
{
}

bool TagStationDetection::OpenCam(int cam_id)
{
    LINFO(TagStationDetectionTask, "Enabling video capture");

    cap_ = cv::VideoCapture(cam_id);
    cap_.set(cv::CAP_PROP_FRAME_WIDTH, img_width_);
    cap_.set(cv::CAP_PROP_FRAME_HEIGHT, img_height_);

    cv::TickMeter meter;
    meter.start();
    if (!cap_.isOpened())
    {
        LERROR(TagStationDetectionTask, "Couldn't open video capture device");
        return false;
    }
    else
    {
        meter.stop();
        LINFO(TagStationDetectionTask,
              "Detector initialized in " << meter.getTimeSec() << "s"
                                         << ", " << cap_.get(cv::CAP_PROP_FRAME_WIDTH)
                                         << "x" << cap_.get(cv::CAP_PROP_FRAME_HEIGHT)
                                         << " @" << cap_.get(cv::CAP_PROP_FPS) << "FPS");
        meter.reset();
        return true;
    }
}

void TagStationDetection::ReleaseCam()
{
    cap_.release();
    cv::destroyAllWindows();
}

bool TagStationDetection::OpenVideo(const std::string &video_name)
{
    cap_.open(video_name.c_str());
    if (!cap_.isOpened())
    {
        std::cerr << "Couldn't open video " << video_name.c_str() << std::endl;
        return false;
    }
    else
        return true;
}

bool TagStationDetection::DetectCamFrameTag(TagPose &tag_pose)
{
    bool if_found = false;
    cap_ >> frame_;
    if (frame_.empty())
        return if_found;
    if (TAG_IMG_UNDISTORT)
    {
        cv::Mat UndistortImage;
        remap(frame_, UndistortImage, map1_, map2_, cv::INTER_LINEAR);//去畸变
        frame_ = UndistortImage;
    }
    cvtColor(frame_, gray_, cv::COLOR_BGR2GRAY);
    image_u8_t im = {.width = gray_.cols,
                     .height = gray_.rows,
                     .stride = gray_.cols,
                     .buf = gray_.data};//转为apriltag库的格式
    zarray_t *detections = apriltag_detector_detect(td_, &im);//检测tag
    /*typedef struct apriltag_detection apriltag_detection_t;
    struct apriltag_detection
    {
        apriltag_family_t *family;
        int id;//tag的id

        int hamming;
        float decision_margin;

        matd_t *H; //代表从(-1,1), (1,1), (1,-1),(-1,-1)转到像素坐标
        double c[2]; //tag中心点在像素坐标下的坐标
        double p[4][2]; //像素坐标下的坐标,4个点从左下角开始逆时针旋转
    };*/

    for (int i = 0; i < zarray_size(detections); i++)
    {
        apriltag_detection_t *det;
        zarray_get(detections, i, &det);
        if (det->id == 0)//第0个tag
        {
            if (det->decision_margin > 50.0)
            {
                if_found = true;
                info_.det = det;
                apriltag_pose_t pose;
                // pose origin : center of tag
                //使用apriltag自带的库求解。这就会涉及库的坐标系和我们的坐标系转换
                /*apriltag_detection_info_t tag_info;
                tag_info.cx=cameraParam.m_cx;
                tag_info.cy=cameraParam.m_cy;
                tag_info.fx=cameraParam.m_fx;
                tag_info.fy=cameraParam.m_fy;
                tag_info.tagsize=find_mark.length;
                tag_info.det=det;
                apriltag_pose_t pose;

                estimate_tag_pose(&tag_info, &pose);
                Vector3d ori_relative_P;
                Matrix3d ori_rotation_matrix3d;
                memcpy(&ori_relative_P, pose.t->data, sizeof(Vector3d));
                memcpy(&ori_rotation_matrix3d, pose.R->data, sizeof(Matrix3d));*/
                double err = estimate_tag_pose(&info_, &pose);
                CalculatePose(pose, tag_pose);
                if (APRILTAG_DETECTED_IMG_VISUALIZATION)
                    DetectedVisualization(det);
            }
            break;
        }
        else
            continue;
    }
    if (not if_found)
    {
        if (APRILTAG_DETECTED_IMG_VISUALIZATION)
            DetectedVisualization();
    }
    apriltag_detections_destroy(detections);
    return if_found;
}

bool TagStationDetection::DetectImg(const std::string &img_name, TagPose &tag_pose)
{
    bool if_found = false;
    cv::Mat imge_raw = cv::imread(img_name.c_str());
    if (TAG_IMG_UNDISTORT)
    {
        cv::Mat UndistortImage;
        remap(imge_raw, UndistortImage, map1_, map2_, cv::INTER_LINEAR);
        // cv::imwrite("UndistortImage.png", UndistortImage);
        frame_ = UndistortImage;
    }
    else
        frame_ = imge_raw;

    cvtColor(frame_, gray_, cv::COLOR_BGR2GRAY);
    image_u8_t im = {.width = gray_.cols,
                     .height = gray_.rows,
                     .stride = gray_.cols,
                     .buf = gray_.data};

    zarray_t *detections = apriltag_detector_detect(td_, &im);
    for (int i = 0; i < zarray_size(detections); i++)
    {
        apriltag_detection_t *det;
        zarray_get(detections, i, &det);
        if (det->id == 0)
        {
            if_found = true;
            info_.det = det;
            apriltag_pose_t pose;
            double err = estimate_tag_pose(&info_, &pose);

            CalculatePose(pose, tag_pose);
            if (APRILTAG_DETECTED_IMG_VISUALIZATION)
                DetectedVisualization(det, 1);
            break;
        }
        else
            continue;
    }
    if (not if_found)
        LWARN(TagStationDetectionTask, "No effective tag detected");
    apriltag_detections_destroy(detections);
    return if_found;
}

void TagStationDetection::DetectedVisualization(apriltag_detection_t *det, int d)
{
    line(frame_, cv::Point(det->p[0][0], det->p[0][1]),
         cv::Point(det->p[1][0], det->p[1][1]),
         cv::Scalar(0, 0xff, 0), 2);
    line(frame_, cv::Point(det->p[0][0], det->p[0][1]),
         cv::Point(det->p[3][0], det->p[3][1]),
         cv::Scalar(0, 0, 0xff), 2);
    line(frame_, cv::Point(det->p[1][0], det->p[1][1]),
         cv::Point(det->p[2][0], det->p[2][1]),
         cv::Scalar(0xff, 0, 0), 2);
    line(frame_, cv::Point(det->p[2][0], det->p[2][1]),
         cv::Point(det->p[3][0], det->p[3][1]),
         cv::Scalar(0xff, 0, 0), 2);
    std::cout << "pixel coord: (" << det->p[0][0] << ", " << det->p[0][1] << "); "
              << "      (" << det->p[1][0] << ", " << det->p[1][1] << ")" << std::endl;
    std::stringstream ss;
    ss << det->id;
    cv::String text = ss.str();
    int fontface = cv::FONT_HERSHEY_SCRIPT_SIMPLEX;
    double fontscale = 1.0;
    int baseline;
    cv::Size textsize = cv::getTextSize(text, fontface, fontscale, 2,
                                        &baseline);
    putText(frame_, text, cv::Point(det->c[0] - textsize.width / 2, det->c[1] + textsize.height / 2),
            fontface, fontscale, cv::Scalar(0xff, 0x99, 0), 2);
    imshow("Tag Detections", frame_);
    cv::waitKey(d);
}

void TagStationDetection::DetectedVisualization()
{
    imshow("Tag Detections", frame_);
    cv::waitKey(1);
}

void TagStationDetection::CalculatePose(const apriltag_pose_t &pose, TagPose &tag_pose)
{
    // pose.R->data a matrix structure for holding double-precision values with
    // data in row-major order (i.e. index = row*ncols + col).
    Eigen::Matrix3d rot_mat_cam_tag = rot_cam_slope_ * (Eigen::Map<Eigen::Matrix3d>(pose.R->data).inverse());//转置
    //pose.R->data tag系下camera位置 (pose.R->data).inverse()=(pose.R->data).transpose() Tt_c(-1)=Tc_t
    Eigen::Vector3d trans_vector = -Eigen::Map<Eigen::Vector3d>(pose.t->data);
    Eigen::Translation3d translation(trans_vector[0], trans_vector[1], trans_vector[2]);
    //Translation3d是表示平移的一种形式,构造函数为Translation3d(x,y,z),它可以直接和旋转向量、四元数、旋转矩阵相乘,得到的结果为Affine3d类型
    Eigen::Affine3d Tc_t = translation * rot_mat_cam_tag;//相机坐标系下tag位置 -R(-1)*T

    Eigen::Affine3d T_target;
    T_target.matrix() = (Tb_c_.matrix() * Tc_t.matrix() * Tt_tt_.matrix()).inverse();//计算tag坐标系下相机的位置 Tb_tt(-1)=Ttt_b
    Eigen::Vector3d rpy_target = R2ypr(T_target.rotation());

    tag_pose.x = T_target.matrix()(0, 3);
    tag_pose.y = T_target.matrix()(1, 3);
    tag_pose.yaw = rpy_target(0);

    Eigen::Affine3d T_target_car;
    T_target_car.matrix() = T_target.matrix() * T_car_.matrix();
    tag_pose.y_car = T_target_car.matrix()(1, 3);

    std::cout << "pose x,y,z,yaw: " << tag_pose.x << ", " << tag_pose.y << ", " << T_target.matrix()(2, 3) << std::endl;
}

Eigen::Vector3d TagStationDetection::R2ypr(const Eigen::Matrix3d &R)
{
    Eigen::Vector3d n = R.col(0);
    Eigen::Vector3d o = R.col(1);
    Eigen::Vector3d a = R.col(2);

    Eigen::Vector3d ypr(3);
    double y = atan2(n(1), n(0));
    double p = atan2(-n(2), n(0) * cos(y) + n(1) * sin(y));
    double r = atan2(a(0) * sin(y) - a(1) * cos(y), -o(0) * sin(y) + o(1) * cos(y));
    ypr(0) = y;
    ypr(1) = p;
    ypr(2) = r;
    return ypr;
}

void TagStationPub::init()
{
    ctx_ = zmq::context_t{1};
    publisher_ = zmq::socket_t(ctx_, zmq::socket_type::pub);
    publisher_.connect("tcp://127.0.0.1:5555");
}

void TagStationPub::TagPosePublish(const TagPose &tag_pose)
{
    pose_2d_.data = tag_pose;
    Pose2dPublish();
    LINFO(TagStationDetectionTask,
          "pub tag pose (x, y, yaw, y_car): " << pose_2d_.data.x << ", " << pose_2d_.data.y
                                              << ", " << pose_2d_.data.yaw << ". " << pose_2d_.data.y_car);
}

void TagStationPub::TagPosePublish()
{
    pose_2d_.data.x = 15;
    pose_2d_.data.y = 15;
    pose_2d_.data.yaw = 15;
    pose_2d_.data.y_car = 15;
    Pose2dPublish();
    LWARN(TagStationDetectionTask,
          "no detection, pub: " << pose_2d_.data.x << ", " << pose_2d_.data.y
                                << ", " << pose_2d_.data.yaw << ". " << pose_2d_.data.y_car);
}

void TagStationPub::Pose2dPublish()
{
    publisher_.send(zmq::str_buffer("/charge_station_detection "), zmq::send_flags::sndmore);
    zmq::message_t msg(sizeof(pose_2d_));
    memcpy(msg.data(), pose_2d_.buffer, sizeof(pose_2d_));
    publisher_.send(msg);
}

apritag_vision: 充电站与apritag之间进行定位,确定车站与车之间的位置关系

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

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

相关文章

Matlab:绘制日期时间

Matlab&#xff1a;绘制日期时间绘制日期时间数据指定坐标区范围指定刻度值指定刻度格式存储日期时间的坐标区属性导出和转换数据提示值绘制来自文件的日期时间数据此示例说明如何使用存储为 datetime 和 duration 数组的日期时间创建线图。datetime 数据类型表示时间点&#x…

Linux I/O 原理和 Zero-copy 技术全面分析

两万字长文从虚拟内存、I/O 缓冲区&#xff0c;用户态&内核态以及 I/O 模式等等知识点全面而又详尽地剖析 Linux 系统的 I/O 底层原理&#xff0c;分析了 Linux 传统的 I/O 模式的弊端&#xff0c;进而引入 Linux Zero-copy 零拷贝技术的介绍和原理解析&#xff0c;将零拷贝…

项目终于收尾了,第一次体验到专业项目管理软件的魅力

转眼到了年底&#xff0c;我跟进的项目也到了收尾阶段。之前陆陆续续给大家分享了入职新公司后&#xff0c;使用新引进的项目管理软件做项目的一些体会和心得&#xff0c;其中一些比较高效便捷的技巧和功能模块也引起了大家的兴趣。 最近刚好临近项目尾声&#xff0c;也给大家…

Maven的详解

在java中Maven就是一个包管理工具,在没有包管理工具时,我们要做一个java项目,需要第三方依赖包,将别人打包好的Jar包下载到本地,然后手动指定给项目.操作比较麻烦,比如版本控制,有的甚至还有其他包的依赖,属实是繁琐,技术是不断地迭代的,所以就出现了Maven,用了Maven之后,需要什…

安装nodejs的详细流程保姆级(踩了无数次坑)

node 简述: node的使用已经是前端选手基本的选择,其强大的功能甚至到了要和后端抢活干的地步,同时想要搭建个人的博客用node工具也是非常方便的,作为一名后端选上,刚开始准备下载node的时候是因为想要去搭建个人的博客,但是下载之后,使用npm install命令的时候一直报错,无奈找…

G1D14fraudgitpipenvdf操作APT论文RCE37-40服务器搭建

一、fraud 突然发现电脑上还没有python编译器&#xff0c;xswl&#xff0c;快装一下 &#xff08;一&#xff09;git操作 &#xff08;二&#xff09;git中分支的作用 &#xff08;三&#xff09;虚拟环境 1、pip install后的包一般放在哪里 lib/site-packages下 真的是欸&a…

LiveData

LiveData是一个抽象类&#xff0c;那么我们从简单的MutableLiiveData开始剖析&#xff0c;先看源码 源码太简洁了&#xff0c;就是继承LiveData&#xff0c;然后重写了父类的方法&#xff0c;并且没有多余的操作&#xff0c;都是直接使用父类方法里的逻辑&#xff0c;那我们就根…

安全防护的原则

电力行业 工控安全解决思路保障框架从电力行业对工控安全需求看&#xff0c;电力企业在主要是以合规性建设为主&#xff0c;在 2004 年原电监会 5 号令颁布开始&#xff0c;大部 分的电厂控制系统安全 建设已经按照 5 号令的要求进行了整改&#xff0c;形成“安全分区、网络专…

数电笔记总结(三)(逻辑门电路)

目录逻辑门基础逻辑门电路分立元件基本逻辑门电路TTL集成门电路&#xff08;与非门&#xff09;两种特殊门&#xff08;重点&#xff09;1.集电极开路门&#xff08;OC门&#xff09;2.三态门电路逻辑门基础 逻辑门电路 门电路&#xff1a;具有控制信号通过或不通过能力的电路…

某某桥的检测和加固设计

目录 某某大桥桥梁检测及加固设计报告 1 0 总论 2 0.1 检测目的 2 0.2 桥梁结构混凝土强度检测[1] 2 0.3 结构综合评定指标 4 0.4桥梁承载能力[3] 4 0.5 桥梁结构荷载试验 6 0.6 主要结果与结论 8 1某某大桥简介 11 1.1某某大桥简介 11 1.2 检测仪器与设备 15 2 外观检查与检测…

【跟学C++】C++STL标准模板库——算法整理(上)(Study18)

文章目录1、STL简介2、STL算法分类及常用函数2.1、非变序算法2.1.1 计数算法(2个)2.1.2 搜索算法(7个)2.1.3 比较算法(2个)3、总结 【说明】 大家好&#xff0c;本专栏主要是跟学C内容&#xff0c;自己学习了这位博主【 AI菌】的【C21天养成计划】&#xff0c;讲的十分清晰&am…

每个程序员都要知道的一个网站

在日常开发过程中&#xff0c;你是不是经常回到搜索引擎&#xff0c;搜索某个功能的实现方式&#xff0c;比如&#xff1a;Javascript 数组排序、正则表达式等等。 今天给大家推荐的这个网站&#xff0c;就可以满足大家的需求&#xff0c;它叫&#xff1a;30secondsofcode&…

抽象类与接口

目录 1. 抽象类 1.1 抽象类概念 1. 2&#x1f414;抽象类特性 1.3 抽象类的作用 2. 接口 2.1 接口是什么 2.2 语法规则 2.3 方法的使用 2.4 接口特性 2.5 实现接口 VS 继承类 2.6 抽象类 VS 接口&#xff08;总结&#xff09; 2.6 接口间的继承 &#x1f413; 随着…

漫画脸头像怎么制作?这几种方法可以帮到你

你们会经常更换头像吗&#xff1f;我身边就有一些朋友会这样做&#xff0c;看到喜欢的头像就换&#xff0c;而且他基本上都是找那些漫画脸来当头像。那如果我们把自己的人像制作成漫画脸&#xff0c;就不容易跟别人撞头像了&#xff0c;还显得有个性。 估计有很多小伙伴不知道漫…

NXP BootLoader源码分析并改写SD卡启动

1 官方资料 NXP官方提供了MCUBoot SDK&#xff1a;NXP_Kinetis_Bootloader_2.0.0 package&#xff0c;里面包含了各种型号芯片的的BootLoader。 BootLoader参考手册&#xff1a;doc/Kinetis Bootloader v2.0.0 Reference Manual.pdf上位机程序参考手册&#xff1a;Kinetis Fl…

疯狂小杨哥被王海打假

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 知名打假人王海 发布视频&#xff0c;说疯狂小杨哥三只羊直播间售卖的金正破壁机和绞肉机虚标功率。破壁机标注功率为300W&#xff0c;实际为105W&#xff0c;绞肉机标注功率300W&#xff0c;实际功…

[附源码]java毕业设计咖啡销售管理系统-

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

总线仲裁的方式

总线仲裁的基本概念 同一时刻只能有一个设备控制总线传输操作&#xff0c;可以有一个或多个设备从总线接收数据。 将总线上所连接的各类设备按其对总线有无控制功能分为&#xff1a; 主设备&#xff1a;获得总线控制权的设备。 从设备&#xff1a;被主设备访问的设备&#xff0…

C++11标准模板(STL)- 算法(std::stable_partition)

定义于头文件 <algorithm> 算法库提供大量用途的函数&#xff08;例如查找、排序、计数、操作&#xff09;&#xff0c;它们在元素范围上操作。注意范围定义为 [first, last) &#xff0c;其中 last 指代要查询或修改的最后元素的后一个元素。 将元素分为两组&#xff0…

内网的基本构造

内网的基本构造 内网也就是常说的局域网(Local Area Network,简称LAN)&#xff0c;是由两个或两个以上相连的计算机组成&#xff0c;局域网是包含在较小区域内的网络&#xff0c;覆盖范围一般是方圆几千米之内&#xff0c;通常位于建筑物内。家庭WiFi网络和小型企业网络是常见…