OpenCV实战(31)——基于级联Haar特征的目标检测

news2024/11/25 0:34:57

OpenCV实战(31)——基于级联Haar特征的目标检测

    • 0. 前言
    • 1. Haar 特征图像表示
    • 2. 基于级联 Haar 特征的二分类分类器
    • 3. 级联分类器算法流程
    • 4. 使用 Haar 级联检测器进行人脸检测
    • 5. 完整代码
    • 小结
    • 系列链接

0. 前言

在机器学习基础一节中,我们介绍了机器学习的一些基本概念,并通过使用不同类别的样本来构建分类器。但这种方法训练分类器需要存储所有样本的表示,然后通过查看最近标记点(最近邻居)来预测新实例的标签。对于大多数机器学习方法,训练是一个迭代过程,在此过程中通过循环遍历样本来构建机器学习模型。通过使用更多的样本,得到的分类器性能会逐渐提高。当模型性能达到预设值或者当无法从当前训练数据集中获得更多改进时,学习过程将停止。本节中,我们将介绍一种遵循以上过程的机器学习算法,即级联分类器。

1. Haar 特征图像表示

在我们继续学习该分类器之前,首先将介绍 Haar 特征图像表示。我们已经知道,良好的图像表示是生成鲁棒性分类器的基本要素。
生成分类器的第一步是获取大量图像样本集合,这些样本包含要识别的对象类别的不同实例,样本的表示方式对利用样本构建的分类器的性能具有重要影响。像素级表示通常由于过于低级,而无法鲁棒地描述每一类对象的内在特征。相反,可以在多个尺度上描述图像中存在的独特图案的表示能够更好的表示图像特征。这就是 Haar 特征 (Haar features) 的基本思想,也称为类 Haar 特征 (Haar-like features),因为它们都源自 Haar 变换基函数 (Haar transform basis functions)。
Haar 特征定义了像素的小矩形区域,稍后通过简单的减法进行比较。通常可以使用三种不同的配置,即 2-矩形3-矩形4-矩形特征:

矩形区域

这些特征可以是任意大小并应用于要表示的图像的任何区域。例如,下图中包括两个应用于人脸图像的 Haar 特征:

应用于人脸图像的 Haar 特征
构建 Haar 表示包括选择任何给定类型、大小和位置的多个 Haar 特征,并将它们应用于图像。从选定的一组 Haar 特征中获得的一组特定值构成了图像表示。接下来,面临的挑战是确定要选择哪一组特征。事实上,为了区分不同对象,使用其中某些 Haar 特征可能比其他特征更有效。例如,在人脸图像样本中,在眼睛之间应用 3-矩形 Haar 特征(如上图所示)可能更有效。当然,由于存在数十万个可能的 Haar 特征,因此手动进行选择是十分困难的,我们需要使用机器学习方法来为给定的对象类别选择最相关的特征。

2. 基于级联 Haar 特征的二分类分类器

在本节中,我们将学习如何使用 OpenCV 构建增强的级联特征以生成二分类分类器。二分类分类器是一种可以从其他类别(例如,不包含人脸的图像)中识别出指定类(例如,人脸图像)实例的分类器,即分类任务中仅存在两个类别。在这种情况下,我们使用正样本(即人脸图像)和负样本(即非人脸图像)表示两种类别。本节使用的分类器由一系列简单的分类器组成,一次应用这些分类器。级联分类器的每一阶段都将根据为一小部分特征获得的值快速决定是否拒绝对象。在每个阶段通过做出更准确的决策来改进(或提升)前一个阶段的分类器性能,这种级联结构提升了分类器性能。这种方法的主要优点是级联的早期阶段由简单的检测组成,然后可以快速拒绝无关的类实例,使得级联分类器的计算更快,因为当通过扫描图像搜索一类对象时,大多数要测试的子窗口不属于我们感兴趣的类别。这样,只有少数窗口需要通过所有阶段才能决定被接受或拒绝。

(1) 为了训练增强级联分类器,OpenCV 提供用于执行这些操作的工具。安装 OpenCV 库时,会创建两个可执行程序并位于 bin 目录中:opencv_createsamplesopencv_traincascade

可执行程序

(2) 在训练分类器时,首先需要收集样本。正赝本由目标类实例的图像组成,在本节中,我们旨在训练一个分类器来识别交通标志,使用的正样本如下:

