21、使用 OpenGL 进行高速图像过滤

使用 OpenGL 进行高速图像过滤

引言

在以往处理图像和视频时,我们大多借助 OpenCV 并依靠 CPU 完成相关操作。而现在,我们将探索一种新的图像过滤方式,即利用 OpenGL 将图像过滤任务从 CPU 转移到图形处理单元(GPU)上。

在许多软件(如 Google Chrome 浏览器)的设置页面中,你可能会看到“硬件加速”之类的选项。这意味着软件正在利用显卡(即 GPU)进行渲染或计算,这种借助其他处理器而非 CPU 进行计算或渲染的方式被称为异构计算。异构计算的实现方式有很多,像 OpenCL 就是其中之一,而我们接下来要介绍的 OpenGL 同样属于异构计算的范畴,不过它主要用于 3D 图形渲染,在这里我们将用它在 GPU 上进行图像过滤。

以下是本文将涵盖的主要内容:
1. OpenGL 简介
2. 在 Qt 中使用 OpenGL
3. 利用 OpenGL 在 GPU 上过滤图像
4. OpenGL 与 OpenCV 的结合使用

技术要求
  • 具备 C 和 C++ 编程语言的基础知识。
  • 由于 OpenGL 是本文的核心内容,对其有深入理解会更有优势。
  • 至少安装 Qt 5 版本和 OpenCV 4.0.0,安装方式与之前相同。
  • 本文所有代码可在 代码仓库 中找到。
  • 你可以通过 这个视频 查看代码的运行效果。
OpenGL 简介

OpenGL 并非像 OpenCV 或 Qt 那样的传统编程库。它由 Khronos 组织维护,该组织仅负责设计和定义 OpenGL 的 API 规范,而具体实现则由显卡制造商负责。像 Intel、AMD 和 Nvidia 等大多数制造商都会在其显卡驱动中提供 OpenGL 的实现。在 Linux 系统中,有一个名为 Mesa 的 OpenGL 实现,它既支持软件渲染,在显卡驱动正常的情况下也支持硬件渲染。

如今,学习 OpenGL 颇具挑战性,因为你不仅要理解异构架构,还需掌握另一种编程语言——OpenGL 着色语言,以及 C 和 C++。在本文中,我们将使用 OpenGL V4.0 引入并回溯到 V3.3 的新风格 API 来进行图像的渲染和过滤。下面我们通过一个简单的示例来开启 OpenGL 的学习之旅。

准备工作

在开始示例之前,我们需要确保计算机上安装了 OpenGL 及其相关的辅助库。不同操作系统的安装方式如下:
- Windows :只要安装了最新的显卡驱动,OpenGL 库也会随之安装。
- macOS :现代 macOS 系统已预装了 Apple 实现的 OpenGL 库。
- Linux :可以选择使用 Mesa 实现或已安装显卡的专有硬件驱动。使用 Mesa 更为简便,只需安装其运行时和开发包,就能完成 OpenGL 的安装。

