我想从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();
结果图片:
答案 0 :(得分:0)
在您包含的代码中,您要将相机纹理(image.Y
的 Y 通道复制到单通道 RGB纹理({{1 }}),而无需进行任何转换。
YUV 和 RGB 具有三个通道,但是您仅使用一个。 在RGB中,通道通常具有相同的大小,但在YUV中,它们通常不同。 U和V可以是Y大小的分数,具体分数取决于所使用的格式。
由于此纹理来自Android相机,因此特定格式应为 Y'UV420p ,这是一种平面格式,请参见Wikipedia页面,了解useful visual representation频道的显示方式值分组:
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相机图像而无需进行任何转换,这可能会使您的用例更容易。
对此有一些要求:
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);
扩展名需要受支持