正样本

(3) 使用的正样本列表在名为 sign.txt 的文本文件中指定,文件包含图像文件名和边界框坐标:

1.png 1 0 0 64 64
2.png 1 0 0 64 64
3.png 1 0 0 64 64
4.png 1 0 0 64 64
5.png 1 0 0 64 64
6.png 1 0 0 64 64
7.png 1 0 0 64 64
8.png 1 0 0 64 64
9.png 1 0 0 64 64

(4) 文件名后的第一个数字是图像中包含的正样本数,紧接着的两个值是包含正样本的边界框的左上角坐标,最后两个值是目标的宽度和高度。在本节中,我们已经在原始图像中提取了正样本,因此每个文件中有且仅有一个样本并且左上角坐标为 (0, 0)。接下来,通过运行提取器工具来创建正样本文件:

opencv_createsamples -info sign.txt -vec sign.vec -w 24 -h 24 -num 9

(5) 以上代码将创建 stop.vec 输出文件,其中包含输入文本文件中指定的所有正样本。需要注意的是,我们使样本大小 (24×24) 小于原始大小 (64×64),提取器工具将所有样本的大小调整为指定的大小。通常,Haar 特征与较小的模板能够更好的配合,但这必须根据具体情况进行验证。

(6) 负样本是不包含感兴趣类别实例的图像(在本节中,指不包含交通标志的图像)。除此之外,这些图像应该包含分类器期望获得的各类图像。这些负样本可以是任意大小,因为训练工具可以从这些图像中提取随机负样本:

负样本

(7) 一旦正负样本准备完毕,就可以训练级联分类器了:

opencv_traincascade -data classifier -vec sign.vec -bg neg.txt -numPos 9 -numNeg 18 -numStages 20 -minHitRate 0.95 -maxFalseAlarmRate 0.5 -w 24 -h 24

需要注意的是,训练过程可能需要很长时间,在涉及数千个样本的复杂分类器中,训练过程甚至可能需要数天时间。运行训练程序时,级联分类器会在每次完成一个阶段的训练时打印出性能报告,给出分类器当前的命中率 (hit rate, HR),这是级联分类器当前接受的正样本的百分比(即,它们被正确识别为正样本,这种情况也称为真正例),我们希望该数字尽可能接近 1.0。报告还会提供当前的误报率 (false alarm rate, FAR),即被错误分类为正样本(也称为假正例)的已测试负样本的数量,我们希望该数字尽可能接近 0.0
在我们的分类器中训练只需几秒钟,生成的分类器的结构在训练阶段产生的 XML 文件中进行描述。训练完成后,就可以使用分类器了,我们可以向它输入任意样本,分类器将输出预测结果。

(8) 在本节中,我们用 24×24 的图像训练了级联分类器,但是,一般来说,我们需要找出任意尺寸图像的任意位置是否有类对象的实例。为了实现这一目标,我们需要扫描输入图像并提取样本大小的所有可能窗口。如果分类器足够准确,则只有包含目标对象的窗口才会返回正样本预测。但是,这仅在正样本具有合适大小时才有效,要在多个尺度上检测对象实例,必须通过构建图像金字塔,在每个级别将原始图像的大小进行缩放。通过这种方式,沿着金字塔向下,更大的目标示例最终将缩放至合适的大小。这是一个漫长的过程,但是 OpenCV 提供了实现此过程的类。首先,需要通过加载合适的 XML 文件来构建分类器:

    cv::CascadeClassifier cascade;
    if (!cascade.load("classifier/cascade.xml")) { 
        std::cout << "Error when loading the cascade classfier!" << std::endl; 
        return -1; 
    }

(9) 然后,使用输入图像调用检测方法:

    // 预测图片标签
    std::vector<cv::Rect> detections;
    cascade.detectMultiScale(inputImage,    // 输入图像
                            detections,     // 检测结果
                            1.1,            // 尺度缩放因子
                            1,              // 所需邻居检测数
                            0,              // 标志位
                            cv::Size(48, 48),       // 要检测的最小对象大小
                            cv::Size(200, 200));    // 要检测的最大对象大小
    std::cout << "detections= " << detections.size() << std::endl;
    for (int i = 0; i < detections.size(); i++)
        cv::rectangle(inputImage, detections[i], cv::Scalar(255, 255, 255), 2);
    cv::imshow("Sign detection", inputImage);

