重新渲染YUV420P格式的OpenGL
最编程
2024-08-15 15:58:22
...
- 一 、YUV420P数据格式
- 二 、GLFW渲染YUV420P
- 2.1 定义顶点数据
- 2.2 创建YUV三张纹理
- 2.3上行YUV420数据
- 2.4 渲染纹理
- 2.5 着色器
- 三、代码地址以及存在的问题
- 四 、解决存在的问题
一 、YUV420P数据格式
结合上图可以看出YUV420P的特点如下:
①无论在横向还是纵向上都是两个亮度(Y)共享一组色度(UV),所以UV的宽度和高度都是Y的1/2
②在内存中有三片数据,也就是三个数据指针分别指向Y、U、V
ffmpeg中avframe保存yuv420p的数据时是直接申请一整个的 image_size, 亮度数据y的地址是起始地址,u 在 y 的基础上偏移, v 在 u 的基础上偏移
//以HD的yuv420p 8 bit数据为例
uint8_t* yuv420_data = new [1920*1080*1.5]();
uint8_t* data_y = yuv420_data;
uint8_t* data_u = yuv420_data + 1920*1080;
uint8_t* data_v = data_u + 1920*1080/4;
delete[] yuv420_data;
二 、GLFW渲染YUV420P
2.1 定义顶点数据
float vertex_coord_data[] = {
-1.f, -1.f, 0.f, 0.f, 1.f,
-1.f, 1.f, 0.f, 0.f, 0.f,
1.f, 1.f, 0.f, 1.f, 0.f,
1.f, -1.f, 0.f, 1.f, 1.f,
};
uint32_t vertx_index_data[] = {
0, 1, 2,
2, 3, 0
};
uint32_t m_vertex_buffer, m_index_buffer;
glGenBuffers(1, &m_vertex_buffer);
glGenBuffers(1, &m_index_buffer);
glGenVertexArrays(1, &m_vertex_array);
glBindVertexArray(m_vertex_array);
glBindBuffer(GL_ARRAY_BUFFER, m_vertex_buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_coord_data), vertex_coord_data, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_index_buffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(vertx_index_data), vertx_index_data, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3*sizeof(float)));
glEnableVertexAttribArray(1);
2.2 创建YUV三张纹理
// 纹理 y
glGenTextures(1, &m_tex_y);
glBindTexture(GL_TEXTURE_2D, m_tex_y);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
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_RED, m_tex_width, m_wnd_height, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);
glGenerateMipmap(GL_TEXTURE_2D);
// 纹理 u
glGenTextures(1, &m_tex_u);
glBindTexture(GL_TEXTURE_2D, m_tex_u);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
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_RED, m_tex_width/2, m_wnd_height/2, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);
glGenerateMipmap(GL_TEXTURE_2D);
// 纹理 v
glGenTextures(1, &m_tex_v);
glBindTexture(GL_TEXTURE_2D, m_tex_v);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
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_RED, m_tex_width/2, m_wnd_height/2, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);
glGenerateMipmap(GL_TEXTURE_2D);
2.3上行YUV420数据
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_tex_y);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_tex_width, m_tex_height, GL_RED, GL_UNSIGNED_BYTE, y);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, m_tex_u);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_tex_width/2, m_tex_height/2, GL_RED, GL_UNSIGNED_BYTE, u);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, m_tex_v);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_tex_width/2, m_tex_height/2, GL_RED, GL_UNSIGNED_BYTE, v);
2.4 渲染纹理
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_tex_y);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, m_tex_u);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, m_tex_v);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
2.5 着色器
/顶点着色器///
#version 330 core
layout (location = 0) in vec3 vertex_pos;
layout (location = 1) in vec2 tex_pos;
out vec2 tex_uv;
void main()
{
gl_Position = vec4(vertex_pos, 1.f);
tex_uv = vec2(tex_pos);
}
/像素着色器///
#version 330 core
out vec4 frage_color;
in vec2 tex_uv;
uniform sampler2D tex_y;
uniform sampler2D tex_u;
uniform sampler2D tex_v;
void main()
{
vec3 yuv = vec3(0.f);
//按照BT709的协议来转换YUV至RGB
yuv.x = texture2D(tex_y, tex_uv).r - 16.f/235.f;
yuv.y = texture2D(tex_u, tex_uv).r - 128.f/240.f;
yuv.z = texture2D(tex_v, tex_uv).r - 128.f/240.f;
yuv = clamp(yuv, 0.f, 1.f);
mat3 yuv_to_rgb = mat3(1.164f, 0.f, 1.793f,
1.164f, -0.213f, -0.533f,
1.164f, 2.112f, 0.f);
vec3 rgb = yuv_to_rgb *yuv;
frage_color = vec4(rgb.r, rgb.g, rgb.b, 1.f);
}
三、代码地址以及存在的问题
demo中使用封装好的 ffmpeg 来获取YUV420P数据,相关代码地址:https://github.com/pengguoqing/samples_code.git
使用demo中的代码渲染后的效果画面会变紫,我尝试了用其他YUV_RGB的转换矩阵,也是一样的会变紫,各位老师看过后能不能纠正一下哪里不正确
本demo的渲染效果如下:
正确渲染效果如下:
四 、解决存在的问题
经过这两天的思考和排查,以及请教行业前辈终于知道原因了,具体如下:
①y、u、v 三张纹理被声明成了 uniform 类型, 所以每次更新纹理数后都需要更新一下
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_tex_y);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_tex_width, m_tex_height, GL_RED, GL_UNSIGNED_BYTE, y);
m_shader_parse.setInt("tex_y", 0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, m_tex_u);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_tex_width/2, m_tex_height/2, GL_RED, GL_UNSIGNED_BYTE, u);
m_shader_parse.setInt("tex_u", 1);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, m_tex_v);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_tex_width/2, m_tex_height/2, GL_RED, GL_UNSIGNED_BYTE, v);
m_shader_parse.setInt("tex_v", 2);
//或一次性通知更新
//m_shader_parse.setInt("tex_y", 0);
//m_shader_parse.setInt("tex_u", 1);
//m_shader_parse.setInt("tex_v", 2);
② shader里面需要对左乘矩阵进行转置
因为shader里面的向量是列向量,所以 yuv_rgb 的矩阵需要左乘 采样的 yuv纹理数据
/*mat3 yuv_to_rgb = mat3(1.164f, 0.f, 1.793f,
1.164f, -0.213f, -0.533f,
1.164f, 2.112f, 0.f);*/
//709_YUV_to_RGB
mat3 yuv_to_rgb = mat3(1.164f, 1.164f, 1.164f,
0.f, -0.213f, 2.112f,
1.793f, -0.533f, 0.f);
修改后的渲染效果正确了
推荐阅读
-
重新表达该标题:编码格式YUV420的解释
-
【Codecs】YUV主要格式YV12,I420,YUV420P的区别
-
重新渲染YUV420P格式的OpenGL
-
不同YUV格式的解读:YU12、I420、YV12、NV12、NV21、YUV420P、YUV420SP、YUV422P和YUV444P的差异解析
-
使用OpenGL渲染YUV数据的方法
-
Opencv实现yuv420P格式的读取
-
两种方法将YUV420P文件转换为PNG图像格式的Python实现
-
渲染YUV数据的OpenGL实现
-
10bit YUV(P010)的存储结构和处理-随着计算机处理信息的能力越来越厉害,这种能表现更高动态范围的图像存储格式将会逐渐成为主流,但是现在很多算法都不能直接处理 10bit 的 YUV ,都是先将其转换为 8bit YUV ,然后再进行处理,这实际上是丢弃了 10bit YUV 的图像高动态范围优势。 令人遗憾的是在渲染图像时,目前 OpenGL 也无法直接对 10bit YUV 进行渲染,也是需要先转换为 8bit YUV 。 接下来以一种常见的 10bit YUV (P010) 格式为例,介绍一下 10bit YUV 到 8bit YUV 的转换过程。 P010 最早是微软定义的格式,表示的是 YUV 4:2:0 的采样方式,也就是说 P010 表示的是一类 YUV 格式,它的内存排布方式可能是 NVNVYUYV12 。
-
14-傅里叶变换的代码实现-一、numpy实现傅里叶变换和逆傅里叶变换 1.numpy实现傅里叶变换numpy.fft.fft2实现傅里叶变换,返回一个复数数组(complex ndarray),也就是频谱图像numpy.fft.fftshift将零频率分量移到频谱中心(将左上角的低频区域,移到中心位置) 20*np.log(np.abs(fshift))设置频谱的范围。可以理解为,之前通过傅里叶变换得到复数的数组,是不能通过图像的方法展示出来的,需要转换为灰度图像(映射到[0,255]区间)需要注意的是1> 傅里叶得到低频、高频信息,针对低频、高频处理能够实现不同的目的2> 傅里叶过程是可逆的,图像经过傅里叶变换、逆傅里叶变换后,能够恢复到原始图像3> 在频域对图像进行处理,在频域的处理会反映在逆变换图像上 # 将绘制的图显示在窗口 %matplotlib qt5 import cv2 import numpy as np import matplotlib.pyplot as plt img = cv2.imread(r"image\lena.bmp",cv2.IMREAD_GRAYSCALE) # 傅里叶变换 f = np.fft.fft2(img) # 移动中心位置 fshift = np.fft.fftshift(f) # 调整值范围 result = 20*np.log(np.abs(fshift)) plt.subplot(1,2,1) plt.imshow(img,cmap=plt.cm.gray) plt.title("original") plt.axis("off") plt.subplot(1,2,2) plt.imshow(result,cmap=plt.cm.gray) plt.title("result") plt.axis("off") plt.show 傅里叶变换的频谱图像: 2.numpy实现逆傅里叶变换numpy.fft.ifft2实现逆傅里叶变换,返回一个复数数组(complex ndarray)numpy.fft.ifftshiftfftshift函数的逆函数,将中心位置的低频,重新移到左上角iimg = np.abs(逆傅里叶变化结果)设置值的范围,映射到[0,255]区间 # 将绘制的图显示在窗口 %matplotlib qt5 import cv2 import numpy as np import matplotlib.pyplot as plt img = cv2.imread(r"image\boat.bmp",cv2.IMREAD_GRAYSCALE) # 傅里叶变换 f = np.fft.fft2(img) fshift = np.fft.fftshift(f) # 逆傅里叶变换 ishift = np.fft.ifftshift(fshift) iimg = np.fft.ifft2(ishift) iimg = np.abs(iimg) plt.subplot(1,2,1) plt.imshow(img,cmap=plt.cm.gray) plt.title("original") plt.axis("off") plt.subplot(1,2,2) plt.imshow(iimg,cmap=plt.cm.gray) plt.title("iimg") plt.axis("off") plt.show 将一副图像,进行傅里叶变换和逆傅里叶变换后,进行对比(一样的) 实例:通过numpy实现高通滤波,保留图像的边缘信息 获取图像的形状rows,cols = img.shape获取图像的中心点crow,ccol = int(rows/2),int(cols/2)将频谱图像的中心区域(低频区域)设置为0(黑色)fshift[crow-30:crow+30,ccol-30:ccol+30] = 0 # 将绘制的图显示在窗口 %matplotlib qt5 import cv2 import numpy as np import matplotlib.pyplot as plt img = cv2.imread(r"image\boat.bmp",cv2.IMREAD_GRAYSCALE) # 傅里叶变换 f = np.fft.fft2(img) fshift = np.fft.fftshift(f) # 高通滤波 rows,cols = img.shape crow,ccol = int(rows/2),int(cols/2) fshift[crow-30:crow+30,ccol-30:ccol+30] = 0 # 逆傅里叶变换 ishift = np.fft.ifftshift(fshift) iimg = np.fft.ifft2(ishift) iimg = np.abs(iimg) plt.subplot(1,2,1) plt.imshow(img,cmap=plt.cm.gray) plt.title("original") plt.axis("off") plt.subplot(1,2,2) plt.imshow(iimg,cmap=plt.cm.gray) plt.title("iimg") plt.axis("off") plt.show 使用numpy实现高通滤波的实验结果: 二、opencv实现傅里叶变换和逆傅里叶变换 1.opencv实现傅里叶变换 返回结果 = cv2.dft(原始图像,转换标识)1> 返回结果:是双通道的,第一个通道是结果的实数部分,第二个通道是结果的虚数部分2> 原始图像:输入图像要首先转换成np.float32(img)格式3> 转换标识:flags = cv2.DFT_COMPLEX_OUTPUT,输出一个复数阵列numpy.fft.fftshift将零频率分量移到频谱中心(将左上角的低频区域,移到中心位置)调整频谱的范围,将上面频谱图像的复数数组,转换为可以显示的灰度图像(映射到[0,255]区间)返回值 = 20*np.log(cv2.magnitude(参数1,参数2))1> 参数1:浮点型X坐标值,也就是实部2> 参数2:浮点型Y坐标值,也就是虚部 # 将绘制的图显示在窗口 %matplotlib qt5 import cv2 import numpy as np import matplotlib.pyplot as plt img = cv2.imread(r"image\lena.bmp",cv2.IMREAD_GRAYSCALE) # 傅里叶变换 dft = cv2.dft(np.float32(img),flags = cv2.DFT_COMPLEX_OUTPUT) # 移动中心位置 dftShift = np.fft.fftshift(dft) # 调整频谱的范围 result = 20*np.log(cv2.magnitude(dftShift[:,:,0],dftShift[:,:,1])) plt.subplot(1,2,1) plt.imshow(img,cmap=plt.cm.gray) plt.title("original") plt.axis("off") plt.subplot(1,2,2) plt.imshow(result,cmap=plt.cm.gray) plt.title("result") plt.axis("off") plt.show 傅里叶变换的频谱图像: 2.opencv实现逆傅里叶变换返回结果 = cv2.idft(原始数据)1> 返回结果:取决于原始数据的类型和大小2> 原始数据:实数或者复数均可numpy.fft.ifftshiftfftshift函数的逆函数,将中心位置的低频,重新移到左上角调整频谱的范围,映射到[0,255]区间返回值 = cv2.magnitude(参数1,参数2)1> 参数1:浮点型X坐标值,也就是实部2> 参数2:浮点型Y坐标值,也就是虚部 # 将绘制的图显示在窗口 %matplotlib qt5 import cv2 import numpy as np import matplotlib.pyplot as plt img = cv2.imread(r"image\lena.bmp",cv2.IMREAD_GRAYSCALE) # 傅里叶变换 dft = cv2.dft(np.float32(img),flags = cv2.DFT_COMPLEX_OUTPUT) dftShift = np.fft.fftshift(dft) # 逆傅里叶变换 ishift = np.fft.ifftshift(dftShift) iimg = cv2.idft(ishift) iimg = cv2.magnitude(iimg[:,:,0],iimg[:,:,1]) plt.subplot(1,2,1) plt.imshow(img,cmap=plt.cm.gray) plt.title("original") plt.axis("off") plt.subplot(1,2,2) plt.imshow(iimg,cmap=plt.cm.gray) plt.title("inverse") plt.axis("off") plt.show 将一副图像,进行傅里叶变换和逆傅里叶变换后,进行对比(一样的) 实例:通过opencv实现低通滤波,模糊一副图像