在使用 OpenGL 之前,我们必须创建一个 OpenGL 上下文以及与之关联的窗口,用于显示渲染后的图形。这一工作通常依赖于具体的平台,但幸运的是,有许多库可以隐藏这些平台相关的细节,并提供统一的 API。这里我们将使用 GLFW( https://www.glfw.org/ )和 GLEW( http://glew.sourceforge.net/ )库。GLFW 库可帮助我们创建 OpenGL 上下文和显示渲染图形的窗口,而 GLEW 库则负责处理 OpenGL 头文件和扩展。在类 UNIX 系统中,我们可以从源代码构建这些库,也可以使用系统包管理器轻松安装;在 Windows 系统中,我们可以从这两个库的官方网站下载二进制包进行安装。

编写 OpenGL 程序的步骤

编写 OpenGL 程序通常包含以下步骤:
1. 创建上下文和窗口。
2. 准备要绘制对象(3D)的数据。
3. 通过调用 OpenGL API 将数据传递给 GPU。
4. 调用绘图指令,让 GPU 绘制对象。在绘制过程中,GPU 会对数据进行多种操作,这些操作可以通过编写 OpenGL 着色语言的着色器进行定制。
5. 编写在 GPU 上运行的着色器,以操作 GPU 上的数据。

示例代码实现
1. 创建源文件并添加必要的包含指令和主函数
#include <stdio.h>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
int main() {
    return 0;
}
2. 创建 OpenGL 上下文和窗口
// init glfw and GL context
if (!glfwInit()) {
    fprintf(stderr, "ERROR: could not start GLFW3\n");
    return 1;
}

glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // 3.3 or 4.x
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow *window = NULL;
window = glfwCreateWindow(640, 480, "Hello OpenGL", NULL, NULL);
if (!window) {
    fprintf(stderr, "ERROR: could not open window with GLFW3\n");
    glfwTerminate();
    return 1;
}
glfwMakeContextCurrent(window);

在这段代码中,我们首先调用 glfwInit 函数初始化 GLFW 库,然后使用 glfwWindowHint 函数设置一些提示信息:
- GLFW_CONTEXT_VERSION_MAJOR GLFW_CONTEXT_VERSION_MINOR 用于指定 OpenGL 的版本,由于我们使用的是新风格 API,至少应使用 V3.3。
- GLFW_OPENGL_FORWARD_COMPAT 将 OpenGL 的向前兼容性设置为 true。
- GLFW_OPENGL_PROFILE 用于设置创建 OpenGL 上下文时使用的配置文件。通常有核心配置文件和兼容配置文件两种选择。使用核心配置文件时,只能使用新风格 API;使用兼容配置文件时,制造商可能会同时支持新旧 API,但在运行新版本着色器时可能会出现一些问题,因此这里我们选择核心配置文件。

设置完提示信息后,我们声明并创建窗口,新窗口的宽度为 640 像素,高度为 480 像素,标题为“Hello OpenGL”。同时,与窗口关联的 OpenGL 上下文也会被创建,我们调用 glfwMakeContextCurrent 函数将其设置为当前上下文。

3. 初始化 GLEW 库
// start GLEW extension handler
GLenum ret = glewInit();
if ( ret != GLEW_OK) {
    fprintf(stderr, "Error: %s\n", glewGetErrorString(ret));
}
4. 准备要绘制对象的数据

我们将绘制一个三角形,因为在 OpenGL 中,几乎所有绘制的图形都是由三角形组成的,它是最基本的图形。三角形的数据如下:

GLfloat points[] = {+0.0f, +0.5f, +0.0f,
                    +0.5f, -0.5f, +0.0f,
                    -0.5f, -0.5f, +0.0f };

这里的数据是一个包含九个浮点数的数组,每个顶点用三个浮点数表示其在 3D 空间中的坐标,由于我们不关注 3D 渲染,所以将每个顶点的 z 坐标都设为 0.0,将三角形绘制为 2D 形状。

OpenGL 使用归一化设备坐标(NDC)系统,所有坐标都被限制在 -1.0 到 1.0 的范围内。如果对象的坐标超出这个范围,将不会在 OpenGL 视口中显示。

5. 将数据传递给 GPU

我们通过顶点缓冲对象(VBO)和顶点数组对象(VAO)将数据传递给 GPU,具体代码如下:

// vbo
GLuint vbo;
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, 9 * sizeof(GLfloat), points, GL_STATIC_DRAW);

在上述代码中, glGenBuffers 函数用于生成一个顶点缓冲对象,并将其名称存储在 vbo 变量中。接着, glBindBuffer 函数将顶点缓冲对象绑定到当前 OpenGL 上下文的 GL_ARRAY_BUFFER 类型,这意味着该对象用于存储顶点属性数据。最后, glBufferData 函数创建数据存储并使用我们的顶点数据进行初始化,最后一个参数告诉 OpenGL 我们的数据不会改变,这是一个优化提示。

GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);

为了让顶点缓冲对象在 GPU 上可见,我们需要引入顶点数组对象,并在其中放置一个指向缓冲的指针。首先,我们生成并绑定顶点数组对象,然后调用 glEnableVertexAttribArray 函数启用索引为 0 的通用顶点属性数组指针,你可以将其看作在顶点数组对象中为我们创建的顶点缓冲对象预留一个座位,座位编号为 0。接着, glVertexAttribPointer 函数让当前绑定的顶点缓冲对象坐在预留的座位上,该函数的参数含义如下:
| 参数 | 含义 |
| ---- | ---- |
| index | 指定索引(即座位编号) |
| size | 指定每个顶点属性的组件数量,这里每个顶点有三个浮点数,所以为 3 |
| type | 缓冲区的元素类型或顶点属性组件的数据类型 |
| normalized | 指定数据在被 GPU 访问前是否需要由 OpenGL 进行归一化,由于我们使用的是归一化数据,所以不需要再次归一化 |
| stride | 连续通用顶点属性之间的偏移量,这里为 0 表示数据紧密排列无偏移 |
| pointer | 缓冲区中第一个通用顶点属性的第一个组件的偏移量,使用 NULL 表示零偏移 |

