融媒体服务中PBO进行多重采样抗锯齿(MSAA)

news2024/11/25 15:25:51

如果不理解pbo 那先去了解概念,在此不再解释,这是我为了做融合服务器viewpointserver做的一部分工作,融合服务器的功能是将三维和流媒体,AI融合在一起,viewpointserver会直接读取三维工程的文件,同时融合rtsp视频流,将视频流作为纹理给材质,最后赋值给三维模型如fbx的表面。由于没有窗口,三维作为服务运行,里面有一项工作就是三维的抗锯齿采集。

PBO(Pixel Buffer Object)进行多重采样抗锯齿(MSAA)的一般步骤

1 初始化 OpenGL 和 MSAA 帧缓冲:

1.1开启 MSAA:

在初始化 OpenGL 时,通过设置相关参数开启多重采样抗锯齿。例如,使用 glEnable(GL_MULTISAMPLE) 函数来启用 OpenGL 的多重采样功能。

1.2 创建帧缓冲:

创建一个帧缓冲对象(FBO),用于存储多重采样后的图像数据。这可以通过 glGenFramebuffers 和 glBindFramebuffer 等函数来完成。

1.3 配置 FBO 的附件:

为 FBO 配置颜色附件和深度附件。对于颜色附件,创建一个纹理,并将其绑定到 FBO 上;对于深度附件,可以创建一个渲染缓冲对象(RBO)并绑定到 FBO。确保纹理和 RBO 的尺寸与渲染窗口的尺寸相匹配。

2 渲染到 MSAA 帧缓冲:

2.1 绑定 MSAA 帧缓冲:

在渲染场景之前,使用 glBindFramebuffer 函数将之前创建的 MSAA FBO 绑定为当前的渲染目标。这样,后续的渲染操作将把图像数据渲染到 MSAA FBO 中。

2.2 进行常规渲染:

按照正常的 OpenGL 渲染流程绘制场景中的物体。由于启用了 MSAA,OpenGL 会在每个像素内进行多个子采样,以实现抗锯齿效果。

3 使用 PBO 读取 MSAA 数据:

创建 PBO:使用 glGenBuffers 函数创建一个 PBO,并使用 glBindBuffer 函数将其绑定到 GL_PIXEL_PACK_BUFFER 目标上。然后,使用 glBufferData 函数为 PBO 分配足够的内存空间,以存储从 MSAA FBO 读取的像素数据。
读取像素数据到 PBO:在渲染完成后,使用 glReadPixels 函数将 MSAA FBO 中的像素数据读取到 PBO 中。由于 PBO 的存在,这个操作可以在后台异步进行,减少对 CPU 的阻塞。

4 处理和解析 PBO 中的数据:

4.1 映射 PBO:

使用 glMapBuffer 函数将 PBO 映射到 CPU 可访问的内存空间,以便读取和处理像素数据。这将返回一个指向 PBO 内存的指针,可以通过该指针访问像素数据。

4.2 解析像素数据:

根据需要,对 PBO 中的像素数据进行处理和解析。例如,可以将像素数据转换为图像格式,以便保存为文件或进行其他操作。在处理像素数据时,需要考虑 MSAA 的子采样信息,通常需要对多个子采样点的颜色值进行合并或平均,以得到最终的抗锯齿效果。

4.3 取消映射 PBO:

完成对 PBO 数据的处理后,使用 glUnmapBuffer 函数取消对 PBO 的映射,释放 CPU 对 PBO 内存的访问。

5 显示或使用抗锯齿后的图像:

5.1 将处理后的像素数据显示在屏幕上:

如果需要在屏幕上显示抗锯齿后的图像,可以使用 glDrawPixels 或其他相关的 OpenGL 函数将处理后的像素数据绘制到默认的帧缓冲中,然后通过交换缓冲区来显示在屏幕上。

5.2 保存为图像文件:

可以将处理后的像素数据保存为图像文件,以便后续使用。这可以通过使用图像库(如 stb_image 库)来实现,将像素数据写入图像文件中。

使用深度缓冲的例子

以下使用qt来做,qt的优点是很多都是封装好的,比较容易实现,不用引入额外的库,当然我们也可以使用glfw来做,后面会给出例子

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QtWidgets/QWidget>
#include <QOpenGLWidget>
#include <QOpenGLFunctions_4_5_Core>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include <QOpenGLTexture>
#include <QOpenGLShader>
#include <QOpenGLShaderProgram>
#include "ui_MainWindow.h"

