【问题标题】:OpenCV image loading for OpenGL TextureOpenGL 纹理的 OpenCV 图像加载
【发布时间】:2013-05-24 11:22:36
【问题描述】:

我想用 OpenCV 作为 OpenGL 纹理加载图像(jpg 和 png)。

这是我将图像加载到 OpenGL 的方式:

glEnable(GL_TEXTURE_2D);
  textureData = loadTextureData("textures/trashbin.png");
  cv::Mat image = cv::imread("textures/trashbin.png");
  if(image.empty()){
      std::cout << "image empty" << std::endl;
  }else{
      glGenTextures( 1, &textureTrash );
      glBindTexture( GL_TEXTURE_2D, textureTrash );
      glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
      glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
      glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S , GL_REPEAT );
      glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
      glTexImage2D(GL_TEXTURE_2D,0,3,image.cols, image.rows,0,GL_RGB,GL_UNSIGNED_BYTE, image.data);
  }

图片已加载,因为 "image.empty" 总是返回 false

这是我使用创建的纹理渲染场景的方法:

  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D, textureTrash);
  glm_ModelViewMatrix.top() = glm::translate(glm_ModelViewMatrix.top(),0.0f,-13.0f,-10.0f);
  glUniformMatrix4fv(uniformLocations["modelview"], 1, false, glm::value_ptr(glm_ModelViewMatrix.top()));

  std::cout << "textureShaderID: " << glGetUniformLocation(shaderProgram,"texture") << std::endl;

  glUniform1i(glGetUniformLocation(shaderProgram,"texture"), 0);
  objLoader->getMeshObj("trashbin")->render();

最后是我想将纹理应用到几何体的片段着色器

#version 330
in vec2 tCoord;

// texture //
// TODO: set up a texture uniform //
uniform sampler2D texture;

// this defines the fragment output //
out vec4 color;

void main() {
  // TODO: get the texel value from your texture at the position of the passed texture coordinate //
  color = texture2D(texture, tCoord);
}

纹理坐标来自一个顶点缓冲区对象,并从 .obj 文件中正确设置。当我将颜色设置为例如时,我也可以在场景中看到对象。片段着色器中的红色,或 vec4(tCoord,0,1);然后对象以不同的颜色着色。

不幸的是,当我想应用纹理时,屏幕一直是黑的……有人可以帮我告诉我为什么一直黑吗?

