ARCore - Как конвертировать кадр камеры YUV в кадр RGB в C #? - PullRequest
1 голос
/ 08 мая 2019

Я хочу получить текущее изображение с камеры из сеанса 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 голосов
/ 25 мая 2019

В коде, который вы включили, вы копируете Y канал текстуры камеры (image.Y) в одиночный канал RGB-текстуру (TextureFormat.R8), не делая любое преобразование.

YUV и RGB имеют три канала, но вы используете только один. В RGB каналы обычно имеют одинаковый размер, но в YUV они часто различаются. U и V могут быть частью размера Y, а конкретная доля зависит от используемого формата.

Поскольку эта текстура исходит от камеры Android, конкретный формат должен быть Y'UV420p , который является плоским форматом, см. Страницу Википедии для полезного визуального представления из как сгруппированы значения канала: Single Frame YUV420

Структура API CameraImageBytes требует, чтобы вы извлекали каналы по отдельности, а затем снова соединяли их программно.

К вашему сведению, существует более простой способ , чтобы получить уже преобразованную в RGB текстуру камеры, но получить доступ к ней можно только через шейдер, а не код C #.

Предполагая, что вы все еще хотите сделать это в C #, чтобы собрать все каналы из текстуры YUV, вам нужно обрабатывать УФ-каналы иначе, чем Y-канал. Вы должны создать отдельный буфер для УФ-каналов. Вот пример того, как это сделать, в выпуске в репозитории github Unity-Technologies / экспериментальный-ARInterface:

//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:

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

Теперь, когда у вас есть все 3 канала, сохраненные в 2 Texture2D, вы можете конвертировать их либо через шейдер, либо в C #.

Конкретную формулу преобразования для использования YUV-изображения камеры Android можно найти на странице YUV wikipedia :

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);
}

переведено в шейдер Unity, который будет:

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);
}

Текстура, полученная таким способом, имеет другой размер по сравнению с фоновым видео, поступающим из ARCore, поэтому, если вы хотите, чтобы они соответствовали на экране, вам нужно будет использовать UV и другие данные, поступающие из Frame.CameraImage.

Итак, чтобы передать UV в шейдер:

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);

и чтобы использовать их в шейдере, вам нужно перетянуть их, как в EdgeDetectionBackground .

В этом же шейдере вы найдете пример того, как получить доступ к изображению с камеры RGB из шейдера, без необходимости выполнять какое-либо преобразование, которое может оказаться проще для вашего варианта использования.

Для этого есть несколько требований:

  • шейдер должен быть в glsl
  • это можно сделать только в OpenGL ES3
  • должно поддерживаться расширение GL_OES_EGL_image_external_essl3
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...