QT_BEGIN_NAMESPACE
namespace Ui {
	class CMainWindow;
}
QT_END_NAMESPACE

class CMainWindow : public QOpenGLWidget, protected QOpenGLFunctions_4_5_Core
{
	Q_OBJECT

public:
	CMainWindow(QWidget *parent = Q_NULLPTR);
	~CMainWindow();

	void initFBO();

protected:
	void initializeGL();
	void paintGL();
	void resizeGL(int w, int h);

private:
	QImage m_img;
	GLsizei m_width = 0;
	GLsizei m_height = 0;
  GLsizeiptr m_dataSize = 0;
	GLuint m_VBO = 0;
	GLuint m_VAO = 0;
	GLuint m_EBO = 0;
	GLuint m_texture = 0;
	GLuint m_frameBuffer = 0;
	GLuint m_RBO = 0;
	GLuint m_fVBO = 0;
	GLuint m_fVAO = 0;
	GLuint m_textureFBO = 0;
	QOpenGLShaderProgram *m_programScreen = nullptr;
	QOpenGLShaderProgram *m_shaderProgram = nullptr;
private:
	Ui::CMainWindow *ui;
};
#endif //MAINWINDOW_H
#include "MainWindow.h"

CMainWindow::CMainWindow(QWidget* parent)
	: QOpenGLWidget(parent), ui(new Ui::CMainWindow)
{
	ui->setupUi(this);
	m_img = QImage("2.jpg");//picture
	m_width = m_img.width();
	m_height = m_img.height();
	m_dataSize = m_width * m_height * 3;
}

CMainWindow::~CMainWindow()
{
	delete ui;
	glDeleteVertexArrays(1, &m_VAO);
	glDeleteBuffers(1, &m_VBO);
	glDeleteBuffers(1, &m_EBO);
	glDeleteVertexArrays(1, &m_fVAO);
	glDeleteBuffers(1, &m_fVBO);
	glDeleteBuffers(1, &m_frameBuffer);
	glDeleteBuffers(1, &m_RBO);
}
void CMainWindow::initFBO()
{
	glGenFramebuffers(1, &m_frameBuffer);
	glBindFramebuffer(GL_FRAMEBUFFER, m_frameBuffer);

	glGenTextures(1, &m_textureFBO);
	glBindTexture(GL_TEXTURE_2D, m_textureFBO);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, m_width, m_height, 0, GL_BGRA, GL_UNSIGNED_BYTE, nullptr);//TODO
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glBindTexture(GL_TEXTURE_2D, 0);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_textureFBO, 0);

	glGenRenderbuffers(1, &m_RBO);
	glBindRenderbuffer(GL_RENDERBUFFER, m_RBO);
	glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, m_width, m_height);
	glBindRenderbuffer(GL_RENDERBUFFER, 0);
	glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_RBO);
	if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
		qDebug() << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" ;
	glBindFramebuffer(GL_FRAMEBUFFER, 0);

	char vertexShaderSource[] =
		"#version 450 core\n"
		"layout (location = 0) in vec2 aPos;\n"
		"layout (location = 1) in vec2 aTexCoords;\n"
		"out vec2 TexCoords;\n"
		"void main()\n"
		"{\n"
		"   gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0);\n"
		"TexCoords = aTexCoords;\n"
		"}\n";
	char fragmentShaderSource[] =
		"#version 450 core\n"
		"out vec4 FragColor;\n"
		"in vec2 TexCoords;\n"
		"uniform sampler2D screenTexture;\n"
		"void main()\n"
		"{\n"
		"   FragColor = texture(screenTexture, TexCoords);\n"
		"}\n";

	m_programScreen = new QOpenGLShaderProgram;
	m_programScreen->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource);
	m_programScreen->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource);
	m_programScreen->link();

	m_programScreen->bind();
	m_programScreen->setUniformValue("screenTexture", 1);
	m_programScreen->release();

	float fb_Vertices[] = {
		// positions   // texCoords
		-1.0f,  1.0f,  0.0f, 1.0f,
		-1.0f, -1.0f,  0.0f, 0.0f,
		 1.0f, -1.0f,  1.0f, 0.0f,

		-1.0f,  1.0f,  0.0f, 1.0f,
		 1.0f, -1.0f,  1.0f, 0.0f,
		 1.0f,  1.0f,  1.0f, 1.0f
	};

	glGenVertexArrays(1, &m_fVAO);
	glBindVertexArray(m_fVAO);
	glGenBuffers(1, &m_fVBO);
	glBindBuffer(GL_ARRAY_BUFFER, m_fVBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(fb_Vertices), fb_Vertices, GL_STATIC_DRAW);
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (void*)0);
	glEnableVertexAttribArray(1);
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (void*)(2 * sizeof(float)));
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindVertexArray(0);

}

