OpenCV实战(26)——视频序列处理

news2024/10/6 4:14:25

OpenCV实战(26)——视频序列处理

    • 0. 前言
    • 1. 读取视频序列
    • 2. 处理视频帧
      • 2.1 视频处理
      • 2.2 自定义视频处理类 VideoProcessor
      • 2.3 处理一系列图像
      • 2.4 使用帧处理器类
    • 3. 存储视频序列
      • 3.1 存储视频文件
      • 3.2 修改 VideoProcessor 类
      • 3.3 编解码器四字符编码
    • 4. 完整代码
    • 小结
    • 系列链接

0. 前言

视频信号包含丰富的视觉信息,视频由一系列图像组成,每一图像称为一帧 (frame),这些图像以固定的时间间隔(通过指定帧率,单位为秒/帧)拍摄并显示运动场景。随着计算机算力的提升,现在已经可以对视频序列执行复杂的视觉分析,处理速度已经接近甚至快于实际视频帧率 (frame rate)。本节将学习如何读取、处理和存储视频序列。

1. 读取视频序列

为了处理视频序列,我们需要能够读取视频的每一帧。OpenCV 提供了一个易于使用的类 cv::VideoCapture,用于从视频文件、USBIP 摄像机中提取帧数据。为了读取视频序列帧,需要创建一个 cv::VideoCapture 类的实例,然后循环提取每个视频帧。

(1) 首先打开待读取的视频,并检查视频是否已成功打开:

int main()
{
    // 打开视频文件
    cv::VideoCapture capture("spiderman_binary.mp4");
    if (!capture.isOpened())
        return 1;

要打开视频,需要在 cv::VideoCapture 对象的构造函数中指定视频文件名。创建 cv::VideoCapture 后,就可以使用 open 方法打开视频。视频成功打开(通过 isOpened 方法验证)后,就可以提取视频帧。

(2) 提取视频属性,例如帧率等:

    // 获取帧率
    double rate= capture.get(cv::CAP_PROP_FPS);

可以通过使用带有所需标志的 get 方法查询 cv::VideoCapture 对象并获取与视频文件相关联的信息,例如使用 CAP_PROP_FPS 标志获取帧率,由于它是一个泛型函数,会返回双精度值,即使我们可能需要其他数据类型。如果我们需要其他数据类型,例如,视频文件中的总帧数为整数:

long t= static_cast<long>(capture.get(CAP_PROP_FRAME_COUNT));

使用不同的标志,可以获取与视频相关的其它有用信息。
此外,set 方法用于为 cv::VideoCapture 实例设定参数。例如,可以使用 CAP_PROP_POS_FRAMES 标志将指针移动到特定帧:

double position= 100.0;
capture.set(CAP_PROP_POS_FRAMES, position);

可以使用 CAP_PROP_POS_MSEC 指定以毫秒为单位的位置,或者也可以使用 CAP_PROP_POS_AVI_RATIO 指定视频内部的相对位置 (0.0 对应于视频的开头,1.0 对应于视频的结尾),如果请求的参数设置成功,则该方法返回 true。需要注意的是,获取或设置特定视频参数取决于压缩和存储视频序列使用的编解码器,如果使用某些参数不成功,那可能是由于使用了错误的编解码器。

(3) 创建一个变量存储每一帧,并在窗口中显示提取的帧图像:

    bool stop(false);
    cv::Mat frame;      // 当前帧
    cv::namedWindow("Extracted Frame");

(4) 计算每帧之间的延迟(单位为毫秒):

    int delay= 1000/rate;

(5) 在每次迭代中,从 capture 中读取一个帧并显示,直到视频结束退出循环:

    while (!stop) {
        // 读取下一帧
        if (!capture.read(frame))
            break;
        cv::imshow("Extracted Frame",frame);
        std::string name(b);
        std::ostringstream ss; ss << std::setfill('0') << std::setw(3) << i; name+= ss.str(); i++;
        name+=ext;
        std::cout << name <<std::endl;
        cv::Mat test;
        // cv::resize(frame, test, cv::Size(), 0.2,0.2);
        // cv::imwrite(name, frame);
        // cv::imwrite(name, test);
        // 时延
        if (cv::waitKey(delay) >= 0)
                stop= true;
    }

成功打开视频后,可以通过重复调用 read 方法来获取帧,也可以调用重载的读取运算符:capture >> frame;
也可以调用以下两个基本方法:

capture.grab();
capture.retrieve(frame);

通过使用 cv::waitKey 函数在显示每一帧时引入延迟,我们将延迟设置为与输入视频帧速率相对应的值(如果每秒帧数为 fps,则两帧之间的延迟为 1000/fps,以毫秒为单位),可以通过修改延迟值以较慢/快的速度显示视频。但是,如果要显示视频帧,需要确保窗口有足够的时间进行刷新。cv::waitKey 函数可以通过任意按键来中断读取过程,该函数返回按下的键的 ASCII 代码。如果指定给 cv::waitKey 函数的延迟为 0,则将无限期地等待用户按下按键。

(6) 当完成循环时,关闭并释放视频资源:

    // 关闭视频文件
    capture.release();
    cv::waitKey();

调用 release 方法可以关闭视频文件,但是,并非必须显式调用该方法,因为 cv::VideoCapture 析构函数也调用了 release 方法。
执行以上代码,可以在窗口中播放视频:

视频播放

需要注意的是,为了打开指定的视频文件,计算机中必须安装相应的编解码器;否则,cv::VideoCapture 将无法解码输入文件。通常,如果能够使用计算机上的视频播放器打开视频文件,那么 OpenCV 也应该能够读取该文件。
我们也可以读取连接到计算机的摄像头(例如 USB 摄像头)的视频流。此时,我们只需为 open 函数指定一个 ID 号(整数),将 ID 指定为 0 会打开默认的相机。在这种情况下,为了防止相机的视频流被无限读取,需要使用适当的方法来停止视频序列处理。除此之外,我们还可以加载网络视频,只需要提供正确的网络地址作为参数:

cv::VideoCapture capture("http://example/spiderman_binary.mp4");

2. 处理视频帧

本节中,我们的目标是对视频序列的每一帧应用图像处理函数,可以通过将 OpenCV 视频捕获框架封装到自定义类中实现。除此之外,自定义类允许我们指定一个处理函数,每次提取新帧时都会调用该函数。
我们需要能够指定一个处理函数(回调函数),该函数会被调用处理视频序列的每一帧。这个函数可以定义为接收一个 cv::Mat 实例并输出处理过的视频帧。

2.1 视频处理

(1) 根据以上分析,在我们的框架中,处理函数必须具有以下签名才能成为有效的回调:

void processFrame(cv::Mat& img, cv::Mat& out);

(2) 作为处理函数的示例,使用计算输入图像边缘的简单函数 canny

// 处理函数
void canny(cv::Mat& img, cv::Mat& out) {
    // 转换为灰度图像
    if (img.channels()==3)
        cv::cvtColor(img,out,cv::COLOR_BGR2GRAY);
    // Canny 边缘检测
    cv::Canny(out,out,100,200);
    // 反转图像
    cv::threshold(out,out,128,255,cv::THRESH_BINARY_INV);
}

(3) VideoProcessor 类封装了视频处理任务的所有内容,使用这个类时,首先创建一个类实例,指定一个输入视频文件,设定回调函数:

    // 使用 VideoProcessor 类创建实例
    VideoProcessor processor;
    // 打开视频文件
    processor.setInput("spiderman_binary.mp4");
    // 展示输入、输出
    processor.displayInput("Input Video");
    processor.displayOutput("Output Video");
    processor.setDelay(1000./processor.getFrameRate());
    // 设置回调函数
    processor.setFrameProcessor(canny);
    // 输出处理结果
    processor.setOutput("spiderman_binary.mp4",-1,15);
    processor.stopAtFrameNo(51);
    processor.run();

运行此代码,则两个窗口将以原始帧速率 (setDelay 方法引入延迟)播放输入视频和输出结果,输出窗口如下所示:

边缘检测结果

2.2 自定义视频处理类 VideoProcessor

我们的目标是创建一个类来封装视频处理算法的通用函数,该类包括几个控制视频帧处理不同方面的成员变量:

class VideoProcessor {
    private:
        // OpenCV video capture
        cv::VideoCapture capture;
        // 回调函数
        void (*process)(cv::Mat &, cv::Mat &);
        // 指向实现FrameProcessor接口的类的指针 
        FrameProcessor *frameProcessor;
        // 是否调用回调函数
        bool callIt;
        // 输入窗口名
        std::string windowNameInput;
        // 输出窗口名
        std::string windowNameOutput;
        // 时延
        int delay;
        // 处理帧的数量
        long fnumber;
        // 停止帧编号
        long frameToStop;
        bool stop;

第一个成员变量是 cv::VideoCapture 对象,第二个属性是进程函数指针(指向回调函数),可以使用相应的 setter 方法指定此函数:

        // 设置回调函数
        void setFrameProcessor(void (*frameProcessingCallback)(cv::Mat&, cv::Mat&)) {
            frameProcessor = 0;
            process = frameProcessingCallback;
            callProcess();
        }

打开视频文件:

        // 设置相机ID
        bool setInput(int id) {
            fnumber = 0;
            capture.release();
            images.clear();
            return capture.open(id);
        }

使用两个方法来创建显示窗口,分别显示输入帧和输出帧:

        // 显示输入帧
        void displayInput(std::string wn) {
            windowNameInput = wn;
            cv::namedWindow(windowNameInput);
        }
        // 显示处理后的帧
        void displayOutput(std::string wn) {
            windowNameOutput = wn;
            cv::namedWindow(windowNameOutput);
        }

主要方法 run 是包含帧提取循环的方法:

        void run() {
            // 当前帧
            cv::Mat frame;
            // 输出帧
            cv::Mat output;
            if (!isOpened())
                return;
            stop = false;
            while (!isStopped()) {
                // 读取下一帧
                if (!readNextFrame(frame))
                    break;
                // 显示输入帧
                if (windowNameInput.length() != 0) 
                    cv::imshow(windowNameInput,frame);
                if (callIt) {
                    if (process)
                        process(frame, output);
                    else if (frameProcessor) 
                        frameProcessor->process(frame,output);
                    fnumber++;
                } else {
                    output = frame;
                }
                if (outputFile.length() != 0)
                    writeNextFrame(output);
                // 显示输出帧
                if (windowNameOutput.length() != 0) 
                    cv::imshow(windowNameOutput,output);
                if (delay >= 0 && cv::waitKey(delay) >= 0)
                    stopIt();
                if (frameToStop >= 0 && getFrameNumber() == frameToStop)
                    stopIt();
            }
        }
};

run 方法使用读取帧的私有方法:

        void run() {
            // 当前帧
            cv::Mat frame;
            // 输出帧
            cv::Mat output;
            if (!isOpened())
                return;
            stop = false;
            while (!isStopped()) {
                // 读取下一帧
                if (!readNextFrame(frame))
                    break;
                // 显示输入帧
                if (windowNameInput.length() != 0) 
                    cv::imshow(windowNameInput,frame);
                if (callIt) {
                    process(frame, output);
                    fnumber++;
                } else {
                    output = frame;
                }
                // 显示输出帧
                if (windowNameOutput.length() != 0) 
                    cv::imshow(windowNameOutput,output);
                if (delay >= 0 && cv::waitKey(delay) >= 0)
                    stopIt();
                if (frameToStop >= 0 && getFrameNumber() == frameToStop)
                    stopIt();
            }
        }
        // 停止处理
        void stopIt() {
            stop = true;
        }
        // 是否停止处理
        bool isStopped() {
            return stop;
        }
        bool isOpened() {
            capture.isOpened();
        }
        // 设置两帧之间时延
        void setDelay(int d) {
            delay = d;
        }

此方法使用读取帧的私有方法:

        bool readNextFrame(cv::Mat& frame) {
            return capture.read(frame);
        }

run 方法首先调用 cv::VideoCapture 类的 read 方法。然后执行一系列操作,但在调用每个操作之前,会进行检查以确定是否已经执行请求。只有指定了输入窗口名称(使用 displayInput 方法),才会显示输入窗口;只有在指定了一个回调函数时才会调用回调函数(使用 setFrameProcessor);仅当定义了输出窗口名称时才显示输出窗口(使用 displayOutput);仅当已指定延迟时才会引入延迟(使用 setDelay 方法)。最后,如果定义了停止帧(使用 stopAtFrameNo),则需要检查当前帧编号。
如果我们仅仅需要简单地打开和播放视频文件(不调用回调函数),可以使用以下方法来指定是否要调用回调函数:

        // 调用回调函数
        void callProcess() {
            callIt = true;
        }
        // 不调用回调函数
        void dontCallProcess() {
            callIt = false;
        }

最后,该类还提供了在特定帧处停止的方法:

        // 停止帧
        void stopAtFrameNo(long frame) {
            frameToStop = frame;
        }
        // 返回下一帧的帧号 
        long getFrameNumber() {
            if (images.size() == 0) {
                long f = static_cast<long>(capture.get(cv::CAP_PROP_POS_FRAMES));
                return f; 
            } else {
                return static_cast<long>(itImg-images.begin());
            }
        }

该类还包含许多 gettersetter 方法,它们基本上是 cv::VideoCapture 框架的通用 setget 方法的包装器。VideoProcessor 类用于简化视频处理模块的部署,我们可以对其进行额外的改进。

2.3 处理一系列图像

有时,输入序列由一系列单独存储在不同文件中的图像组成。可以修改自定义类以适应此类输入,只需要添加一个成员变量,该变量保存图像文件名向量及其对应的迭代器:

        // 输入图像文件名矢量
        std::vector<std::string> images;
        // 图像矢量迭代器
        std::vector<std::string>::const_iterator itImg;
新的 setInput 方法用于指定要读取的文件名:
        // 设置输入图像矢量
        bool setInput(const std::vector<std::string> &imgs) {
            fnumber = 0;
            capture.release();
            images = imgs;
            itImg = images.begin();
            return true;
        }
isOpened 方法修改如下:
        bool isOpened() {
            return capture.isOpened() || !images.empty();
        }

最后一个需要修改的方法是私有方法 readNextFrame,该方法将从视频或文件名向量中读取内容,具体取决于已指定的输入。如果图像文件名的向量不为空,则输入是一系列单独存储的图像序列,使用视频文件名调用 setInput 会清空此向量:

        // 获取下一帧
        bool readNextFrame(cv::Mat &frame) {
            if (images.size() == 0) return capture.read(frame);
            else {
                if (itImg != images.end()) {
                    frame = cv::imread(*itImg);
                    itImg++;
                    return frame.data != 0;
                }
                return false;
            }
        }

2.4 使用帧处理器类

在面向对象中,使用帧处理类替代帧处理函数更加灵活,在定义视频处理算法时,类会带来更大的灵活性。因此,我们可以定义一个接口,任何希望在 VideoProcessor 中使用的类都需要实现该接口:

class FrameProcessor {
    public:
        virtual void process(cv::Mat &input, cv::Mat &output) = 0;
};

setter 方法允许将 FrameProcessor 实例输入到 VideoProcessor 框架,并将其分配给添加的 frameProcessor 成员变量,该变量定义为指向 FrameProcessor 对象的指针:

        // 设置实现FrameProcessor接口的类实例
        void setFrameProcessor(FrameProcessor* frameProcessorPtr) {
            process = 0;
            frameProcessor = frameProcessorPtr;
            callProcess();
        }

当指定帧处理器类实例时,它会使之前已设置的帧处理函数无效。修改 run 方法的 while 循环以适配此修改:

        void run() {
            // 当前帧
            cv::Mat frame;
            // 输出帧
            cv::Mat output;
            if (!isOpened())
                return;
            stop = false;
            while (!isStopped()) {
                // 读取下一帧
                if (!readNextFrame(frame))
                    break;
                // 显示输入帧
                if (windowNameInput.length() != 0) 
                    cv::imshow(windowNameInput,frame);
                if (callIt) {
                    if (process)
                        process(frame, output);
                    else if (frameProcessor) 
                        frameProcessor->process(frame,output);
                    fnumber++;
                } else {
                    output = frame;
                }
                // 显示输出帧
                if (windowNameOutput.length() != 0) 
                    cv::imshow(windowNameOutput,output);
                if (delay >= 0 && cv::waitKey(delay) >= 0)
                    stopIt();
                if (frameToStop >= 0 && getFrameNumber() == frameToStop)
                    stopIt();
            }
        }

3. 存储视频序列

在上一小节中,我们学习了如何读取视频文件并提取视频帧。本节将介绍如何写入视频帧,从而创建视频文件。通过本节学习,我们将能够完成典型的视频处理流程——读取输入视频流,处理视频帧,然后将结果存储在新的视频文件中。

3.1 存储视频文件

(1)OpenCV 中,写入视频文件需要使用 cv::VideoWriter 类。通过指定文件名、生成视频的播放帧率、每帧的大小以及是否创建彩色视频来构造一个实例:

            writer.open(outputFile,    // 文件名
                codec,                        // 编码器
                framerate,                    // 帧率
                getFrameSize(),               // 帧尺寸
                isColor);                     // 是否为彩色视频

(2) 此外,必须通过编解码器参数指定视频数据的保存方式。

(3) 打开视频文件后,可以通过重复调用 write 方法向其中添加帧:

writer.write(frame);

(4) 使用 cv::VideoWriter 类,可以很容易地扩展我们在视频读取一节中介绍的 VideoProcessor 类,以能够写入视频文件。实现完整的视频处理流程,读取视频,处理视频并将结果写入视频文件:

    VideoProcessor processor;
    // 打开视频文件
    processor.setInput("spiderman_binary.mp4");
    // 设置回调函数
    processor.setFrameProcessor(canny);
    // 输出处理结果
    processor.setOutput("spiderman_binary.mp4",-1,15);
    processor.run();

(5) 同时,我们还希望让用户可以将帧写为单独的图像。在我们的框架中,采用了一种命名约定,视频帧由前缀名称后跟给定位数组成的数字组成。当帧被保存时,数字会自动增加。然后,要将输出结果保存为一系列图像:

processor.setOutput("example",  // 前缀
        ".jpg",                 // 文件扩展名
        3,                      // 编号位数
        0)                      // 开始索引

3.2 修改 VideoProcessor 类

修改 VideoProcessor 类以使其能够写入视频文件。首先,必须将 cv::VideoWriter 成员变量以及一些其它属性添加到类中:

class VideoProcessor {
    private:
        // ...

        // OpenCV 视频写入对象
        cv::VideoWriter writer;
        // 输出文件名
        std::string outputFile;

        // 当前帧索引
        int currentIndex;
        int digits;
        std::string extension;

setOutput 方法用于指定(和打开)输出视频文件:

        // 设置输出视频文件
        bool setOutput(const std::string &filename, int codec=0, double framerate=0.0, bool isColor=true) {
            outputFile = filename;
            extension.clear();
            if (framerate == 0.0) 
                framerate = getFrameRate();
            char c[4];
            if (codec == 0) { 
                codec = getCodec(c);
            }
            // 打开输出视频
            return writer.open(outputFile,    // 文件名
                codec,                        // 编码器
                framerate,                    // 帧率
                getFrameSize(),               // 帧尺寸
                isColor);                     // 是否为彩色视频
        }

私有方法 writeNextFrame 处理帧写入过程(作为视频文件中或一系列图像):

        // 写入帧
        void writeNextFrame(cv::Mat &frame) {
            if (extension.length()) {
                std::stringstream ss;
                ss << outputFile << std::setfill('0') << std::setw(digits) << currentIndex++ << extension;
                cv::imwrite(ss.str(), frame);
            } else {
                writer.write(frame);
            }
        }

对于输出由单个图像文件组成的情况,我们需要一个额外的 setter 方法:

        // 将输出设置为一系列图像文件
        bool setOutput(const std::string &filename, // 文件名前缀
            const std::string &ext,                 // 图像文件扩展名
            int numberOfDigits = 3,                   // 编号位数
            int startIndex = 0) {                     // 开始索引
            if (numberOfDigits<0) return false;

            outputFile = filename;
            extension = ext;
            digits = numberOfDigits;
            currentIndex = startIndex;
            return true;
        }

最后,在 run 方法的视频捕获循环中添加新步骤:

        void run() {
            // 当前帧
            cv::Mat frame;
            // 输出帧
            cv::Mat output;
            if (!isOpened())
                return;
            stop = false;
            while (!isStopped()) {
                // 读取下一帧
                if (!readNextFrame(frame))
                    break;
                // 显示输入帧
                if (windowNameInput.length() != 0) 
                    cv::imshow(windowNameInput,frame);
                if (callIt) {
                    if (process)
                        process(frame, output);
                    else if (frameProcessor) 
                        frameProcessor->process(frame,output);
                    fnumber++;
                } else {
                    output = frame;
                }
                if (outputFile.length() != 0)
                    writeNextFrame(output);
                // 显示输出帧
                if (windowNameOutput.length() != 0) 
                    cv::imshow(windowNameOutput,output);
                if (delay >= 0 && cv::waitKey(delay) >= 0)
                    stopIt();
                if (frameToStop >= 0 && getFrameNumber() == frameToStop)
                    stopIt();
            }
        }

将视频写入文件时,会使用编解码器进行保存。编解码器是能够对视频流进行编码和解码的软件模块。编解码器定义了文件的格式和用于存储信息的压缩方案。显然,使用给定编解码器编码的视频必须使用相同的编解码器进行解码。因此,使用四字符编码唯一标识编解码器。当软件需要写入视频文件时,通过读取指定的四字符编码来确定要使用的编解码器。

3.3 编解码器四字符编码

顾名思义,四字符代码由 4ASCII 字符组成,也可以通过将它们相加后转换为整数。使用 cv::VideoCapture 实例 get 方法的 CAP_PROP_FOURCC 标志,可以获得打开的视频文件的编码。可以在 VideoProcessor 类中定义一个方法来返回输入视频的四字符编码:

        // 获取输入视频的编码
        int getCodec(char codec[4]) {
            if (images.size() != 0) return -1;
            union {
                int value;
                char code[4]; } returned;
            returned.value = static_cast<int>(capture.get(cv::CAP_PROP_FOURCC));
            codec[0] = returned.code[0];
            codec[1] = returned.code[1];
            codec[2] = returned.code[2];
            codec[3] = returned.code[3];
            return returned.value;
        }

get 方法返回一个 double 类型值,然后需要将其转换为整数,表示提取的四字符编码。打开测试视频序列,获取四字符编码:

    // 编码
    char codec[4];
    processor.getCodec(codec);
    std::cout << "Codec: " << codec[0] << codec[1] << codec[2] << codec[3] << std::endl;

写入视频文件时,必须使用四字符编码指定编解码器,这是 cv::VideoWriter 类的 open 方法中的第二个参数。例如,可以使用与输入视频相同的选项 (setOutput 方法中的默认选项);也可以传递值 -1,使用该值将弹出一个窗口,要求用户从可用编解码器列表中选择一个编解码器。可以在此窗口的列表中看到计算机上已安装的编解码器列表,然后所选编解码器的编码会自动传递到 open 方法。

4. 完整代码

头文件 (videoprocessor.h) 和主函数文件 (videoprocessing.cpp) 完整代码可以在 gitcode 中获取。

小结

本节介绍了如何读取、处理和存储视频序列,为了便于处理视频文件,创建了 videoProcessor 类来封装视频处理算法的通用函数。

系列链接

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场景重建

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

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

相关文章

第九章:子查询

第九章&#xff1a;子查询 9.1&#xff1a;子查询的基本使用 子查询的基本语法结构 SELECT .... FROM .... WHERE expr operator (SELECT ...FROM ...WHERE ...);子查询(内查询)在主查询之前一次执行完成。子查询的结果被主查询(外查询)使用。注意事项 子查询要包含在括号内。…

【JavaSE】Java(五十五):核心要点总结

文章目录 1. 为什么不允许静态方法访问非静态变量2. Java的内存模型3. 在Java中什么时候用重载什么时候用重写4. 举例说明什么情况下更倾向于用抽象类而不是接口5. 实例化对象有哪几种方式 1. 为什么不允许静态方法访问非静态变量 在Java中&#xff0c;静态方法属于类级别的方法…

【HTML】第 4 节 - 列表标签

欢迎来到博主 Apeiron 的博客&#xff0c;祝您旅程愉快 &#xff01; 时止则止&#xff0c;时行则行。动静不失其时&#xff0c;其道光明。 目录 1、缘起 2、列表 3、无序列表 4、有序列表 5、定义列表 6、总结 1、缘起 微信小程序的列表标签是一种用于展示多个数据项的…

量子 AI,是融合还是颠覆?

光子盒研究院 前言&#xff1a;如今&#xff0c;量子技术早已走出实验室、广泛赋能电力、化学、医学等各个领域&#xff1b;创新赛道上&#xff0c;加速奔跑的量子产业&#xff0c;将带来无限可能。现在&#xff0c;光子盒特开启「量子」专栏&#xff0c;一一解读量子技术将为下…

chatgpt赋能python:Python安装教程:从下载到配置

Python安装教程&#xff1a;从下载到配置 Python作为一门高级编程语言&#xff0c;越来越受到开发人员的欢迎。Python的灵活性和易用性&#xff0c;让许多人选择Python作为他们的程序语言。本文将详细介绍Python安装教程&#xff0c;帮助初学者轻松入门。 1. 下载Python安装包…

【JavaSE】Java(五十四):核心要点总结

文章目录 1. try-catch-finally中 如果 catch 中 return 了&#xff0c;finally 还会执行吗?2. 常见的异常类有哪些3. hashcode 是什么 &#xff0c;有什么作用4. java中操作字符串有哪些类&#xff0c;他们之间有什么区别5. Java 中有哪些引用类型 1. try-catch-finally中 如…

$2$驱动模块

目录 1.驱动模块&#xff08;驱动程序的框架&#xff09; 2.内核中的打印函数&#xff08;编写第一个驱动程序&#xff09; Source Insight 使用&#xff1a; 打印函数编写 分析 3.驱动的多文件编译 4.模块传递参数 安装好驱动之后如何传参&#xff1f; 多驱动之间调用&…

智能照明控制系统在现代建筑工程中的应用 安科瑞 许敏

摘要&#xff1a; 文章分析了在现代建筑工程中智能照明控制系统所具有的优越性&#xff0c;并对如何解决该技术在实际应用中遇到的问题提出了看法与建议。 关键词&#xff1a;智能照明 控制系统 应用节能 引言 随着人们的物质和精神生活水平不断提高&#xff0c;对生活的追求…

MMC整流器Matlab仿真模型子模块个数N=18(含技术文档)

资源地址&#xff1a; MMC整流器Matlab仿真模型子模块个数N&#xff1d;18&#xff08;含技术文档&#xff09;资源-CSDN文库 模型介绍&#xff1a; 1.MMC工作在整流侧&#xff0c;子模块个数N&#xff1d;18&#xff0c;直流侧电压Udc&#xff1d;25.2kV&#xff0c;交流侧…

算法设计与分析期末复习(二)

动态规划 基本思想&#xff1a;把求解的问题分成许多阶段或多个子问题&#xff0c;然后按顺序求解各个子问题。**前一个子问题的解为后一个子问题的求解提供了有用的信息。**在求解任何一子问题时&#xff0c;列出各种可能的局部解&#xff0c;通过决策保留那些有可能达到最优…

Linux面试题汇总

Linux面试题汇总 网络拓展Linux 概述什么是LinuxUnix和Linux有什么区别&#xff1f;什么是 Linux 内核&#xff1f;Linux的基本组件是什么&#xff1f;Linux 的体系结构BASH和DOS之间的基本区别是什么&#xff1f;Linux 开机启动过程&#xff1f;Linux系统缺省的运行级别&#…

javaScript蓝桥杯----外卖给好评

目录 一、介绍二、准备三、⽬标四、代码五、完成 一、介绍 外卖是现代⽣活中必备的⼀环。收到外卖后&#xff0c;各⼤平台软件常常会邀请⽤户在⼝味&#xff0c;配送速度等多个⽅⾯给与评分。在 element-ui 组件中&#xff0c;已经有相应的 Rate 组件&#xff0c;但是已有组件…

前端052_单点登录SSO_单点退出系统

单点退出系统 1、 需求分析2、EasyMock 添加退出系统模拟接口3、定义Api调用退出接口4、定义 Vuex 退出行为1、 需求分析 所有应用系统退出,全部发送请求到当前认证中心进行处理,发送请求后台删除用户登录数据,并将 cookie 中的用户数据清除。 2、EasyMock 添加退出系统模拟…

大数据分析案例-基于LightGBM算法构建银行客户流失预测模型

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

第四章:运算符

第四章&#xff1a;运算符 4.1&#xff1a;算术运算符 ​ 算术运算符主要用于数学运算&#xff0c;其可以连接运算符前后的两个数值或表达值&#xff0c;对数值或表达式进行加()、减(-)、乘(*)、除(/)、取模(%)运算。 运算符名称作用示例加法运算符计算两个值或表达式的和SE…

chatgpt赋能python:Python如何遍历文件:一篇完整的指南

Python如何遍历文件: 一篇完整的指南 在进行文件操作时&#xff0c;遍历文件是相当普遍的需求。Python中提供了多种方法来遍历文件夹和文件&#xff0c;包括os模块&#xff0c;glob模块和os.walk方法。这篇文章将会介绍这些方法及其应用。 什么是遍历文件&#xff1f; 遍历文…

使用 ConstraintLayout

ConstraintLayout解析 1.前言2.了解ConstraintLayout3.基本用法3.1 看一个布局3.2再看一个布局 1.前言 你是不是一直不敢用ConstraintLayout&#xff0c;是以为属性太多太复杂&#xff1f;你心理上的惰性&#xff0c;畏惧它。它其实很好用很强大&#xff0c;如果要用就需要一个…

Day_40关于图的总结

一. 实际问题的抽象与建模 如果我们需要研究一个实际问题&#xff0c;首先第一步就是对这个实际问题进行抽象&#xff0c;抽象是从众多的事物中抽取出共同的、本质性的特征&#xff0c;而舍弃其非本质的特征的过程。具体地说&#xff0c;抽象就是人们在实践的基础上&#xff0c…

Java中的金钱陷阱

前言 有多少小伙伴是被标题 骗 吸引进来的呢&#xff0c;我可不是标题党&#xff0c;今天的文章呢确实跟”金钱“有关系。 但是我们说的不是过度追求金钱而掉入陷阱&#xff0c;而是要说一说在Java程序中&#xff0c;各种跟金钱运算有关的陷阱。 日常工作中我们经常会涉及到…

chatgpt赋能python:Python字幕滚动:如何让你的视频内容更吸引人

Python字幕滚动&#xff1a;如何让你的视频内容更吸引人 如果你是一位视频创作者&#xff0c;你可能知道如何通过字幕来增加你的视频的吸引力。Python提供了一种简单且高效的方法来制作字幕滚动。字幕滚动是指将文字逐个显示在视频下方&#xff0c;以帮助观众跟上视频的进展。…