6. OpenGL 图形管道阶段

OpenGL 图形管道大致可分为六个阶段:

graph LR
    A[顶点着色器] --> B[形状组装]
    B --> C[几何着色器]
    C --> D[光栅化]
    D --> E[片段着色器]
    E --> F[混合]
  • 顶点着色器 :接收顶点属性数据作为输入,输出每个顶点的位置。OpenGL 在此阶段不提供默认的着色器程序,需要我们自己编写。
  • 形状组装 :用于组装形状,如生成顶点并定位它们,这是一个可选阶段,在本示例中我们将忽略它。
  • 几何着色器 :用于生成或删除几何图形,同样是可选阶段,无需编写着色器程序。
  • 光栅化 :将 3D 形状(主要是三角形)转换为 2D 像素,此阶段不需要着色器程序。
  • 片段着色器 :为光栅化阶段的片段着色,OpenGL 在此阶段也不提供默认的着色器程序,需要我们自行编写。
  • 混合 :将 2D 图形渲染到屏幕或帧缓冲区。

每个阶段都将前一阶段的输出作为输入,并将输出传递给下一阶段。在某些阶段,我们可以或需要编写着色器程序来参与工作。着色器程序是用 OpenGL 着色语言编写的代码,在应用程序运行时由 OpenGL 实现进行编译。

7. 编写着色器程序
// shader and shader program
GLuint vert_shader, frag_shader;
GLuint shader_prog;
const char *vertex_shader_code = "#version 330\n"
    "layout (location = 0) in vec3 vp;"
    "void main () {"
    " gl_Position = vec4(vp, 1.0);"
    "}";
const char *fragment_shader_code = "#version 330\n"
    "out vec4 frag_colour;"
    "void main () {"
    " frag_colour = vec4(0.5, 1.0, 0.5, 1.0);"
    "}";
vert_shader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vert_shader, 1, &vertex_shader_code, NULL);
glCompileShader(vert_shader);
frag_shader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(frag_shader, 1, &fragment_shader_code, NULL);
glCompileShader(frag_shader);
shader_prog = glCreateProgram();
glAttachShader(shader_prog, frag_shader);
glAttachShader(shader_prog, vert_shader);
glLinkProgram(shader_prog);

在这段代码中,我们首先定义了三个变量: vert_shader frag_shader 用于存储对应阶段所需的着色器程序, shader_prog 用于存储整个着色器程序。然后,我们将着色器程序以字符串的形式写在代码中,接着调用 glCreateShader 函数创建每个着色器程序,将源字符串附加到它们上面并进行编译。最后,我们创建整个着色器程序对象,将各个阶段的着色器程序附加到它上面并进行链接。

顶点着色器代码如下:

#version 330
layout (location = 0) in vec3 vp;
void main() {
    gl_Position = vec4(vp, 1.0);
}

第一行指定了 OpenGL 着色语言的版本,这里使用 330 版本,与我们使用的 OpenGL 版本相对应。第二行声明了输入数据的变量, layout (location = 0) 限定符表示该输入数据与当前绑定的顶点数组对象的索引 0 相关联, in 关键字表示这是一个输入变量, vec3 表示数据类型为三个浮点数的向量, vp 是变量名。在主函数中,我们将输入的顶点坐标转换为四维向量并赋值给 gl_Position 变量,该变量是预定义的,用于表示顶点的位置。

片段着色器代码如下:

#version 330
out vec4 frag_colour;
void main () {
    frag_colour = vec4(0.5, 1.0, 0.5, 1.0);
}

在这个着色器中,我们使用 out 关键字定义了一个 vec4 类型的输出变量,它表示 RGBA 格式的颜色。在主函数中,我们将一个常量颜色(浅绿色)赋值给该输出变量。

8. 启动图形管道
while (!glfwWindowShouldClose(window)) {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glUseProgram(shader_prog);
    glBindVertexArray(vao);
    glDrawArrays(GL_TRIANGLES, 0, 3);
    // update other events like input handling
    glfwPollEvents();
    // put the stuff we've been drawing onto the display
    glfwSwapBuffers(window);
}
glfwTerminate();