(10) 返回 cv::Rect 实例的向量,要可视化检测结果,只需在输入图像上绘制这些矩形:

    for (int i = 0; i < detections.size(); i++)
        cv::rectangle(inputImage, detections[i], cv::Scalar(255, 255, 255), 2);
    cv::imshow("Sign detection", inputImage);

使用分类器在图像上进行测试,可以得到以下结果:

分类器测试结果

3. 级联分类器算法流程

在上一小节中,我们介绍了如何使用正负样本构建 OpenCV 级联分类器,接下来,我们将介绍训练级联分类器的基本步骤。我们介绍的级联分类器使用了 Haar 特征进行训练,但是,我们也可以使用其他图像特征构建级联分类器。
级联分类器背后包含两个核心思想。第一个是可以通过将几个性能较弱的分类器(即基于简单特征的分类器)组合在一起来构建得到性能较强的分类器;其次,在机器视觉中,负样本出现的概率比正样本更频繁,因此可以通过划分阶段进行有效的分类。早期阶段迅速拒绝明显的负实例,后期阶段对更复杂的样本做出更精细的决策。基于以上思想,我们继续介绍提升级联学习算法 (boosted cascade learning algorithm),我们所使用的算法基于 boosting 的变体算法 AdaBoost
在本节中,我们使用 Haar 特征来构建弱分类器。当应用一个 Haar 特征(给定类型、大小和位置)时,将获得一个值。然后通过找到最能根据该特征值对负实例和正实例进行分类的阈值来获得一个简单的分类器。为了找到最佳阈值,我们可以使用一些正样本和负样本 (opencv_traincascade 使用的正样本和负样本的数量由 -numPos-numNeg 参数指定)。由于有大量可能的 Haar 特征,检查所有特征并选择能够对样本集进行分类的最佳特征,显然,这个基础的分类器可能出错,即出现错误分类的情况,因此我们需要构建数个这种分类器,这些分类器是迭代添加的,每次搜索新的 Haar 特征时都会给出最佳分类。但是,由于在每次迭代中,我们都希望关注当前被错误分类的样本,因此通过对错误分类的样本赋予更高的权重来衡量分类性能。因此,最终将获得一组简单的分类器,然后根据这些弱分类器的加权和构建强分类器(即,性能更好的分类器被赋予更高的权重)。使用这种方法,可以通过组合数百个简单的特征来获得具有良好性能的强分类器。
然而,训练早期我们不希望由大量弱分类器组成强分类器。相反,我们需要找到仅使用少量 Haar 特征的简单分类器,以便快速拒绝明显的负样本,同时保留所有正样本。在经典形式中,AdaBoost 旨在通过计算假阴性(即被错误分类为负样本的正样本)和假阳性(即错误分类为正样本的负样本)的数量来最小化总分类误差。我们的目标是将绝大多数甚至全部正样本正确分类,同时最小化误报率,可以通过修改 AdaBoost 以便在预测真正例时给予更高的奖励。因此,在训练级联分类器的每个阶段,必须设置两个标准——最小命中率和最大误报率;在 opencv_traincascade 中,使用 -minHitRate (默认值为 0.995) 和 -maxFalseAlarmRate (默认值为 0.5) 参数指定。在各个阶段添加 Haar 特征,直到满足这两个性能标准。必须将最小命中率设置得较高,以确保正实例能够进入下一阶段;需要注意的是,如果某个正实例在某个阶段被拒绝,则无法恢复被拒绝的正实例。因此,为了便于生成低复杂度的分类器,应将最大误报率设置得较高。否则,将需要较多的 Haar 特征才能满足性能标准,这与早期训练弱分类器的思想相矛盾。
因此,一个好的级联分类器在早期应使用较少特征的组成,随着级联的增加,每个阶段的特征数量随之增加。在 opencv_traincascade 中,每个阶段的最大特征数使用 -maxWeakCount (默认值为 100) 参数设置,阶段数使用 -numStages (默认值为 20) 参数设置。
当开始新阶段的训练时,必须收集新的负样本,可以从提供的负样本图像中提取。难点是找到通过所有前期阶段的负样本(即那些被错误分类为正样本的样本)。训练的阶段越多,收集这些负样本就越困难,这就是需要为分类器提供大量负样本图像的原因,从难以分类的图像块中提取样本(因为它们类似于正样本)。此外,如果在给定阶段,在不添加任何新特征的情况下满足了两个性能标准,那么级联训练将在停止,这意味着我们可以按原样使用该模型,或者通过提供更多复杂样品。反之,如果无法达到性能标准,则训练也会停止,在这种情况下,我们应该使用更简单的性能标准尝试新的训练过程。
通过由 n 个阶段组成的级联分类器,可以很容易地证明分类器的全局性能至少会优于 minHitRatemaxFalseAlarmRate,这是由于每个阶段都建立在先前级联阶段的结果之上。例如,我们考虑 opencv_traincascade 的默认值,期望分类器具有 0.99 5 20 0.995^{20} 0.99520 的准确率(命中率)和 0. 5 20 0.5^{20} 0.520 的误报率。这意味着 90% 的正样本能被正确识别,0.001% 的负样本将被错误地归类为正样本。需要注意的是,当进行级联时,一部分正样本将丢失,我们必须提供比每个阶段使用的指定样本数量更多的正样本。在以上示例中,我们将 numPos 设置为可用正样本数量的 90%
一个重要的问题是应该使用多少样本进行训练?这需要根据具体应用判断,但显然,正样本集必须足够大才能涵盖大部分类实例。负样本图片也应该是相关的,通常的经验法则是令 numNeg = 2 * numPos
本节中,我们已经介绍了如何使用 Haar 特征构建级联分类器。此类特征也可以使用其他特征来构建,例如 LBP 特征或定向梯度直方图等,在 opencv_traincascade 程序中可以使用 -featureType 参数选择不同的特征类型。
OpenCV 库包含了许多预训练的级联分类器,可以使用这些分类器来检测人脸、面部特征等,我们可以在源目录的数据目录中找到这些 XML 文件。

