How to obtain RGB frames from CameraX ImageAnalysis instead of grayscale Y plane?


CameraX ImageAnalysis only providing grayscale frames - need RGB image for Luxand FaceSDK liveness detection

I am developing a .NET MAUI Android application using CameraX and Luxand FaceSDK.

Currently, my face detection pipeline receives a grayscale image buffer and passes it to Luxand:

public List<FaceRect> ProcessFrame(byte[] gray, int width, int height, int rotationDegrees)
{
    int image;

    int rc = FSDK.LoadImageFromBuffer(
        out image,
        gray,
        width,
        height,
        width,
        FSDK.FSDK_IMAGEMODE.FSDK_IMAGE_GRAYSCALE_8BIT
    );

    ...
}

Face detection and recognition work correctly.

However, I am now implementing Luxand liveness (anti-spoofing) detection and Luxand support informed me that liveness requires a COLOR image and grayscale frames will be rejected.

My current pipeline extracts only the Y plane from CameraX and creates a grayscale buffer that is sent to:

FSDK.LoadImageFromBuffer(
    out image,
    gray,
    width,
    height,
    width,
    FSDK.FSDK_IMAGEMODE.FSDK_IMAGE_GRAYSCALE_8BIT
);

Questions:

  1. How can I get a full RGB image from CameraX ImageAnalysis?

  2. CameraX appears to provide YUV_420_888 frames. What is the recommended way to convert YUV_420_888 to RGB?

  3. After conversion, what buffer format should be passed to Luxand FaceSDK?

  4. Is there a performant solution suitable for real-time face recognition and liveness detection?

Environment:

  • .NET MAUI

  • Android

  • CameraX ImageAnalysis

  • Luxand FaceSDK

  • Real-time face recognition

camerax analyzer code:

using AndroidX.Camera.Core;
using JTClockV2.Platforms.Android.CameraX;
using System;
using Java.Nio;
using Android.App;

using Android.Util;
using ASize = Android.Util.Size;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;

namespace JTClockV2.Platforms.Android.CameraX
{
    public class FrameAnalyzer : Java.Lang.Object, ImageAnalysis.IAnalyzer
    {
        private readonly ICameraFrameListener _listener;

        public FrameAnalyzer(ICameraFrameListener listener)
        {
            _listener = listener;
        }
         ASize ImageAnalysis.IAnalyzer.DefaultTargetResolution
            => new ASize(640, 480);

        //public void Analyze(IImageProxy image)
        //{
        //    try
        //    {


        //        // var argb =YuvToRgb24( image);
        //        // var bgra = ConvertArgbToBgra(argb);

        //        //  _listener.OnFrame(argb, image.Width, image.Height);
        //        //  Log("CAMERAX", $"First byte = {image.GetPlanes()[0].Buffer.Get(0)}"); //time add
        //        int rotationDegrees = image.ImageInfo.RotationDegrees;
        //        var yPlane = image.GetPlanes()[0];
        //        var yBuffer = yPlane.Buffer;

        //        byte[] gray = new byte[image.Width * image.Height];
        //        yBuffer.Get(gray);

        //        _listener.OnFrame(gray, image.Width, image.Height, rotationDegrees);
        //    }
        //    finally
        //    {
        //        image.Close();
        //    }
        //}
        public void Analyze(IImageProxy image)
        {
            try
            {
                int width = image.Width;
                int height = image.Height;

                int rotationDegrees = image.ImageInfo.RotationDegrees;

                var yPlane = image.GetPlanes()[0];
                var yBuffer = yPlane.Buffer;
                int rowStride = yPlane.RowStride;

                byte[] gray = new byte[width * height];

                int pos = 0;

                for (int row = 0; row < height; row++)
                {
                    yBuffer.Position(row * rowStride);
                    yBuffer.Get(gray, pos, width);
                    pos += width;
                }

                // ✅ PASS ROTATION ALSO
                _listener.OnFrame(gray, width, height, rotationDegrees);
            }
            finally
            {
                image.Close();
            }
        }


        private void Log(string msg, string v)
        {
            Debug.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] 🔵 LUXAND: " + msg);

        }