【问题讨论】:

    标签: c++ opengl opencv textures


    【解决方案1】:

    仅查看纹理加载代码,您忽略了许多关于 OpenCV 如何在内存中布局图像的考虑。我已经在this answer 中解释了相反的方向(glGetTexImage 进入 OpenCV 图像),但将在此处概括为 CV-GL 方向:

    首先,OpenCV 不一定会紧密存储图像行,但可能会将它们与某些字节边界对齐(不知道多少,至少 4 个,但可能 8 个或更多?)。如果幸运的话,它将使用 4 字节对齐,并且 GL 也设置为 4 字节对齐的默认像素存储模式。但为了安全起见,最好手动调整像素存储模式:

    //use fast 4-byte alignment (default anyway) if possible
    glPixelStorei(GL_UNPACK_ALIGNMENT, (image.step & 3) ? 1 : 4);
    
    //set length of one complete row in data (doesn't need to equal image.cols)
    glPixelStorei(GL_UNPACK_ROW_LENGTH, image.step/image.elemSize());
    

    然后你必须考虑 OpenCV 从上到下存储图像,而 GL 使用从下到上的事实。这可以通过适当地镜像 t-texture 坐标来解决(可能直接在着色器中),但您也可以在上传之前翻转图像:

    cv::flip(image, flipped, 0);
    image = flipped;              //maybe just cv::flip(image, image, 0)?
    

    最后但并非最不重要的一点是,OpenCV 以 BGR 格式存储彩色图像,因此将其上传为 RGB 会扭曲颜色。所以在glTexImage2D 中使用GL_BGR(需要OpenGL 1.2,但谁没有呢?)。

    这些可能不是您问题的完整解决方案(因为我认为这些错误应该导致图像失真而不是黑色),但它们绝对是需要注意的问题。

    编辑:您的片段着色器是否真的成功编译(在使用纹理的完整版本中)?我问是因为在 GLSL 3.30 中您使用的词 texture 也是内置函数的名称(实际上应该使用它而不是已弃用的 texture2D 函数),所以也许编译器有一些名称解析问题(也许这个错误在你的简化着色器中被忽略了,因为整个统一将被优化掉,并且许多 GLSL 编译器被认为不是严格符合标准的)。所以试着给那个采样器统一起一个不同的名字。

    【讨论】:

    • 我只在使用 GL_UNSIGNED_BYTE 时需要 glPixelStorei,图像宽度不能被 4 整除。当我使用 GL_FLOAT 时,或者当我使用可被 4 整除的宽度时 - 不需要。
    • 深入挖掘后,我意识到答案的某些部分具有误导性。 4 字节对齐没有什么“快”的。在任何情况下,如果尚未对齐,您肯定无法通过在 openGL 中设置对齐来加速任何事情。这个参数只告诉 openGL 在哪里期望下一行像素。如果您的图像紧凑,您应该使用 1。此外,仅当您使用图像的子集(感兴趣的 roi - rect)时,或者由于某种原因行之间的填充非常大时,才需要行长度.否则不需要。
    • @DavidRefaeli 好吧,您的对齐方式当然必须与您的数据相匹配。当您的数据不是 4 对齐时,仅将其设置为 4 是没有意义的。不过,如果这就是答案所暗示的,我会感到惊讶(实际上它直接依赖于数据)。但是,使用 4 对齐数据(并告诉 GL)可能会更快地复制,即使这不是设置它的主要原因。不过,您对行长设置的评论是有道理的。
    • 感谢您的回答!它解决了我在抽取滤波器使行长度不能被 4 整除后的图像失真,行 glPixelStorei(GL_UNPACK_ALIGNMENT, (image.step & 3) ? 1 : 4);为我修好了。干杯!
    【解决方案2】:

    好的,这是我的工作解决方案 - 基于“Christan Rau”的想法 - 谢谢!

    cv::Mat image = cv::imread("textures/trashbin.png");
      //cv::Mat flipped;
      //cv::flip(image, flipped, 0);
      //image = flipped;
      if(image.empty()){
          std::cout << "image empty" << std::endl;
      }else{
          cv::flip(image, image, 0);
          glGenTextures(1, &textureTrash);
          glBindTexture(GL_TEXTURE_2D, textureTrash);
    
          glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
          glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    
            // Set texture clamping method
          glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
          glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    
    
          glTexImage2D(GL_TEXTURE_2D,     // Type of texture
                         0,                 // Pyramid level (for mip-mapping) - 0 is the top level
                         GL_RGB,            // Internal colour format to convert to
                         image.cols,          // Image width  i.e. 640 for Kinect in standard mode
                         image.rows,          // Image height i.e. 480 for Kinect in standard mode
                         0,                 // Border width in pixels (can either be 1 or 0)
                         GL_BGR, // Input image format (i.e. GL_RGB, GL_RGBA, GL_BGR etc.)
                         GL_UNSIGNED_BYTE,  // Image data type
                         image.ptr());        // The actual image data itself
    
          glGenerateMipmap(GL_TEXTURE_2D);
      }
    

    【讨论】:

    • 那么它是这样工作的吗?顺便问一下,您为什么要更改过滤器和钳位模式?为什么不使用 mipmapping 过滤器生成 mipmap?
    • 是的,它正在以这种方式工作 - 当然不是最好的解决方案,并且有很大的改进潜力 - 但它正在工作 - 并且 mipmaps 将作为下一个功能实现,我只是忘记了删除线
    • 我正在使用你的方法来显示图像,但是我在使用 image.ptr() 时遇到了读取访问冲突。请在此处查看我的帖子:stackoverflow.com/questions/45013214/…
    猜你喜欢
    • 1970-01-01
    • 2013-10-13
    • 1970-01-01
    • 1970-01-01
    • 2015-07-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多