基于ModernGL实现用OpenCV加载纹理并保存图像的问题
嘿,我来帮你搞定这两个OpenCV + ModernGL的问题!不管是用OpenCV加载图像作为纹理,还是用它保存处理后的结果,咱们都能彻底摆脱Pillow依赖,让代码跑起来顺顺当当的。
问题1:用cv2.imread加载图像为ModernGL纹理失败
你之前的代码直接把cv2加载的图像转成float32就想当纹理用,忽略了几个关键细节:
- OpenCV默认加载的是BGR格式,而ModernGL的纹理默认期望RGBA格式
- 图像的维度需要调整(从H×W×BGR转成H×W×RGBA)
- ModernGL创建纹理时需要明确指定数据格式和类型
修改后的加载代码应该是这样的:
# 替换原来的纹理加载代码 img = cv2.imread("test6.png", cv2.IMREAD_UNCHANGED) # 带alpha通道的图用这个,普通图用IMREAD_COLOR # 处理通道:BGR转RGBA(如果是普通图,先加alpha通道) if img.shape[2] == 3: img = cv2.cvtColor(img, cv2.COLOR_BGR2RGBA) else: img = cv2.cvtColor(img, cv2.COLOR_BGRA2RGBA) # 转成float32,因为ModernGL常用f4格式,记得归一化到0-1范围! img_float = img.astype('f4') / 255.0 # 创建ModernGL纹理 self.texture = self.ctx.texture( size=(img.shape[1], img.shape[0]), # 注意是W×H,OpenCV是H×W components=4, data=img_float.tobytes(), ) # 可选:设置纹理过滤参数,避免拉伸模糊 self.texture.filter = (moderngl.LINEAR, moderngl.LINEAR)
核心注意点:
- 用
cv2.IMREAD_UNCHANGED可以保留图像的alpha通道(如果有的话) - 必须把BGR转成RGBA,不然颜色会完全错乱
- 图像数据要归一化到0-1的float范围,和ModernGL的纹理格式匹配
- 创建纹理时要传入正确的宽高(OpenCV是H×W,ModernGL需要W×H)
问题2:用cv2.imwrite保存FBO结果无输出
FBO读取到的是RGBA格式的float32数据,而且OpenGL的纹理坐标y轴是翻转的(和OpenCV的图像坐标相反),直接存肯定不行。咱们需要做这几步转换:
- 把float32数据转成uint8(0-255范围)
- 把RGBA转成BGRA(或者BGR,如果不需要alpha的话)
- 翻转y轴,修正坐标方向
修改后的write方法代码:
def write(self, name): # 从FBO读取RGBA float32数据 raw_data = self.fbo.read(components=4, dtype='f4') # 转成numpy数组,形状为 (H*W*4,) buf = np.frombuffer(raw_data, dtype='f4') # 重塑为H×W×4的RGBA图像(FBO的size是W,H,所以要对应H,W,4) img = buf.reshape((self.size[1], self.size[0], 4)) # 1. 归一化到0-255,转成uint8 img = (img * 255).astype(np.uint8) # 2. RGBA转BGRA(OpenCV的带alpha格式),如果不需要alpha就转成BGR img = cv2.cvtColor(img, cv2.COLOR_RGBA2BGRA) # 3. 翻转y轴,修正OpenGL和OpenCV的坐标差异 img = cv2.flip(img, 0) # 保存图像 cv2.imwrite(name, img)
核心注意点:
- FBO的size是(W, H),所以reshape的时候要对应成(H, W, 4)
- 必须把float的0-1值转成uint8的0-255,不然cv2保存的会是全黑的图
- 翻转y轴是因为OpenGL的原点在左下角,OpenCV在左上角
- 如果你的处理不需要alpha通道,可以把
COLOR_RGBA2BGRA改成COLOR_RGBA2BGR,保存普通RGB图
完整修改后的代码
把上面的修改整合到你的代码里,就是这样:
from pathlib import Path from array import array import cv2 import numpy as np import moderngl import moderngl_window class ImageProcessing(moderngl_window.WindowConfig): window_size = 3840 // 2, 2160 // 2 resource_dir = Path(__file__).parent.resolve() def __init__(self, **kwargs): super().__init__(**kwargs) self.image_processing = ImageTransformer(self.ctx, self.window_size) # 用OpenCV加载图像并创建ModernGL纹理 img = cv2.imread("test6.png", cv2.IMREAD_UNCHANGED) # 处理通道格式 if img.shape[2] == 3: img = cv2.cvtColor(img, cv2.COLOR_BGR2RGBA) else: img = cv2.cvtColor(img, cv2.COLOR_BGRA2RGBA) # 转成归一化的float32 img_float = img.astype('f4') / 255.0 # 创建纹理 self.texture = self.ctx.texture( size=(img.shape[1], img.shape[0]), components=4, data=img_float.tobytes(), ) self.texture.filter = (moderngl.LINEAR, moderngl.LINEAR) def render(self, time, frame_time): # 窗口显示 self.image_processing.render(self.texture, target=self.ctx.screen) # 离屏渲染到FBO self.image_processing.render(self.texture) self.image_processing.write("output.png") class ImageTransformer: def __init__(self, ctx, size, program=None): self.ctx = ctx self.size = size self.program = None self.fbo = self.ctx.framebuffer( color_attachments=[self.ctx.texture(self.size, 4)] ) # 创建默认着色器程序 if not program: self.program = self.ctx.program( vertex_shader=""" #version 330 in vec2 in_position; in vec2 in_uv; out vec2 uv; void main() { gl_Position = vec4(in_position, 0.0, 1.0); uv = in_uv; } """, fragment_shader = """ #version 330 uniform sampler2D image; in vec2 uv; out vec4 out_color; void main() { vec4 color = texture(image, uv); // 这里是你的图像处理逻辑(保留原红色通道) out_color = vec4(color.r, 0, 0, color.a); } """, ) # 全屏四边形顶点数据 self.vertices = self.ctx.buffer( array( 'f', [ -1, 1, 0, 1, -1, -1, 0, 0, 1, 1, 1, 1, 1, -1, 1, 0, ] ) ) self.quad = self.ctx.vertex_array( self.program, [ (self.vertices, '2f 2f', 'in_position', 'in_uv'), ] ) def render(self, texture, target=None): if target: target.use() else: self.fbo.use() texture.use(0) self.quad.render(mode=moderngl.TRIANGLE_STRIP) def write(self, name): # 读取FBO数据并转换为OpenCV格式 raw_data = self.fbo.read(components=4, dtype='f4') buf = np.frombuffer(raw_data, dtype='f4') img = buf.reshape((self.size[1], self.size[0], 4)) # 转换为uint8并调整颜色通道 img = (img * 255).astype(np.uint8) img = cv2.cvtColor(img, cv2.COLOR_RGBA2BGRA) # 翻转y轴修正坐标 img = cv2.flip(img, 0) # 保存图像 cv2.imwrite(name, img) if __name__ == "__main__": ImageProcessing.run()
现在运行这个代码,应该就能正常用OpenCV加载纹理,并且保存出正确的图像了,完全不需要Pillow!
内容的提问来源于stack exchange,提问作者Noah




