从glReadPixels()转换数据到OpenCV::Mat

25
我想使用glReadPixels()从OpenGL动画中获取每个帧,并将数据转换为OpenCV::Mat。我知道glReadPixels()按行从下往上,从左到右获取数据。然而,OpenCV以不同的方式存储数据。是否有人知道任何库或教程/示例可以帮助我在C ++中将数据从glReadPixels转换为OpenCV:Mat?
 OpenGL frame      ----------------------->        CV::Mat

Data from left to right,                    Data from left to right,
bottom to top.                              top to bottom.
2个回答

52

首先,我们创建一个空的(或未初始化的)cv::Mat,以便直接读取我们的数据。这可以在启动时完成,但另一方面,当图像已经具有匹配的大小和类型时,cv::Mat::create实际上并不会花费太多时间。类型取决于您的需求,通常是像CV_8UC3这样的24位彩色图像。

cv::Mat img(height, width, CV_8UC3);

或者

img.create(height, width, CV_8UC3);

接下来你需要考虑的是cv::Mat不一定按行连续存储图像。每一行可能会有一个小的填充值,以使行对齐到4个字节(或8个字节?)。因此,您需要调整像素存储模式:

//use fast 4-byte alignment (default anyway) if possible
glPixelStorei(GL_PACK_ALIGNMENT, (img.step & 3) ? 1 : 4);

//set length of one complete row in destination data (doesn't need to equal img.cols)
glPixelStorei(GL_PACK_ROW_LENGTH, img.step/img.elemSize());

接下来,矩阵的类型影响了glReadPixels的格式和类型参数。如果您想要彩色图像,您需要记住OpenCV通常以BGR顺序存储颜色值,因此您需要使用GL_BGR(A)(这是在OpenGL 1.2中添加的)而不是GL_RGB(A)。对于单个分量图像,使用GL_LUMINANCE(它将各个颜色分量相加)或GL_REDGL_GREEN等(以获取单个分量)。因此,针对我们的CV_8UC3图像,将其直接读取到cv::Mat的最终调用如下:

glReadPixels(0, 0, img.cols, img.rows, GL_BGR, GL_UNSIGNED_BYTE, img.data);

最后,OpenCV 将图像储存为从上至下的形式。因此,在获取图像后可能需要将其翻转或者在一开始就在 OpenGL 中呈现翻转(这可以通过调整投影矩阵实现,但在这种情况下要注意三角形的方向)。要垂直翻转 cv::Mat,您可以使用 cv::flip

cv::flip(img, flipped, 0);

因此要记住OpenCV:

  • 从上到下,从左到右存储图像
  • 以BGR顺序存储彩色图像
  • 可能不会紧密地存储图像行

1
@Jav_Rock 它是在OpenGL 1.2中引入的。因此,如果您的硬件和驱动程序支持它(这非常可能,除非您20年前购买了显卡),您只需要定义此符号,通过包含一个相当新的glext.h或者首先使用扩展管理库(如GLEW),它应该带有自己的头文件和常量定义。无论如何,一旦您想要使用任何高于1.1的功能(例如PBOs,如果正确使用可以加速读取性能),这将是必要的。 - Christian Rau
1
是的,glext.h 绝对解决了这个问题。现在我已经用正确的颜色使一切正常工作了。非常感谢。 - Jav_Rock
1
这条评论仍然非常有用,如果可以的话,我会给它点赞20次。 - Jav_Rock
@manatttta 如果你支持GL 1.2,那么它应该被定义。你确定图像格式匹配了吗? - Christian Rau
@ChristianRau 我必须调用wglMakeCurrent(),因为我在Windows上使用MFC。此外,我还必须使用GL_BGR_EXT而不是GL_BGR。 - manatttta
显示剩余5条评论

1
unsigned char* getPixelData( int x1, int y1, int x2, int y2 )
{
    int y_low, y_hi;
    int x_low, x_hi;

    if ( y1 < y2 )
    {
        y_low = y1;
        y_hi  = y2;
    }
    else
    {
        y_low = y2;
        y_hi  = y1;
    }

    if ( x1 < x2 )
    {
        x_low = x1;
        x_hi  = x2;
    }
    else
    {
        x_low = x2;
        x_hi  = x1;
    }

    while ( glGetError() != GL_NO_ERROR )
    {
        ;
    }

    glReadBuffer( GL_BACK_LEFT );

    glDisable( GL_TEXTURE_2D );

    glPixelStorei( GL_PACK_ALIGNMENT, 1 );

    unsigned char *data = new unsigned char[ ( x_hi - x_low + 1 ) * ( y_hi - y_low + 1 ) * 3 ];

    glReadPixels( x_low, y_low, x_hi-x_low+1, y_hi-y_low+1, GL_RGB, GL_UNSIGNED_BYTE, data );

    if ( glGetError() != GL_NO_ERROR )
    {
        delete[] data;
        return 0;
    }
    else
    {
        return data;
    }
}

use:

CvSize size = cvSize( 320, 240 );

unsigned char *pixel_buf = getPixelData( 0, 0, size.width - 1, size.height - 1 );

if ( pixel_buf == 0 )
    return 0;

IplImage *result = cvCreateImage( size, IPL_DEPTH_8U, 3 );
memcpy( result->imageData, pixel_buf, size.width * size.height * 3 );
delete[] pixel_buf;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接