VideoSummarizer: RAG réduit pour le vidéo (Shots → Scenes MSC3 EvidenceMSC4
État d'avancement: En cours de développement dans le cadre du lucidRAG. Source: github.comM SK1scottgal/lucidrag
Lorsque cela convient: VideoSummarizer est le orchestrateur de la lucidRAG famille, combining five pipelines into a unified video analysis engine:
- DocSummarizer - Documents
- ImageSummarizer - Images
- AudioSummarizer - Audio profilage acoustique, diarrisation de l'émetteurM SK3 transcriptionMSC4
- VideoSummarizer (il s'agit de cet articleM SK1 - Vidéo (orchestre tous les trois, ajoute des shotsMSC5structure de la scèneMST6
- Système de gestion des données - Données
Tous suivent la même chose Modèle RAG réduit: extraire les signaux une fois , entreposer des preuves, synthétiser avec l’entrée LLM limitéeM SK3
Le traitement d’une image cinématographique de deux heures avec des embeddings CLIP prendrait des heures et coûterait des centaines de dollars à calculer.
VideoSummarizer résout ce problème grâce à trois améliorations clés:
- Déduplication perceptuelle du hash - Passer à des cadres visuellement similaires avant de recourir à un ML coûteux 40% réduction
- Établissement d'un CLIP par lot - Processus 8 images par passeur GPU au lieu d'une seule (3-5x speedup)
- Composition du pipeline - Image de chaîneSummarizer pour les cadres-clés, AudioSummariser pour la paroleM SK2 NER Reconnaissance d'entités désignéesMSC4 pour les entitésMska5 DocSum marizer pour l'analyse du texteMka6 GraphRAG pour les relations entre des entités
Le résultat: a M SK1 procédés de cinéma d'une heure en ~10-15 minutes, non en heuresCLIP est utilisé pour fast, embeddings sémantiques locaux; Les LLM de vision sont facultatifs et ne peuvent être invoqués que pour l'analyse élargieM SK1
Les mêmes principes d'architecture que ImageSummarizer et AudioSummarizer, mais constitué d’une conduite d’analyse vidéo unifiéeM SK1
Perspectives de base: Video is shots + audio + textM SK2 Tracer chaque domaine avec des outils spécialisés, fusionner les résultats en scènes cohérentesMSC4
- Structure du processus en premier (cutsM SK1 I-frames , segments audioMSC4
- Extraire une fois les signaux modaux croisés- (embeddingsM SK1 transcriptions , entités)
Terminologie:
- Récolte - prise de la caméra entre les coupes (structuraleM SK2 à partir de la détection des scènes FFmpeg)
- Scène - groupe contigu de tirs formant une unité cohérente sémantique
- Evidence -
(start_time, end_time)+ signaux + pointeurs + provenance
Modèles ML clés utilisés:
- CLIP - OpenAI's Contrastive Language -Image PreMSC3trainingM SK4génère des embeddings dimensionnels qui encodent la sémantique visuelle
- Les murmurs - OpenAI 's speech recognition model; transcribe audio to text with timestamps
- BERT-NER - Reconnaissance de l'entité désignée ; extrait des endroits de texte des personnes, organisationsM SK3
- Le temps de fonctionnement d'ONNX - Cross-inférence MML de la plate-formeM SK2 exploite les modèles sur le CPU /GPU sans bouclage de cadreMSC4in
Cet article porte sur:
- Comment le VideoSummarizer orchestre cinq pipelines (ImageSommarizerM SK1 AudioSum marizer , NER, DocSummarzerMSSK4 GraphRAGMSC5
- Architecture des ondes: M SK1 vagues de la normalisation à la production d'éléments probants
- Système de capacités: Téléchargement de modèles lazy, Détection GPUM SK2 routage réactif
- Optimisation du CLIP par lots pour les embeddings de cadres clés (3-5x plus rapidesM SK1
- Déduplication perceptuelle du hash (40% réduction de cadresM SK1
- Multi-cluster des scènes de signalisation M SK1insertion + transcription + type de coupe S+ temporelle )
- Intégration NER pour l'extraction d'entités des transcriptions
- Atomes éphémères pour la limite de vitesse, estimation du tempsM SK1 et pression d'arrière
- Extrait: scènesM SK1 images, transcriptions , pistes de texte à titre d'éléments de preuve RAG
Articles connexes:
- RAG réduit - Le modèle de base
- Modèle de fuzziness restreint - Le modèle fondamental
- Réduction des mises en œuvre de RAG:
- DocSummarizer - Document RAG avec extraction d'entités
- ImageSummarizer - RAG d'image avec 22- intelligence visuelle des ondes
- AudioSummarizer - Caractérisation audio judiciaire
- VideoSummarizer (le présent articleM SK1 - Organisateur de l'intelligence vidéo
Le problème: La vidéo est coûteuse
Indicateurs de référence: Les chiffres ci-dessous mesurés sur AMD 9950X (16-coreM SK3 S/ NVIDIA A4000 | | (16GB | ) \ /\ | SSK9 | GB RAM | ordre-deM SK1effets de grandeur, aucune garantie de performance absolueM SK1
Un film typique contient:
- ~170,000 cadres (2 heures à 24fpsM SK2
- ~2 heures d'audio (discussionM SK1 musique , effets)
- Les couches de texte multiples (sous-titresM SK1crédits , sur le texte de l’écran
L’approche du saumon (aucune personne ne le faitM SK1 mais elle fixe l’échelle):
- Établissement de CLIP par cadre: M SK1ms × | | 9.4 heures
- Vision LLM par cadre: M SK1s × | | 3 | 4 | 94 heures (cloud Vision API ballpark
Même avec l'extraction des cadres clés (say, 500-1000 framesM SK3 queMSC4est encore 100-200 secondes d'inférence CLIP sérieuseMST6
Approche traditionnelle: "Extracter les cadres de clésM SK2 envoyer à Vision LLM, espérer le meilleurMSC4
Problème: Il brûle les calculs sur des cadres redundants ( beaucoup de cadres clés sont visuellement similaires ), les traite en série
Solution: MultiM SK1 Filtration par étapes , Traitement en lots, et composition des pipelinesMSC4
L'architecture du VideoSummarizer
Mise en œuvre de VideoSummarizer RAG réduit pour le vidéo avec une réduction de trois étapes-
flowchart TB
subgraph Input["Video File (.mp4, .mkv, etc.)"]
VF[Video File]
end
subgraph Stage1["Stage 1: Structural Analysis"]
N[NormalizeWave<br/>FFprobe metadata]
SD[ShotDetectionWave<br/>Scene cuts via FFmpeg]
KE[KeyframeExtractionWave<br/>I-frame + dedup]
AE[AudioExtractionWave<br/>FFmpeg audio extract]
end
subgraph Stage2["Stage 2: Content Extraction"]
IS[ImageSummarizer<br/>CLIP, OCR, Vision]
AS[AudioSummarizer<br/>Whisper, Diarization]
NER[NER Service<br/>Entity extraction]
DS[DocSummarizer<br/>Text analysis if long]
GR[GraphRAG<br/>Entity relationships]
end
subgraph Stage3["Stage 3: Scene Assembly"]
SC[SceneClusteringWave<br/>Multi-signal clustering]
EV[EvidenceGenerationWave<br/>RAG chunks]
end
VF --> N
N --> SD --> KE
N --> AE
KE --> IS
AE --> AS
IS --> NER
AS --> NER
NER --> DS
NER --> GR
IS --> SC
DS --> SC
GR --> EV
SC --> EV
style Stage1 stroke:#22c55e,stroke-width:2px
style Stage2 stroke:#3b82f6,stroke-width:2px
style Stage3 stroke:#8b5cf6,stroke-width:2px
Autres éléments de preuve produits
Avant de s’engager dans la mise en oeuvre, ici ' c’est ce que vous obtiendrez M SK2le schéma de sortie):
| Artéfact | Champs clés CMS2 Source CMS3 | |
|---|---|---|
| Scène | id, start_time, end_time, key_terms[], speaker_ids[], embedding[512] |
SceneClusteringWave |
| Récolte | id, start_time, end_time, cut_type, keyframe_path |
ShotDetectionWave |
| Déclaration | id, text, start_time, end_time, speaker_id, confidence |
TranscriptionWave |
| TextTrack | id, text, start_time, text_type (titleMSC1credit/subtitle /ocrM SK4 MSC5 SubtitleExtractionWave SSK6 |
|
| Cadre de clés | id, timestamp, frame_path, dhash, clip_embedding[512] |
KeyframeExtractionWave |
Chaque artefact comprend provenance: onde source , échantillon de temps de traitementM SK2 cote de confiance. Ces artefacts sont les unités de récupération primaire pour les demandes de vidéo; les fichiers bruts ne sont jamais récupérés directement.
Le pipeline de signalisation de la vague
VideoSummarizer utilise un architecture des ondes basée sur le signal- où chaque onde déclare explicitement ses contrats de signaux:
public interface ISignalAwareVideoWave
{
/// <summary>Signals this wave requires before it can run.</summary>
IReadOnlyList<string> RequiredSignals { get; }
/// <summary>Signals this wave can optionally use if available.</summary>
IReadOnlyList<string> OptionalSignals { get; }
/// <summary>Signals this wave emits on successful completion.</summary>
IReadOnlyList<string> EmittedSignals { get; }
/// <summary>Cache keys this wave produces for downstream waves.</summary>
IReadOnlyList<string> CacheEmits { get; }
/// <summary>Cache keys this wave consumes from upstream waves.</summary>
IReadOnlyList<string> CacheUses { get; }
}
Cela permet Coordination des ondes dynamiques:
- Les ondes s'écoulent automatiquement si les signaux requis manquent
- Résolution des dépendances au moment du démarrage (pas d’ordre hardcoded)
- Les reruns partiels sont reproductibles (cache par clé de signalisation)
- Accroître la granularité de l’UI: chaque vague émet le progrès indépendamment
Le pipeline 16-Wave
L’extraction des cadres-clés est mise en œuvre sous la forme de 7 ondes granulaires pour un meilleur parallèle et une plus grande efficacité du cache.
| Écoule MSC1 Priorité MSC2 Besoins SMC3 Emits SMC4 Temps ESC5 | ||||
|---|---|---|---|---|
| Normaliser la vague | 1000 | - | video.duration, video.fps, video.normalized |
~2s S |
| FFmpegShotDetectionWave | 900 | video.normalized |
shots.detected, shots.count |
~5-10s S |
| La vague de détection IFrame | 850 | video.normalized |
keyframes.iframes_detected, keyframes.iframes_count |
~3s S |
| Voie de sélection des cadres-clés | 840 | shots.detected, keyframes.iframes_detected |
keyframes.selected, keyframes.selected_count |
~1s S |
| La vague d'extraction des thumbnails | 830 | keyframes.selected |
keyframes.thumbnails_extracted |
~5s S |
| KeyframeDeduplicationWave | 820 | keyframes.thumbnails_extracted |
keyframes.deduplicated, keyframes.duplicates_skipped |
~1s S |
| KeyframeFullResExtractionWave | 810 | keyframes.deduplicated |
keyframes.extracted, keyframes.count |
~10s S |
| ClipEmbeddingWave | 800 | keyframes.extracted |
clip.embeddings_ready, clip.embeddings_count |
~30s S |
| ImageAnalysisWave | 790 | keyframes.deduplicated |
keyframes.analyzed, ocr.extracted |
~60s S |
| TitleCreditsDetectionWave | 750 | shots.detected |
title.detected, credits.detected |
~5s S |
| AudioExtractionWave | 650 | video.normalized |
audio.extracted, audio.path |
~30s S |
| Voie de transcription | 600 | audio.extracted |
transcription.complete, transcription.utterance_count |
~120s S |
| Sous-titreExtractionWave | 550 | video.normalized |
subtitles.extracted |
~2s S |
| ChapitreExtractionWave | 500 | video.normalized |
chapters.extracted |
~1s S |
| SceneClusteringWave | 400 | shots.detected |
scenes.detected, scene.count |
~5s S |
| EvidenceGenerationWave | 100 | scenes.detected |
evidence.generated |
~2s S |
Notes:
- Utilisations de ImageAnalysisWave
keyframes.deduplicated(non completM SK1res): Le OCR fonctionne sur les thumbnails ; la sous-titrage de vision utilise le pleinMSC4res lorsqu'il est disponible par routage des capacitésMST5 - Les vagues 3-7 sont les vagues granulaires de l’extraction du cadre-clé " sous
Total pour 2-heure de film: ~10-15 minutes (vsM SK3 heures sans optimisation)
Bien-Keys de signalisation connues
Les signaux sont définis comme des constantes pour la cohérence:
public static class VideoSignals
{
// NormalizeWave signals
public const string VideoDuration = "video.duration";
public const string VideoFps = "video.fps";
public const string VideoNormalized = "video.normalized";
// Shot detection signals
public const string ShotsDetected = "shots.detected";
public const string ShotsCount = "shots.count";
// Keyframe signals
public const string IframesDetected = "keyframes.iframes_detected";
public const string KeyframesSelected = "keyframes.selected";
public const string KeyframesDeduplicated = "keyframes.deduplicated";
public const string KeyframesExtracted = "keyframes.extracted";
// CLIP embedding signals
public const string ClipEmbeddingsReady = "clip.embeddings_ready";
// Scene clustering signals
public const string ScenesDetected = "scenes.detected";
public const string SceneCount = "scene.count";
// Transcription signals
public const string TranscriptionComplete = "transcription.complete";
}
Système de capacité: Modèles lazy & Routage
VideoSummarizer utilise un capacité-architecture basée: détecter GPU une fois au démarrage , télécharger les modèles lentement
Manifeste de modèle (YAML + TypeM SK2Constants sûres)
Les modèles sont définis dans models.yaml. Aucune chaîne magique dans le code
# models.yaml (excerpt)
models:
clip-vit-b32:
name: "CLIP ViT-B/32"
download_url: "https://huggingface.co/openai/clip-vit-base-patch32/resolve/main/onnx/visual_model.onnx"
preferred_providers: [CUDAExecutionProvider, DmlExecutionProvider, CPUExecutionProvider]
components:
ClipEmbeddingWave:
models: [clip-vit-b32]
fallback_chain: [ImageAnalysisWave]
// Type-safe constants (no raw strings)
await coordinator.EnsureModelAsync(ModelIds.ClipVitB32);
await coordinator.ActivateWaveAsync(ComponentIds.TranscriptionWave);
// Route with fallback
var route = await coordinator.RouteWorkAsync(new[]
{
ComponentIds.ClipEmbeddingWave, // Primary (GPU)
ComponentIds.ImageAnalysisWave // Fallback (CPU)
});
Atomes d'efficacité du pipeline
Limitation des vitesses, estimation du temps , et pression d'arrière adaptative permettent à l'interface de rester réceptive tout en maximisant le débit
// Time estimation from actual data
var estimator = CapabilityAtoms.CreateTimeEstimator();
using (estimator.Time("clip_embedding")) { await ProcessAsync(); }
var eta = estimator.GetEstimate("clip_embedding", remaining: 50);
// eta.Estimated, eta.Optimistic, eta.Pessimistic, eta.Confidence
Dossiers du système de capacité complète: Voir
Mostlylucid.Summarizer.Core/Capabilities/pour la détection de GPU, signal pub/subM SK2 contrôleurs d'arrière-pressure , et conception de topologie en filet
Optimisation des clés 1: Déduplication perceptuelle de l'abréviation
Avant de lancer des embeddings CLIP coûteux, VideoSummarizer filtre les cadres visuellement similaires en utilisant Différence hash (dHashM SK1.
Comment fonctionne dHash
public class KeyframeDeduplicationService
{
// dHash parameters: 9x8 grayscale = 64 bits
private const int HashWidth = 9;
private const int HashHeight = 8;
private const int DefaultHammingThreshold = 10;
public async Task<ulong> ComputeDHashAsync(string imagePath, CancellationToken ct)
{
using var image = Image.Load<Rgba32>(imagePath);
// Resize to 9x8 (one extra column for gradient comparison)
image.Mutate(x => x
.Resize(HashWidth, HashHeight)
.Grayscale());
ulong hash = 0;
int bit = 0;
// Compare adjacent pixels horizontally
for (int y = 0; y < HashHeight; y++)
{
for (int x = 0; x < HashWidth - 1; x++)
{
var left = image[x, y].R;
var right = image[x + 1, y].R;
// Set bit if left pixel is brighter than right
if (left > right)
{
hash |= (1UL << bit);
}
bit++;
}
}
return hash;
}
public static int HammingDistance(ulong a, ulong b) =>
BitOperations.PopCount(a ^ b);
}
Exemple de sortie:
Input: 50 keyframe candidates (from codec I-frames)
Deduplication (Hamming threshold 10):
Frame 0: hash=0x8f3a2c1d → KEEP (first frame)
Frame 1: hash=0x8f3a2c1e → SKIP (distance=1 from frame 0)
Frame 2: hash=0x8f3a2c1f → SKIP (distance=2 from frame 0)
Frame 3: hash=0xc7e1b4a2 → KEEP (distance=28 from frame 0)
...
Result: 50 → 30 frames (40% reduction)
Processing saved: ~8 seconds of CLIP inference
Pourquoi cela importe:
- Réduction du cadre ~40% sur le contenu typique
- <1ms par cadre pour le calcul du hash (vsM SK1 200ms pour CLIP)
- Filtre les cadres redundants précédemment Opérations GPU coûteuses
Optimisation des clés 2: Embedding de CLIP en série
Au lieu de traiter une image à la fois, Batches vidéoSummarizer M SK1 images par passeur GPU.
Architecture de traitement par lots
public class BatchClipEmbeddingService
{
private const int ClipImageSize = 224;
private const int DefaultBatchSize = 8; // 8 images per GPU pass
public async Task<Dictionary<int, float[]>> GenerateBatchEmbeddingsAsync(
Dictionary<int, string> framePaths,
IBackpressureController backpressure,
ITimeEstimator estimator,
int batchSize = DefaultBatchSize,
CancellationToken ct = default)
{
var session = await GetOrLoadClipModelAsync(ct);
var results = new ConcurrentDictionary<int, float[]>();
// Pre-index batch for O(1) lookup
var batches = framePaths
.Select((kvp, idx) => (idx, kvp.Key, kvp.Value))
.Chunk(batchSize)
.ToList();
foreach (var batch in batches)
{
// Acquire slot from backpressure controller (adaptive concurrency)
using var slot = await backpressure.AcquireSlotAsync(ct);
using var timer = estimator.Time("clip_batch");
// Create batch tensor [batchSize, 3, 224, 224]
var tensor = new DenseTensor<float>(new[] { batch.Length, 3, ClipImageSize, ClipImageSize });
// Preprocess images into tensor slots
for (int i = 0; i < batch.Length; i++)
{
var (_, frameIndex, path) = batch[i];
PreprocessImageToTensor(path, tensor, slotIndex: i);
}
// Single GPU pass for entire batch
var inputs = new List<NamedOnnxValue>
{
NamedOnnxValue.CreateFromTensor("input", tensor)
};
using var outputResults = session.Run(inputs);
// Extract embeddings, record latency for backpressure adjustment
}
backpressure.RecordLatency(estimator.GetAverageDuration("clip_batch"));
return new Dictionary<int, float[]>(results);
}
}
Comparaison des performances:
Input: 30 keyframes (after deduplication)
Serial processing (1 frame at a time):
30 × 200ms = 6,000ms (6.0 seconds)
Batch processing (8 frames per pass):
4 batches × 350ms = 1,400ms (1.4 seconds)
Speedup: 4.3x
Pourquoi le traitement en lots fonctionne:
- Le parallèle GPU est sous-utilisé avec une seule inference d’image-
- Tensor par lots
[8, 3, 224, 224]Utilise la même mémoire GPU que l'image unique (almost) - ONNX Runtime optimise les opérations de lot internement
Optimisation clé 3: Composition du pipeline
VideoSummarizer n’invente pas de nouveau ImageSommarizer ou AudioSum marizer. Il • les chaînes ils.
Keyframe Sub-Pipeline:Intégration ImageSummarizer
L'extraction du cadre de clés est divisée en 7 vagues granulaires (voir la table des vagues ci-dessusM SK2 Voici le modèle de coordination montrant comment elles se chainent ensemble:
// IFrameDetectionWave → KeyframeSelectionWave → ThumbnailExtractionWave
// → KeyframeDeduplicationWave → KeyframeFullResExtractionWave → ClipEmbeddingWave
// ClipEmbeddingWave coordinates with ImageSummarizer
public class ClipEmbeddingWave : IVideoWave, ISignalAwareVideoWave
{
private readonly BatchClipEmbeddingService _batchClipService;
private readonly IBackpressureController _backpressure;
private readonly ITimeEstimator _estimator;
public IReadOnlyList<string> RequiredSignals => [VideoSignals.KeyframesExtracted];
public IReadOnlyList<string> EmittedSignals => [VideoSignals.ClipEmbeddingsReady];
public async Task ProcessAsync(VideoContext context, CancellationToken ct)
{
var keyframes = context.GetCached<Dictionary<int, string>>("keyframes.paths");
// Batch CLIP embedding with backpressure control
var embeddings = await _batchClipService.GenerateBatchEmbeddingsAsync(
keyframes, _backpressure, _estimator, batchSize: 8, ct);
foreach (var (frameIndex, embedding) in embeddings)
context.KeyframeEmbeddings[frameIndex] = embedding;
}
}
// ImageAnalysisWave runs ImageSummarizer on deduplicated frames
public class ImageAnalysisWave : IVideoWave, ISignalAwareVideoWave
{
public IReadOnlyList<string> RequiredSignals => [VideoSignals.KeyframesDeduplicated];
public async Task ProcessAsync(VideoContext context, CancellationToken ct)
{
var keyframePaths = context.GetCached<List<string>>("keyframes.deduplicated_paths");
foreach (var path in keyframePaths)
{
// Run ImageSummarizer for OCR, vision, captions
var result = await _imageOrchestrator.AnalyzeAsync(path, ct);
context.SetCached($"image_analysis.{Path.GetFileName(path)}", result);
}
}
}
TranscriptionWave: AudioSummarizer Integration
L’extraction et la transcription sonores sont maintenant des signaux distincts-onde de sensibilisationM SK1
// AudioExtractionWave runs first (extracts audio track from video)
public class AudioExtractionWave : IVideoWave, ISignalAwareVideoWave
{
public IReadOnlyList<string> RequiredSignals => [VideoSignals.VideoNormalized];
public IReadOnlyList<string> EmittedSignals => ["audio.extracted", "audio.path"];
public async Task ProcessAsync(VideoContext context, CancellationToken ct)
{
var audioPath = await _ffmpegService.ExtractAudioAsync(
context.VideoPath, context.WorkingDirectory, ct);
context.SetCached("audio.path", audioPath);
}
}
// TranscriptionWave depends on audio.extracted signal
public class TranscriptionWave : IVideoWave, ISignalAwareVideoWave
{
public IReadOnlyList<string> RequiredSignals => ["audio.extracted"];
public IReadOnlyList<string> EmittedSignals => [
VideoSignals.TranscriptionComplete,
"transcription.utterance_count"
];
public async Task ProcessAsync(VideoContext context, CancellationToken ct)
{
var audioPath = context.GetCached<string>("audio.path");
// Run AudioSummarizer pipeline (Whisper + diarization)
var audioProfile = await _audioOrchestrator.AnalyzeAsync(audioPath, ct);
// Extract utterances with speaker info
var turns = audioProfile.GetValue<List<SpeakerTurn>>("speaker.turns");
foreach (var turn in turns ?? [])
{
context.Utterances.Add(new Utterance
{
Id = Guid.NewGuid(),
Text = turn.Text,
StartTime = turn.StartSeconds,
EndTime = turn.EndSeconds,
SpeakerId = turn.SpeakerId,
Confidence = turn.Confidence
});
}
// Run NER on full transcript for entity extraction
var transcript = audioProfile.GetValue<string>("transcription.full_text");
if (!string.IsNullOrEmpty(transcript))
{
var entities = await _nerService.ExtractEntitiesAsync(transcript, ct);
context.SetCached("transcript_entities", entities);
// Emit entity signals by type (PER, ORG, LOC, MISC)
foreach (var group in entities.GroupBy(e => e.Type))
{
context.AddSignal($"transcript.entities.{group.Key.ToLowerInvariant()}",
group.Select(e => e.Text).Distinct().ToList());
}
}
}
}
NER Integration: Reconnaissance des entités désignées
VideoSummarizer extrait des entités désignées de transcriptions en utilisant BERT-based NER M SK1ONNX).
OnnxNerService
public class OnnxNerService
{
// Model: dslim/bert-base-NER (ONNX exported)
// Entities: PER (Person), ORG (Organization), LOC (Location), MISC (Miscellaneous)
public async Task<List<EntitySpan>> ExtractEntitiesAsync(string text, CancellationToken ct)
{
var entities = new List<EntitySpan>();
// Chunk long text (BERT max 512 tokens)
foreach (var chunk in ChunkText(text, maxTokens: 400, overlap: 50))
{
// Tokenize with WordPiece
var tokens = _tokenizer.Tokenize(chunk);
// Run ONNX inference
var inputs = PrepareInputs(tokens);
using var results = _session.Run(inputs);
// Decode BIO tags
var predictions = DecodePredictions(results);
var chunkEntities = ExtractEntitySpans(tokens, predictions);
entities.AddRange(chunkEntities);
}
// Deduplicate entities
return entities
.GroupBy(e => (e.Text.ToLowerInvariant(), e.Type))
.Select(g => g.First())
.ToList();
}
}
Exemple de sortie:
Transcript: "Today we're speaking with John Smith from Microsoft about
their new AI lab in Seattle. The project, codenamed Phoenix, builds
on research from Stanford University."
Entities extracted:
PER: John Smith
ORG: Microsoft, Stanford University
LOC: Seattle
MISC: Phoenix
Signals emitted:
transcript.entities.per = ["John Smith"]
transcript.entities.org = ["Microsoft", "Stanford University"]
transcript.entities.loc = ["Seattle"]
transcript.entities.misc = ["Phoenix"]
Pourquoi le NER est important pour la vidéo:
- Enablez des questions comme "Trouver des vidéos mentionnant Microsoft"
- Liens vers le graphique de l'entité DocSummarizer
- Fournit des métadonnées structurées sans inference LLM
Multi-Clustering des scènes de signalisation
L'utilisation d'embeddings CLIP seuls pour la détection des scènes n'est pas efficace't fonctionne bien. Les cadres clés sont peu nombreux par le design M SK2un changement par shot ), mais les prises de vue sont densesMSC4 Avec des embeddeds CLIP5 pour Scènes MCSK6, une couverture C(~2% un regroupement pure d'emplacements ne produit qu'une scène pour un film d'une durée de 2-heure M.
VideoSummarizer utilise un approche de signal multi- qui combine les signaux pondérés 4 pour une détection robuste des limites de la scène . Les limites sont choisies par Évaluation pondérée déterministe, fin non apprise-àM SK2finMSC3
SceneClusteringWave: Multi-Architecture de signalisation
public class SceneClusteringWave : IVideoWave, ISignalAwareVideoWave
{
// Signal weights for boundary scoring
private const double EmbeddingWeight = 0.4; // CLIP embedding dissimilarity
private const double TranscriptWeight = 0.3; // Semantic shift in transcript
private const double CutTypeWeight = 0.2; // Fade/dissolve detection
private const double TemporalWeight = 0.1; // Time since last scene
// Temporal constraints
private const double MinSceneDuration = 15.0; // Don't split scenes < 15s
private const double MaxSceneDuration = 300.0; // Force split at 5 minutes
private const double TargetSceneDuration = 90.0; // Prefer ~90s scenes
public IReadOnlyList<string> RequiredSignals => [VideoSignals.ShotsDetected];
public IReadOnlyList<string> OptionalSignals => [
VideoSignals.ClipEmbeddingsReady,
VideoSignals.TranscriptionComplete,
VideoSignals.KeyframesDeduplicated
];
public IReadOnlyList<string> EmittedSignals => [
VideoSignals.ScenesDetected,
"scene.count",
"scene.avg_duration",
"scene.clustering_method"
];
private List<(int shotIndex, double score)> ComputeBoundaryScores(VideoContext context)
{
var shots = context.Shots.OrderBy(s => s.StartTime).ToList();
var scores = new List<(int, double)>();
// Build embedding map with nearest-neighbor interpolation
var shotEmbeddings = PropagateEmbeddingsToNearbyShots(context, shots);
// Build transcript windows for semantic shift detection
var transcriptWindows = BuildTranscriptWindows(context, shots, windowSeconds: 10);
for (int i = 0; i < shots.Count - 1; i++)
{
double score = 0;
var currentShot = shots[i];
var nextShot = shots[i + 1];
// 1. Embedding dissimilarity (40%)
if (shotEmbeddings.TryGetValue(i, out var currentEmbed) &&
shotEmbeddings.TryGetValue(i + 1, out var nextEmbed))
{
var similarity = CosineSimilarity(currentEmbed, nextEmbed);
score += (1.0 - similarity) * EmbeddingWeight;
}
// 2. Transcript semantic shift (30%)
if (transcriptWindows.TryGetValue(i, out var currentWords) &&
transcriptWindows.TryGetValue(i + 1, out var nextWords))
{
var overlap = currentWords.Intersect(nextWords).Count();
var union = currentWords.Union(nextWords).Count();
var jaccard = union > 0 ? (double)overlap / union : 0;
score += (1.0 - jaccard) * TranscriptWeight;
}
// 3. Cut type signal (20%) - fades/dissolves suggest scene boundaries
if (currentShot.CutType is "fade" or "dissolve")
{
score += CutTypeWeight;
}
// 4. Temporal pressure (10%) - encourage splits near target duration
var timeSinceLastScene = currentShot.EndTime - GetLastSceneBoundary();
if (timeSinceLastScene > TargetSceneDuration)
{
var pressure = Math.Min(1.0, (timeSinceLastScene - TargetSceneDuration) / 60);
score += pressure * TemporalWeight;
}
scores.Add((i, score));
}
return scores;
}
}
Principales innovations
Présence-Propagation de l'emplacement du voisin: Seules les prises de ~2% ont un emplacement CLIP direct.
Windows sémantiques de la traduction: Construit des fenêtres de mots sur une seconde autour d'une image et détecte les déplacements sémantiques à l'aide d'un distance Jaccard ( un proxye de dérive sémantique bon marché mais robuste
Connaissance du type de coupe: FadeM SK1to-les transitions noires et de dissociation indiquent fortement les limites de la scène
Limite d'adaptation: Au lieu d'un seuil fixeM SK1 sélectionne les limites de la partie supérieure 25% des notes
Contraintes temporelles: Exécute le minimum des scènes et les limites de 15 à
Exemple:
Input: 1881 shots from a 2-hour movie
39 keyframes with CLIP embeddings
2302 utterances from transcript
Boundary scoring per shot:
Shot 45-46: embedding=0.15, transcript=0.32, cut=0.0, temporal=0.0 → score=0.156
Shot 46-47: embedding=0.08, transcript=0.12, cut=0.0, temporal=0.0 → score=0.068
Shot 47-48: embedding=0.35, transcript=0.41, cut=0.2, temporal=0.05 → score=0.388 ← BOUNDARY
...
Adaptive threshold (top 25%): 0.25
Natural boundaries found: 45
Output: 47 scenes (avg 2.6 minutes per scene)
- Min scene: 15.2s
- Max scene: 298.4s
- Total coverage: 100%
Signals:
scenes.detected = true
scene.count = 47
scene.avg_duration = 156.3
scene.clustering_method = "multi_signal_weighted"
Le contrat de signalisation vidéo
VideoSummarizer prolonge le contrat de signalisation d’ImageSum marizer et AudioSommarizer:
public record VideoSignal
{
public required string Key { get; init; } // "scene.count", "transcript.entities.per"
public object? Value { get; init; }
public double Confidence { get; init; } = 1.0;
public required string Source { get; init; } // "SceneClusteringWave"
// Video-specific: time range
public double? StartTime { get; init; }
public double? EndTime { get; init; }
public DateTime Timestamp { get; init; }
public Dictionary<string, object>? Metadata { get; init; }
public List<string>? Tags { get; init; } // ["visual", "scene"]
}
public static class VideoSignalTags
{
public const string Visual = "visual";
public const string Audio = "audio";
public const string Speech = "speech";
public const string Ocr = "ocr";
public const string Motion = "motion";
public const string Scene = "scene";
public const string Shot = "shot";
public const string Metadata = "metadata";
}
Signaux clés émis:
| Système | Source | Description S |
|---|---|---|
video.duration |
Normalisation de la vague | Durée totale en secondes |
video.resolution |
Normalisation de la vague | largeurM SK2 hauteur |
video.fps |
Normalisation de la vague | Taux d'étalonnage |
shots.count |
ShotDetectionWave | Nombre de tirs détectés |
keyframes.count |
KeyframeExtractionWave | |
keyframes.duplicates_skipped |
KeyframeExtractionWave | |
scene.count |
SceneClusteringWave | Segments de scène cohérents |
transcript.entities.per |
TranscriptionWave | Nom des personnes provenant de l'NER |
transcript.entities.org |
TranscriptionWave | Nom de l'organisation MSC2 |
transcript.word_count |
TranscriptionWave | Total des mots dans la transcription |
VideoPipeline: Output RAG
L'article 5 de la Loi sur l'immigration et le statut des réfugiés VideoPipeline convert les signaux vidéo en ContentChunk pour l'indexation RAG:
public class VideoPipeline : PipelineBase
{
public override string PipelineId => "video";
public override IReadOnlySet<string> SupportedExtensions => new HashSet<string>
{
".mp4", ".mkv", ".avi", ".mov", ".wmv", ".webm", ".flv", ".m4v", ".mpeg", ".mpg"
};
private List<ContentChunk> BuildContentChunks(VideoContext context, string filePath)
{
var chunks = new List<ContentChunk>();
// 1. Scene-based chunks (best for video retrieval)
foreach (var scene in context.Scenes)
{
var sceneText = BuildSceneText(context, scene);
var embedding = context.GetCached<float[]>($"scene_centroid.{scene.Id}");
chunks.Add(new ContentChunk
{
Text = sceneText,
ContentType = ContentType.Summary,
Embedding = embedding, // Proper vector column, not metadata
Metadata = new Dictionary<string, object?>
{
["source"] = "video_scene",
["scene_id"] = scene.Id,
["key_terms"] = scene.KeyTerms,
["speakers"] = scene.SpeakerIds,
["start_time"] = scene.StartTime,
["end_time"] = scene.EndTime
}
});
}
// 2. Transcript chunks (1-minute windows)
var transcriptChunks = BuildTranscriptChunks(context, filePath);
chunks.AddRange(transcriptChunks);
// 3. Text track chunks (on-screen text/subtitles)
foreach (var textTrack in context.TextTracks)
{
chunks.Add(new ContentChunk
{
Text = $"On-screen text: {textTrack.Text}",
ContentType = ContentType.ImageOcr,
Metadata = new Dictionary<string, object?>
{
["source"] = "video_ocr",
["text_type"] = textTrack.TextType.ToString(),
["start_time"] = textTrack.StartTime
}
});
}
return chunks;
}
private string BuildSceneText(VideoContext context, SceneSegment scene)
{
var parts = new List<string>();
if (!string.IsNullOrEmpty(scene.Label))
parts.Add($"Scene: {scene.Label}");
parts.Add($"[{FormatTime(scene.StartTime)} - {FormatTime(scene.EndTime)}]");
if (scene.KeyTerms.Count > 0)
parts.Add($"Topics: {string.Join(", ", scene.KeyTerms)}");
// Add utterances in this scene
var sceneUtterances = context.Utterances
.Where(u => u.StartTime >= scene.StartTime && u.EndTime <= scene.EndTime)
.OrderBy(u => u.StartTime);
if (sceneUtterances.Any())
parts.Add($"Speech: {string.Join(" ", sceneUtterances.Select(u => u.Text))}");
return string.Join("\n", parts);
}
}
Exemple de sortie d'un film:
{
"chunks": [
{
"text": "Scene: Opening montage\n[0:00 - 2:34]\nTopics: city, night, traffic\nSpeech: The year is 2049. The world has changed.",
"contentType": "Summary",
"metadata": {
"source": "video_scene",
"scene_id": "abc123",
"key_terms": ["city", "night", "traffic"],
"start_time": 0.0,
"end_time": 154.0
}
},
{
"text": "The detective arrived at the crime scene. Forensics had already processed the area.",
"contentType": "Transcript",
"metadata": {
"source": "video_transcript",
"time_window": "2:34 - 3:34",
"utterance_count": 4
}
},
{
"text": "On-screen text: LOS ANGELES 2049",
"contentType": "ImageOcr",
"metadata": {
"source": "video_ocr",
"text_type": "Title"
}
}
]
}
Caractéristiques du rendement
Temps de traitement (2-film d'une heureM SK1 1080p)
| Étape MSC1 Temps MSC2 Notes ♫ MSC3 ♫ | ||
|---|---|---|
| FFprobe métadonnées | ||
| Détection des coups | MSC2 s MSC3 Filtre de scène FFmpeg S | |
| Extraction des cadres-clés | ~30s ≥ | ≤500 I-frames |
| deduplication dHash | MSC2s MSC3 \MSC4 \ MSC5 \ msc6 \ cadres \ | |
| Établissement d'un CLIP en série | CSK2s | \300 cadres, en série |
| ImageSummarizer OCR | ||
| Extraction de l'audio | MSC2s MSC3 FFmpeg SMC4 | |
| Transcription de murmurer | ||
| Diarisation de l'émetteur | ||
| Extraction de l’NER | ~10s M | BERTM SK4NER sur la transcription P |
| Groupement des scènes | ~5s ≥ | ≤ |
| Production de données probantes | ||
| Total | ~8-10 minutes |
Sans optimisation
| Optimisation | Épargne | |
|---|---|---|
| déduplication dHash | ~40% cadres filtrés CLIP conservé DS= | |
| CLIP en série | MSC2x plus rapide SMC3 | SMC4s conservés SMC5 |
| Composition du pipeline | Réutilise l'imageSummarizerM SK2Vêtes audioSommarizer | |
| Total des économies | ~3-4 minutes |
Utilisation de la mémoire
| Composante | Mémoire |
|---|---|
| CLIP ViTM SK1B/32 ONNX | ~350MB SSK5 |
| Base de whisper | |
| ECAPAM SK1TDNN | ~100MB S |
| BERTM SK1NER | ~500MB S |
| Prêt | ~1.5GB |
L'intégration avec lucidRAG
VideoSummarizer s'inscrit comme un IPipeline pour routage automatique:
// In Program.cs
builder.Services.AddDocSummarizer(builder.Configuration.GetSection("DocSummarizer"));
builder.Services.AddDocSummarizerImages(builder.Configuration.GetSection("Images"));
builder.Services.AddVideoSummarizer(); // NEW
builder.Services.AddPipelineRegistry(); // Must be last
// Auto-routing by extension
var registry = services.GetRequiredService<IPipelineRegistry>();
var pipeline = registry.FindForFile("movie.mp4"); // Returns VideoPipeline
var result = await pipeline.ProcessAsync("movie.mp4");
Extensions supportées:
.mp4,.mkv,.avi,.mov,.wmv,.webm,.flv,.m4v,.mpeg,.mpg
Ce que vous obtiez
- Échantillons RAG de niveau Scene-: Segments cohérents avec les transcriptions , termes clés
- Méthodes de preuve multimodales: Visual (keyframe embeddings), audio (speaker diarizationM SK4texte MSC5OCRMSC6 sous-titresMST7
- Entités désignées: PersonnesM SK1 Organisations , emplacements à partir de la transcription NER
- provenance vérifiable: Tous les signaux ont une vague de source
- Traitement efficace: 10-15 minutes pour un film d’une durée de 2 heures.
Quels sont les coûts?
- Memory GPU ~1.5GB pour tous les modèles ONNX
- ~8-10 traitement des minutes par 2-heure de film
- espace de disques pour les fichiers intermédiaires ( nettoyé automatiquement)
- Complexité: L’orchestrement de cinq pipelines exige une compréhension des dépendances d’ondes
Conclusion
VideoSummarizer démontre que composition du pipeline échelles:
- Réutilisation de pipelines spécialisés: DonM SK1t reinventer ImageSummarizer ou AudioSommarizer. Chain them
- Filtrer avant les opérations coûteuses: coûts de déduplication dHash <1msM SK2 permet d'éviter 40% des conclusions du CLIP
- Opérations GPU par lots: 8 images par passe = ≥3-5x accélération
- Extraire la structure avant le contenu: Shots → Scenes → Evidence (not raw frames SME4 LLMSMA5
- Gestion du modèle lazi: Télécharger les modèles seulement si nécessaire, détecter GPU automatiquement
- Routage réactif: Travail de route vers les composants disponibles , retour gracieux
Le résultat : est de transformer un film d’une heure en un livret de signalisation structuré comportant des scènes, des transcriptions et des entités, ainsi que des embeddings.
- "Trouver des scènes où John Smith discute de Microsoft
- "Pour voir des clips avec le texte de l’écran sur le projet Phoenix
- "Trouver des vidéos similaires à cette scène
Le modèle RAG réduit pour le vidéo:
Ingestion: Video → 16 waves → Signals + Evidence (scenes, transcripts, entities)
Storage: Signals (indexed) + Embeddings (CLIP, voice) + Evidence (chunks)
Query: Filter (SQL) → Search (BM25 + vector) → Synthesize (LLM, ~5 results)
Le système de capacité:
Startup: Detect GPU → Load ModelManifest (YAML) → Initialize SignalSink
Activation: Component requests model → Lazy download → Signal "ModelAvailable"
Routing: Route to best provider → Fallback chain → Backpressure control
Atoms: Rate limiting + Time estimation + Pipeline balancing
Il s'agit de Fuzziness restreinte à l’échelle
- Components probabilistes proposent des signaux: embeddings (CLIPM SK2 hypothèses OCR, prédictions de diarisation
- La cotation déterministe assemble la structure: cotes limites pondérées, sélection de seuilM SK2 contraintes temporelles
- LLM (optionnel) synthétise à partir de données probantes: contexte limité , provenance vérifiable
Le LLM fonctionne sur pré-computed,éléments probants vérifiables, jamais vidéo bruteM SK1
Ressources
lucidRAG Documentation
- VideoSummarizer Library - Code de source
- Documentation de l'NER - Architecture d'extraction des entités
Bibliothèques connexes
- ImageSummarizer - Pipeline d'intelligence visuelle
- AudioSummarizer - Pipeline audio judiciaire
- DocSummarizer - Pipeline de documents
Modèles ONNX
- CLIP ViT-BM SK1 - Emplacements visuels
- ECAPA-TDNN - Emplacements de haut-parleur
- BERT-NER - Reconnaissance des entités désignées
- Les murmurs - Transcription de la parole
Articles connexes
Patterns de base:
- RAG réduit - Le modèle de base
- Modèle de fuzziness restreint - Modèle fondamental
Réduction des mises en œuvre de RAG:
- DocSummarizer - Document RAG avec extraction d'entités
- ImageSummarizer - RAG d'image avec 22- intelligence visuelle des ondes
- Three-Tier OCR Pipeline - escalation de l’OCR
- AudioSummarizer - Caractérisation audio judiciaire
- VideoSummarizer (le présent articleM SK1 - Organisateur de l'intelligence vidéo
- Système de gestion des données - Profilage des données et détermination du schéma
La série
| Partie | Modèle MSC2 Concentration MSC3 | |
|---|---|---|
| 1 | Fuzziness restreinte Composante unique | |
| 2 | MoM Fuzzy restreint | Composantes multiples |
| 3 | Traçage du contexte | Temps / mémoire |
| 4 | Intelligence de l'image | Architecture des vaguesM SK1 22 ondes |
| 4.1 | Three-Tier OCR Pipeline | OCRM SK1 Modèles ONNX, bandes de films |
| 4.2 | AudioSummarizer | Audio médico-légal, diarisation de l'émetteur |
| 4.3 | VideoSummarizer (le présent articleM SK1 | Orchestrement vidéo, lots CLIP, NER |
Suivant: MultiM SK1 Graphique modal RAG avec lucidRAG. Composant les cinq résumés en un graphe de connaissance unifié à l'aide d'une liaison croisée
Toutes les parties suivent la même invariante: Les composantes probabilistes proposent; les systèmes déterministes persistent.