4. 使用 Haar 级联检测器进行人脸检测

OpenCV 中已经提供了预训练的人脸检测模型,我们要做的就是使用适当的 XML 文件创建 cv::CascadeClassifier 类的实例:

    cv::CascadeClassifier faceCascade;
    if (!faceCascade.load("haarcascade_frontalface_default.xml")) {
        std::cout << "Error when loading the face cascade classfier!" << std::endl;
        return -1;
    }

然后,要检测具有 Haar 特征的人脸:

   faceCascade.detectMultiScale(picture,   // 输入图像
                detections,                 // 检测结果
                1.1,                        // 尺度缩放因子
                3,                          // 所需邻居检测数
                0,                          // 标记位
                cv::Size(48, 48),           // 要检测的最小对象大小
                cv::Size(200, 200));        // 要检测的最大对象大小
    std::cout << "detections= " << detections.size() << std::endl;

也可以使用相同的过程检测人物眼睛:

人脸检测

5. 完整代码

完整代码 detectObjects.cpp 如下所示:

#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/objdetect.hpp>

int main() {
    // 打开正样本
    std::vector<cv::Mat> referenceImages;
    referenceImages.push_back(cv::imread("1.png"));
    referenceImages.push_back(cv::imread("2.png"));
    referenceImages.push_back(cv::imread("3.png"));
    referenceImages.push_back(cv::imread("4.png"));
    referenceImages.push_back(cv::imread("5.png"));
    referenceImages.push_back(cv::imread("6.png"));
    referenceImages.push_back(cv::imread("7.png"));
    referenceImages.push_back(cv::imread("8.png"));
    referenceImages.push_back(cv::imread("9.png"));
    // 组合图像
    cv::Mat positveImages(2 * referenceImages[0].rows, 4 * referenceImages[0].cols, CV_8UC3);
    for (int i = 0; i < 2; i++)
        for (int j = 0; j < 4; j++) {

            referenceImages[i * 2 + j].copyTo(positveImages(cv::Rect(j*referenceImages[i * 2 + j].cols, i*referenceImages[i * 2 + j].rows, referenceImages[i * 2 + j].cols, referenceImages[i * 2 + j].rows)));
        }
    cv::imshow("Positive samples", positveImages);
    cv::Mat negative = cv::imread("n1.jpg");
    cv::resize(negative, negative, cv::Size(), 0.33, 0.33);
    cv::imshow("One negative sample", negative);
    cv::Mat inputImage = cv::imread("sign.png");
    cv::resize(inputImage, inputImage, cv::Size(), 0.5, 0.5);
    cv::CascadeClassifier cascade;
    if (!cascade.load("classifier/cascade.xml")) { 
        std::cout << "Error when loading the cascade classfier!" << std::endl; 
        return -1; 
    }

    // 预测图片标签
    std::vector<cv::Rect> detections;
    cascade.detectMultiScale(inputImage,    // 输入图像
                            detections,     // 检测结果
                            1.1,            // 尺度缩放因子
                            1,              // 所需邻居检测数
                            0,              // 标志位
                            cv::Size(48, 48),       // 要检测的最小对象大小
                            cv::Size(200, 200));    // 要检测的最大对象大小
    std::cout << "detections= " << detections.size() << std::endl;
    for (int i = 0; i < detections.size(); i++)
        cv::rectangle(inputImage, detections[i], cv::Scalar(255, 255, 255), 2);
    cv::imshow("Sign detection", inputImage);

    // 人脸检测
    cv::Mat picture = cv::imread("girl.png");
    cv::CascadeClassifier faceCascade;
    if (!faceCascade.load("haarcascade_frontalface_default.xml")) {
        std::cout << "Error when loading the face cascade classfier!" << std::endl;
        return -1;
    }

    faceCascade.detectMultiScale(picture,   // 输入图像
                detections,                 // 检测结果
                1.1,                        // 尺度缩放因子
                3,                          // 所需邻居检测数
                0,                          // 标记位
                cv::Size(48, 48),           // 要检测的最小对象大小
                cv::Size(200, 200));        // 要检测的最大对象大小
    std::cout << "detections= " << detections.size() << std::endl;
    // 绘制检测到的对象边界框
    for (int i = 0; i < detections.size(); i++)
        cv::rectangle(picture, detections[i], cv::Scalar(255, 255, 255), 2);

    // 检测眼睛
    cv::CascadeClassifier eyeCascade;
    if (!eyeCascade.load("haarcascade_eye.xml")) {
        std::cout << "Error when loading the eye cascade classfier!" << std::endl;
        return -1;
    }

    eyeCascade.detectMultiScale(picture,    // 输入图像
                detections,                 // 检测结果
                1.1,                        // 尺度缩放因子
                3,                          // 所需邻居检测数
                0,                          // 标记位
                cv::Size(24, 24),           // 要检测的最小对象大小
                cv::Size(36, 36));          // 要检测的最大对象大小

    std::cout << "detections= " << detections.size() << std::endl;
    // 绘制检测到的对象边界框
    for (int i = 0; i < detections.size(); i++)
        cv::rectangle(picture, detections[i], cv::Scalar(0, 0, 0), 2);
    cv::imshow("Detection results", picture);
    cv::waitKey();
    return 0;
}