void CMainWindow::initializeGL()
{
	this->initializeOpenGLFunctions();

	char vertexShaderSource[] =
		"#version 450 core\n"
		"layout(location = 0) in vec3 aPos;\n"
		"layout(location = 1) in vec3 aColor;\n"
		"layout(location = 2) in vec2 aTexcood;\n"
		"out vec3 vertexColor;\n"
		"out vec2 m_tex;\n"
		"void main()\n"
		"{\n"
		"   m_tex = aTexcood;\n"
		"   vertexColor = aColor;\n"
		"   gl_Position = vec4(aPos, 1.0);\n"
		"}\n";

	char fragmentShaderSource[] =
		"#version 450 core\n"
		"out vec4 FragColor;\n"
		"in vec3 vertexColor;\n"
		"in vec2 m_tex;\n"
		"uniform sampler2D ourTexture;\n"
		"void main()\n"
		"{\n"
		"   FragColor = texture2D(ourTexture, m_tex) * vec4(vertexColor, 1.0f);\n"
		"}\n";

	m_shaderProgram = new QOpenGLShaderProgram;
	m_shaderProgram->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource);
	m_shaderProgram->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource);
	m_shaderProgram->link();

	m_shaderProgram->bind();
	m_shaderProgram->setUniformValue("ourTexture", 0);
	m_shaderProgram->release();

	GLfloat vertices[] = {
		-1.0f, -1.0f,0.0f, 0.0f,0.0f,1.0f, 0.0f,0.0f,
		 1.0f, -1.0f,0.0f, 0.0f,1.0f,0.0f, 1.0f,0.0f,
		 1.0f,  1.0f,0.0f, 1.0f,0.0f,0.0f, 1.0f,1.0f,
		-1.0f,  1.0f,0.0f, 0.0f,1.0f,1.0f, 0.0f,1.0f,
	};

	GLuint indices[] = {
		0,1,2,
		0,2,3
	};

	glGenVertexArrays(1, &m_VAO);
	glBindVertexArray(m_VAO);
	glGenBuffers(1, &m_VBO);
	glBindBuffer(GL_ARRAY_BUFFER, m_VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
	glGenBuffers(1, &m_EBO);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_EBO);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (void*)0);
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (void*)(3 * sizeof(float)));
	glEnableVertexAttribArray(1);
	glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (void*)(6 * sizeof(float)));
	glEnableVertexAttribArray(2);

	glBindVertexArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

	glGenTextures(1, &m_texture);
	glBindTexture(GL_TEXTURE_2D, m_texture);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, m_width, m_height, 0, GL_BGRA, GL_UNSIGNED_BYTE, m_img.bits());//TODO
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glGenerateMipmap(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, 0);
	
	//初始化FBO
	initFBO();
	
}

void CMainWindow::paintGL()
{
	glBindFramebuffer(GL_FRAMEBUFFER, m_frameBuffer);
	glEnable(GL_DEPTH_TEST);

	glClearColor(0.5f, 0.0f, 0.0f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	m_shaderProgram->bind();
	glBindVertexArray(m_VAO);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, m_texture);
	glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, NULL);
	glBindVertexArray(0);
	glBindTexture(GL_TEXTURE_2D, 0);
	m_shaderProgram->release();

	/********** 关键之处 ***********/
	GLuint fb = context()->defaultFramebufferObject();
	glBindFramebuffer(GL_FRAMEBUFFER, fb);

	glDisable(GL_DEPTH_TEST);
	glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT);
	glViewport(0, 0, m_width, m_height);

	m_programScreen->bind();
	glBindVertexArray(m_fVAO);
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, m_textureFBO);
	glDrawArrays(GL_TRIANGLES, 0, 6);
	glBindVertexArray(0);
	glBindTexture(GL_TEXTURE_2D, 0);
	m_programScreen->release();

}