在这段代码中,我们会持续运行一个代码块,直到应用程序窗口关闭。在代码块中,我们首先清除窗口上的位平面区域,然后使用我们创建的着色器程序并绑定顶点数组对象,接着调用 glDrawArrays 函数启动图形管道来绘制对象。 glDrawArrays 函数的第一个参数指定要绘制的基本类型,这里我们要绘制三角形,所以使用 GL_TRIANGLES ;第二个参数是为顶点缓冲对象启用的缓冲区索引;最后一个参数是我们要使用的顶点数量。

绘制完成后,我们调用 glfwPollEvents 函数获取窗口上发生的事件,并调用 glfwSwapBuffers 函数将绘制的图形显示出来,这是因为 GLFW 库使用了双缓冲优化。当用户关闭窗口时,我们跳出代码块并调用 glfwTerminate 函数释放 GLFW 库分配的所有资源,然后应用程序退出。

9. 编译和运行应用程序

在 Linux 系统中,使用以下命令编译和运行应用程序:

gcc -Wall -std=c99 -o main.exe main.c -lGLEW -lglfw -lGL -lm
./main.exe

在 Windows 系统中,使用 MinGW 编译时可以使用以下命令:

gcc -Wall -std=c99 -o main.exe main.c libglew32.dll.a glfw3dll.a -lOpenGL32 -lglew32 -lglfw3 -lm

不要忘记使用 -I -L 选项指定 GLFW 和 GLEW 库的包含路径和库路径。

总结

通过以上步骤,我们完成了第一个使用 OpenGL 的应用程序,成功绘制了一个绿色的三角形。然而,GLFW 并非一个功能完备的 GUI 库,与我们常用的 Qt 库相比,它虽然可以创建窗口并处理 UI 事件,但缺乏丰富的控件。那么,在应用程序中同时需要 OpenGL 和一些控件时,能否在 Qt 中使用 OpenGL 呢?答案是肯定的,我们将在后续内容中进行演示。

使用 OpenGL 进行高速图像过滤

在 Qt 中使用 OpenGL

前面我们使用 GLFW 库完成了 OpenGL 的基本示例,但它的 GUI 功能相对有限。而 Qt 是一个功能强大的 GUI 库,接下来我们探讨如何在 Qt 中使用 OpenGL。

Qt 与 OpenGL 结合的优势
  • 丰富的控件支持 :Qt 提供了各种各样的控件,如按钮、文本框、列表框等,可以方便地构建复杂的用户界面。
  • 跨平台兼容性 :Qt 支持多种操作系统,包括 Windows、macOS、Linux 等,使用 Qt 结合 OpenGL 可以轻松实现跨平台的图形应用程序。
在 Qt 中使用 OpenGL 的步骤
  1. 创建 Qt 项目 :打开 Qt Creator,创建一个新的 Qt Widgets Application 项目。
  2. 包含 OpenGL 头文件 :在项目的源文件中包含 OpenGL 相关的头文件。
#include <QOpenGLWidget>
#include <QOpenGLFunctions>
  1. 创建自定义 OpenGL 窗口类 :继承 QOpenGLWidget QOpenGLFunctions 类,并重写相关的虚函数。
class OpenGLWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
    Q_OBJECT
public:
    explicit OpenGLWidget(QWidget *parent = nullptr);
    ~OpenGLWidget();