小结

Haar 特征能够在多个尺度上描述图像中存在的独特图案的表示,可以更好的表示图像特征,级联分类器通过将几个性能较弱的分类器组合在一起来构建得到性能较强的分类器,本节介绍了使用 cv::CascadeClassifier 函数实现级联 Haar 特征执行目标检测。

系列链接

OpenCV实战(1)——OpenCV与图像处理基础
OpenCV实战(2)——OpenCV核心数据结构
OpenCV实战(3)——图像感兴趣区域
OpenCV实战(4)——像素操作
OpenCV实战(5)——图像运算详解
OpenCV实战(6)——OpenCV策略设计模式
OpenCV实战(7)——OpenCV色彩空间转换
OpenCV实战(8)——直方图详解
OpenCV实战(9)——基于反向投影直方图检测图像内容
OpenCV实战(10)——积分图像详解
OpenCV实战(11)——形态学变换详解
OpenCV实战(12)——图像滤波详解
OpenCV实战(13)——高通滤波器及其应用
OpenCV实战(14)——图像线条提取
OpenCV实战(15)——轮廓检测详解
OpenCV实战(16)——角点检测详解
OpenCV实战(17)——FAST特征点检测
OpenCV实战(18)——特征匹配
OpenCV实战(19)——特征描述符
OpenCV实战(20)——图像投影关系
OpenCV实战(21)——基于随机样本一致匹配图像
OpenCV实战(22)——单应性及其应用
OpenCV实战(23)——相机标定
OpenCV实战(24)——相机姿态估计
OpenCV实战(25)——3D场景重建
OpenCV实战(26)——视频序列处理
OpenCV实战(27)——追踪视频中的特征点
OpenCV实战(28)——光流估计
OpenCV实战(29)——视频对象追踪
OpenCV实战(30)——OpenCV与机器学习的碰撞

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

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

