Guide d'empreinte vidéo en temps réel¶
Ce guide montre comment traiter les flux vidéo en direct et générer des empreintes en temps réel à l'aide du VisioForge Video Fingerprinting SDK. L'approche utilise un traitement par fragments pour construire les empreintes à partir de flux vidéo continus.
Vue d'ensemble¶
L'empreinte en temps réel fonctionne ainsi : 1. Capture des images d'une source en direct (caméra, flux IP, etc.) 2. Accumulation des images dans des fragments basés sur le temps 3. Construction des empreintes à partir des fragments complets 4. Recherche de correspondances par rapport aux empreintes de référence 5. Utilisation de fragments qui se chevauchent pour une meilleure précision de détection
Composants principaux¶
Classe VFPSearch¶
La classe VFPSearch fournit des méthodes statiques pour le traitement d'images en temps réel :
Process()— traite des images individuelles et les ajoute aux données de rechercheBuild()— construit une empreinte à partir des données de recherche accumulées
Classe VFPSearchData¶
Conteneur pour accumuler les données d'images pendant le traitement en temps réel :
// Créer le conteneur de données de recherche pour une durée spécifique
var searchData = new VFPSearchData(TimeSpan.FromSeconds(10));
Exemple complet : traitement d'empreinte en temps réel¶
Voici un exemple complet basé sur du code de production réel qui traite la vidéo en direct et détecte les correspondances de contenu :
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using VisioForge.Core.VideoFingerPrinting;
using VisioForge.Core.VideoCaptureX;
using VisioForge.Core.Types.Events;
public class RealTimeFingerprintProcessor
{
// Conteneur de données en direct par fragments
public class FingerprintLiveData : IDisposable
{
public VFPSearchData Data { get; private set; }
public DateTime StartTime { get; private set; }
public FingerprintLiveData(TimeSpan duration, DateTime startTime)
{
Data = new VFPSearchData(duration);
StartTime = startTime;
}
public void Dispose()
{
Data?.Dispose();
}
}
// Classe principale du processeur
private FingerprintLiveData _searchLiveData;
private FingerprintLiveData _searchLiveOverlapData;
private ConcurrentQueue<FingerprintLiveData> _fingerprintQueue;
private List<VFPFingerPrint> _referenceFingerprints;
private VideoCaptureCoreX _videoCapture;
private IntPtr _tempBuffer;
private long _fragmentDuration;
private int _fragmentCount;
private int _overlapFragmentCount;
private readonly object _processingLock = new object();
public async Task InitializeAsync()
{
// Initialiser la capture vidéo
_videoCapture = new VideoCaptureCoreX();
_videoCapture.OnVideoFrameBuffer += OnVideoFrameReceived;
// Initialiser la file de traitement
_fingerprintQueue = new ConcurrentQueue<FingerprintLiveData>();
// Charger les empreintes de référence (publicités, contenu protégé, etc.)
_referenceFingerprints = await LoadReferenceFingerprintsAsync();
// Calculer la durée optimale de fragment
// Doit être au moins 2x la durée du contenu de référence le plus long
var maxReferenceDuration = GetMaxReferenceDuration();
_fragmentDuration = ((maxReferenceDuration + 1000) / 1000 + 1) * 1000 * 2;
}
private async Task<List<VFPFingerPrint>> LoadReferenceFingerprintsAsync()
{
var fingerprints = new List<VFPFingerPrint>();
// Charger ou générer les empreintes de référence
foreach (var videoFile in GetReferenceVideos())
{
VFPFingerPrint fp;
// Vérifier si l'empreinte existe déjà
var fpFile = videoFile + ".vfsigx";
if (File.Exists(fpFile))
{
fp = VFPFingerPrint.Load(fpFile);
}
else
{
// Générer et enregistrer l'empreinte. La signature réelle est
// (VFPFingerprintSource, VFPErrorCallback errorDelegate = null,
// VFPProgressCallback progressDelegate = null) — il n'y a pas
// de paramètre CancellationToken.
var source = new VFPFingerprintSource(videoFile);
fp = await VFPAnalyzer.GetSearchFingerprintForVideoFileAsync(
source,
errorDelegate: null,
progressDelegate: null);
fp.Save(fpFile);
}
fingerprints.Add(fp);
}
return fingerprints;
}
private void OnVideoFrameReceived(object sender, VideoFrameXBufferEventArgs e)
{
// Allouer un tampon temporaire si nécessaire
if (_tempBuffer == IntPtr.Zero)
{
var stride = ((e.Frame.Width * 3 + 3) / 4) * 4; // Stride RGB24
_tempBuffer = Marshal.AllocCoTaskMem(stride * e.Frame.Height);
}
// Traiter le fragment principal
ProcessMainFragment(e);
// Traiter le fragment chevauchant pour une meilleure détection
ProcessOverlappingFragment(e);
}
private void ProcessMainFragment(VideoFrameXBufferEventArgs e)
{
// Initialiser un nouveau fragment si nécessaire
if (_searchLiveData == null)
{
_searchLiveData = new FingerprintLiveData(
TimeSpan.FromMilliseconds(_fragmentDuration),
DateTime.Now);
_fragmentCount++;
}
// Calculer l'horodatage relatif au début du fragment
long timestamp = (long)(e.Frame.Timestamp.TotalMilliseconds * 1000);
// Vérifier si nous sommes toujours dans la durée du fragment actuel
if (timestamp < _fragmentDuration * _fragmentCount)
{
// Copier les données d'image vers le tampon temporaire
CopyMemory(_tempBuffer, e.Frame.Data, e.Frame.DataSize);
// Calculer l'horodatage corrigé (relatif au début du fragment)
var correctedTimestamp = timestamp - _fragmentDuration * (_fragmentCount - 1);
// Traiter l'image et l'ajouter aux données de recherche
VFPSearch.Process(
_tempBuffer, // Données d'image
e.Frame.Width, // Largeur
e.Frame.Height, // Hauteur
((e.Frame.Width * 3 + 3) / 4) * 4, // Stride
TimeSpan.FromMilliseconds(correctedTimestamp), // Horodatage
ref _searchLiveData.Data); // Données de recherche
}
else
{
// Fragment complet, mettre en file pour traitement
_fingerprintQueue.Enqueue(_searchLiveData);
_searchLiveData = null;
// Traiter les fragments en file
ProcessQueuedFragments();
}
}
private void ProcessOverlappingFragment(VideoFrameXBufferEventArgs e)
{
long timestamp = (long)(e.Frame.Timestamp.TotalMilliseconds * 1000);
// Démarrer le traitement de chevauchement après la moitié de la durée du fragment
if (timestamp < _fragmentDuration / 2)
return;
// Initialiser le fragment chevauchant si nécessaire
if (_searchLiveOverlapData == null)
{
_searchLiveOverlapData = new FingerprintLiveData(
TimeSpan.FromMilliseconds(_fragmentDuration),
DateTime.Now);
_overlapFragmentCount++;
}
// Vérifier si nous sommes dans la durée du fragment chevauchant
if (timestamp < _fragmentDuration * _overlapFragmentCount + _fragmentDuration / 2)
{
CopyMemory(_tempBuffer, e.Frame.Data, e.Frame.DataSize);
VFPSearch.Process(
_tempBuffer,
e.Frame.Width,
e.Frame.Height,
((e.Frame.Width * 3 + 3) / 4) * 4,
TimeSpan.FromMilliseconds(timestamp),
ref _searchLiveOverlapData.Data);
}
else
{
// Fragment chevauchant complet
_fingerprintQueue.Enqueue(_searchLiveOverlapData);
_searchLiveOverlapData = null;
ProcessQueuedFragments();
}
}
private void ProcessQueuedFragments()
{
lock (_processingLock)
{
if (_fingerprintQueue.TryDequeue(out var fragmentData))
{
// Construire l'empreinte à partir des données du fragment
long dataSize;
IntPtr fingerprintPtr = VFPSearch.Build(out dataSize, ref fragmentData.Data);
// Créer l'objet empreinte
var fingerprint = new VFPFingerPrint
{
Data = new byte[dataSize],
OriginalFilename = string.Empty
};
Marshal.Copy(fingerprintPtr, fingerprint.Data, 0, (int)dataSize);
// Rechercher des correspondances par rapport aux empreintes de référence
SearchForMatches(fingerprint, fragmentData.StartTime);
// Nettoyer
fragmentData.Data.Free();
fragmentData.Dispose();
}
}
}
private void SearchForMatches(VFPFingerPrint liveFingerprint, DateTime fragmentStartTime)
{
foreach (var referenceFingerprint in _referenceFingerprints)
{
// Rechercher les correspondances avec un seuil de différence configurable.
// VFPAnalyzer.SearchAsync retourne Task<List<TimeSpan>>. La contrepartie
// synchrone est VFPSearch.Search (note : VFPSearch.SearchAsync n'existe PAS —
// l'asynchrone vit sur VFPAnalyzer). Noms réels des paramètres :
// maxDifference, allowMultipleFragments.
var matches = await VFPAnalyzer.SearchAsync(
referenceFingerprint, // Empreinte de référence
liveFingerprint, // Empreinte en direct à rechercher
referenceFingerprint.Duration, // Durée de la recherche
maxDifference: 10, // Seuil de similarité (0-100)
allowMultipleFragments: true); // Trouver toutes les correspondances
if (matches.Count > 0)
{
foreach (var match in matches)
{
// Calculer l'horodatage réel de la correspondance
var matchTime = fragmentStartTime.AddMilliseconds(match.TotalMilliseconds);
OnMatchFound(new MatchResult
{
ReferenceFile = referenceFingerprint.OriginalFilename,
Timestamp = matchTime,
Position = match,
Confidence = CalculateConfidence(maxDifference: 10)
});
}
}
}
}
public async Task StartCameraStreamAsync(string deviceName, string format, double frameRate)
{
// Configurer la source caméra
var devices = await _videoCapture.Video_SourcesAsync();
var device = devices.FirstOrDefault(d => d.Name == deviceName);
var videoFormat = device?.VideoFormats.FirstOrDefault(f => f.Name == format);
var settings = new VideoCaptureDeviceSourceSettings(device)
{
Format = videoFormat.ToFormat()
};
settings.Format.FrameRate = new VideoFrameRate(frameRate);
_videoCapture.Video_Source = settings;
_videoCapture.Start();
}
public async Task StartNetworkStreamAsync(string streamUrl)
{
// Configurer la source de flux réseau
var sourceSettings = await UniversalSourceSettings.CreateAsync(streamUrl);
// Pour les caméras IP avec authentification
if (requiresAuth)
{
sourceSettings.Login = "username";
sourceSettings.Password = "password";
}
await _videoCapture.StartAsync(sourceSettings);
}
public void Stop()
{
_videoCapture?.Stop();
// Traiter les fragments restants
while (_fingerprintQueue.TryDequeue(out var fragment))
{
ProcessQueuedFragments();
}
// Nettoyer
if (_tempBuffer != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(_tempBuffer);
_tempBuffer = IntPtr.Zero;
}
_searchLiveData?.Dispose();
_searchLiveOverlapData?.Dispose();
}
// Méthodes utilitaires
private long GetMaxReferenceDuration()
{
return _referenceFingerprints.Max(fp => (long)fp.Duration.TotalMilliseconds);
}
private List<string> GetReferenceVideos()
{
// Retourner la liste des fichiers vidéo de référence
return new List<string> { "ad1.mp4", "ad2.mp4", "copyrighted_content.mp4" };
}
private double CalculateConfidence(int maxDifference)
{
return (100.0 - maxDifference) / 100.0;
}
private void OnMatchFound(MatchResult result)
{
Console.WriteLine($"Correspondance trouvée : {result.ReferenceFile} à {result.Timestamp:HH:mm:ss.fff}");
}
[DllImport("msvcrt.dll", EntryPoint = "memcpy")]
private static extern void CopyMemory(IntPtr dest, IntPtr src, int length);
}
// Classes de support
public class MatchResult
{
public string ReferenceFile { get; set; }
public DateTime Timestamp { get; set; }
public TimeSpan Position { get; set; }
public double Confidence { get; set; }
}
Concepts clés¶
Durée du fragment¶
La durée du fragment détermine la quantité de données vidéo accumulées avant la construction d'une empreinte :
// Calculer la durée optimale du fragment
// Doit être au moins 2x la durée du contenu le plus long que vous voulez détecter
var maxContentDuration = 30000; // 30 secondes en millisecondes
var fragmentDuration = ((maxContentDuration + 1000) / 1000 + 1) * 1000 * 2;
// Résultat : fragments de 62000 ms (62 secondes)
Fragments qui se chevauchent¶
L'utilisation de fragments chevauchants améliore la précision de détection en garantissant que le contenu n'est pas manqué aux frontières des fragments :
// Fragment principal : 0-60 secondes
// Fragment de chevauchement 1 : 30-90 secondes (démarre à 50 % du principal)
// Fragment de chevauchement 2 : 60-120 secondes
// Ceci garantit que tout contenu est entièrement capturé dans au moins un fragment
Traitement des images¶
Chaque image est traitée individuellement et ajoutée aux données de recherche du fragment actuel :
VFPSearch.Process(
frameData, // Pointeur de données d'image RGB24
width, // Largeur de l'image
height, // Hauteur de l'image
stride, // Stride de ligne en octets
timestamp, // Horodatage de l'image
ref searchData // Accumulateur pour ce fragment
);
Construction des empreintes¶
Une fois un fragment complet, construisez l'empreinte :
long dataSize;
IntPtr fingerprintPtr = VFPSearch.Build(out dataSize, ref searchData);
// Copier vers un tableau managé
var fingerprintData = new byte[dataSize];
Marshal.Copy(fingerprintPtr, fingerprintData, 0, (int)dataSize);
Considérations de performance¶
Gestion de la mémoire¶
- Pré-allouer les tampons pour éviter les allocations répétées
- Utiliser
Marshal.AllocCoTaskMempour les tampons non managés - Libérer correctement les objets
VFPSearchDataaprès usage - Libérer les données de recherche avec
searchData.Free()après construction
Stratégie de traitement¶
- Utiliser une file d'attente pour découpler la capture d'images du traitement d'empreintes
- Traiter les fragments dans un thread séparé pour éviter de bloquer la capture
- Ajuster la durée du fragment selon la longueur du contenu de référence
- Utiliser des fragments chevauchants pour une meilleure précision de détection
Conseils d'optimisation¶
- Durée du fragment : fragments plus longs = meilleure précision mais latence plus élevée
- Pourcentage de chevauchement : 50 % de chevauchement est typique, à ajuster selon les besoins
- Seuil de différence : valeurs plus basses = correspondance plus stricte (échelle 0-100)
- Taille du tampon : pré-allouer selon la taille d'image maximale attendue
Cas d'usage courants¶
Surveillance de diffusion en direct¶
Surveillez les diffusions TV en direct à la recherche de contenu protégé ou de publicités :
// Initialiser avec une bibliothèque de contenu diffusé
var processor = new RealTimeFingerprintProcessor();
await processor.LoadReferenceFingerprintsAsync("ads_library/");
// Démarrer le traitement du flux diffusé
await processor.StartNetworkStreamAsync("rtsp://broadcast-server/stream1");
Analyse de caméras de sécurité¶
Détectez des événements ou objets spécifiques dans les flux de caméras de sécurité :
// Traiter plusieurs flux de caméras
var processors = new List<RealTimeFingerprintProcessor>();
foreach (var camera in GetSecurityCameras())
{
var processor = new RealTimeFingerprintProcessor();
await processor.StartNetworkStreamAsync(camera.StreamUrl);
processors.Add(processor);
}
Conformité de contenu¶
Garantissez la conformité des plateformes de streaming aux restrictions de contenu :
// Surveiller les flux générés par les utilisateurs
var processor = new RealTimeFingerprintProcessor();
await processor.LoadReferenceFingerprintsAsync("prohibited_content/");
// Traiter le flux entrant
await processor.StartNetworkStreamAsync(userStreamUrl);
Dépannage¶
Aucune correspondance trouvée¶
- Vérifiez que la durée du fragment est appropriée à la longueur du contenu de référence
- Vérifiez que le seuil de différence n'est pas trop strict
- Assurez-vous que les fragments chevauchants sont activés
- Vérifiez que les empreintes de référence ont été générées correctement
Utilisation mémoire élevée¶
- Réduisez la durée du fragment si possible
- Traitez et libérez les fragments plus fréquemment
- Utilisez un plus petit pourcentage de chevauchement
- Libérez correctement les tampons non managés
Latence de traitement¶
- Utilisez des threads séparés pour la capture et le traitement
- Implémentez l'abandon d'images si le traitement ne peut pas suivre
- Optimisez la recherche d'empreintes de référence (index, recherche parallèle)
- Envisagez l'accélération GPU si disponible
Résumé¶
L'empreinte en temps réel avec le SDK VisioForge utilise une approche par fragments qui : - Accumule les images dans des fragments basés sur le temps avec VFPSearchData - Traite les images en temps réel avec VFPSearch.Process() - Construit des empreintes à partir de fragments avec VFPSearch.Build() - Utilise des fragments chevauchants pour une détection robuste - Permet la correspondance de contenu en direct par rapport aux empreintes de référence
Cette approche a fait ses preuves en environnements de production pour la surveillance de diffusion, la conformité de contenu et les applications de sécurité.
Application d'exemple complète¶
Pour un exemple de travail complet d'empreinte vidéo en temps réel, consultez l'application d'exemple MMT Live : - Exemple MMT Live sur GitHub
Cet exemple démontre tous les concepts couverts dans ce guide avec une application Windows Forms complète pour la détection de publicités en direct.