        private static byte[] ConvertArgbToBgra(byte[] argb)
        {
            byte[] bgra = new byte[argb.Length];

            for (int i = 0; i < argb.Length; i += 4)
            {
                byte a = argb[i];
                byte r = argb[i + 1];
                byte g = argb[i + 2];
                byte b = argb[i + 3];

                bgra[i] = b;
                bgra[i + 1] = g;
                bgra[i + 2] = r;
                bgra[i + 3] = a;
            }

            return bgra;
        }

        private static byte[] YuvToRgb24(IImageProxy image)
        {
            int width = image.Width;
            int height = image.Height;

            var yPlane = image.GetPlanes()[0];
            var uPlane = image.GetPlanes()[1];
            var vPlane = image.GetPlanes()[2];

            ByteBuffer yBuf = yPlane.Buffer;
            ByteBuffer uBuf = uPlane.Buffer;
            ByteBuffer vBuf = vPlane.Buffer;

            int yRowStride = yPlane.RowStride;
            int uvRowStride = uPlane.RowStride;
            int uvPixelStride = uPlane.PixelStride;

            byte[] rgb = new byte[width * height * 3];
            int idx = 0;

            for (int y = 0; y < height; y++)
            {
                int yOffset = y * yRowStride;
                int uvOffset = (y / 2) * uvRowStride;

                for (int x = 0; x < width; x++)
                {
                    int Y = yBuf.Get(yOffset + x) & 0xFF;
                    int U = (uBuf.Get(uvOffset + (x / 2) * uvPixelStride) & 0xFF) - 128;
                    int V = (vBuf.Get(uvOffset + (x / 2) * uvPixelStride) & 0xFF) - 128;

                    int r = (int)(Y + 1.402 * V);
                    int g = (int)(Y - 0.344 * U - 0.714 * V);
                    int b = (int)(Y + 1.772 * U);

                    rgb[idx++] = (byte)Math.Clamp(r, 0, 255);
                    rgb[idx++] = (byte)Math.Clamp(g, 0, 255);
                    rgb[idx++] = (byte)Math.Clamp(b, 0, 255);
                }
            }
            return rgb;
        }



        // 🔥 CORE CONVERSION (YUV_420_888 → RGBA)
        private static byte[] Yuv4ToRgba(IImageProxy image)
        {
            int width = image.Width;
            int height = image.Height;

            var yPlane = image.GetPlanes()[0];
            var uPlane = image.GetPlanes()[1];
            var vPlane = image.GetPlanes()[2];

            ByteBuffer yBuffer = yPlane.Buffer;
            ByteBuffer uBuffer = uPlane.Buffer;
            ByteBuffer vBuffer = vPlane.Buffer;

            int yRowStride = yPlane.RowStride;
            int uvRowStride = uPlane.RowStride;
            int uvPixelStride = uPlane.PixelStride;

            byte[] rgba = new byte[width * height * 4];

            int index = 0;

            for (int row = 0; row < height; row++)
            {
                int yRowOffset = row * yRowStride;
                int uvRowOffset = (row / 2) * uvRowStride;

                for (int col = 0; col < width; col++)
                {
                    int yIndex = yRowOffset + col;
                    int uvIndex = uvRowOffset + (col >> 1) * uvPixelStride;

                    int y = yBuffer.Get(yIndex) & 0xFF;
                    int u = (uBuffer.Get(uvIndex) & 0xFF) - 128;
                    int v = (vBuffer.Get(uvIndex) & 0xFF) - 128;

                    // YUV → RGB conversion
                    int c = y - 16;
                    int d = u - 128;
                    int e = v - 128;

                    int r = (298 * c + 409 * e + 128) >> 8;
                    int g = (298 * c - 100 * d - 208 * e + 128) >> 8;
                    int b = (298 * c + 516 * d + 128) >> 8;

                    rgba[index++] = (byte)Clamp(r);
                    rgba[index++] = (byte)Clamp(g);
                    rgba[index++] = (byte)Clamp(b);
                    rgba[index++] = 255; // Alpha
                }
            }

            return rgba;
        }

     
        private static int Clamp(int value)
        {
            if (value < 0) return 0;
            if (value > 255) return 255;
            return value;
        }
    }
}

Any sample code or recommendations would be greatly appreciated.

2
Jun 9 at 8:25 AM
User AvatarKajal
#android#image-processing#maui#android-camera#android-camerax

No answer found for this question yet.