相关文章

【Linux】文件缓冲区

目录 一、缓冲区图解二、自定义实现文件操作函数三、强制刷新内核缓冲区&#xff08;fsync&#xff09; 提到文件缓冲区这个概念我们好像并不陌生&#xff0c;但是我们对于这个概念好像又是模糊的存在脑海中&#xff0c;之间我们在介绍c语言文件操作已经简单的提过这个概念&…

NoSQL MongoDB Redis E-R图 UML类图概述

NoSQL NoSQL(Not only SQL)是对不同于传统的关系数据库的数据库管理系统的统称&#xff0c;即广义地来说可以把所有不是关系型数据库的数据库统称为NoSQL。 NoSQL 数据库专门构建用于特定的数据模型&#xff0c;并且具有灵活的架构来构建现代应用程序。NoSQL 数据库使用各种数…

CIM和websockt-实现实时消息通信:双人聊天和消息列表展示

欢迎大佬的来访&#xff0c;给大佬奉茶 一、文章背景 有一个业务需求是&#xff1a;实现一个聊天室&#xff0c;我和对方可以聊天&#xff1b;以及有一个消息列表展示我和对方&#xff08;多个人&#xff09;的聊天信息和及时接收到对方发来的消息并展示在列表上。 项目框架概…

SNP 分享:SAP S/4HANA Cloud 私有云版本及其独特优势

近几年来&#xff0c;SAP一直强调其愿景是帮助客户达成智慧型企业(Intelligent Enterprise)&#xff0c;为此其相关产品也在不断进行快速迭代&#xff0c;其核心就是S4HANA。同时SAP一直强调其要成为一家云计算公司&#xff0c;近些年也一直在推行云优先战略(Cloud First)。因此…

指针(通过指针间接访问内存)

#include <iostream> #include <algorithm> using namespace std; int main() { int a 2;//定义指针 &#xff1a; 数据类型 *指针变量名;int *p &a;cout << &a << " " << p << endl;//使用指针 &#xff1a; 可以通过…

怎么把表情包做成动态?分享一个简单的方法

表情包在我们的日常交流中已经成为了一种非常流行的表达方式&#xff0c;而将表情包做成动态则可以让它更加生动有趣。本文将介绍如何将表情包制作成动态图&#xff0c;以及一些简单的方法和制作注意事项。 制作动态表情包的方法有很多种&#xff0c;以下是其中两种简单易行的方…

SpringCloud面试题大全(Netflix+Alibaba)

SpringCloud面试题大全 ​ Spring cloud 是一个基于 Spring Boot 实现的服务治理工具包&#xff0c;用于微服务架构中管理和协调服务的。Spring Cloud 是一系列框架的有序集合。它利用 Spring Boot 的开发便利性巧妙地简化了分布式系统基础设施的开发&#xff0c;如服务发现注…

华为云云服务器评测 | 3分钟搞懂如何在华为云服务器安装Nginx并配置静态访问页面

文章目录 一、什么是Nginx&#xff1f;二、申请华为云服务器三、使用XShell连接华为云服务器并安装Nginx四、FileZilla连接服务器五、Linux下安装Nginx❇️配置80端口并关闭Linux防火墙✳️测试 六、配置静态html至华为云服务器并访问⚠️在华为服务器新建路径⏰使用Filezilla上…

java+ssm+mysql电费管理系统

项目介绍&#xff1a; 使用javassmmysql开发的用户电费管理系统&#xff0c;系统包含超级管理员&#xff0c;系统管理员、用户角色&#xff0c;功能如下&#xff1a; 超级管理员&#xff1a;管理员管理、用户管理、用电管理&#xff08;用电记录、缴费提醒&#xff09;、电费…

C++多态案例-设计计算器类