void CMainWindow::resizeGL(int w, int h)
{
	glViewport(0, 0, w, h);
}

qt中采集场景如图
在这里插入图片描述

下面给出glfw的例子

glfw

首先初始化 OpenGL、GLFW 和 GLEW,也可以用glad,并创建了 MSAA FBO、颜色纹理、深度 RBO 和 PBO。然后,在渲染循环中,先将场景渲染到 MSAA FBO 中,再将 FBO 中的像素数据读取到 PBO 中,并对 PBO 中的像素数据进行处理。最后,清理资源并退出程序。当然实际应用中确实需要根据具体需求进行更多的错误处理和优化。

#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <cstdlib>

// 窗口尺寸
const int WIDTH = 800;
const int HEIGHT = 600;

// 创建 PBO
GLuint pbo;
// 创建 MSAA FBO
GLuint fbo;
// 颜色纹理
GLuint colorTexture;
// 深度 RBO
GLuint depthRBO;

void init() {
    // 初始化 GLFW
    if (!glfwInit()) {
        std::cerr << "Failed to initialize GLFW" << std::endl;
        exit(EXIT_FAILURE);
    }

    // 创建窗口
    GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "MSAA with PBO", nullptr, nullptr);
    if (!window) {
        std::cerr << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        exit(EXIT_FAILURE);
    }

    // 将窗口设置为当前上下文
    glfwMakeContextCurrent(window);

    // 初始化 GLEW
    glewExperimental = GL_TRUE;
    if (glewInit()!= GLEW_OK) {
        std::cerr << "Failed to initialize GLEW" << std::endl;
        exit(EXIT_FAILURE);
    }

    // 开启多重采样抗锯齿
    glEnable(GL_MULTISAMPLE);

    // 创建 FBO
    glGenFramebuffers(1, &fbo);
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    // 创建颜色纹理
    glGenTextures(1, &colorTexture);
    glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, colorTexture);
    glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGBA8, WIDTH, HEIGHT, GL_TRUE);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, colorTexture, 0);

    // 创建深度 RBO
    glGenRenderbuffers(1, &depthRBO);
    glBindRenderbuffer(GL_RENDERBUFFER, depthRBO);
    glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH24_STENCIL8, WIDTH, HEIGHT);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthRBO);

    // 检查 FBO 是否完整
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER)!= GL_FRAMEBUFFER_COMPLETE) {
        std::cerr << "Failed to create FBO" << std::endl;
        exit(EXIT_FAILURE);
    }

    // 创建 PBO
    glGenBuffers(1, &pbo);
    glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
    glBufferData(GL_PIXEL_PACK_BUFFER, WIDTH * HEIGHT * 4, nullptr, GL_STREAM_READ);
}

