CVImageBuffer带有额外的列填充。我该如何裁剪?

时间:2011-06-30 22:09:25

标签: ios opengl-es core-graphics core-video

我有一个CVImageBuffer,其记录高度为640px,宽度为852px。每行的字节数是3456.你会注意到3456 / 852px!= 4(它类似于4.05)。经过一些检查,864将是使bytesPerRow / width = 4.0的宽度。所以,似乎每行都有额外的12px(在右边填充)。我假设这是因为这些缓冲区针对此图像没有的某些倍数进行了优化。

当我在OpenGl中渲染这个缓冲区时,它看起来很糟糕(见下文)。我注意到这个模式每71px重复一次,这是有道理的,因为如果有额外的12px那么(852 / 12px = 71)。因此,额外的12个像素似乎导致了这个问题。

如何快速摆脱这些额外的像素,然后使用这些数据读入OpenGL ES?或者更确切地说,如何通过在每一行上跳过这些额外的像素来读入OpenGL ES?

enter image description here

2 个答案:

答案 0 :(得分:4)

与高速图像处理算法一起使用的图像在每行的末尾都有一个填充,这样图像的像素或字节大小是4,8,16,32的倍数,这是很常见的, 等等。这使得优化某些算法的速度变得更加容易,特别是与SIMD上的SSE或ARM上的NEON等SIMD指令集相结合。在你的情况下,填充是12像素,这意味着Apple似乎优化了他们的算法,每行处理32个像素; 852不能被8,864分割,因此线被填充12个像素以保持32像素对准。正确的技术术语是尺寸和步幅尺寸,或者在图像,宽度和步幅宽度的情况下。宽度是每行的实际像素数据量,步幅宽度是实线大小,包括像素数据和行尾的可选填充。

标准OpenGL允许加载步幅宽度大于实际纹理宽度的纹理。这是通过相应地设置glPixelStore参数GL_PACK_ROW_LENGTH来实现的。请注意,此“跨步填充跳过”通常在驱动程序的CPU部分内实现,因此这不会在GPU上执行操作,实际上驱动程序将在将数据上载到GPU之前删除额外的填充。由于OpenGL ES设计为在可能具有非常有限的CPU资源的嵌入式设备上运行,因此从OpenGL ES中删除了该选项以保持驱动程序开发简单,即使对于非常弱的嵌入式CPU也是如此。这为您提供了四个选项来处理您的问题:

  1. 预处理纹理以使用C复制循环移除填充,跳过每行末尾的额外像素。这种实现相当缓慢但易于实现。

  2. 像选项(1)一样预处理纹理,但是使用编译器SIMD宏来使用NEON指令。这将比选项(1)快2倍,但它也更难实现,你需要一些关于NEON指令的知识以及如何使用它们来实现这一目标。

  3. 按照选项(2)的情况预处理纹理,但是使用纯装配实现。这将比选项(2)快约3倍,因此比选项(1)快6倍,但实现起来也要困难得多,因为您需要有关ARM汇编编程+ NEON指令的知识。

  4. 使用填充加载纹理并调整OpenGL的纹理坐标,使其忽略填充像素。根据纹理映射的复杂程度,这可能非常容易实现,它比上面的任何其他选项更快,唯一的缺点是你在GPU上浪费了更多的纹理内存。

  5. 我对ARM汇编编程知之甚少,甚至对NEON指令知之甚少,所以我无法真正帮助你选择(2)和(3)。我可以向你展示选项(1)的实现,但是,我担心它可能对你的目的来说太慢了。这只留下了我过去常常使用过的最后一个选项。

    我们声明了3个变量:宽度,高度和步幅宽度。

    GLsizei width = 852;
    GLsizei height = 640;
    GLsizei strideWidth = 864;
    

    当你加载纹理数据时(假设rawData指向原始图像字节),你假装strideWidth是“实际宽度”:

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 
        strideWidth, height, 0, GL_RGB, GL_UNSIGNED_BYTE, rawData);
    

    OpenGL中的纹理坐标被标准化,这意味着左下角始终为(0.0f, 0.0f),右上角始终为(1.0f, 1.0f),无论纹理的实际像素大小如何。这两个值可以称为(x, y),但为了不将它们与顶点坐标混淆,它们被称为(s, t)

    要使OpenGL切断填充像素,您只需要将所有s坐标调整一定的因子,我们称之为SPCF(Stride Padding Cut Factor),您可以通过以下方式计算:

    float SPCF = (float)width / strideWidth;
    

    因此,您可以使用(0.35f, 0.6f)代替纹理坐标(0.35f * SPCF, 0.6f)。当然,每个渲染帧不应执行一次此计算。相反,您应该复制原始纹理坐标,通过SPCF调整所有s坐标一次,然后在渲染帧时使用这些调整后的坐标。如果您将来重新加载纹理并且SPCF已更改,请重复调整过程。在宽度等于strideWidth的情况下,该算法也可以工作,因为在这种情况下SPCF是1.0f,因此根本不会改变s坐标,这是正确的,因为没有填充要切断。

    这个技巧的缺点是纹理在你的情况下需要比其他方面需要多2.4%的内存,这也意味着通过glTexImage2D上传的纹理将慢2.4%。我想这是可以接受的,并且仍然比上面任何其他CPU密集型选项快得多。

答案 1 :(得分:0)

您可以将GL_PACK_ROW_LENGTH状态变量与GL_PACK_ALIGNMENT结合使用来控制数据的行长度。 您可以在例如的联机帮助页中查找 glPixelStorei ,或者更好地使用这里的一些图片:http://www.opengl.org/resources/features/KilgardTechniques/oglpitfall/

我猜它会是这样的:

glPixelStorei(GL_PACK_ROW_LENGTH, nPixelsPerRow);
glPixelStorei(GL_PACK_ALIGNMENT, nPadBytes);

请注意,我没有测试上面的代码。它只是作为一个提示。

欢呼声