ARCore –如何在C#中将YUV摄像机帧转换为RGB帧?

时间:2019-05-08 09:34:49

标签: c# unity3d augmented-reality arcore yuv

我想从ARCore会话中获取当前的相机图像。我正在使用Frame.CameraImage.AcquireCameraImageBytes()方法获取此图像。然后,我将此图像以Texture2D的格式加载到TextureFormat.R8中。但是纹理是红色的并且倒置。我知道ARCore使用的格式是YUV,但是我找不到将这种格式转换为RGB的方法。我该怎么办?

关于此问题有2或3个问题,但未提供解决方案。

代码如下:

CameraImageBytes image = Frame.CameraImage.AcquireCameraImageBytes();
int width = image.Width;
int height = image.Height;
int size = width*height;


Texture2D texture = new Texture2D(width, height, TextureFormat.R8, false, false);
byte[] m_EdgeImage = new byte[size];

System.Runtime.InteropServices.Marshal.Copy(image.Y, m_EdgeImage, 0, size);

texture.LoadRawTextureData(m_EdgeImage);
texture.Apply();

结果图片:

enter image description here

1 个答案:

答案 0 :(得分:0)

在您包含的代码中,您要将相机纹理(image.Y Y 通道复制到单通道 RGB纹理({{1 }}),而无需进行任何转换。

YUV RGB 具有三个通道,但是您仅使用一个。 在RGB中,通道通常具有相同的大小,但在YUV中,它们通常不同。 U和V可以是Y大小的分数,具体分数取决于所使用的格式。

由于此纹理来自Android相机,因此特定格式应为 Y'UV420p ,这是一种平面格式,请参见Wikipedia页面,了解useful visual representation频道的显示方式值分组: Single Frame YUV420

CameraImageBytes API结构要求您分别提取通道,然后以编程方式将它们重新组合在一起。

仅供参考,有一种简便的方法可以将已经转换为RGB相机纹理,但是只能通过着色器而不是C#代码进行访问。

假设您仍然想在C#中执行此操作,要从YUV纹理中收集所有通道,需要将UV通道与Y通道区别对待。 您必须为UV通道创建一个单独的缓冲区。在Unity-Technologies / experimental-ARInterface github repo上的issue中有一个如何执行此操作的示例:

TextureFormat.R8

此代码将生成原始纹理数据,可以将其加载到格式为//We expect 2 bytes per pixel, interleaved U/V, with 2x2 subsampling bufferSize = imageBytes.Width * imageBytes.Height / 2; cameraImage.uv = new byte[bufferSize]; //Because U an V planes are returned separately, while remote expects interleaved U/V //same as ARKit, we merge the buffers ourselves unsafe { fixed (byte* uvPtr = cameraImage.uv) { byte* UV = uvPtr; byte* U = (byte*) imageBytes.U.ToPointer(); byte* V = (byte*) imageBytes.V.ToPointer(); for (int i = 0; i < bufferSize; i+= 2) { *UV++ = *U; *UV++ = *V; U += imageBytes.UVPixelStride; V += imageBytes.UVPixelStride; } } } 的Texture2D中:

TextureFormat.RG16

现在,所有3个通道都存储在2个Texture2D中,您可以通过着色器或C#对其进行转换。

可在YUV wikipedia page上找到用于Android摄像机YUV图像的特定转换公式:

Texture2D texUVchannels = new Texture2D(imageBytes.Width / 2, imageBytes.Height / 2, TextureFormat.RG16, false, false);
texUVchannels.LoadRawTextureData(rawImageUV);
texUVchannels.Apply();

翻译为Unity着色器,该着色器将是:

void YUVImage::yuv2rgb(uint8_t yValue, uint8_t uValue, uint8_t vValue,
        uint8_t *r, uint8_t *g, uint8_t *b) const {
    int rTmp = yValue + (1.370705 * (vValue-128));
    int gTmp = yValue - (0.698001 * (vValue-128)) - (0.337633 * (uValue-128));
    int bTmp = yValue + (1.732446 * (uValue-128));
    *r = clamp(rTmp, 0, 255);
    *g = clamp(gTmp, 0, 255);
    *b = clamp(bTmp, 0, 255);
}

通过这种方式获得的纹理与来自ARCore的背景视频相比具有不同的大小,因此,如果希望它们在屏幕上匹配,则需要使用UV和来自Frame.CameraImage的其他数据。 / p>

因此,将UV传递给着色器:

float3 YUVtoRGB(float3 c)
{
    float yVal = c.x;
    float uVal = c.y;
    float vVal = c.z;

    float r = yVal + 1.370705 * (vVal - 0.5);
    float g = yVal - 0.698001 * (vVal - 0.5) - (0.337633 * (uVal - 0.5));
    float b = yVal + 1.732446 * (uVal - 0.5);

    return float3(r, g, b);
}

并在着色器中使用它们,您将需要像EdgeDetectionBackground shader中那样使用它们。

在同一着色器中,您将找到一个示例,该示例说明如何从着色器访问RGB相机图像而无需进行任何转换,这可能会使您的用例更容易。

对此有一些要求:

  • 着色器必须位于 glsl
  • 只能在OpenGL ES3中完成
  • var uvQuad = Frame.CameraImage.ImageDisplayUvs; mat.SetVector("_UvTopLeftRight", new Vector4(uvQuad.TopLeft.x, uvQuad.TopLeft.y, uvQuad.TopRight.x, uvQuad.TopRight.y)); mat.SetVector("_UvBottomLeftRight", new Vector4(uvQuad.BottomLeft.x, uvQuad.BottomLeft.y, uvQuad.BottomRight.x, uvQuad.BottomRight.y)); camera.projectionMatrix = Frame.CameraImage.GetCameraProjectionMatrix(camera.nearClipPlane, camera.farClipPlane); 扩展名需要受支持