void renderScene() {
    // 绑定 MSAA FBO 并渲染场景
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glViewport(0, 0, WIDTH, HEIGHT);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // 在这里进行场景的绘制操作

    // 解除绑定 FBO
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

void processPixels() {
    // 将 MSAA FBO 中的像素数据读取到 PBO
    glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
    glReadPixels(0, 0, WIDTH, HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    // 映射 PBO 到 CPU 可访问的内存
    GLubyte* pixels = (GLubyte*)glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
    if (pixels) {
        // 在这里处理像素数据,例如保存为图像或进行其他操作
        // 为了简单起见,这里只是打印一些像素信息
        for (int i = 0; i < 10; i++) {
            std::cout << "Pixel " << i << ": ("
                      << (int)pixels[4 * i] << ", "
                      << (int)pixels[4 * i + 1] << ", "
                      << (int)pixels[4 * i + 2] << ", "
                      << (int)pixels[4 * i + 3] << ")" << std::endl;
        }

        // 取消映射 PBO
        glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
    }
}

int main() {
    init();

    // 渲染循环
    while (!glfwWindowShouldClose(glfwGetCurrentContext())) {
        renderScene();
        processPixels();

        // 交换缓冲区并处理事件
        glfwSwapBuffers(glfwGetCurrentContext());
        glfwPollEvents();
    }

    // 清理资源
    glDeleteBuffers(1, &pbo);
    glDeleteTextures(1, &colorTexture);
    glDeleteRenderbuffers(1, &depthRBO);
    glDeleteFramebuffers(1, &fbo);
    glfwTerminate();

    return 0;
}

存文件测试

static void saveImage(GLuint& pbo, const std::string& filename, int width, int height) {

    glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);

    // 将帧缓冲区内容读取到 PBO
    glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    // 映射 PBO 获取数据指针
    GLubyte* pixels = (GLubyte*)glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
    if (pixels)
    {
        stbi_write_png(filename.c_str(), width, height, 4, pixels, width * 4);
        glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
    }
    else
    {
        std::cerr << "Failed to map." << std::endl;
    }
    glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
}

如下图所示,存的图像是倒立镜像的,实际上是正立的三角形,还没有将视频附上去,工作量有些大,等到下一个文章再写。
在这里插入图片描述

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

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

相关文章

MySQL基础之约束

MySQL基础之约束 概述 概念&#xff1a;约束是作用在字段的规则&#xff0c;限制表中数据 演示 # 多个约束之间不需要加逗号 # auto_increment 自增 create table user(id int primary key auto_increment comment 主键,name varchar(10) not null unique comment 姓名,age i…

表达式求值(可以计算两位数以上)

此程序可计算两位数以上的表达式 import java.util.Stack;public class ExpressionEvaluator {public int evaluate(String s) {Stack<Integer> numbers new Stack<>();Stack<Character> operators new Stack<>();int i 0;char c s.charAt(i);whil…

stm32定时器中断和外部中断

一&#xff0c;中断系统的介绍 中断&#xff1a;在主程序运行过程中&#xff0c;出现了特定的中断触发条件&#xff08;中断源&#xff09;&#xff0c;使得CPU暂停当前正在运行的程序&#xff0c;转而去处理中断程序&#xff0c;处理完成后又返回原来被暂停的位置继续运行 中…

平衡二叉树——AVl树

AVL树 AVl树的概念AVL树的实现AVL树的结构AVl树的插入AVL树的旋转左单旋右单旋左右双旋右左双旋 AVl树以高度递归实现完整代码 AVl树的概念 AVL树是一种自平衡的二叉搜索树&#xff08;Binary Search Tree, BST&#xff09;&#xff0c;由两位苏联数学家 Georgy Adelson-Velsk…

jQuery——事件处理

1、事件绑定&#xff08;2种&#xff09; ① eventName&#xff08;function&#xff08;&#xff09;{ }&#xff09; 绑定对应事件名的监听&#xff0c;例如&#xff1a;$&#xff08;‘#div’&#xff09;. click&#xff08;function&#xff08;&#xff09;{ }&#xf…

【复习】CSS中的选择器

文章目录 东西有点多 以实战为主选择器盒子模型 东西有点多 以实战为主 选择器 CSS选择器&#xff08;CSS Selectors&#xff09;是用于在HTML或XML文档中查找和选择元素&#xff0c;以便应用CSS样式的一种方式。 元素选择器&#xff08;Type Selector&#xff09; 选择所有…

在谷歌colab运行YOLO系列(重要比在云服务器搭建运行快)

在谷歌colab运行YOLO系列&#xff08;重要比在云服务器搭建运行快&#xff09; 一、谷歌云硬盘二、克隆 YOLOV5进行运行测试1&#xff0c;修改prepare文件中的参数2&#xff0c;修改voc.yaml3&#xff0c;修改yolov5s.yaml4&#xff0c;防止colab自动断开的方式&#xff08;必须…

今日指数项目个股描述功能实现

个股描述功能实现 1 个股描述功能实现说明 1&#xff09;原型示意 2&#xff09;接口说明 功能描述&#xff1a;个股主营业务查询接口 服务路径&#xff1a;/api/quot/stock/describe 服务方法&#xff1a;GET 请求参数&#xff1a;code #股票编码 响应参数&#xff1a; {…

Koa2项目实战3 (koa-body,用于处理 HTTP 请求中的请求体)

以用户注册接口为例&#xff0c;需要在请求里携带2个参数&#xff1a;用户名&#xff08;user_name&#xff09;和密码&#xff08;password&#xff09;。 开发者需要在接口端&#xff0c;解析出user_name 、password。 在使用Koa开发的接口中&#xff0c;如何解析出请求携带…

八、Drf解析器

八、解析器 8.1概念 解析用户请求发送过来的数据&#xff08;常用的是JSON&#xff09; 请求类型&#xff1a; get: ​ 方式1&#xff1a; http://127.0.0.1/web/?arg1v1&arg2v2 ​ 方式2&#xff1a;通过请求头发送 post: ​ 请求头&#xff1a; ​ content-typ…

数据库管理-第247期 23ai:全球分布式数据库-Schema对象(20241004)

数据库管理247期 2024-10-04 数据库管理-第247期 23ai&#xff1a;全球分布式数据库-Schema对象&#xff08;20241004&#xff09;1 分区、表空间和Chunk&#xff08;块&#xff09;2 表空间组3 分片表4 分片表族5 复制表6 在所有分片上创建的非表对象总结 数据库管理-第247期 …

C++——模板进阶、继承

文章目录 一、模板1. 非类型模板参数2. 模板的特化函数模板特化类模板特化1. 全特化2. 偏特化部分特化参数更进一步的限制 二、继承1. 概念2. 定义定义格式 3. 继承基类成员访问⽅式的变化4. 继承类模板5.基类和派⽣类间的转换6. 继承中的作⽤域隐藏规则&#xff1a; 7. 派⽣类…

LinuxO(1)调度算法

概念 在Linux中&#xff0c;O(1)调度算法是一种进程调度算法。O(1)表示算法的时间复杂度是常数级别的&#xff0c;与系统中的进程数量无关。 运行队列结构 他采用了两个运行队列&#xff0c;一个活动队列和一个过期队列。活动队列中的进程是有资格获取CPU时间片的进程&#x…

阳台山足球营地的停车位探寻

阳台山足球营地的环境是真好哈。停车场名称&#xff1a;阳台山森林公园配套停车场。应该很多爬山的人车子也停在这里。而且我没想到的&#xff0c;阳台山的山泉水还有不少居民每天提着空桶去山上装。看来环境是真的很好哈 停车场有地面和地下停车场&#xff0c;停车位个数查不…

Linux驱动开发(速记版)--设备模型

第八十章 设备模型基本框架-kobject 和 kset 80.1 什么是设备模型 设备模型使Linux内核处理复杂设备更高效。 字符设备驱动适用于简单设备&#xff0c;但对于电源管理和热插拔&#xff0c;不够灵活。 设备模型允许开发人员以高级方式描述硬件及关系&#xff0c;提供API处理设备…

若依使用(二次开发)

RouYi-MT RouYi-MT下载&#xff1a; 下载地址 RouYi-MT的使用&#xff08;修改若依代码中文件夹得统一包名&#xff09; 将对应的Springboot文件压缩成压缩包。 填写对应的参数&#xff0c;生成修改后的文件。 开发步骤 1.创建子项目到RouYi-springboot中&#xff0c;添加…

简单易懂的springboot整合Camunda 7工作流入门教程

简单易懂的Spring Boot整合Camunda7入门教程 因为关于Spring Boot结合Camunda7的教程在网上比较少&#xff0c;而且很多都写得有点乱&#xff0c;很多概念写得太散乱&#xff0c;讲解不清晰&#xff0c;导致看不懂&#xff0c;本人通过研究学习之后就写出了这篇教学文档。 介…

我的创作纪念日一年

目录 机缘 收获 日常 成就 憧憬 机缘 我之所以开始写CSDN博客&#xff0c;源于一段特殊的时光。去年此时&#xff0c;我独自待在实验室&#xff0c;周围的世界仿佛与我无关。没有旅游&#xff0c;没有与朋友的欢聚&#xff0c;情感的挫折和学业的压力如潮水般袭来。在这样的…

2025舜宇招聘【内推码】

【2025内推码】 DSwNQ9yu DSJXN8Mr 舜宇集团2025届全球校园招聘正式启动&#xff01;&#xff01;&#xff01; 专业需求&#xff1a;机械、自动化、电子、电气、通信、控制、测控、计算机、软件、物理、光学等专业&#xff1b; 工作地点&#xff1a;宁波余姚、浙江杭州、广东…

97. UE5 GAS RPG 实现闪电链技能(二)

书接上回&#xff0c;如果没有查看上一篇文章的同学推荐先看上一章&#xff0c;我们接着实现闪电链技能。 在上一章最后&#xff0c;我们实现了闪电链的第一条链&#xff0c;能够正确显示特效&#xff0c;接下来&#xff0c;我们先实现它的音效和一些bug修复。 我们在多端网络里…