如何将OpenCV的mat图像转换为OpenGL中的纹理并显示

如果你还不知道什么是纹理,可以先看一下LearnOpenGL-CN中的纹理章节

简单的思路就是用OpenGL画一个矩形,然后将一张图片作为纹理贴到这个正方形上。为了能够将纹理映射(Map)到三角形上,我们需要指定三角形的每个顶点各自对应纹理的哪个部分,这样的话每个顶点都会关联一个纹理坐标。

首先,需要定义好矩形(两个三角形)的顶点和纹理坐标的对应关系:

在OpenGL中,所有的坐标都是3D坐标(x, y, z),OpenGL会把所有的3D坐标转换为所谓的标准化设备坐标,坐标在范围为[-1.0, 1.0],只有落在这个范围内的坐标最终才会呈现在屏幕上。
标准化设备坐标的坐标系: 原点在屏幕中间,y轴正方向向上,x轴正方向向右,z轴正方向垂直屏幕指向屏幕前的你。 所以,如果我们要顶一个矩形的4个顶点,使它撑满视窗的话,坐标定义如下:
在这里插入图片描述
然后定义每个顶点对应的纹理坐标: 我们使用的是2D纹理图像,纹理坐标在x轴和y轴,范围在[0,1],纹理坐标起始于(0, 0),也就是纹理图像的左下角。坐标定义如下所示:
在这里插入图片描述
于是,矩形的坐标和纹理的坐标的对应关系就找到了。定义为数组的形式如下:

GLfloat vertices[] = {
		// Positions          // Texture Coords
		 1.0f,  1.0f, 0.0f,   1.0f, 1.0f, // Top Right
		 1.0f, -1.0f, 0.0f,   1.0f, 0.0f, // Bottom Right
		-1.0f, -1.0f, 0.0f,   0.0f, 0.0f, // Bottom Left
		-1.0f,  1.0f, 0.0f,   0.0f, 1.0f  // Top Left 
	};

然后要解决的是如何将cvMat转换为OpenGL中的纹理:
首先,定义一个函数用来创建一个纹理,返回该纹理对应的ID:

static GLuint createTexture() 
{
		GLuint textureID;
		// 创建一个纹理对象,并返回一个独一无二的标识保存在textureID中
		glGenTextures(1, &textureID);
		// 绑定textureID标识的纹理,之后的所有操作都是相对于该纹理的
		glBindTexture(GL_TEXTURE_2D, textureID);	
		// 设置纹理环绕方式
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
		// 设置纹理过滤方式
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		// 解绑
		glBindTexture(GL_TEXTURE_2D, 0);			
		return textureID;
}

然后用下面的函数设置纹理的内容:

static void cvmatToTexture(GLuint& textureId, const cv::Mat& mat) 
{
		// 绑定textureID标识的纹理,之后的所有操作都是相对于该纹理的
		glBindTexture(GL_TEXTURE_2D, textureId);
		// 注意OpenCV中图像通道的顺序是BGR
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, mat.cols, mat.rows, 0, GL_BGR_EXT, GL_UNSIGNED_BYTE, mat.data);
		glGenerateMipmap(GL_TEXTURE_2D);
		// 解绑
		glBindTexture(GL_TEXTURE_2D, 0);
}

剩下的就是常规的渲染正方形的操作了。参考LearnOpenGL-CN的代码即可。


下面基于上面的内容,编写一个小的工程,用OpenCV从USB摄像头获取图像,然后用OpenGL显示。
获取摄像头图像线程函数定义如下:

void captureImage(int deviceId) 
{
	cv::VideoCapture cap(deviceId);
	if (!cap.isOpened()) {
		printf("Open camera failed!!!\n");
		exit(-1);
	}

	while (true) {
		std::this_thread::sleep_for(std::chrono::milliseconds(5));
		if (prepared) 
			continue;

		cv::Mat frame;
		cap >> frame;

		if (!frame.data)
			continue;

		imageBuf[bufId] = frame;
		prepared = true;
	}
}

主线程定义如下:

