В коде, который вы включили, вы копируете Y канал текстуры камеры (image.Y
) в одиночный канал RGB-текстуру (TextureFormat.R8
), не делая любое преобразование.
YUV и RGB имеют три канала, но вы используете только один.
В RGB каналы обычно имеют одинаковый размер, но в YUV они часто различаются. U и V могут быть частью размера Y, а конкретная доля зависит от используемого формата.
Поскольку эта текстура исходит от камеры Android, конкретный формат должен быть Y'UV420p , который является плоским форматом, см. Страницу Википедии для полезного визуального представления из как сгруппированы значения канала:
![Single Frame YUV420](https://i.stack.imgur.com/TtIZe.png)
Структура 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