1.前置知识点 多态是面向对象的三大特性之一 多态分为两类 静态多态&#xff1a;函数重载和运算符重载都属于静态多态&#xff0c;复用函数名动态多态&#xff1a;派生类和虚函数实现运行时多态 静态多态和动态多态的区别 静态多态的函数地址早绑定-----编译阶段确定函数地…

Navicat Premium 16.2.7 for Mac

Navicat Premium 16是一款功能强大的跨平台数据库管理工具&#xff0c;支持多种数据库类型&#xff0c;如MySQL、MariaDB、Oracle、SQLite、PostgreSQL等等。它提供了丰富的数据库管理功能和工具&#xff0c;可以帮助开发人员和数据库管理员快速地创建、管理和维护数据库。 Nav…

采用第11代Intel®Core处理器的多网口嵌入式边缘计算平台

Intel Core™ 11th i7/i5/i3/Celeron 处理器 及 8GB DDR4 3200Mb/s 内存4 x GbE, 3 x USB 3.2 Gen2, 1 x USB2.0, 1 x HDMI 1.4, 1 x DP 1.4a, 4 x RS232/422/485可选的第二堆栈支持多达2 x iDoor扩展&#xff0c;用于扩展无线连接、工业现场总线或更多I/O紧凑型无风扇设计零电…

1.15 自实现GetProcAddress

在正常情况下&#xff0c;要想使用GetProcAddress函数&#xff0c;需要首先调用LoadLibraryA函数获取到kernel32.dll动态链接库的内存地址&#xff0c;接着在调用GetProcAddress函数时传入模块基址以及模块中函数名即可动态获取到特定函数的内存地址&#xff0c;但在有时这个函…

做答题小程序前期需要准备哪些工作

做一个答题小程序前期需要做哪些准备工作呢&#xff1f; 第一、要有明确的答题活动规则需求&#xff0c;比如是想用个人答题形式、还是pk答题形式&#xff0c;每个模式具体的出题规则和得分规则&#xff0c;这些要计划清楚&#xff0c;让开发答题小程序的公司能够充分理解你的需…

vue3哪个数组方法在vue2上做了升级处理

在 Vue 3 中&#xff0c;v-for 指令的数组更新行为进行了升级处理。在 Vue 2 中&#xff0c;当使用 v-for 渲染数组时&#xff0c;如果对数组进行了以下操作&#xff0c;Vue 无法检测到变化&#xff1a; 直接通过索引修改数组元素&#xff0c;例如 arr[0] newValue修改数组的…

MySQL 存储引擎,你了解几个?

引言 MySQL是一种流行的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;它支持多种不同的数据库引擎。数据库引擎是用于存储、管理和检索数据的核心组件&#xff0c;它们直接影响着数据库的性能、可靠性和功能&#xff0c;接下来本文介绍下一些常见的MySQL数据…

华为云云服务器评测 | 从零开始:云耀云服务器L实例的全面使用解析指南

文章目录 一、前言二、云耀云服务器L实例要点介绍2.1 什么是云耀云服务器L实例2.1.1 浅析云耀云服务器L实例 2.2 云耀云服务器L实例的产品定位2.3 云耀云服务器L实例优势2.4 云耀云服务器L实例支持的镜像与应用场景2.5 云耀云服务器L实例与弹性云服务器&#xff08;ECS&#xf…

MySQL基础篇:掌握数据表操作的基础知识

表(table)是一种结构化的文件&#xff0c;可以用来存储特定类型的数据&#xff0c;如&#xff1a;学生信息&#xff0c;课程信息&#xff0c;都可以放到表中。另外表都有特定的名称&#xff0c;而且不能重复。表中具有几个概念&#xff1a;列、行、主键。 列叫做字段(Column),行…

C#,《小白学程序》第十一课:双向链表(Linked-List)其二,链表的插入与删除的方法(函数)与代码

1 文本格式 /// <summary> /// 改进的车站信息类 class /// 增加了 链表 需要的两个属性 Last Next /// </summary> public class StationAdvanced { /// <summary> /// 编号 /// </summary> public int Id { get; set; } 0; ///…

无涯教程-JavaScript - CUBEVALUE函数

描述 CUBEVALUE函数从多维数据集返回一个聚合值。 语法 CUBEVALUE (connection, [member_expression1], [member_expression2], …)争论 Argument描述Required/OptionalconnectionThe name of the connection to the cube. - A text stringRequiredmember_expression 多维表…