int main() {
	std::thread t(captureImage, 0);
	t.detach();

	// 初始化GLFW
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	// 创建一个OpenGL上下文
	GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "Camera", nullptr, nullptr);
	glfwMakeContextCurrent(window);
	glfwSetKeyCallback(window, key_callback);
	// 初始化glew
	glewExperimental = GL_TRUE;
	glewInit();

	glViewport(0, 0, WIDTH, HEIGHT);

	// 创建并编译shader程序
	Shader ourShader("./vertex_shader.vs", "./fragment_shader.frag");

	// 定义顶点属性
	GLfloat vertices[] = {
		// Positions          // Texture Coords
		 1.0f,  1.0f, 0.0f,   1.0f, 1.0f, // Top Right
		 1.0f, -1.0f, 0.0f,   1.0f, 0.0f, // Bottom Right
		-1.0f, -1.0f, 0.0f,   0.0f, 0.0f, // Bottom Left
		-1.0f,  1.0f, 0.0f,   0.0f, 1.0f  // Top Left 
	};

	// 一个正方形是由两个三角形组成的
	GLuint indices[] = {  
		0, 1, 3, // First Triangle
		1, 2, 3  // Second Triangle
	};

	GLuint VBO, VAO, EBO;
	glGenVertexArrays(1, &VAO);
	glGenBuffers(1, &VBO);
	glGenBuffers(1, &EBO);

	glBindVertexArray(VAO);		// 绑定VAO
	// 给VBO指定缓冲目标,并设置缓冲数据
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
	// 给EBO指定缓冲目标,并设置缓冲数据
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
	// 指定顶点数据格式
	glVertexAttribPointer(0, 3, GL_FLOAT, false, 5 * sizeof(GL_FLOAT), (void*)0);
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(1, 2, GL_FLOAT, false, 5 * sizeof(GL_FLOAT), (void*)(3 * sizeof(GL_FLOAT)));
	glEnableVertexAttribArray(1);
	glBindVertexArray(0);		// 解绑

	// 创建一个纹理
	GLuint texture = createTexture();
	while (!glfwWindowShouldClose(window)) {
		glfwPollEvents();
		// 清空颜色缓冲区
		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);

		if (prepared) {
			cvmatToTexture(texture, imageBuf[bufId]);
			bufId = (bufId + 1) % 2;
			prepared = false;

			// 激活纹理
			glActiveTexture(GL_TEXTURE0);
			glBindTexture(GL_TEXTURE_2D, texture);
			glUniform1i(glGetUniformLocation(ourShader.program, "ourTexture1"), 0);

			ourShader.Use();

			glBindVertexArray(VAO);
			glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
			glBindVertexArray(0);

			glfwSwapBuffers(window);
		}
		std::this_thread::sleep_for(std::chrono::milliseconds(5));
	}

	glDeleteVertexArrays(1, &VAO);
	glDeleteBuffers(1, &VBO);
	glDeleteBuffers(1, &EBO);
	glfwTerminate();
	return 0;
}

运行结果:
在这里插入图片描述
完整工程代码可以参考OpenGL_show_camera

参考文献:
LearnOpenGL-CN入门章节
如何将OpenCV中的Mat类绑定为OpenGL中的纹理

  • 3
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
将 RGB 图像转换为 MP4 视频可以使用 OpenCV的 VideoWriter 类。VideoWriter 类可以将一系列图像帧编码为视频格式,并将其保存为文件。 在 C++ 使用 OpenGL ES 和 OpenCV,您可以将图像或视频帧从 OpenGL 纹理复制到内存,然后将其写入 VideoWriter 对象,以将其编码为视频。 具体实现步骤如下: 1. 使用 OpenGL ES 创建纹理对象,并将 RGB 图像数据绑定到纹理上。 2. 使用 OpenCV 的 cv::Mat 类创建一个与纹理大小相同的矩阵。 3. 使用 glReadPixels 将纹理数据读取到 cv::Mat 对象。 4. 使用 VideoWriter 类将 cv::Mat 对象图像帧编码为视频,并将其保存到文件。 以下是代码示例: ```c++ #include <opencv2/opencv.hpp> #include <GLES3/gl3.h> using namespace cv; int main() { // 创建 OpenGL 纹理对象并绑定 RGB 图像数据 GLuint textureId; glGenTextures(1, &textureId); glBindTexture(GL_TEXTURE_2D, textureId); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); // 创建 OpenCV Mat 对象 Mat frame(height, width, CV_8UC3); // 读取纹理数据到 Mat 对象 glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, frame.data); // 创建 VideoWriter 对象并将图像帧编码为视频 VideoWriter writer("output.mp4", VideoWriter::fourcc('X','2','6','4'), fps, Size(width, height)); writer.write(frame); // 释放资源 writer.release(); glDeleteTextures(1, &textureId); return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值