protected:
    void initializeGL() override;
    void resizeGL(int w, int h) override;
    void paintGL() override;
};
  1. 实现虚函数
    • initializeGL() :在这个函数中进行 OpenGL 的初始化操作,如创建着色器程序、初始化顶点数据等。
      ```cpp
      void OpenGLWidget::initializeGL()
      {
      initializeOpenGLFunctions();

      // 创建并编译顶点着色器
      GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
      const char *vertexShaderSource = “#version 330 core\n”
      “layout (location = 0) in vec3 aPos;\n”
      “void main()\n”
      “{\n”
      ” gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n”
      “}\0”;
      glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
      glCompileShader(vertexShader);

      // 创建并编译片段着色器
      GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
      const char *fragmentShaderSource = “#version 330 core\n”
      “out vec4 FragColor;\n”
      “void main()\n”
      “{\n”
      ” FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n”
      “}\0”;
      glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
      glCompileShader(fragmentShader);

      // 创建着色器程序
      shaderProgram = glCreateProgram();
      glAttachShader(shaderProgram, vertexShader);
      glAttachShader(shaderProgram, fragmentShader);
      glLinkProgram(shaderProgram);

      // 删除着色器
      glDeleteShader(vertexShader);
      glDeleteShader(fragmentShader);

      // 初始化顶点数据
      float vertices[] = {
      -0.5f, -0.5f, 0.0f,
      0.5f, -0.5f, 0.0f,
      0.0f, 0.5f, 0.0f
      };

      // 创建顶点缓冲对象(VBO)和顶点数组对象(VAO)
      glGenVertexArrays(1, &VAO);
      glGenBuffers(1, &VBO);

      // 绑定 VAO 和 VBO
      glBindVertexArray(VAO);
      glBindBuffer(GL_ARRAY_BUFFER, VBO);

      // 将顶点数据复制到 VBO
      glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

      // 设置顶点属性指针
      glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
      glEnableVertexAttribArray(0);

      // 解绑 VBO 和 VAO
      glBindBuffer(GL_ARRAY_BUFFER, 0);
      glBindVertexArray(0);
      }
      - `resizeGL(int w, int h)`:在窗口大小改变时调用,用于设置视口大小。 cpp
      void OpenGLWidget::resizeGL(int w, int h)
      {
      glViewport(0, 0, w, h);
      }
      - `paintGL()`:在这个函数中进行实际的绘制操作。 cpp
      void OpenGLWidget::paintGL()
      {
      // 清除颜色缓冲区
      glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
      glClear(GL_COLOR_BUFFER_BIT);

      // 使用着色器程序
      glUseProgram(shaderProgram);

      // 绑定 VAO
      glBindVertexArray(VAO);

      // 绘制三角形
      glDrawArrays(GL_TRIANGLES, 0, 3);

      // 解绑 VAO
      glBindVertexArray(0);
      }
      ```
      5. 在 Qt 窗口中使用自定义 OpenGL 窗口类 :在主窗口的构造函数中创建并添加自定义 OpenGL 窗口。

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    OpenGLWidget *openGLWidget = new OpenGLWidget(this);
    setCentralWidget(openGLWidget);
}
利用 OpenGL 在 GPU 上过滤图像

除了基本的图形绘制,OpenGL 还可以用于在 GPU 上进行图像过滤。以下是一个简单的图像过滤示例,使用 OpenGL 实现灰度化过滤。

实现步骤
  1. 加载图像 :使用 Qt 的 QImage 类加载图像。
QImage image("path/to/image.jpg");
  1. 将图像数据传递给 GPU :创建纹理对象,并将图像数据复制到纹理中。
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);

// 设置纹理参数
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

// 将图像数据复制到纹理
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.width(), image.height(), 0, GL_BGRA, GL_UNSIGNED_BYTE, image.bits());
glGenerateMipmap(GL_TEXTURE_2D);
  1. 编写灰度化着色器程序
    • 顶点着色器
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;

out vec2 TexCoord;

void main()
{
    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
    TexCoord = aTexCoord;
}
- **片段着色器**
#version 330 core
in vec2 TexCoord;
out vec4 FragColor;

uniform sampler2D ourTexture;

void main()
{
    vec4 color = texture(ourTexture, TexCoord);
    float gray = 0.299 * color.r + 0.587 * color.g + 0.114 * color.b;
    FragColor = vec4(gray, gray, gray, color.a);
}
  1. 绘制过滤后的图像
// 使用着色器程序
glUseProgram(shaderProgram);

// 绑定纹理
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);

// 设置纹理采样器
glUniform1i(glGetUniformLocation(shaderProgram, "ourTexture"), 0);

// 绘制四边形
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
OpenGL 与 OpenCV 的结合

OpenGL 和 OpenCV 可以相互结合,发挥各自的优势。OpenCV 擅长图像和视频处理,而 OpenGL 则适合进行图形渲染和加速。以下是一个结合 OpenGL 和 OpenCV 的示例,使用 OpenCV 读取视频帧,然后使用 OpenGL 进行渲染。

实现步骤
  1. 使用 OpenCV 读取视频帧
#include <opencv2/opencv.hpp>

cv::VideoCapture cap("path/to/video.mp4");
cv::Mat frame;
cap >> frame;
  1. 将 OpenCV 图像数据转换为 OpenGL 纹理
// 创建纹理对象
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);

// 设置纹理参数
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

// 将 OpenCV 图像数据复制到纹理
cv::cvtColor(frame, frame, cv::COLOR_BGR2RGB);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, frame.cols, frame.rows, 0, GL_RGB, GL_UNSIGNED_BYTE, frame.data);
glGenerateMipmap(GL_TEXTURE_2D);
  1. 使用 OpenGL 渲染视频帧
// 使用着色器程序
glUseProgram(shaderProgram);

// 绑定纹理
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);

