Traiter des images multimédias avec MediaFrameReader
Cet article vous montre comment utiliser un MediaFrameReader avec MediaCapture pour obtenir des images multimédias à partir d’une ou plusieurs sources disponibles, notamment des caméras couleur, profondeur et infrarouge, des appareils audio ou même des sources d’images personnalisées telles que celles qui produisent des images de suivi squelette. Cette fonctionnalité est conçue pour être utilisée par les applications qui effectuent le traitement en temps réel des images multimédias, telles que les applications de caméra prenant en charge la profondeur et de réalité augmentée.
Si vous souhaitez simplement capturer des vidéos ou des photos, comme une application de photographie classique, vous souhaitez probablement utiliser l’une des autres techniques de capture prises en charge par MediaCapture. Pour obtenir la liste des techniques de capture multimédia disponibles et des articles montrant comment les utiliser, consultez Caméra.
Remarque
Les fonctionnalités décrites dans cet article sont disponibles uniquement à partir de Windows 10 version 1607.
Remarque
Il existe un exemple d’application Windows universelle qui illustre l’utilisation de MediaFrameReader pour afficher des images provenant de différentes sources d’images, notamment des caméras couleur, profondeur et infrarouge. Pour plus d’informations, consultez l’exemple d’images caméra.
Remarque
Un nouvel ensemble d’API pour utiliser MediaFrameReader avec des données audio a été introduit dans Windows 10, version 1803. Pour plus d’informations, veuillez consulter la rubrique Traiter les trames audio avec MediaFrameReader.
Configuration de votre projet
Comme pour toute application qui utilise MediaCapture, vous devez déclarer que votre application utilise la fonctionnalité webcam avant de tenter d’accéder à n’importe quel appareil photo. Si votre application capture à partir d’un appareil audio, vous devez également déclarer la fonctionnalité d’appareil microphone .
Ajouter des fonctionnalités au manifeste de l’application
- Dans Microsoft Visual Studio, dans Explorateur de solutions, ouvrez le concepteur du manifeste de l’application en double-cliquant sur l’élément package.appxmanifest.
- Sélectionnez l’onglet Fonctionnalités.
- Cochez la case Webcam et la case Microphone.
- Pour l’accès à la bibliothèque d’images et de vidéos, cochez les cases Bibliothèque d’images et Bibliothèque de vidéos.
L’exemple de code de cet article utilise des API à partir des espaces de noms suivants, en plus de ceux inclus par le modèle de projet par défaut.
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;
Sélectionner des sources d’images et des groupes de sources d’images
De nombreuses applications qui traitent les images multimédias doivent obtenir des images à partir de plusieurs sources à la fois, telles que les caméras de couleur et de profondeur d’un appareil. L’objet MediaFrameSourceGroup représente un ensemble de sources de trames multimédias qui peuvent être utilisées simultanément. Appelez la méthode statique MediaFrameSourceGroup.FindAllAsync pour obtenir la liste de tous les groupes de sources d’images prises en charge par l’appareil actuel.
var frameSourceGroups = await MediaFrameSourceGroup.FindAllAsync();
Vous pouvez également créer un DeviceWatcher à l’aide de DeviceInformation.CreateWatcher et la valeur retournée par MediaFrameSourceGroup.GetDeviceSelector pour recevoir des notifications lorsque les groupes sources d’images disponibles sur l’appareil changent, par exemple lorsqu’une caméra externe est branchée. Pour plus d’informations, consultez Énumérer les appareils.
Un MediaFrameSourceGroup possède une collection d’objets MediaFrameSourceInfo qui décrivent les sources d’images incluses dans le groupe. Après avoir récupéré les groupes de sources d’images disponibles sur l’appareil, vous pouvez sélectionner le groupe qui expose les sources d’images qui vous intéressent.
L’exemple suivant montre la façon la plus simple de sélectionner un groupe source d’images. Ce code effectue simplement une boucle sur tous les groupes disponibles, puis effectue une boucle sur chaque élément de la collection SourceInfos. Chaque MediaFrameSourceInfo est vérifié pour voir s’il prend en charge les fonctionnalités que nous recherchons. Dans ce cas, la propriété MediaStreamType est vérifiée pour la valeur VideoPreview, ce qui signifie que l’appareil fournit un flux d’aperçu vidéo et que la propriété SourceKind est vérifiée pour la valeur Color, indiquant que la source fournit des images de couleur.
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;
}
}
Cette méthode d’identification du groupe source d’images souhaité et des sources d’images fonctionne pour des cas simples, mais si vous souhaitez sélectionner des sources d’images basées sur des critères plus complexes, elle peut rapidement devenir fastidieuse. Une autre méthode consiste à utiliser la syntaxe Linq et les objets anonymes pour effectuer la sélection. L’exemple suivant utilise la méthode Select extension pour transformer les objets MediaFrameSourceGroup dans la liste frameSourceGroups en un objet anonyme avec deux champs : sourceGroup, représentant le groupe lui-même et colorSourceInfo, qui représente la source du cadre de couleur dans le groupe. Le champ colorSourceInfo est défini sur le résultat de FirstOrDefault, qui sélectionne le premier objet pour lequel le prédicat fourni est résolu en true. Dans ce cas, le prédicat est vrai si le type de flux est VideoPreview, le type source est Color et si l’appareil photo se trouve sur le panneau frontal de l’appareil.
Dans la liste des objets anonymes retournés par la requête décrite ci-dessus, la méthode Where extension est utilisée pour sélectionner uniquement les objets où le champ colorSourceInfo n’est pas null. Enfin, FirstOrDefault est appelé pour sélectionner le premier élément de la liste.
Vous pouvez maintenant utiliser les champs de l’objet sélectionné pour obtenir des références au MediaFrameSourceGroup sélectionné et à l’objet MediaFrameSourceInfo représentant l’appareil photo couleur. Celles-ci seront utilisées ultérieurement pour initialiser l’objet MediaCapture et créer un MediaFrameReader pour la source sélectionnée. Enfin, vous devez tester si le groupe source est null, ce qui signifie que l’appareil actuel n’a pas vos sources de capture demandées.
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;
}
L’exemple suivant utilise une technique similaire comme décrit ci-dessus pour sélectionner un groupe source qui contient des caméras couleur, profondeur et infrarouge.
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];
Remarque
À compter de Windows 10, version 1803, vous pouvez utiliser la classe MediaCaptureVideoProfile pour sélectionner une source de trame multimédia avec un ensemble de fonctionnalités souhaitées. Pour plus d’informations, consultez la section Utiliser des profils vidéo pour sélectionner une source de trame plus loin dans cet article.
Initialiser l’objet MediaCapture pour utiliser le groupe source d’images sélectionné
L’étape suivante consiste à initialiser l’objet MediaCapture pour utiliser le groupe source d’images que vous avez sélectionné à l’étape précédente.
L’objet MediaCapture est généralement utilisé à partir de plusieurs emplacements au sein de votre application. Vous devez donc déclarer une variable membre de classe pour la contenir.
MediaCapture mediaCapture;
Créez une instance de l’objet MediaCapture en appelant le constructeur. Ensuite, créez un objet MediaCaptureInitializationSettings qui sera utilisé pour initialiser l’objet MediaCapture. Dans cet exemple, les paramètres suivants sont utilisés :
- SourceGroup : indique au système quel groupe source vous utiliserez pour obtenir des images. N’oubliez pas que le groupe source définit un ensemble de sources d’images multimédias qui peuvent être utilisées simultanément.
- SharingMode : indique au système si vous avez besoin d’un contrôle exclusif sur les appareils sources de capture. Si vous définissez ce paramètre sur ExclusiveControl, cela signifie que vous pouvez modifier les paramètres de l’appareil de capture, tels que le format des images qu’il produit, mais cela signifie que si une autre application a déjà un contrôle exclusif, votre application échoue lorsqu’elle tente d’initialiser l’appareil de capture multimédia. Si vous définissez cette valeur sur SharedReadOnly, vous pouvez recevoir des images à partir des sources d’images même si elles sont utilisées par une autre application, mais vous ne pouvez pas modifier les paramètres des appareils.
- MemoryPreference : si vous spécifiez l’UC, le système utilise la mémoire processeur qui garantit que lorsque les images arrivent, elles seront disponibles en tant qu’objets SoftwareBitmap. Si vous spécifiez Auto, le système choisit dynamiquement l’emplacement de mémoire optimal pour stocker les trames. Si le système choisit d’utiliser la mémoire GPU, les trames multimédias arrivent en tant qu’objet IDirect3DSurface et non en tant qu’objet SoftwareBitmap.
- StreamingCaptureMode : définissez cette valeur sur Vidéo pour indiquer que l’audio n’a pas besoin d’être diffusé en continu.
Appelez InitializeAsync pour initialiser MediaCapture avec vos paramètres souhaités. Veillez à l’appeler dans un bloc try en cas d’échec de l’initialisation.
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;
}
Définir le format préféré pour la source de trame
Pour définir le format préféré d’une source de trame, vous devez obtenir un objet MediaFrameSource représentant la source. Vous obtenez cet objet en accédant au dictionnaire Frames de l’objet MediaCapture initialisé, en spécifiant l’identificateur de la source de trame que vous souhaitez utiliser. C’est pourquoi nous avons enregistré l’objet MediaFrameSourceInfo lors de la sélection d’un groupe source d’images.
La propriété MediaFrameSource.SupportedFormats contient une liste d’objets MediaFrameFormat décrivant les formats pris en charge pour la source d’images. Utilisez la méthode d’extension Where Linq pour sélectionner un format en fonction des propriétés souhaitées. Dans cet exemple, un format est sélectionné avec une largeur de 1 080 pixels et peut fournir des images au format RVB 32 bits. La méthode d’extension FirstOrDefault sélectionne la première entrée dans la liste. Si le format sélectionné est null, le format demandé n’est pas pris en charge par la source de trame. Si le format est pris en charge, vous pouvez demander que la source utilise ce format en appelant 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);
Créer un lecteur d’images pour la source d’images
Pour recevoir des images pour une source d’image multimédia, utilisez un MediaFrameReader.
MediaFrameReader mediaFrameReader;
Instanciez le lecteur d’images en appelant CreateFrameReaderAsync sur votre objet MediaCapture initialisé. Le premier argument de cette méthode est la source d’images à partir de laquelle vous souhaitez recevoir des images. Vous pouvez créer un lecteur d’images distinct pour chaque source d’images que vous souhaitez utiliser. Le deuxième argument indique au système le format de sortie dans lequel vous souhaitez que les images arrivent. Cela peut vous éviter d’avoir à effectuer vos propres conversions en images à mesure qu’elles arrivent. Notez que si vous spécifiez un format qui n’est pas pris en charge par la source de trame, une exception est levée. Veillez donc à ce que cette valeur se trouve dans la collection SupportedFormats.
Après avoir créé le lecteur d’images, inscrivez un gestionnaire pour l’événement FrameArrived qui est déclenché chaque fois qu’une nouvelle image est disponible à partir de la source.
Indiquez au système de commencer à lire des images à partir de la source en appelant StartAsync.
mediaFrameReader = await mediaCapture.CreateFrameReaderAsync(colorFrameSource, MediaEncodingSubtypes.Argb32);
mediaFrameReader.FrameArrived += ColorFrameReader_FrameArrived;
await mediaFrameReader.StartAsync();
Gérer l’événement frame arrivé
L’événement MediaFrameReader.FrameArrived est déclenché chaque fois qu’une nouvelle image est disponible. Vous pouvez choisir de traiter chaque image qui arrive ou utilise uniquement des images quand vous en avez besoin. Étant donné que le lecteur d’images déclenche l’événement sur son propre thread, vous devrez peut-être implémenter une logique de synchronisation pour vous assurer que vous ne tentez pas d’accéder aux mêmes données à partir de plusieurs threads. Cette section vous montre comment synchroniser des cadres de couleur de dessin avec un contrôle d’image dans une page XAML. Ce scénario traite de la contrainte de synchronisation supplémentaire qui nécessite que toutes les mises à jour des contrôles XAML soient effectuées sur le thread d’interface utilisateur.
La première étape de l’affichage des cadres en XAML consiste à créer un contrôle Image.
<Image x:Name="imageElement" Width="320" Height="240" />
Dans votre page code-behind, déclarez une variable membre de classe de type SoftwareBitmap qui sera utilisée comme mémoire tampon de retour vers laquelle toutes les images entrantes seront copiées. Notez que les données d’image proprement dites ne sont pas copiées, mais uniquement les références d’objet. De plus, déclarez une valeur booléenne pour suivre si notre opération d’interface utilisateur est en cours d’exécution.
private SoftwareBitmap backBuffer;
private bool taskRunning = false;
Étant donné que les images arrivent en tant qu’objets SoftwareBitmap, vous devez créer un objet SoftwareBitmapSource qui vous permet d’utiliser un SoftwareBitmap comme source pour un contrôle XAML. Vous devez définir la source d’image quelque part dans votre code avant de démarrer le lecteur d’images.
imageElement.Source = new SoftwareBitmapSource();
Il est maintenant temps d’implémenter le gestionnaire d’événements FrameArrived . Lorsque le gestionnaire est appelé, le paramètre expéditeur contient une référence à l’objet MediaFrameReader qui a déclenché l’événement. Appelez TryAcquireLatestFrame sur cet objet pour tenter d’obtenir la dernière image. Comme le nom l’indique, TryAcquireLatestFrame peut ne pas réussir à renvoyer une trame. Par conséquent, lorsque vous accédez à VideoMediaFrame, puis aux propriétés SoftwareBitmap, veillez à tester la valeur Null. Dans cet exemple, l’opérateur conditionnel Null ? est utilisé pour accéder au SoftwareBitmap , puis l’objet récupéré est vérifié pour null.
Le contrôle Image ne peut afficher des images qu’au format BRGA8 avec une valeur alpha pré multipliée ou non. Si le cadre arrivant n’est pas dans ce format, la méthode statique Convert est utilisée pour convertir la bitmap logicielle au format correct.
Ensuite, la méthode Interlocked.Exchange est utilisée pour échanger la référence de l’arrivée d’une bitmap avec la bitmap backbuffer. Cette méthode échange ces références dans une opération atomique qui est thread-safe. Après l’échange, l’ancienne image backbuffer, désormais dans la variable softwareBitmap est supprimée pour nettoyer ses ressources.
Ensuite, coreDispatcher associé à l’élément Image est utilisé pour créer une tâche qui s’exécutera sur le thread d’interface utilisateur en appelant RunAsync. Étant donné que les tâches asynchrones sont effectuées dans la tâche, l’expression lambda passée à RunAsync est déclarée avec le mot clé asynchrone .
Dans la tâche, la variable _taskRunning est vérifiée pour vous assurer qu’une seule instance de la tâche est en cours d’exécution à la fois. Si la tâche n’est pas déjà en cours d’exécution, _taskRunning a la valeur true pour empêcher l’exécution de la tâche. Dans une boucle while, Interlocked.Exchange est appelé à copier à partir du backbuffer dans un SoftwareBitmap temporaire jusqu’à ce que l’image backbuffer soit null. Pour chaque fois que la bitmap temporaire est remplie, la propriété Source de l’image est convertie en SoftwareBitmapSource, puis SetBitmapAsync est appelée pour définir la source de l’image.
Enfin, la variable _taskRunning est définie sur false afin que la tâche puisse être réexécuter la prochaine fois que le gestionnaire est appelé.
Remarque
Si vous accédez aux objets SoftwareBitmap ou Direct3DSurface fournis par la propriété VideoMediaFrame d’un MediaFrameReference, le système crée une référence forte à ces objets, ce qui signifie qu’ils ne seront pas supprimés lorsque vous appelez Dispose sur le MediaFrameReference contenant. Vous devez appeler explicitement la méthode Dispose de SoftwareBitmap ou Direct3DSurface directement pour que les objets soient immédiatement supprimés. Sinon, le garbage collector libère la mémoire de ces objets, mais vous ne pouvez pas savoir quand cela se produit, et si le nombre de bitmaps ou surfaces allouées dépasse la quantité maximale autorisée par le système, le flux de nouvelles images s’arrête. Vous pouvez copier des images récupérées à l’aide de la méthode SoftwareBitmap.Copy , par exemple, puis libérer les images d’origine pour surmonter cette limitation. En outre, si vous créez MediaFrameReader à l’aide de la surcharge CreateFrameReaderAsync(Windows.Media.Capture.Frames.MediaFrameSource inputSource, System.String outputSubtype, Windows.Graphics.Imaging.BitmapSize outputSize) ou CreateFrameReaderAsync(Windows.Media.Capture.Frames.MediaFrameSource inputSource, System.String outputSubtype), les images retournées sont des copies des données d’images d’origine et ne provoquent donc pas l’arrêt de l’acquisition d’images lorsqu’elles sont effectuées Conservé.
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();
}
Nettoyer les ressources
Lorsque vous avez terminé de lire des images, veillez à arrêter le lecteur d’images multimédias en appelant StopAsync, en annulant l’inscription du gestionnaire FrameArrived et en supprimant l’objet MediaCapture.
await mediaFrameReader.StopAsync();
mediaFrameReader.FrameArrived -= ColorFrameReader_FrameArrived;
mediaCapture.Dispose();
mediaCapture = null;
Pour plus d’informations sur le nettoyage des objets de capture multimédia lorsque votre application est suspendue, consultez Afficher l’aperçu de l’appareil photo.
Classe d’assistance FrameRenderer
L’exemple d’images de caméra Windows universelle fournit une classe d’assistance qui facilite l’affichage des images à partir de sources de couleur, d’infrarouge et de profondeur dans votre application. En règle générale, vous voudrez faire quelque chose de plus avec les données de profondeur et infrarouge que de l’afficher à l’écran, mais cette classe d’assistance est un outil utile pour démontrer la fonctionnalité lecteur d’images et pour déboguer votre propre implémentation de lecteur d’images.
La classe d’assistance FrameRenderer implémente les méthodes suivantes.
- Constructeur FrameRenderer : le constructeur initialise la classe d’assistance pour utiliser l’élément Image XAML que vous transmettez pour afficher des images multimédias.
- ProcessFrame : cette méthode affiche une trame multimédia, représentée par un MediaFrameReference, dans l’élément Image que vous avez passé dans le constructeur. Vous devez généralement appeler cette méthode à partir de votre gestionnaire d’événements FrameArrived, en passant le frame retourné par TryAcquireLatestFrame.
- ConvertToDisplayableImage : cette méthode vérifie le format du cadre multimédia et, si nécessaire, la convertit en format affichable. Pour les images de couleur, cela signifie que le format de couleur est BGRA8 et que le mode alpha bitmap est prémultiplié. Pour les trames de profondeur ou infrarouge, chaque ligne de balayage est traitée pour convertir les valeurs de profondeur ou d’infrarouge en dégradé psuedocolor, à l’aide de la classe PsuedoColorHelper qui est également incluse dans l’exemple et répertoriée ci-dessous.
Remarque
Pour effectuer une manipulation de pixels sur des images SoftwareBitmap , vous devez accéder à une mémoire tampon de mémoire native. Pour ce faire, vous devez utiliser l’interface COM IMemoryBufferByteAccess incluse dans la liste de codes ci-dessous et vous devez mettre à jour vos propriétés de projet pour autoriser la compilation de code non sécurisé. Pour plus d’informations, consultez Créer, modifier et enregistrer des images bitmap.
[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;
});
}
}
}
Utiliser MultiSourceMediaFrameReader pour obtenir des images corellées dans le temps à partir de plusieurs sources
À compter de Windows 10, version 1607, vous pouvez utiliser MultiSourceMediaFrameReader pour recevoir des images corellées dans le temps à partir de plusieurs sources. Cette API facilite le traitement qui nécessite des images provenant de plusieurs sources qui ont été prises à proximité temporelle proche, comme l’utilisation de la classe DepthCorrelatedCoordinateMapper. L’une des limitations de l’utilisation de cette nouvelle méthode est que les événements arrivés aux images ne sont déclenchés qu’à la vitesse de la source de capture la plus lente. Des images supplémentaires provenant de sources plus rapides seront supprimées. En outre, étant donné que le système s’attend à ce que les images arrivent de différentes sources à différents taux, elle ne reconnaît pas automatiquement si une source a cessé de générer des images complètement. L’exemple de code de cette section montre comment utiliser un événement pour créer votre propre logique de délai d’expiration qui est appelée si les images corrélées ne arrivent pas dans une limite de temps définie par l’application.
Les étapes d’utilisation de MultiSourceMediaFrameReader sont similaires aux étapes d’utilisation de MediaFrameReader décrites précédemment dans cet article. Cet exemple utilise une source de couleur et une source de profondeur. Déclarez certaines variables de chaîne pour stocker les ID sources d’images multimédias qui seront utilisés pour sélectionner des images à partir de chaque source. Ensuite, déclarez un ManualResetEventSlim, un CancellationTokenSource et un EventHandler qui sera utilisé pour implémenter la logique de délai d’expiration pour l’exemple.
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;
À l’aide des techniques décrites précédemment dans cet article, interrogez un MediaFrameSourceGroup qui inclut les sources de couleur et de profondeur requises pour cet exemple de scénario. Après avoir sélectionné le groupe source d’images souhaité, obtenez MediaFrameSourceInfo pour chaque source de trame.
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];
Créez et initialisez un objet MediaCapture , en passant le groupe source d’images sélectionné dans les paramètres d’initialisation.
mediaCapture = new MediaCapture();
var settings = new MediaCaptureInitializationSettings()
{
SourceGroup = selectedGroup,
SharingMode = MediaCaptureSharingMode.ExclusiveControl,
MemoryPreference = MediaCaptureMemoryPreference.Cpu,
StreamingCaptureMode = StreamingCaptureMode.Video
};
await mediaCapture.InitializeAsync(settings);
Après avoir initialisé l’objet MediaCapture, récupérez les objets MediaFrameSource pour les caméras de couleur et de profondeur. Stockez l’ID pour chaque source afin de pouvoir sélectionner le cadre arrivant pour la source correspondante.
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;
Créez et initialisez MultiSourceMediaFrameReader en appelant CreateMultiSourceFrameReaderAsync et en transmettant un tableau de sources d’images que le lecteur utilisera. Inscrivez un gestionnaire d’événements pour l’événement FrameArrived . Cet exemple crée une instance de la classe d’assistance FrameRenderer , décrite précédemment dans cet article, pour afficher des images dans un contrôle Image . Démarrez le lecteur d’images en appelant StartAsync.
Inscrivez un gestionnaire d’événements pour l’événement CorellationFailed déclaré précédemment dans l’exemple. Nous signalons cet événement si l’une des sources d’images multimédias utilisées cesse de produire des images. Enfin, appelez Task.Run pour appeler la méthode d’assistance du délai d’attente, NotifyAboutCorrelationFailure, sur un thread distinct. L’implémentation de cette méthode est présentée plus loin dans cet article.
_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));
L’événement FrameArrived est déclenché chaque fois qu’une nouvelle image est disponible à partir de toutes les sources d’images multimédias gérées par MultiSourceMediaFrameReader. Cela signifie que l’événement sera déclenché à la cadence de la source multimédia la plus lente. Si une source produit plusieurs images dans le temps où une source plus lente produit une image, les images supplémentaires de la source rapide sont supprimées.
Obtenez l’attribut MultiSourceMediaFrameReference associé à l’événement en appelant TryAcquireLatestFrame. Obtenez l’élément MediaFrameReference associé à chaque source de trame multimédia en appelant TryGetFrameReferenceBySourceId, en passant les chaînes d’ID stockées lorsque le lecteur d’images a été initialisé.
Appelez la méthode Set de l’objet ManualResetEventSlim pour signaler que les images sont arrivées. Nous allons vérifier cet événement dans la méthode NotifyCorrelationFailure qui s’exécute dans un thread distinct.
Enfin, effectuez tout traitement sur les images multimédias en corrélation temporelle. Cet exemple affiche simplement le cadre de la source de profondeur.
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);
}
}
La méthode d’assistance NotifyCorrelationFailure a été exécutée sur un thread distinct après le démarrage du lecteur d’images. Dans cette méthode, vérifiez si l’événement reçu de la trame a été signalé. N’oubliez pas que dans le gestionnaire FrameArrived , nous définissons cet événement chaque fois qu’un ensemble d’images corrélées arrive. Si l’événement n’a pas été signalé pendant une période définie par l’application - 5 secondes est une valeur raisonnable - et que la tâche n’a pas été annulée à l’aide de CancellationToken, il est probable que l’une des sources d’images multimédias ait arrêté la lecture des images. Dans ce cas, vous souhaitez généralement arrêter le lecteur d’images. Déclenchez donc l’événement CorrelationFailed défini par l’application. Dans le gestionnaire de cet événement, vous pouvez arrêter le lecteur d’images et nettoyer ses ressources associées, comme indiqué précédemment dans cet article.
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;
}
Utiliser le mode d’acquisition d’images mis en mémoire tampon pour conserver la séquence d’images acquises
À compter de Windows 10, version 1709, vous pouvez définir la propriété AcquisitionMode d’un MediaFrameReader ou multiSourceMediaFrameReader sur Buffered pour conserver la séquence d’images passées dans votre application à partir de la source d’images.
mediaFrameReader.AcquisitionMode = MediaFrameReaderAcquisitionMode.Buffered;
En mode d’acquisition par défaut, en temps réel, si plusieurs images sont acquises à partir de la source pendant que votre application gère toujours l’événement FrameArrived pour une image précédente, le système envoie à votre application le cadre le plus récemment acquis et supprime les images supplémentaires en attente dans la mémoire tampon. Cela fournit à votre application le cadre disponible le plus récent à tout moment. Il s’agit généralement du mode le plus utile pour les applications de vision par ordinateur en temps réel.
En mode d’acquisition mis en mémoire tampon, le système conserve toutes les images dans la mémoire tampon et les fournit à votre application via l’événement FrameArrived dans l’ordre reçu. Notez que dans ce mode, lorsque la mémoire tampon du système pour les images est remplie, le système cesse d’acquérir de nouvelles images jusqu’à ce que votre application termine l’événement FrameArrived pour les images précédentes, ce qui libère davantage d’espace dans la mémoire tampon.
Utiliser MediaSource pour afficher des cadres dans un MediaPlayerElement
À compter de Windows, version 1709, vous pouvez afficher des images acquises à partir d’un MediaFrameReader directement dans un contrôle MediaPlayerElement dans votre page XAML. Pour ce faire, utilisez MediaSource.CreateFromMediaFrameSource pour créer un objet MediaSource qui peut être utilisé directement par un MediaPlayer associé à un MediaPlayerElement. Pour plus d’informations sur l’utilisation de MediaPlayer et de MediaPlayerElement, consultez Lire l’audio et la vidéo avec MediaPlayer.
Les exemples de code suivants vous montrent une implémentation simple qui affiche les images à partir d’une caméra frontale et arrière simultanément dans une page XAML.
Tout d’abord, ajoutez deux contrôles MediaPlayerElement à votre page XAML.
<MediaPlayerElement x:Name="mediaPlayerElement1" Width="320" Height="240"/>
<MediaPlayerElement x:Name="mediaPlayerElement2" Width="320" Height="240"/>
Ensuite, à l’aide des techniques présentées dans les sections précédentes de cet article, sélectionnez un MediaFrameSourceGroup qui contient des objets MediaFrameSourceInfo pour les caméras de couleur sur le panneau frontal et le panneau arrière. Notez que MediaPlayer ne convertit pas automatiquement les images à partir de formats non coloris, tels qu’une profondeur ou des données infrarouges, en données de couleur. L’utilisation d’autres types de capteurs peut produire des résultats inattendus.
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];
Initialisez l’objet MediaCapture pour utiliser le MediaFrameSourceGroup sélectionné.
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;
}
Enfin, appelez MediaSource.CreateFromMediaFrameSource pour créer un MediaSource pour chaque source d’images à l’aide de la propriété Id de l’objet MediaFrameSourceInfo associé pour sélectionner l’une des sources de frame dans la collection FrameSources de l’objet MediaCapture. Initialisez un nouvel objet MediaPlayer et affectez-le à un MediaPlayerElement en appelant SetMediaPlayer. Définissez ensuite la propriété Source sur l’objet MediaSource nouvellement créé.
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;
Utiliser des profils vidéo pour sélectionner une source de trame
Un profil d’appareil photo, représenté par un objet MediaCaptureVideoProfile , représente un ensemble de fonctionnalités qu’un appareil de capture particulier fournit, comme les fréquences d’images, les résolutions ou les fonctionnalités avancées telles que la capture HDR. Un appareil de capture peut prendre en charge plusieurs profils, ce qui vous permet de sélectionner celui qui est optimisé pour votre scénario de capture. À compter de Windows 10, version 1803, vous pouvez utiliser MediaCaptureVideoProfile pour sélectionner une source de trame multimédia avec des fonctionnalités particulières avant d’initialiser l’objet MediaCapture . L’exemple de méthode suivant recherche un profil vidéo qui prend en charge HDR avec Wide Color Gamut (WCG) et retourne un objet MediaCaptureInitializationSettings qui peut être utilisé pour initialiser MediaCapture afin d’utiliser l’appareil et le profil sélectionnés.
Tout d’abord, appelez MediaFrameSourceGroup.FindAllAsync pour obtenir la liste de tous les groupes de sources d’images multimédias disponibles sur l’appareil actuel. Parcourez chaque groupe de sources et appelez MediaCapture.FindKnownVideoProfiles pour obtenir la liste de tous les profils vidéo du groupe de sources actuel qui prennent en charge le profil spécifié, dans ce cas HDR avec photo WCG. Si un profil répondant aux critères est trouvé, créez un objet MediaCaptureInitializationSettings et définissez VideoProfile sur le profil sélectionné et VideoDeviceId sur la propriété Id du groupe de sources d’images multimédias actuel.
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);
}
Pour plus d’informations sur l’utilisation des profils d’appareil photo, consultez Profils de caméra.
Rubriques connexes
- Appareil photo
- Capture photo, vidéo et audio de base à l’aide de MediaCapture
- Exemple d’images de caméra