I wanted for follow up on this. The bug is part of the Image Capture Example script. To fix the ghosting issue, call PreCaptureAEAWB()
before CaptureImage()
, then make sure that AWAWB is complete using the meta data . I have linked the updated script below incase anyone references this thread in the future
Async Camera Capture Script
using System.Collections;
using UnityEngine;
using UnityEngine.XR.MagicLeap;
using static UnityEngine.XR.MagicLeap.MLCameraBase.Metadata;
using Debug = UnityEngine.Debug;
/// <summary>
/// This script provides an example of capturing images using the Magic Leap 2's Main Camera stream and Magic Leap 2 Camera APIs
/// It handles permissions, connects to the camera, captures images at regular intervals, and sends the result data to the Camera Capture visualizer.
/// </summary>
public class ImageCaptureExample : MonoBehaviour
{
[SerializeField, Tooltip("The renderer to show the camera capture on JPEG format")]
private Renderer _screenRendererJPEG = null;
// Indicates if the camera is connected
private bool isCameraConnected;
// Reference to the MLCamera object that will access the device's camera
private MLCamera colorCamera;
// Indicates if the camera device is available
private bool cameraDeviceAvailable;
// Indicates if an image is currently being captured
private bool isCapturingImage;
// Reference to the MLPermissions.Callbacks object that will handle the permission requests and responses
private readonly MLPermissions.Callbacks permissionCallbacks = new MLPermissions.Callbacks();
// Used to display the JPEG image.
private Texture2D imageTexture;
// Register the permission callbacks in the Awake method
private void Awake()
{
permissionCallbacks.OnPermissionGranted += OnPermissionGranted;
permissionCallbacks.OnPermissionDenied += OnPermissionDenied;
permissionCallbacks.OnPermissionDeniedAndDontAskAgain += OnPermissionDenied;
}
// Request the camera permission in the Start method
void Start()
{
MLResult result = MLPermissions.RequestPermission(MLPermission.Camera, permissionCallbacks);
if (!result.IsOk)
{
Debug.LogErrorFormat("Error: ImageCaptureExample failed to get requested permissions, disabling script. Reason: {0}", result);
enabled = false;
}
}
/// <summary>
/// Stop the camera, unregister callbacks, and stop input and permissions APIs.
/// </summary>
void OnDisable()
{
permissionCallbacks.OnPermissionGranted -= OnPermissionGranted;
permissionCallbacks.OnPermissionDenied -= OnPermissionDenied;
permissionCallbacks.OnPermissionDeniedAndDontAskAgain -= OnPermissionDenied;
if (colorCamera != null && isCameraConnected)
{
DisableMLCamera();
}
}
// Handle the permission denied event by logging an error message
private void OnPermissionDenied(string permission)
{
MLPluginLog.Error($"{permission} denied, test won't function.");
}
// Handle the permission granted event by starting two coroutines:
// one to enable the camera and one to capture images in a loop
private void OnPermissionGranted(string permission)
{
StartCoroutine(EnableMLCamera());
StartCoroutine(CaptureImagesLoop());
}
// Define a coroutine that will enable the camera by checking its availability,
// creating and connecting it, and preparing it for capture
private IEnumerator EnableMLCamera()
{
// Loop until the camera device is available
while (!cameraDeviceAvailable)
{
MLResult result = MLCamera.GetDeviceAvailabilityStatus(MLCamera.Identifier.Main, out cameraDeviceAvailable);
if (!(result.IsOk && cameraDeviceAvailable))
{
// Wait until camera device is available
yield return new WaitForSeconds(1.0f);
}
}
Debug.Log("Camera device available.");
// Create and connect the camera with a context that enables video stabilization and camera only capture
ConnectCamera();
// Wait until the camera is connected since this script uses the async "CreateAndConnectAsync" Method to connect to the camera.
while (!isCameraConnected)
{
yield return null;
}
Debug.Log("Camera device connected.");
// Prepare the camera for capture with a configuration that specifies JPEG output format, frame rate, and resolution
ConfigureAndPrepareCapture();
}
// Define a coroutine that will capture images every 3 seconds if the camera is connected and supports image capture type.
// The image is then captured async
private IEnumerator CaptureImagesLoop()
{
while (true)
{
if (isCameraConnected && !isCapturingImage)
{
if (MLCamera.IsCaptureTypeSupported(colorCamera, MLCamera.CaptureType.Image))
{
CaptureImage();
}
}
yield return new WaitForSeconds(3.0f);
}
}
// Define an async method that will create and connect the camera with a context that enables video stabilization and Video only capture
private async void ConnectCamera()
{
MLCamera.ConnectContext context = MLCamera.ConnectContext.Create();
context.EnableVideoStabilization = false;
context.Flags = MLCameraBase.ConnectFlag.CamOnly;
// Use the CreateAndConnectAsync method to create and connect the camera asynchronously
colorCamera = await MLCamera.CreateAndConnectAsync(context);
if (colorCamera != null)
{
// Register a callback for when a raw image is available after capture
colorCamera.OnRawImageAvailable += OnCaptureRawImageComplete;
isCameraConnected = true;
}
}
// Define an async method that will prepare the camera for capture with a configuration that specifies
// JPEG output format, frame rate, and resolution
private async void ConfigureAndPrepareCapture()
{
MLCamera.CaptureStreamConfig[] imageConfig = new MLCamera.CaptureStreamConfig[1]
{
new MLCamera.CaptureStreamConfig()
{
OutputFormat = MLCamera.OutputFormat.JPEG,
CaptureType = MLCamera.CaptureType.Image,
Width = 1920,
Height = 1080
}
};
MLCamera.CaptureConfig captureConfig = new MLCamera.CaptureConfig()
{
StreamConfigs = imageConfig,
CaptureFrameRate = MLCamera.CaptureFrameRate._30FPS
};
// Use the PrepareCapture method to set the capture configuration and get the metadata handle
MLResult prepareCaptureResult = colorCamera.PrepareCapture(captureConfig, out MLCamera.Metadata _);
if (!prepareCaptureResult.IsOk)
{
return;
}
}
/// <summary>
/// Disconnects the MLCamera if it was ever created or connected.
/// </summary>
private void DisableMLCamera()
{
if (colorCamera != null)
{
colorCamera.Disconnect();
// Explicitly set to false here as the disconnect was attempted.
isCameraConnected = false;
}
}
/// <summary>
/// Takes a picture async with the device's camera using the camera's CaptureImageAsync method.
/// </summary>
private async void CaptureImage()
{
// Set the flag to indicate that an image capture is in progress
isCapturingImage = true;
var aeawbResult = await colorCameraPreCaptureAE.AWBAsync();
if (!aeawbResult.IsOk)
{
Debug.LogError("Image capture failed!");
}
else
{
var result = await colorCamera.CaptureImageAsync(1);
if (!result.IsOk)
{
Debug.LogError("Image capture failed!");
}
}
// Reset the flag to indicate that image capture is complete
isCapturingImage = false;
}
/// <summary>
/// Handles the event of a new image getting captured and visualizes it with the Image Visualizer
/// </summary>
/// <param name="capturedImage">Captured frame.</param>
/// <param name="resultExtras">Results Extras.</param>
private void OnCaptureRawImageComplete(MLCamera.CameraOutput capturedImage, MLCamera.ResultExtras resultExtras, MLCamera.Metadata metadataHandle)
{
MLResult aeStateResult = metadataHandle.GetControlAEStateResultMetadata(out ControlAEState controlAEState);
MLResult awbStateResult = metadataHandle.GetControlAWBStateResultMetadata(out ControlAWBState controlAWBState);
if (aeStateResult.IsOk && awbStateResult.IsOk)
{
bool autoExposureComplete = controlAEState == MLCameraBase.Metadata.ControlAEState.Converged || controlAEState == MLCameraBase.Metadata.ControlAEState.Locked;
bool autoWhiteBalanceComplete = controlAWBState == MLCameraBase.Metadata.ControlAWBState.Converged || controlAWBState == MLCameraBase.Metadata.ControlAWBState.Locked;
if (autoExposureComplete && autoWhiteBalanceComplete)
{
// This example is configured to render JPEG images only.
if(capturedImage.Format == MLCameraBase.OutputFormat.JPEG)
{
UpdateJPGTexture(capturedImage.Planes[0], _screenRendererJPEG);
}
}
}
}
private void UpdateJPGTexture(MLCamera.PlaneInfo imagePlane, Renderer renderer)
{
if (imageTexture != null)
{
Destroy(imageTexture);
}
imageTexture = new Texture2D(8, 8);
bool status = imageTexture.LoadImage(imagePlane.Data);
if (status && (imageTexture.width != 8 && imageTexture.height != 8))
{
renderer.material.mainTexture = imageTexture;
}
}
}