// 设置纹理采样器
glUniform1i(glGetUniformLocation(shaderProgram, "ourTexture"), 0);

// 绘制四边形
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
总结

通过以上内容,我们介绍了 OpenGL 的基本概念和使用方法,包括使用 GLFW 库创建 OpenGL 上下文、绘制基本图形,以及在 Qt 中使用 OpenGL 构建复杂的 GUI 应用程序。同时,我们还探讨了如何利用 OpenGL 在 GPU 上进行图像过滤,以及将 OpenGL 与 OpenCV 结合使用。

OpenGL 作为一种强大的图形编程接口,能够充分发挥 GPU 的并行计算能力,提高图形渲染和图像处理的效率。在实际应用中,我们可以根据具体需求选择合适的库和技术,实现更加复杂和高效的图形应用程序。

以下是一个简单的流程图,总结了在 Qt 中使用 OpenGL 进行图像渲染的主要步骤:

graph LR
    A[创建 Qt 项目] --> B[包含 OpenGL 头文件]
    B --> C[创建自定义 OpenGL 窗口类]
    C --> D[实现虚函数]
    D --> E[在 Qt 窗口中使用自定义 OpenGL 窗口类]
    E --> F[加载图像或视频帧]
    F --> G[将图像数据传递给 GPU]
    G --> H[编写着色器程序]
    H --> I[绘制过滤后的图像或视频帧]

希望通过本文的介绍,你对 OpenGL 的使用有了更深入的了解,并能够在实际项目中应用这些技术。

内容概要:本文设计了一种基于PLC的全自动洗衣机控制系统内容概要:本文设计了一种,采用三菱FX基于PLC的全自动洗衣机控制系统,采用3U-32MT型PLC作为三菱FX3U核心控制器,替代传统继-32MT电器控制方式,提升了型PLC作为系统的稳定性与自动化核心控制器,替代水平。系统具备传统继电器控制方式高/低水,实现洗衣机工作位选择、柔和过程的自动化控制/标准洗衣模式切换。系统具备高、暂停加衣、低水位选择、手动脱水及和柔和、标准两种蜂鸣提示等功能洗衣模式,支持,通过GX Works2软件编写梯形图程序,实现进洗衣过程中暂停添加水、洗涤、排水衣物,并增加了手动脱水功能和、脱水等工序蜂鸣器提示的自动循环控制功能,提升了使用的,并引入MCGS组便捷性与灵活性态软件实现人机交互界面监控。控制系统通过GX。硬件设计包括 Works2软件进行主电路、PLC接梯形图编程线与关键元,完成了启动、进水器件选型,软件、正反转洗涤部分完成I/O分配、排水、脱、逻辑流程规划水等工序的逻辑及各功能模块梯设计,并实现了大形图编程。循环与小循环的嵌; 适合人群:自动化套控制流程。此外、电气工程及相关,还利用MCGS组态软件构建专业本科学生,具备PL了人机交互C基础知识和梯界面,实现对洗衣机形图编程能力的运行状态的监控与操作。整体设计涵盖了初级工程技术人员。硬件选型、; 使用场景及目标:I/O分配、电路接线、程序逻辑设计及组①掌握PLC在态监控等多个方面家电自动化控制中的应用方法;②学习,体现了PLC在工业自动化控制中的高效全自动洗衣机控制系统的性与可靠性。;软硬件设计流程 适合人群:电气;③实践工程、自动化及相关MCGS组态软件与PLC的专业的本科生、初级通信与联调工程技术人员以及从事;④完成PLC控制系统开发毕业设计或工业的学习者;具备控制类项目开发参考一定PLC基础知识。; 阅读和梯形图建议:建议结合三菱编程能力的人员GX Works2仿真更为适宜。; 使用场景及目标:①应用于环境与MCGS组态平台进行程序高校毕业设计或调试与运行验证课程项目,帮助学生掌握PLC控制系统的设计,重点关注I/O分配逻辑、梯形图与实现方法;②为工业自动化领域互锁机制及循环控制结构的设计中类似家电控制系统的开发提供参考方案;③思路,深入理解PL通过实际案例理解C在实际工程项目PLC在电机中的应用全过程。控制、时间循环、互锁保护、手动干预等方面的应用逻辑。; 阅读建议:建议结合三菱GX Works2编程软件和MCGS组态软件同步实践,重点理解梯形图程序中各环节的时序逻辑与互锁机制,关注I/O分配与硬件接线的对应关系,并尝试在仿真环境中调试程序以加深对全自动洗衣机控制流程的理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值