Procesar fotogramas multimedia con MediaFrameReader
En este artículo se muestra cómo usar mediaFrameReader con MediaCapture para obtener fotogramas multimedia de uno o varios orígenes disponibles, como cámaras de color, profundidad e infrarrojos, dispositivos de audio o incluso orígenes de fotogramas personalizados, como los que producen fotogramas de seguimiento esquelético. Esta característica se diseñó para que la usen las aplicaciones que realizan procesamiento en tiempo real de fotogramas multimedia, como las aplicaciones de realidad aumentada y las de cámara con reconocimiento de profundidad.
Si está interesado en simplemente capturar vídeos o fotos, como una aplicación de fotografía típica, es probable que quiera usar una de las otras técnicas de captura compatibles con MediaCapture. Para obtener una lista de las técnicas de captura multimedia disponibles y artículos que muestran cómo usarlos, consulte Cámara.
Nota:
Las características descritas en este artículo solo están disponibles a partir de Windows 10, versión 1607.
Nota:
Hay un ejemplo de aplicación universal de Windows que muestra el uso de MediaFrameReader para mostrar fotogramas de diferentes orígenes de fotogramas, como cámaras de color, profundidad e infrarrojos. Para obtener más información, consulte Ejemplo de fotogramas de cámara.
Nota:
En Windows 10, versión 1803, se introdujo un nuevo conjunto de API para usar MediaFrameReader con datos de audio. Para obtener más información, consulte Proceso de secuencias de audio con MediaFrameReader.
Configurar tu proyecto
Al igual que con cualquier aplicación que use MediaCapture, debes declarar que la aplicación usa la funcionalidad de cámara web antes de intentar acceder a cualquier dispositivo de cámara. Si la aplicación se capturará desde un dispositivo de audio, también debe declarar la funcionalidad del dispositivo de micrófono .
Adición de funcionalidades al manifiesto de la aplicación
- En Microsoft Visual Studio, en el Explorador de soluciones, abra el diseñador para el manifiesto de aplicación haciendo doble clic en el elemento package.appxmanifest.
- Seleccione la pestaña Funcionalidades.
- Active la casilla Cámara web y la casilla Micrófono.
- Para obtener acceso a la biblioteca de imágenes y vídeos, active las casillas Biblioteca de imágenes y Biblioteca de vídeos.
El código de ejemplo de este artículo usa las API de los siguientes espacios de nombres, además de los incluidos en la plantilla de proyecto predeterminada.
using Windows.Media.Capture.Frames;
using Windows.Devices.Enumeration;
using Windows.Media.Capture;
using Windows.UI.Xaml.Media.Imaging;
using Windows.Media.MediaProperties;
using Windows.Graphics.Imaging;
using System.Threading;
using Windows.UI.Core;
using System.Threading.Tasks;
using Windows.Media.Core;
using System.Diagnostics;
using Windows.Media;
using Windows.Media.Devices;
using Windows.Media.Audio;
Selección de orígenes de fotogramas y grupos de orígenes de fotogramas
Muchas aplicaciones que procesan fotogramas multimedia necesitan obtener fotogramas de varios orígenes a la vez, como las cámaras de color y profundidad de un dispositivo. El objeto MediaFrameSourceGroup representa un conjunto de orígenes de fotogramas multimedia que se pueden usar simultáneamente. Llame al método estático MediaFrameSourceGroup.FindAllAsync para obtener una lista de todos los grupos de orígenes de fotogramas admitidos por el dispositivo actual.
var frameSourceGroups = await MediaFrameSourceGroup.FindAllAsync();
También puede crear un DeviceWatcher mediante DeviceInformation.CreateWatcher y el valor devuelto de MediaFrameSourceGroup.GetDeviceSelector para recibir notificaciones cuando los grupos de origen de fotogramas disponibles en el dispositivo cambian, como cuando se conecta una cámara externa. Para obtener más información, consulte Enumerar dispositivos.
Un objeto MediaFrameSourceGroup tiene una colección de objetos MediaFrameSourceInfo que describen los orígenes de marco incluidos en el grupo. Después de recuperar los grupos de origen de fotogramas disponibles en el dispositivo, puede seleccionar el grupo que expone los orígenes de fotogramas que le interesan.
En el ejemplo siguiente se muestra la manera más sencilla de seleccionar un grupo de origen de fotogramas. Este código simplemente recorre en bucle todos los grupos disponibles y, a continuación, recorre en bucle cada elemento de la colección SourceInfos. Cada mediaFrameSourceInfo se comprueba para ver si admite las características que buscamos. En este caso, la propiedad MediaStreamType se comprueba para el valor VideoPreview, lo que significa que el dispositivo proporciona una secuencia de vista previa de vídeo y la propiedad SourceKind se comprueba para el valor Color, lo que indica que el origen proporciona fotogramas de color.
var frameSourceGroups = await MediaFrameSourceGroup.FindAllAsync();
MediaFrameSourceGroup selectedGroup = null;
MediaFrameSourceInfo colorSourceInfo = null;
foreach (var sourceGroup in frameSourceGroups)
{
foreach (var sourceInfo in sourceGroup.SourceInfos)
{
if (sourceInfo.MediaStreamType == MediaStreamType.VideoPreview
&& sourceInfo.SourceKind == MediaFrameSourceKind.Color)
{
colorSourceInfo = sourceInfo;
break;
}
}
if (colorSourceInfo != null)
{
selectedGroup = sourceGroup;
break;
}
}
Este método para identificar el grupo de origen de fotogramas deseado y los orígenes de fotogramas funciona en casos sencillos, pero si desea seleccionar orígenes de fotogramas en función de criterios más complejos, puede resultar complicado rápidamente. Otro método consiste en usar la sintaxis linq y los objetos anónimos para realizar la selección. En el ejemplo siguiente se usa el método de extensión Select para transformar los objetos MediaFrameSourceGroup en la lista frameSourceGroups en un objeto anónimo con dos campos: sourceGroup, que representa el propio grupo y colorSourceInfo, que representa el origen de marco de color del grupo. El campo colorSourceInfo se establece en el resultado de FirstOrDefault, que selecciona el primer objeto para el que el predicado proporcionado se resuelve en true. En este caso, el predicado es true si el tipo de secuencia es VideoPreview, el tipo de origen es Color y si la cámara está en el panel frontal del dispositivo.
En la lista de objetos anónimos devueltos por la consulta descrita anteriormente, el método de extensión Where se usa para seleccionar solo los objetos en los que el campo colorSourceInfo no es NULL. Por último, se llama a FirstOrDefault para seleccionar el primer elemento de la lista.
Ahora puede usar los campos del objeto seleccionado para obtener referencias al objeto MediaFrameSourceGroup seleccionado y al objeto MediaFrameSourceInfo que representa la cámara de color. Estos se usarán más adelante para inicializar el objeto MediaCapture y crear un Objeto MediaFrameReader para el origen seleccionado. Por último, debe probar para ver si el grupo de origen es NULL, lo que significa que el dispositivo actual no tiene los orígenes de captura solicitados.
var selectedGroupObjects = frameSourceGroups.Select(group =>
new
{
sourceGroup = group,
colorSourceInfo = group.SourceInfos.FirstOrDefault((sourceInfo) =>
{
// On Xbox/Kinect, omit the MediaStreamType and EnclosureLocation tests
return sourceInfo.MediaStreamType == MediaStreamType.VideoPreview
&& sourceInfo.SourceKind == MediaFrameSourceKind.Color
&& sourceInfo.DeviceInformation?.EnclosureLocation.Panel == Windows.Devices.Enumeration.Panel.Front;
})
}).Where(t => t.colorSourceInfo != null)
.FirstOrDefault();
MediaFrameSourceGroup selectedGroup = selectedGroupObjects?.sourceGroup;
MediaFrameSourceInfo colorSourceInfo = selectedGroupObjects?.colorSourceInfo;
if (selectedGroup == null)
{
return;
}
En el ejemplo siguiente se usa una técnica similar como se ha descrito anteriormente para seleccionar un grupo de origen que contenga cámaras de color, profundidad e infrarrojos.
var allGroups = await MediaFrameSourceGroup.FindAllAsync();
var eligibleGroups = allGroups.Select(g => new
{
Group = g,
// For each source kind, find the source which offers that kind of media frame,
// or null if there is no such source.
SourceInfos = new MediaFrameSourceInfo[]
{
g.SourceInfos.FirstOrDefault(info => info.SourceKind == MediaFrameSourceKind.Color),
g.SourceInfos.FirstOrDefault(info => info.SourceKind == MediaFrameSourceKind.Depth),
g.SourceInfos.FirstOrDefault(info => info.SourceKind == MediaFrameSourceKind.Infrared),
}
}).Where(g => g.SourceInfos.Any(info => info != null)).ToList();
if (eligibleGroups.Count == 0)
{
System.Diagnostics.Debug.WriteLine("No source group with color, depth or infrared found.");
return;
}
var selectedGroupIndex = 0; // Select the first eligible group
MediaFrameSourceGroup selectedGroup = eligibleGroups[selectedGroupIndex].Group;
MediaFrameSourceInfo colorSourceInfo = eligibleGroups[selectedGroupIndex].SourceInfos[0];
MediaFrameSourceInfo infraredSourceInfo = eligibleGroups[selectedGroupIndex].SourceInfos[1];
MediaFrameSourceInfo depthSourceInfo = eligibleGroups[selectedGroupIndex].SourceInfos[2];
Nota:
A partir de Windows 10, versión 1803, puedes usar la clase MediaCaptureVideoProfile para seleccionar un origen de fotogramas multimedia con un conjunto de funcionalidades deseadas. Para obtener más información, consulte la sección Uso de perfiles de vídeo para seleccionar un origen de fotogramas más adelante en este artículo.
Inicialización del objeto MediaCapture para usar el grupo de origen de fotogramas seleccionado
El siguiente paso consiste en inicializar el objeto MediaCapture para usar el grupo de origen de fotogramas seleccionado en el paso anterior.
El objeto MediaCapture se usa normalmente desde varias ubicaciones dentro de la aplicación, por lo que debe declarar una variable miembro de clase para contenerla.
MediaCapture mediaCapture;
Cree una instancia del objeto MediaCapture llamando al constructor . A continuación, cree un objeto MediaCaptureInitializationSettings que se usará para inicializar el objeto MediaCapture. En este ejemplo, se usan las siguientes opciones de configuración:
- SourceGroup : indica al sistema qué grupo de origen va a usar para obtener fotogramas. Recuerde que el grupo de origen define un conjunto de orígenes de fotogramas multimedia que se pueden usar simultáneamente.
- SharingMode : indica al sistema si necesita un control exclusivo sobre los dispositivos de origen de captura. Si estableces esto en ExclusiveControl, significa que puedes cambiar la configuración del dispositivo de captura, como el formato de los fotogramas que genera, pero esto significa que si otra aplicación ya tiene control exclusivo, se producirá un error en la aplicación cuando intente inicializar el dispositivo de captura multimedia. Si estableces esto en SharedReadOnly, puedes recibir fotogramas de los orígenes de fotogramas incluso si están en uso por otra aplicación, pero no puedes cambiar la configuración de los dispositivos.
- MemoryPreference: si especifica cpu, el sistema usará memoria de CPU que garantiza que cuando lleguen los fotogramas, estarán disponibles como objetos SoftwareBitmap. Si especifica Auto, el sistema elegirá dinámicamente la ubicación de memoria óptima para almacenar fotogramas. Si el sistema decide usar la memoria de GPU, los fotogramas multimedia llegarán como un objeto IDirect3DSurface y no como softwareBitmap.
- StreamingCaptureMode : establézcalo en Vídeo para indicar que no es necesario transmitir audio.
Llame a InitializeAsync para inicializar MediaCapture con la configuración deseada. Asegúrese de llamar a esto dentro de un bloque try en caso de que se produzca un error en la inicialización.
mediaCapture = new MediaCapture();
var settings = new MediaCaptureInitializationSettings()
{
SourceGroup = selectedGroup,
SharingMode = MediaCaptureSharingMode.ExclusiveControl,
MemoryPreference = MediaCaptureMemoryPreference.Cpu,
StreamingCaptureMode = StreamingCaptureMode.Video
};
try
{
await mediaCapture.InitializeAsync(settings);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("MediaCapture initialization failed: " + ex.Message);
return;
}
Establecer el formato preferido para el origen del marco
Para establecer el formato preferido para un origen de fotogramas, debe obtener un objeto MediaFrameSource que represente el origen. Para obtener este objeto, acceda al diccionario Frames del objeto MediaCapture inicializado y especifique el identificador del origen de fotogramas que desea usar. Este es el motivo por el que guardamos el objeto MediaFrameSourceInfo al seleccionar un grupo de origen de fotogramas.
La propiedad MediaFrameSource.SupportedFormats contiene una lista de objetos MediaFrameFormat que describen los formatos admitidos para el origen del marco. Use el método de extensión Where Linq para seleccionar un formato basado en las propiedades deseadas. En este ejemplo, se selecciona un formato que tiene un ancho de 1080 píxeles y puede proporcionar fotogramas en formato RGB de 32 bits. El método de extensión FirstOrDefault selecciona la primera entrada de la lista. Si el formato seleccionado es NULL, el origen del marco no admite el formato solicitado. Si se admite el formato, puede solicitar que el origen use este formato llamando a SetFormatAsync.
var colorFrameSource = mediaCapture.FrameSources[colorSourceInfo.Id];
var preferredFormat = colorFrameSource.SupportedFormats.Where(format =>
{
return format.VideoFormat.Width >= 1080
&& format.Subtype == MediaEncodingSubtypes.Argb32;
}).FirstOrDefault();
if (preferredFormat == null)
{
// Our desired format is not supported
return;
}
await colorFrameSource.SetFormatAsync(preferredFormat);
Creación de un lector de fotogramas para el origen del marco
Para recibir fotogramas para un origen de fotogramas multimedia, use un objeto MediaFrameReader.
MediaFrameReader mediaFrameReader;
Cree una instancia del lector de fotogramas llamando a CreateFrameReaderAsync en el objeto MediaCapture inicializado. El primer argumento para este método es el origen del marco desde el que desea recibir fotogramas. Puede crear un lector de fotogramas independiente para cada origen de fotogramas que quiera usar. El segundo argumento indica al sistema el formato de salida en el que desea que lleguen fotogramas. Esto puede ahorrarle tener que realizar sus propias conversiones a fotogramas a medida que llegan. Tenga en cuenta que si especifica un formato que no es compatible con el origen de fotogramas, se producirá una excepción, por lo que debe asegurarse de que este valor se encuentra en la colección SupportedFormats.
Después de crear el lector de fotogramas, registre un controlador para el evento FrameArrived que se genera cada vez que haya un nuevo fotograma disponible desde el origen.
Indique al sistema que empiece a leer fotogramas desde el origen llamando a StartAsync.
mediaFrameReader = await mediaCapture.CreateFrameReaderAsync(colorFrameSource, MediaEncodingSubtypes.Argb32);
mediaFrameReader.FrameArrived += ColorFrameReader_FrameArrived;
await mediaFrameReader.StartAsync();
Controlar el evento de llegada del marco
El evento MediaFrameReader.FrameArrived se genera cada vez que hay disponible un nuevo fotograma. Puede optar por procesar cada fotograma que llegue o usar solo fotogramas cuando los necesite. Dado que el lector de fotogramas genera el evento en su propio subproceso, es posible que tenga que implementar cierta lógica de sincronización para asegurarse de que no está intentando acceder a los mismos datos desde varios subprocesos. En esta sección se muestra cómo sincronizar fotogramas de color de dibujo con un control de imagen en una página XAML. Este escenario aborda la restricción de sincronización adicional que requiere que se realicen todas las actualizaciones de los controles XAML en el subproceso de la interfaz de usuario.
El primer paso para mostrar fotogramas en XAML es crear un control Image.
<Image x:Name="imageElement" Width="320" Height="240" />
En la página de código subyacente, declare una variable miembro de clase de tipo SoftwareBitmap , que se usará como búfer de reserva en el que se copiarán todas las imágenes entrantes. Tenga en cuenta que los datos de imagen en sí no se copian, solo las referencias de objeto. Además, declare un valor booleano para realizar un seguimiento de si la operación de la interfaz de usuario se está ejecutando actualmente.
private SoftwareBitmap backBuffer;
private bool taskRunning = false;
Dado que los fotogramas llegarán como objetos SoftwareBitmap, debes crear un objeto SoftwareBitmapSource que te permita usar un SoftwareBitmap como origen para un control XAML. Debe establecer el origen de la imagen en algún lugar del código antes de iniciar el lector de fotogramas.
imageElement.Source = new SoftwareBitmapSource();
Ahora es el momento de implementar el controlador de eventos FrameArrived . Cuando se llama al controlador, el parámetro sender contiene una referencia al objeto MediaFrameReader que generó el evento. Llame a TryAcquireLatestFrame en este objeto para intentar obtener el fotograma más reciente. Como indica el nombre, TryAcquireLatestFrame puede no devolver correctamente un fotograma. Por lo tanto, cuando acceda a las propiedades VideoMediaFrame y SoftwareBitmap, asegúrese de probar para null. En este ejemplo, el operador condicional NULL ? se usa para tener acceso a SoftwareBitmap y, a continuación, se comprueba si el objeto recuperado es NULL.
El control Imagen solo puede mostrar imágenes en formato BRGA8 con pre multiplicado o sin alfa. Si el marco de llegada no está en ese formato, el método estático Convert se usa para convertir el mapa de bits de software al formato correcto.
A continuación, el método Interlocked.Exchange se usa para intercambiar la referencia de a la asignación de bits de llegada con el mapa de bits del búfer de retroceso. Este método intercambia estas referencias en una operación atómica que es segura para subprocesos. Después de intercambiar, la imagen anterior de backbuffer, ahora en la variable softwareBitmap se elimina de para limpiar sus recursos.
A continuación, coreDispatcher asociado al elemento Image se usa para crear una tarea que se ejecutará en el subproceso de la interfaz de usuario mediante una llamada a RunAsync. Dado que las tareas asincrónicas se realizarán dentro de la tarea, la expresión lambda pasada a RunAsync se declara con la palabra clave async.
Dentro de la tarea, se comprueba la variable _taskRunning para asegurarse de que solo se ejecuta una instancia de la tarea a la vez. Si la tarea aún no se está ejecutando, _taskRunning se establece en true para evitar que la tarea se ejecute de nuevo. En un bucle while, se llama a Interlocked.Exchange para copiar desde el backbuffer en un softwareBitmap temporal hasta que la imagen de backbuffer sea null. Por cada vez que se rellena el mapa de bits temporal, se convierte la propiedad Source de la imagen en softwareBitmapSource y, a continuación, se llama a SetBitmapAsync para establecer el origen de la imagen.
Por último, la variable _taskRunning se vuelve a establecer en false para que la tarea se pueda volver a ejecutar la próxima vez que se llame al controlador.
Nota:
Si accede a los objetos SoftwareBitmap o Direct3DSurface proporcionados por la propiedad VideoMediaFrame de mediaFrameReference, el sistema crea una referencia segura a estos objetos, lo que significa que no se eliminarán cuando llame a Dispose en la clase MediaFrameReference contenedora. Debe llamar explícitamente al método Dispose de SoftwareBitmap o Direct3DSurface directamente para que los objetos se eliminen inmediatamente. De lo contrario, el recolector de elementos no utilizados liberará finalmente la memoria de estos objetos, pero no se puede saber cuándo se producirá, y si el número de mapas de bits asignados o superficies supera la cantidad máxima permitida por el sistema, se detendrá el flujo de nuevos fotogramas. Puede copiar fotogramas recuperados mediante el método SoftwareBitmap.Copy , por ejemplo, y luego liberar los fotogramas originales para superar esta limitación. Además, Si crea mediaFrameReader mediante la sobrecarga CreateFrameReaderAsync(Windows.Media.Capture.Frames.MediaFrameSource inputSource, System.String outputSubtype, Windows.Graphics.Imaging.BitmapSize outputSize) o CreateFrameReaderAsync(Windows.Media.Capture.Frames.MediaFrameSource InputSource, System.String outputSubtype), los fotogramas devueltos son copias de los datos de fotogramas originales y, por lo tanto, no hacen que la adquisición de fotogramas se detenga cuando se detengan. Retenido.
private void ColorFrameReader_FrameArrived(MediaFrameReader sender, MediaFrameArrivedEventArgs args)
{
var mediaFrameReference = sender.TryAcquireLatestFrame();
var videoMediaFrame = mediaFrameReference?.VideoMediaFrame;
var softwareBitmap = videoMediaFrame?.SoftwareBitmap;
if (softwareBitmap != null)
{
if (softwareBitmap.BitmapPixelFormat != Windows.Graphics.Imaging.BitmapPixelFormat.Bgra8 ||
softwareBitmap.BitmapAlphaMode != Windows.Graphics.Imaging.BitmapAlphaMode.Premultiplied)
{
softwareBitmap = SoftwareBitmap.Convert(softwareBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
}
// Swap the processed frame to _backBuffer and dispose of the unused image.
softwareBitmap = Interlocked.Exchange(ref backBuffer, softwareBitmap);
softwareBitmap?.Dispose();
// Changes to XAML ImageElement must happen on UI thread through Dispatcher
var task = imageElement.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
async () =>
{
// Don't let two copies of this task run at the same time.
if (taskRunning)
{
return;
}
taskRunning = true;
// Keep draining frames from the backbuffer until the backbuffer is empty.
SoftwareBitmap latestBitmap;
while ((latestBitmap = Interlocked.Exchange(ref backBuffer, null)) != null)
{
var imageSource = (SoftwareBitmapSource)imageElement.Source;
await imageSource.SetBitmapAsync(latestBitmap);
latestBitmap.Dispose();
}
taskRunning = false;
});
}
mediaFrameReference.Dispose();
}
Limpieza de recursos
Cuando haya terminado de leer fotogramas, asegúrese de detener el lector de fotogramas multimedia llamando a StopAsync, anulando el registro del controlador FrameArrived y eliminando el objeto MediaCapture.
await mediaFrameReader.StopAsync();
mediaFrameReader.FrameArrived -= ColorFrameReader_FrameArrived;
mediaCapture.Dispose();
mediaCapture = null;
Para obtener más información sobre cómo limpiar los objetos de captura multimedia cuando se suspende la aplicación, consulte Visualización de la vista previa de la cámara.
La clase auxiliar FrameRenderer
El ejemplo de fotogramas de cámara universal de Windows proporciona una clase auxiliar que facilita la visualización de los fotogramas de orígenes de color, infrarrojos y profundidad en la aplicación. Normalmente, querrá hacer algo más con datos de profundidad e infrarrojos que simplemente mostrarlos en la pantalla, pero esta clase auxiliar es una herramienta útil para demostrar la característica lector de fotogramas y para depurar su propia implementación de lector de fotogramas.
La clase auxiliar FrameRenderer implementa los métodos siguientes.
- Constructor FrameRenderer: el constructor inicializa la clase auxiliar para usar el elemento Imagen XAML que pasas para mostrar fotogramas multimedia.
- ProcessFrame: este método muestra un marco multimedia, representado por mediaFrameReference, en el elemento Image que ha pasado al constructor. Normalmente, debes llamar a este método desde el controlador de eventos FrameArrived, pasando el marco devuelto por TryAcquireLatestFrame.
- ConvertToDisplayableImage : este método comprueba el formato del marco multimedia y, si es necesario, lo convierte en un formato que se puede mostrar. Para las imágenes de color, esto significa asegurarse de que el formato de color es BGRA8 y que el modo alfa del mapa de bits está premultiplicado. Para fotogramas infrarrojos o de profundidad, cada línea de exploración se procesa para convertir los valores de profundidad o infrarrojos en un degradado psuedocolor, utilizando la clase PsuedoColorHelper que también se incluye en la muestra y se muestra a continuación.
Nota:
Para realizar la manipulación de píxeles en imágenes softwareBitmap , debe acceder a un búfer de memoria nativo. Para ello, debe usar la interfaz COM IMemoryBufferByteAccess incluida en la lista de código siguiente y debe actualizar las propiedades del proyecto para permitir la compilación de código no seguro. Para obtener más información, vea Crear, editar y guardar imágenes de mapa de bits.
[ComImport]
[Guid("5B0D3235-4DBA-4D44-865E-8F1D0E4FD04D")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
unsafe interface IMemoryBufferByteAccess
{
void GetBuffer(out byte* buffer, out uint capacity);
}
class FrameRenderer
{
private Image _imageElement;
private SoftwareBitmap _backBuffer;
private bool _taskRunning = false;
public FrameRenderer(Image imageElement)
{
_imageElement = imageElement;
_imageElement.Source = new SoftwareBitmapSource();
}
// Processes a MediaFrameReference and displays it in a XAML image control
public void ProcessFrame(MediaFrameReference frame)
{
var softwareBitmap = FrameRenderer.ConvertToDisplayableImage(frame?.VideoMediaFrame);
if (softwareBitmap != null)
{
// Swap the processed frame to _backBuffer and trigger UI thread to render it
softwareBitmap = Interlocked.Exchange(ref _backBuffer, softwareBitmap);
// UI thread always reset _backBuffer before using it. Unused bitmap should be disposed.
softwareBitmap?.Dispose();
// Changes to xaml ImageElement must happen in UI thread through Dispatcher
var task = _imageElement.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
async () =>
{
// Don't let two copies of this task run at the same time.
if (_taskRunning)
{
return;
}
_taskRunning = true;
// Keep draining frames from the backbuffer until the backbuffer is empty.
SoftwareBitmap latestBitmap;
while ((latestBitmap = Interlocked.Exchange(ref _backBuffer, null)) != null)
{
var imageSource = (SoftwareBitmapSource)_imageElement.Source;
await imageSource.SetBitmapAsync(latestBitmap);
latestBitmap.Dispose();
}
_taskRunning = false;
});
}
}
// Function delegate that transforms a scanline from an input image to an output image.
private unsafe delegate void TransformScanline(int pixelWidth, byte* inputRowBytes, byte* outputRowBytes);
/// <summary>
/// Determines the subtype to request from the MediaFrameReader that will result in
/// a frame that can be rendered by ConvertToDisplayableImage.
/// </summary>
/// <returns>Subtype string to request, or null if subtype is not renderable.</returns>
public static string GetSubtypeForFrameReader(MediaFrameSourceKind kind, MediaFrameFormat format)
{
// Note that media encoding subtypes may differ in case.
// https://docs.microsoft.com/en-us/uwp/api/Windows.Media.MediaProperties.MediaEncodingSubtypes
string subtype = format.Subtype;
switch (kind)
{
// For color sources, we accept anything and request that it be converted to Bgra8.
case MediaFrameSourceKind.Color:
return Windows.Media.MediaProperties.MediaEncodingSubtypes.Bgra8;
// The only depth format we can render is D16.
case MediaFrameSourceKind.Depth:
return String.Equals(subtype, Windows.Media.MediaProperties.MediaEncodingSubtypes.D16, StringComparison.OrdinalIgnoreCase) ? subtype : null;
// The only infrared formats we can render are L8 and L16.
case MediaFrameSourceKind.Infrared:
return (String.Equals(subtype, Windows.Media.MediaProperties.MediaEncodingSubtypes.L8, StringComparison.OrdinalIgnoreCase) ||
String.Equals(subtype, Windows.Media.MediaProperties.MediaEncodingSubtypes.L16, StringComparison.OrdinalIgnoreCase)) ? subtype : null;
// No other source kinds are supported by this class.
default:
return null;
}
}
/// <summary>
/// Converts a frame to a SoftwareBitmap of a valid format to display in an Image control.
/// </summary>
/// <param name="inputFrame">Frame to convert.</param>
public static unsafe SoftwareBitmap ConvertToDisplayableImage(VideoMediaFrame inputFrame)
{
SoftwareBitmap result = null;
using (var inputBitmap = inputFrame?.SoftwareBitmap)
{
if (inputBitmap != null)
{
switch (inputFrame.FrameReference.SourceKind)
{
case MediaFrameSourceKind.Color:
// XAML requires Bgra8 with premultiplied alpha.
// We requested Bgra8 from the MediaFrameReader, so all that's
// left is fixing the alpha channel if necessary.
if (inputBitmap.BitmapPixelFormat != BitmapPixelFormat.Bgra8)
{
System.Diagnostics.Debug.WriteLine("Color frame in unexpected format.");
}
else if (inputBitmap.BitmapAlphaMode == BitmapAlphaMode.Premultiplied)
{
// Already in the correct format.
result = SoftwareBitmap.Copy(inputBitmap);
}
else
{
// Convert to premultiplied alpha.
result = SoftwareBitmap.Convert(inputBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
}
break;
case MediaFrameSourceKind.Depth:
// We requested D16 from the MediaFrameReader, so the frame should
// be in Gray16 format.
if (inputBitmap.BitmapPixelFormat == BitmapPixelFormat.Gray16)
{
// Use a special pseudo color to render 16 bits depth frame.
var depthScale = (float)inputFrame.DepthMediaFrame.DepthFormat.DepthScaleInMeters;
var minReliableDepth = inputFrame.DepthMediaFrame.MinReliableDepth;
var maxReliableDepth = inputFrame.DepthMediaFrame.MaxReliableDepth;
result = TransformBitmap(inputBitmap, (w, i, o) => PseudoColorHelper.PseudoColorForDepth(w, i, o, depthScale, minReliableDepth, maxReliableDepth));
}
else
{
System.Diagnostics.Debug.WriteLine("Depth frame in unexpected format.");
}
break;
case MediaFrameSourceKind.Infrared:
// We requested L8 or L16 from the MediaFrameReader, so the frame should
// be in Gray8 or Gray16 format.
switch (inputBitmap.BitmapPixelFormat)
{
case BitmapPixelFormat.Gray16:
// Use pseudo color to render 16 bits frames.
result = TransformBitmap(inputBitmap, PseudoColorHelper.PseudoColorFor16BitInfrared);
break;
case BitmapPixelFormat.Gray8:
// Use pseudo color to render 8 bits frames.
result = TransformBitmap(inputBitmap, PseudoColorHelper.PseudoColorFor8BitInfrared);
break;
default:
System.Diagnostics.Debug.WriteLine("Infrared frame in unexpected format.");
break;
}
break;
}
}
}
return result;
}
/// <summary>
/// Transform image into Bgra8 image using given transform method.
/// </summary>
/// <param name="softwareBitmap">Input image to transform.</param>
/// <param name="transformScanline">Method to map pixels in a scanline.</param>
private static unsafe SoftwareBitmap TransformBitmap(SoftwareBitmap softwareBitmap, TransformScanline transformScanline)
{
// XAML Image control only supports premultiplied Bgra8 format.
var outputBitmap = new SoftwareBitmap(BitmapPixelFormat.Bgra8,
softwareBitmap.PixelWidth, softwareBitmap.PixelHeight, BitmapAlphaMode.Premultiplied);
using (var input = softwareBitmap.LockBuffer(BitmapBufferAccessMode.Read))
using (var output = outputBitmap.LockBuffer(BitmapBufferAccessMode.Write))
{
// Get stride values to calculate buffer position for a given pixel x and y position.
int inputStride = input.GetPlaneDescription(0).Stride;
int outputStride = output.GetPlaneDescription(0).Stride;
int pixelWidth = softwareBitmap.PixelWidth;
int pixelHeight = softwareBitmap.PixelHeight;
using (var outputReference = output.CreateReference())
using (var inputReference = input.CreateReference())
{
// Get input and output byte access buffers.
byte* inputBytes;
uint inputCapacity;
((IMemoryBufferByteAccess)inputReference).GetBuffer(out inputBytes, out inputCapacity);
byte* outputBytes;
uint outputCapacity;
((IMemoryBufferByteAccess)outputReference).GetBuffer(out outputBytes, out outputCapacity);
// Iterate over all pixels and store converted value.
for (int y = 0; y < pixelHeight; y++)
{
byte* inputRowBytes = inputBytes + y * inputStride;
byte* outputRowBytes = outputBytes + y * outputStride;
transformScanline(pixelWidth, inputRowBytes, outputRowBytes);
}
}
}
return outputBitmap;
}
/// <summary>
/// A helper class to manage look-up-table for pseudo-colors.
/// </summary>
private static class PseudoColorHelper
{
#region Constructor, private members and methods
private const int TableSize = 1024; // Look up table size
private static readonly uint[] PseudoColorTable;
private static readonly uint[] InfraredRampTable;
// Color palette mapping value from 0 to 1 to blue to red colors.
private static readonly Color[] ColorRamp =
{
Color.FromArgb(a:0xFF, r:0x7F, g:0x00, b:0x00),
Color.FromArgb(a:0xFF, r:0xFF, g:0x00, b:0x00),
Color.FromArgb(a:0xFF, r:0xFF, g:0x7F, b:0x00),
Color.FromArgb(a:0xFF, r:0xFF, g:0xFF, b:0x00),
Color.FromArgb(a:0xFF, r:0x7F, g:0xFF, b:0x7F),
Color.FromArgb(a:0xFF, r:0x00, g:0xFF, b:0xFF),
Color.FromArgb(a:0xFF, r:0x00, g:0x7F, b:0xFF),
Color.FromArgb(a:0xFF, r:0x00, g:0x00, b:0xFF),
Color.FromArgb(a:0xFF, r:0x00, g:0x00, b:0x7F),
};
static PseudoColorHelper()
{
PseudoColorTable = InitializePseudoColorLut();
InfraredRampTable = InitializeInfraredRampLut();
}
/// <summary>
/// Maps an input infrared value between [0, 1] to corrected value between [0, 1].
/// </summary>
/// <param name="value">Input value between [0, 1].</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] // Tell the compiler to inline this method to improve performance
private static uint InfraredColor(float value)
{
int index = (int)(value * TableSize);
index = index < 0 ? 0 : index > TableSize - 1 ? TableSize - 1 : index;
return InfraredRampTable[index];
}
/// <summary>
/// Initializes the pseudo-color look up table for infrared pixels
/// </summary>
private static uint[] InitializeInfraredRampLut()
{
uint[] lut = new uint[TableSize];
for (int i = 0; i < TableSize; i++)
{
var value = (float)i / TableSize;
// Adjust to increase color change between lower values in infrared images
var alpha = (float)Math.Pow(1 - value, 12);
lut[i] = ColorRampInterpolation(alpha);
}
return lut;
}
/// <summary>
/// Initializes pseudo-color look up table for depth pixels
/// </summary>
private static uint[] InitializePseudoColorLut()
{
uint[] lut = new uint[TableSize];
for (int i = 0; i < TableSize; i++)
{
lut[i] = ColorRampInterpolation((float)i / TableSize);
}
return lut;
}
/// <summary>
/// Maps a float value to a pseudo-color pixel
/// </summary>
private static uint ColorRampInterpolation(float value)
{
// Map value to surrounding indexes on the color ramp
int rampSteps = ColorRamp.Length - 1;
float scaled = value * rampSteps;
int integer = (int)scaled;
int index =
integer < 0 ? 0 :
integer >= rampSteps - 1 ? rampSteps - 1 :
integer;
Color prev = ColorRamp[index];
Color next = ColorRamp[index + 1];
// Set color based on ratio of closeness between the surrounding colors
uint alpha = (uint)((scaled - integer) * 255);
uint beta = 255 - alpha;
return
((prev.A * beta + next.A * alpha) / 255) << 24 | // Alpha
((prev.R * beta + next.R * alpha) / 255) << 16 | // Red
((prev.G * beta + next.G * alpha) / 255) << 8 | // Green
((prev.B * beta + next.B * alpha) / 255); // Blue
}
/// <summary>
/// Maps a value in [0, 1] to a pseudo RGBA color.
/// </summary>
/// <param name="value">Input value between [0, 1].</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static uint PseudoColor(float value)
{
int index = (int)(value * TableSize);
index = index < 0 ? 0 : index > TableSize - 1 ? TableSize - 1 : index;
return PseudoColorTable[index];
}
#endregion
/// <summary>
/// Maps each pixel in a scanline from a 16 bit depth value to a pseudo-color pixel.
/// </summary>
/// <param name="pixelWidth">Width of the input scanline, in pixels.</param>
/// <param name="inputRowBytes">Pointer to the start of the input scanline.</param>
/// <param name="outputRowBytes">Pointer to the start of the output scanline.</param>
/// <param name="depthScale">Physical distance that corresponds to one unit in the input scanline.</param>
/// <param name="minReliableDepth">Shortest distance at which the sensor can provide reliable measurements.</param>
/// <param name="maxReliableDepth">Furthest distance at which the sensor can provide reliable measurements.</param>
public static unsafe void PseudoColorForDepth(int pixelWidth, byte* inputRowBytes, byte* outputRowBytes, float depthScale, float minReliableDepth, float maxReliableDepth)
{
// Visualize space in front of your desktop.
float minInMeters = minReliableDepth * depthScale;
float maxInMeters = maxReliableDepth * depthScale;
float one_min = 1.0f / minInMeters;
float range = 1.0f / maxInMeters - one_min;
ushort* inputRow = (ushort*)inputRowBytes;
uint* outputRow = (uint*)outputRowBytes;
for (int x = 0; x < pixelWidth; x++)
{
var depth = inputRow[x] * depthScale;
if (depth == 0)
{
// Map invalid depth values to transparent pixels.
// This happens when depth information cannot be calculated, e.g. when objects are too close.
outputRow[x] = 0;
}
else
{
var alpha = (1.0f / depth - one_min) / range;
outputRow[x] = PseudoColor(alpha * alpha);
}
}
}
/// <summary>
/// Maps each pixel in a scanline from a 8 bit infrared value to a pseudo-color pixel.
/// </summary>
/// /// <param name="pixelWidth">Width of the input scanline, in pixels.</param>
/// <param name="inputRowBytes">Pointer to the start of the input scanline.</param>
/// <param name="outputRowBytes">Pointer to the start of the output scanline.</param>
public static unsafe void PseudoColorFor8BitInfrared(
int pixelWidth, byte* inputRowBytes, byte* outputRowBytes)
{
byte* inputRow = inputRowBytes;
uint* outputRow = (uint*)outputRowBytes;
for (int x = 0; x < pixelWidth; x++)
{
outputRow[x] = InfraredColor(inputRow[x] / (float)Byte.MaxValue);
}
}
/// <summary>
/// Maps each pixel in a scanline from a 16 bit infrared value to a pseudo-color pixel.
/// </summary>
/// <param name="pixelWidth">Width of the input scanline.</param>
/// <param name="inputRowBytes">Pointer to the start of the input scanline.</param>
/// <param name="outputRowBytes">Pointer to the start of the output scanline.</param>
public static unsafe void PseudoColorFor16BitInfrared(int pixelWidth, byte* inputRowBytes, byte* outputRowBytes)
{
ushort* inputRow = (ushort*)inputRowBytes;
uint* outputRow = (uint*)outputRowBytes;
for (int x = 0; x < pixelWidth; x++)
{
outputRow[x] = InfraredColor(inputRow[x] / (float)UInt16.MaxValue);
}
}
}
// Displays the provided softwareBitmap in a XAML image control.
public void PresentSoftwareBitmap(SoftwareBitmap softwareBitmap)
{
if (softwareBitmap != null)
{
// Swap the processed frame to _backBuffer and trigger UI thread to render it
softwareBitmap = Interlocked.Exchange(ref _backBuffer, softwareBitmap);
// UI thread always reset _backBuffer before using it. Unused bitmap should be disposed.
softwareBitmap?.Dispose();
// Changes to xaml ImageElement must happen in UI thread through Dispatcher
var task = _imageElement.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
async () =>
{
// Don't let two copies of this task run at the same time.
if (_taskRunning)
{
return;
}
_taskRunning = true;
// Keep draining frames from the backbuffer until the backbuffer is empty.
SoftwareBitmap latestBitmap;
while ((latestBitmap = Interlocked.Exchange(ref _backBuffer, null)) != null)
{
var imageSource = (SoftwareBitmapSource)_imageElement.Source;
await imageSource.SetBitmapAsync(latestBitmap);
latestBitmap.Dispose();
}
_taskRunning = false;
});
}
}
}
Uso de MultiSourceMediaFrameReader para obtener fotogramas con núcleo temporal de varios orígenes
A partir de Windows 10, versión 1607, puedes usar MultiSourceMediaFrameReader para recibir fotogramas con núcleo temporal de varios orígenes. Esta API facilita el procesamiento que requiere fotogramas de varios orígenes que se tomaron en proximidad temporal cercana, como el uso de la clase DepthCorrelatedCoordinateMapper. Una limitación del uso de este nuevo método es que los eventos de fotograma llegado solo se generan a la velocidad del origen de captura más lento. Se quitarán fotogramas adicionales de orígenes más rápidos. Además, dado que el sistema espera que los fotogramas lleguen de diferentes orígenes a diferentes velocidades, no reconoce automáticamente si un origen ha dejado de generar fotogramas por completo. El código de ejemplo de esta sección muestra cómo usar un evento para crear su propia lógica de tiempo de espera que se invoque si los marcos correlacionados no llegan dentro de un límite de tiempo definido por la aplicación.
Los pasos para usar MultiSourceMediaFrameReader son similares a los pasos para usar MediaFrameReader descritos anteriormente en este artículo. En este ejemplo se usará un origen de color y un origen de profundidad. Declare algunas variables de cadena para almacenar los identificadores de origen del marco multimedia que se usarán para seleccionar fotogramas de cada origen. A continuación, declare manualResetEventSlim, cancellationTokenSource y eventHandler que se usará para implementar la lógica de tiempo de espera del ejemplo.
private MultiSourceMediaFrameReader _multiFrameReader = null;
private string _colorSourceId = null;
private string _depthSourceId = null;
private readonly ManualResetEventSlim _frameReceived = new ManualResetEventSlim(false);
private readonly CancellationTokenSource _tokenSource = new CancellationTokenSource();
public event EventHandler CorrelationFailed;
Con las técnicas descritas anteriormente en este artículo, consulte un objeto MediaFrameSourceGroup que incluya los orígenes de color y profundidad necesarios para este escenario de ejemplo. Después de seleccionar el grupo de origen de fotogramas deseado, obtenga MediaFrameSourceInfo para cada origen de fotogramas.
var allGroups = await MediaFrameSourceGroup.FindAllAsync();
var eligibleGroups = allGroups.Select(g => new
{
Group = g,
// For each source kind, find the source which offers that kind of media frame,
// or null if there is no such source.
SourceInfos = new MediaFrameSourceInfo[]
{
g.SourceInfos.FirstOrDefault(info => info.SourceKind == MediaFrameSourceKind.Color),
g.SourceInfos.FirstOrDefault(info => info.SourceKind == MediaFrameSourceKind.Depth)
}
}).Where(g => g.SourceInfos.Any(info => info != null)).ToList();
if (eligibleGroups.Count == 0)
{
System.Diagnostics.Debug.WriteLine("No source group with color, depth or infrared found.");
return;
}
var selectedGroupIndex = 0; // Select the first eligible group
MediaFrameSourceGroup selectedGroup = eligibleGroups[selectedGroupIndex].Group;
MediaFrameSourceInfo colorSourceInfo = eligibleGroups[selectedGroupIndex].SourceInfos[0];
MediaFrameSourceInfo depthSourceInfo = eligibleGroups[selectedGroupIndex].SourceInfos[1];
Cree e inicialice un objeto MediaCapture , pasando el grupo de origen de fotogramas seleccionado en la configuración de inicialización.
mediaCapture = new MediaCapture();
var settings = new MediaCaptureInitializationSettings()
{
SourceGroup = selectedGroup,
SharingMode = MediaCaptureSharingMode.ExclusiveControl,
MemoryPreference = MediaCaptureMemoryPreference.Cpu,
StreamingCaptureMode = StreamingCaptureMode.Video
};
await mediaCapture.InitializeAsync(settings);
Después de inicializar el objeto MediaCapture, recupere los objetos MediaFrameSource para las cámaras de color y profundidad. Almacene el identificador de cada origen para que pueda seleccionar el marco de llegada para el origen correspondiente.
MediaFrameSource colorSource =
mediaCapture.FrameSources.Values.FirstOrDefault(
s => s.Info.SourceKind == MediaFrameSourceKind.Color);
MediaFrameSource depthSource =
mediaCapture.FrameSources.Values.FirstOrDefault(
s => s.Info.SourceKind == MediaFrameSourceKind.Depth);
if (colorSource == null || depthSource == null)
{
System.Diagnostics.Debug.WriteLine("MediaCapture doesn't have the Color and Depth streams");
return;
}
_colorSourceId = colorSource.Info.Id;
_depthSourceId = depthSource.Info.Id;
Cree e inicialice MultiSourceMediaFrameReader llamando a CreateMultiSourceFrameReaderAsync y pasando una matriz de orígenes de fotogramas que usará el lector. Registre un controlador de eventos para el evento FrameArrived. En este ejemplo se crea una instancia de la clase auxiliar FrameRenderer , descrita anteriormente en este artículo, para representar fotogramas en un control Image . Inicie el lector de fotogramas llamando a StartAsync.
Registre un controlador de eventos para el evento CorellationFailed declarado anteriormente en el ejemplo. Señalaremos este evento si uno de los orígenes de fotogramas multimedia que se usan deja de producir fotogramas. Por último, llame a Task.Run para llamar al método auxiliar de tiempo de espera NotifyAboutCorrelationFailure en un subproceso independiente. La implementación de este método se muestra más adelante en este artículo.
_multiFrameReader = await mediaCapture.CreateMultiSourceFrameReaderAsync(
new[] { colorSource, depthSource });
_multiFrameReader.FrameArrived += MultiFrameReader_FrameArrived;
_frameRenderer = new FrameRenderer(imageElement);
MultiSourceMediaFrameReaderStartStatus startStatus =
await _multiFrameReader.StartAsync();
if (startStatus != MultiSourceMediaFrameReaderStartStatus.Success)
{
throw new InvalidOperationException(
"Unable to start reader: " + startStatus);
}
this.CorrelationFailed += MainPage_CorrelationFailed;
Task.Run(() => NotifyAboutCorrelationFailure(_tokenSource.Token));
El evento FrameArrived se genera cada vez que hay un nuevo fotograma disponible en todos los orígenes de fotogramas multimedia administrados por MultiSourceMediaFrameReader. Esto significa que el evento se generará en la cadencia del origen multimedia más lento. Si un origen genera varios fotogramas en el tiempo en que un origen más lento genera un fotograma, se quitarán los fotogramas adicionales del origen rápido.
Obtenga la clase MultiSourceMediaFrameReference asociada al evento llamando a TryAcquireLatestFrame. Obtenga la clase MediaFrameReference asociada a cada origen de fotogramas multimedia mediante una llamada a TryGetFrameReferenceBySourceId, pasando las cadenas de identificador almacenadas cuando se inicializó el lector de fotogramas.
Llame al método Set del objeto ManualResetEventSlim para indicar que han llegado los marcos. Comprobaremos este evento en el método NotifyCorrelationFailure que se ejecuta en un subproceso independiente.
Por último, realice cualquier procesamiento en los fotogramas multimedia correlacionados con el tiempo. En este ejemplo simplemente se muestra el marco del origen de profundidad.
private void MultiFrameReader_FrameArrived(MultiSourceMediaFrameReader sender, MultiSourceMediaFrameArrivedEventArgs args)
{
using (MultiSourceMediaFrameReference muxedFrame =
sender.TryAcquireLatestFrame())
using (MediaFrameReference colorFrame =
muxedFrame.TryGetFrameReferenceBySourceId(_colorSourceId))
using (MediaFrameReference depthFrame =
muxedFrame.TryGetFrameReferenceBySourceId(_depthSourceId))
{
// Notify the listener thread that the frame has been received.
_frameReceived.Set();
_frameRenderer.ProcessFrame(depthFrame);
}
}
El método auxiliar NotifyCorrelationFailure se ejecutó en un subproceso independiente después de iniciar el lector de fotogramas. En este método, compruebe si se ha señalado el evento de fotograma recibido. Recuerde que, en el controlador FrameArrived , establecemos este evento cada vez que llega un conjunto de fotogramas correlacionados. Si el evento no se ha señalado durante algún período de tiempo definido por la aplicación ( 5 segundos es un valor razonable) y la tarea no se canceló mediante CancellationToken, es probable que uno de los orígenes de fotogramas multimedia haya dejado de leer fotogramas. En este caso, normalmente quiere apagar el lector de fotogramas, por lo que genera el evento CorrelationFailed definido por la aplicación. En el controlador de este evento, puede detener el lector de fotogramas y limpiar los recursos asociados, como se muestra anteriormente en este artículo.
private void NotifyAboutCorrelationFailure(CancellationToken token)
{
// If in 5 seconds the token is not cancelled and frame event is not signaled,
// correlation is most likely failed.
if (WaitHandle.WaitAny(new[] { token.WaitHandle, _frameReceived.WaitHandle }, 5000)
== WaitHandle.WaitTimeout)
{
CorrelationFailed?.Invoke(this, EventArgs.Empty);
}
}
private async void MainPage_CorrelationFailed(object sender, EventArgs e)
{
await _multiFrameReader.StopAsync();
_multiFrameReader.FrameArrived -= MultiFrameReader_FrameArrived;
mediaCapture.Dispose();
mediaCapture = null;
}
Usar el modo de adquisición de fotogramas almacenados en búfer para conservar la secuencia de fotogramas adquiridos
A partir de Windows 10, versión 1709, puedes establecer la propiedad AcquisitionMode de mediaFrameReader o MultiSourceMediaFrameReader en Buffered para conservar la secuencia de fotogramas pasados a la aplicación desde el origen de fotogramas.
mediaFrameReader.AcquisitionMode = MediaFrameReaderAcquisitionMode.Buffered;
En el modo de adquisición predeterminado, En tiempo real, si se adquieren varios fotogramas desde el origen mientras la aplicación sigue controlando el evento FrameArrived de un fotograma anterior, el sistema enviará a la aplicación el fotograma adquirido más recientemente y quitará fotogramas adicionales en espera en el búfer. Esto proporciona a la aplicación el fotograma disponible más reciente en todo momento. Este suele ser el modo más útil para las aplicaciones computer vision en tiempo real.
En el modo de adquisición almacenado en búfer, el sistema mantendrá todos los fotogramas en el búfer y los proporcionará a la aplicación a través del evento FrameArrived en el orden recibido. Ten en cuenta que en este modo, cuando se rellena el búfer del sistema para fotogramas, el sistema dejará de adquirir nuevos fotogramas hasta que la aplicación complete el evento FrameArrived para fotogramas anteriores, liberando más espacio en el búfer.
Usar MediaSource para mostrar fotogramas en mediaPlayerElement
A partir de Windows, versión 1709, puedes mostrar fotogramas adquiridos desde mediaFrameReader directamente en un control MediaPlayerElement en tu página XAML. Esto se logra mediante el uso de MediaSource.CreateFromMediaFrameSource para crear un objeto MediaSource que un Objeto MediaPlayer asociado a mediaPlayerElement pueda usar directamente. Para obtener información detallada sobre cómo trabajar con MediaPlayer y MediaPlayerElement, consulta Reproducir audio y vídeo con MediaPlayer.
Los ejemplos de código siguientes muestran una implementación sencilla que muestra los fotogramas desde una cámara frontal y orientada hacia atrás simultáneamente en una página XAML.
En primer lugar, agrega dos controles MediaPlayerElement a tu página XAML.
<MediaPlayerElement x:Name="mediaPlayerElement1" Width="320" Height="240"/>
<MediaPlayerElement x:Name="mediaPlayerElement2" Width="320" Height="240"/>
A continuación, con las técnicas que se muestran en las secciones anteriores de este artículo, seleccione un objeto MediaFrameSourceGroup que contenga objetos MediaFrameSourceInfo para cámaras de color en el panel frontal y el panel posterior. Tenga en cuenta que MediaPlayer no convierte automáticamente fotogramas de formatos que no sean de color, como datos de profundidad o infrarrojos, en datos de color. El uso de otros tipos de sensor puede producir resultados inesperados.
var allGroups = await MediaFrameSourceGroup.FindAllAsync();
var eligibleGroups = allGroups.Select(g => new
{
Group = g,
// For each source kind, find the source which offers that kind of media frame,
// or null if there is no such source.
SourceInfos = new MediaFrameSourceInfo[]
{
g.SourceInfos.FirstOrDefault(info => info.DeviceInformation?.EnclosureLocation.Panel == Windows.Devices.Enumeration.Panel.Front
&& info.SourceKind == MediaFrameSourceKind.Color),
g.SourceInfos.FirstOrDefault(info => info.DeviceInformation?.EnclosureLocation.Panel == Windows.Devices.Enumeration.Panel.Back
&& info.SourceKind == MediaFrameSourceKind.Color)
}
}).Where(g => g.SourceInfos.Any(info => info != null)).ToList();
if (eligibleGroups.Count == 0)
{
System.Diagnostics.Debug.WriteLine("No source group with front and back-facing camera found.");
return;
}
var selectedGroupIndex = 0; // Select the first eligible group
MediaFrameSourceGroup selectedGroup = eligibleGroups[selectedGroupIndex].Group;
MediaFrameSourceInfo frontSourceInfo = selectedGroup.SourceInfos[0];
MediaFrameSourceInfo backSourceInfo = selectedGroup.SourceInfos[1];
Inicialice el objeto MediaCapture para usar el objeto MediaFrameSourceGroup seleccionado.
mediaCapture = new MediaCapture();
var settings = new MediaCaptureInitializationSettings()
{
SourceGroup = selectedGroup,
SharingMode = MediaCaptureSharingMode.ExclusiveControl,
MemoryPreference = MediaCaptureMemoryPreference.Cpu,
StreamingCaptureMode = StreamingCaptureMode.Video
};
try
{
await mediaCapture.InitializeAsync(settings);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("MediaCapture initialization failed: " + ex.Message);
return;
}
Por último, llame a MediaSource.CreateFromMediaFrameSource para crear un objeto MediaSource para cada origen de fotogramas mediante la propiedad Id del objeto MediaFrameSourceInfo asociado para seleccionar uno de los orígenes de fotogramas de la colección FrameSources del objeto MediaCapture. Inicialice un nuevo objeto MediaPlayer y asígnelo a mediaPlayerElement llamando a SetMediaPlayer. A continuación, establezca la propiedad Source en el objeto MediaSource recién creado.
var frameMediaSource1 = MediaSource.CreateFromMediaFrameSource(mediaCapture.FrameSources[frontSourceInfo.Id]);
mediaPlayerElement1.SetMediaPlayer(new Windows.Media.Playback.MediaPlayer());
mediaPlayerElement1.MediaPlayer.Source = frameMediaSource1;
mediaPlayerElement1.AutoPlay = true;
var frameMediaSource2 = MediaSource.CreateFromMediaFrameSource(mediaCapture.FrameSources[backSourceInfo.Id]);
mediaPlayerElement2.SetMediaPlayer(new Windows.Media.Playback.MediaPlayer());
mediaPlayerElement2.MediaPlayer.Source = frameMediaSource2;
mediaPlayerElement2.AutoPlay = true;
Usar perfiles de vídeo para seleccionar un origen de fotogramas
Un perfil de cámara, representado por un objeto MediaCaptureVideoProfile , representa un conjunto de funcionalidades que proporciona un dispositivo de captura determinado, como velocidades de fotogramas, resoluciones o características avanzadas como la captura HDR. Un dispositivo de captura puede admitir varios perfiles, lo que le permite seleccionar el que está optimizado para el escenario de captura. A partir de Windows 10, versión 1803, puedes usar MediaCaptureVideoProfile para seleccionar un origen de fotogramas multimedia con funcionalidades concretas antes de inicializar el objeto MediaCapture . El método de ejemplo siguiente busca un perfil de vídeo que admita HDR con gamut de color ancho (WCG) y devuelve un objeto MediaCaptureInitializationSettings que se puede usar para inicializar MediaCapture para usar el dispositivo y el perfil seleccionados.
En primer lugar, llame a MediaFrameSourceGroup.FindAllAsync para obtener una lista de todos los grupos de orígenes de fotogramas multimedia disponibles en el dispositivo actual. Recorra en bucle cada grupo de origen y llame a MediaCapture.FindKnownVideoProfiles para obtener una lista de todos los perfiles de vídeo del grupo de origen actual que admiten el perfil especificado, en este caso HDR con foto WCG. Si se encuentra un perfil que cumpla los criterios, cree un nuevo objeto MediaCaptureInitializationSettings y establezca VideoProfile en el perfil seleccionado y VideoDeviceId en la propiedad de id. del grupo de origen de fotogramas multimedia actual.
public async Task<MediaCaptureInitializationSettings> FindHdrWithWcgPhotoProfile()
{
IReadOnlyList<MediaFrameSourceGroup> sourceGroups = await MediaFrameSourceGroup.FindAllAsync();
MediaCaptureInitializationSettings settings = null;
foreach (MediaFrameSourceGroup sourceGroup in sourceGroups)
{
// Find a device that support AdvancedColorPhoto
IReadOnlyList<MediaCaptureVideoProfile> profileList = MediaCapture.FindKnownVideoProfiles(
sourceGroup.Id,
KnownVideoProfile.HdrWithWcgPhoto);
if (profileList.Count > 0)
{
settings = new MediaCaptureInitializationSettings();
settings.VideoProfile = profileList[0];
settings.VideoDeviceId = sourceGroup.Id;
break;
}
}
return settings;
}
private void StartDeviceWatcherButton_Click(object sender, RoutedEventArgs e)
{
var remoteCameraHelper = new RemoteCameraPairingHelper(this.Dispatcher);
}
Para obtener más información sobre el uso de perfiles de cámara, consulte Perfiles de cámara.
Temas relacionados