VideoSummarizer-:- Reduzierte RAG für Video-(-Shots-Spots-MSC2-Szenen- -→-Evidenz)- (Deutsch (German))

VideoSummarizer-:- Reduzierte RAG für Video-(-Shots-Spots-MSC2-Szenen- -→-Evidenz)-

Thursday, 15 January 2026

//

24 minute read

StatusIn der Entwicklung als Teil von LucidRAG. Quelle: github-.-com./-scottgal./-lucidrag

Wo das passtVideoSummarizer ist der Orchester über die LucidRAG family-me, , das drei Pipelines zu einem einheitlichen Videoanalyse-Engine zusammenfasst

  • DocSummarizer Dokumente - Extraktion der ( - Wissensgraphen - ) -
  • ImageSummarizer Bildmaterial zur visuellen Intelligenz von (22-, CLIP-Embeddings, ,, OCR-),
  • AudioSummarizer Audio- und akustische Profilierung der Lautsprecherdiarisierung der ,, Transkription des )
  • VideoZusammenfassung ( Dieser Artikel ) Aus dem Video (Aus allen drei Vorlagen ,Aus den Aufnahmen /Aus der Szenestruktur )Aus dem Video
  • DatenZusammenfassung Daten zur Schlussfolgerung des (-Schemas

Alle folgen dem gleichen Reduziertes RAG-Muster:-Extraktsignale einmalig ,-Speichernachweis ,-Synthetisieren mit gebundenem LLM-Eingang .-


Die Verarbeitung eines zweistündigen Filmrahmens mit CLIP-Einbettungen würde Stunden dauern und Hunderte von Dollar in Compute kosten... Der naive Ansatz, jeden Rahmen an einen Vision LLM-- zu senden, ist noch schlimmer.

VideoSummarizer löst dies mit drei Optimierungen

  1. Wahrnehmungshasch-Deduplikation Überspringen Sie optisch ähnliche Rahmen vor teurem ML
  2. Batch CLIP Einbettung Verarbeitung von Bildern pro GPU-Pass statt eines Geschwindigkeitsmessers
  3. Zusammensetzung der Pipeline Verkettter ImageSummarizer für KeyframesVerknüpfter AudioSummarinizer für Speech-Verteiler für Entitäten

Das Ergebnis: ein Film-Prozess in einer Stunde in Minuten, nicht Stunden, sondern dieselben Architekturprinzipien wie ImageSummarizer und AudioSummarizer,, aber zusammengesetzt in eine einheitliche Videoanalyse Pipeline

Kerninsight«:- Video ist Aufnahmen Audio Audio Text Verarbeitung jeder Domäne mit spezialisierten Werkzeugen Zusammenführen der Ergebnisse in kohärente Szenen

  • Prozessstruktur zuerst (-Schnitte, ,-Schneiderahmen und Audiosegmente
  • Extrahieren Sie cross---modale Signale einmal (-Einbettungen ,-Transkriptionen ,-Entitäten

Begriffsbestimmungen

  • Schuss Die Kamera nimmt zwischen den Schnitten die ( aus der FFmpeg-Szene auf.
  • Szene Angrenzende Gruppe von Schüssen, die eine kohärente Einheit bilden
  • Nachweise - (start_time, end_time) Signalgeber für die Provenienz

Schlüssel-ML-Modelle verwendet

  • KLEIN Kontrastive Sprache von OpenAI-'Bildvorhersage-Training;® erzeugt T512--dimensionale Einbettungen, die visuelle Semantik kodieren
  • Flüstern OpenAI-''s SpracherkennungsmodellTM;« transkribiert Audio in Text mit Zeitstempeln
  • BERT--NER Benannte Entitätserkennung: ;® extrahiert Menschen, Organisationen und Orte aus Text
  • ONNX Laufzeit --Cross---Plattform ML-Inferenzen;-Führt Modelle auf CPU-/-GPU ohne Rahmensperre-in

Dieser Artikel umfasst:

  • Wie VideoSummarizer drei Pipelines orchestriert.ImageSummarinizer
  • Wellenarchitektur:-Wellen von der Normalisierung bis zur Evidenzgeneration
  • KapazitätssystemLazy-Modell-Downloads - GPU-Detektion - Reaktives Routing
  • Batch CLIP-Optimierung für Keyframe-Embeddings schneller
  • Perzeptuelle Hash-Deduplizierung
  • Multi---Signalszenenclustering Einbettung Ausgeschnittener Typ +-Intertemporaler Typ )
  • NER Integration für Entity-Extraktion aus Transkripten
  • Ephemere Atome für die Geschwindigkeitsbegrenzung – Zeitschätzung – , und Gegendruck
  • Ausgabe-:-Szenen - Aufnahmen von , - Transkripte - S, - Textspuren als RAG-Beweis

Verwandte Artikel:


Das Problem: Video ist teuer

BenchmarksAnzahl der unten auf AMD gemessenen Werte (16-Core, )Core und /Core gemessen NVIDIA A®4000Corm (16Corn )Core /Core RAM /Core-MVMe,Corr 1080P H..264,C Whisper Base .C Ihre Kilometerzahl wird variieren .

Ein typischer Film enthält

  • Rahmen (2, Stunden bei .24, fps, ),
  • Audiostunden ,( , Speech , , , Musik , , , Effekte, ) ,
  • Mehrere Textebenen ,( , Untertitel , , , Credits , , , auf dem Bildschirm

Der Strohmann nähert sich an, und der Körper tut dies, aber er setzt die Skala.

  • KLIP-Einbettung pro Rahmen Stunden
  • Vision LLM pro Rahmen :~2S× -170,000M= Stunden (Cloud Vision API ballpark:Clud Vision

Selbst mit Keyframe-Extraktion (-Say-,-Sages, die immer noch Sekunden der seriellen CLIP-Inferenz .-Segment

Traditioneller AnsatzAusführende Keyframes aus , Aussenden an Vision LLM-, Ausstellen Hoffnung für die besten "

ProblemstellungDieses Brennen berechnet auf redundanten Frames.Viele Keyframes sind optisch ähnlich.), , verarbeitet sie seriell ,( ,GPU-Leerlauf zwischen Frames und verpasst Audiosignale vollständig.

LösungMehrstufiges Filtern von ,, Batch-Verarbeitung und Pipelinezusammensetzung


Die VideoSummarizer Architektur

VideoSummarizer implementiert Reduzierte RAG für Video mit einer dreistufigen Reduktion

flowchart TB
    subgraph Input["Video File (.mp4, .mkv, etc.)"]
        V[Video Stream]
        A[Audio Stream]
    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]
    end

    subgraph Stage2["Stage 2: Content Extraction"]
        IS[ImageSummarizer<br/>CLIP, OCR, Vision]
        AS[AudioSummarizer<br/>Whisper, Diarization]
        NER[NER Service<br/>Entity extraction]
    end

    subgraph Stage3["Stage 3: Scene Assembly"]
        SC[SceneClusteringWave<br/>CLIP similarity]
        EV[EvidenceGenerationWave<br/>RAG chunks]
    end

    V --> N --> SD --> KE
    KE --> IS
    A --> AS
    AS --> NER
    IS --> SC
    NER --> SC
    SC --> EV

    style Stage1 stroke:#22c55e,stroke-width:2px
    style Stage2 stroke:#3b82f6,stroke-width:2px
    style Stage3 stroke:#8b5cf6,stroke-width:2px

Beweise produzierte Artefakte

Bevor du in die Implementierung eintauchst, hier', was du bekommst, das Ausgabeschema von :

Artefakt Hauptfelder Quelle |----------|------------|--------| | Szene | id, start_time, end_time, key_terms[], speaker_ids[], embedding[512] SzeneClusteringWave | Schuss | id, start_time, end_time, cut_type, keyframe_path SchussdetektionWave | Gebrauchstauglichkeit | id, text, start_time, end_time, speaker_id, confidence TranskriptionWave | TextTrack | id, text, start_time, text_type ,(-title , /-credit , /-Subtitle-, /-Ocr , , )-SubtituteExtraktionWave ,|-Extraktion | Schlüsselrahmen | id, timestamp, frame_path, dhash, clip_embedding[512] Schlüsselrahmen-Extraktionswelle

Jedes Artefakt enthält HerkunftQuelle Welle ,Verarbeitungszeitstempel,Beurteilung der Konfidenzwerte.Dies ist das -"Beweisbuch, das nachgelagerte RAG Abfragen auf dem ..betätigen

Die Signal--.Aware Wave Pipeline

VideoSummarizer verwendet eine Signal---basierte Wellenarchitektur wobei jede Welle ihre Signalverträge explizit deklariert

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; }
}

Dies ermöglicht dynamische Wellenkoordination:

  • Wellen überspringen automatisch, wenn erforderliche Signale fehlen
  • Abhängigkeiten lösen sich zur Laufzeit auf
  • Partielle Reruns sind reproduzierbar.
  • UI progress granularity.:, jede Welle sendet unabhängig Fortschritt aus

Die Pipeline mit der Wave

Die Keyframe-Extraktion ist als 7 granulare Wellen für eine bessere Parallelität und Cacheeffizienz implementiert.

, Priorität , benötigt , emittiert Zeit ,
NormalisierenWave 1000 - video.duration, video.fps, video.normalized , , , ~2 , S, ,
FFmpegShotDetectionWave 900 video.normalized shots.detected, shots.count , , , ~5-10 , S, ,
IFrameDetectionWave 850 video.normalized keyframes.iframes_detected, keyframes.iframes_count , , , ~3 , S, ,
SchlüsselrahmenauswahlWave 840 shots.detected, keyframes.iframes_detected keyframes.selected, keyframes.selected_count , , , ~1 , S, ,
ThumbnailExtraktionWave 830 keyframes.selected keyframes.thumbnails_extracted , , , ~5 , S, ,
KeyframeDeduplicationWave 820 keyframes.thumbnails_extracted keyframes.deduplicated, keyframes.duplicates_skipped , , , ~1 , S, ,
KeyframeFullResExtraktionWave 810 keyframes.deduplicated keyframes.extracted, keyframes.count , , , ~10 , S, ,
ClipEmbeddingWave 800 keyframes.extracted clip.embeddings_ready, clip.embeddings_count , , , ~30 , S, ,
BildanalyseWave 790 keyframes.deduplicated keyframes.analyzed, ocr.extracted , , , ~60 , S, ,
TitelCreditsDetectionWave 750 shots.detected title.detected, credits.detected , , , ~5 , S, ,
AudioExtractionWave 650 video.normalized audio.extracted, audio.path , , , ~30 , S, ,
TranskriptionWave 600 audio.extracted transcription.complete, transcription.utterance_count , , , ~120 , S, ,
UntertitelExtraktionWave 550 video.normalized subtitles.extracted , , , ~2 , S, ,
KapitelExtraktionWave 500 video.normalized chapters.extracted , , , ~1 , S, ,
SzeneClusteringWave 400 shots.detected scenes.detected, scene.count , , , ~5 , S, ,
EvidenzerzeugungWave 100 scenes.detected evidence.generated , , , ~2 , S, ,

Anmerkungen

  • ImageAnalysisWave verwendet keyframes.deduplicated Die OCR läuft auf Thumbnails mit der Bildunterschrift ; verwendet Full--, wenn sie über das Funktionsrouting verfügbar ist.
  • Die Wellen sind die "-Tastenrahmenextraktion, die Sub---Pipeline und Granularwellen für den Cache-Effizienzbereich.

Insgesamt für Film,: , , ~10-15 Minuten , ( , vs , . , Stunden ohne Optimierung

Gut bekannte Signalschlüssel

Signale werden als Konstanten für die Konsistenz definiert.

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";
}

Capability System- > :- > Faule Modelle- >&- > Routing

VideoSummarizer verwendet eine Fähigkeit ,--basierte ArchitekturDownload Modelle lazily, route arbeiten an verfügbaren Komponenten

Modell-Manifestierung (-YAML Typ --Safe Constants

Modelle sind definiert in models.yamlKeine magischen Zeichenfolgen im 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)
});

Rohrleitungseffizienz-Atome

Rate limiting-,-Zeitschätzung und adaptiver Gegendruck halten die Benutzeroberfläche responsiv, während sie den Durchsatz maximieren.

// 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

Volle Kapazität System docsSiehe auch: Mostlylucid.Summarizer.Core/Capabilities/ für die Erkennung von GPU-Signalen pub, /, Sub,, Gegendruckregler und Mesh Topologie Design


Schlüssel-Optimierung - Wahrnehmungshash-Deduplizierung

Vor dem Ausführen von teuren CLIP-Einbettungen Filtert VideoSummarizer visuell ähnliche Frames mit Unterschieds-Hash.

Wie dHash funktioniert

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);
}

Beispielausgabe

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

Warum das so wichtig ist

  • Rahmenverkleinerung über typische Inhalte
  • <1-ms pro Rahmen für die Hash-Berechnung
  • Filtert redundante Rahmen Vorher teure GPU-Operationen

Schlüssel-Optimierung - Batch CLIP Einbetten

Anstatt ein Bild zu einem Zeitpunkt zu verarbeiten, werden VideoSummarizer Batches pro GPU-Pass

Batch Verarbeitungsarchitektur

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,
        int batchSize = DefaultBatchSize,
        CancellationToken ct = default)
    {
        var session = await GetOrLoadClipModelAsync(ct);
        var results = new Dictionary<int, float[]>();

        // Pre-index batch for O(1) lookup (not batch.IndexOf!)
        var batches = framePaths
            .Select((kvp, idx) => (idx, kvp.Key, kvp.Value))
            .Chunk(batchSize);

        foreach (var batch in batches)
        {
            // Create batch tensor [batchSize, 3, 224, 224]
            var tensor = new DenseTensor<float>(new[] { batch.Length, 3, ClipImageSize, ClipImageSize });

            // Preprocess images in parallel (simplified; production uses vectorised span copy)
            Parallel.ForEach(batch, item =>
            {
                var (batchIdx, frameIndex, path) = item;
                var localIdx = batchIdx % batchSize;
                PreprocessImageToTensor(path, tensor, localIdx); // ImageSharp pixel buffers
            });

            // Single GPU pass for entire batch
            var inputs = new List<NamedOnnxValue>
            {
                NamedOnnxValue.CreateFromTensor("input", tensor)
            };

            using var outputResults = session.Run(inputs);
            // Extract embeddings from batch output...
        }

        return results;
    }
}

Leistungsvergleich

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

Warum Batch-Verarbeitungsarbeiten?

  • GPU-Parallelität wird mit single.--Bildableitung unterbewertet
  • Tensor für Batch [8, 3, 224, 224] verwendet denselben GPU-Speicher wie einzelnes Bild
  • ONNX Runtime optimiert Batch-Operationen intern

Schlüssel-Optimierung 3:-Pipeline Zusammensetzung

VideoSummarizer nicht neu erfinden ImageSummarinizer oder AudioSummaraizer nicht Ketten sie, .,

Schlüsselrahmen-Sub---Pipeline-:-ImageSummarizer Integration

Die Keyframe-Extraktion ist aufgeteilt in .7 granulare Wellen . ( , siehe Welle Tabelle oben . ). Hier zeigt das Koordinationsmuster, wie sie zusammenketten .

// IFrameDetectionWave → KeyframeSelectionWave → ThumbnailExtractionWave
// → KeyframeDeduplicationWave → KeyframeFullResExtractionWave → ClipEmbeddingWave

// ClipEmbeddingWave coordinates with ImageSummarizer
public class ClipEmbeddingWave : IVideoWave, ISignalAwareVideoWave
{
    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 (3-5x faster than serial)
        var embeddings = await _batchClipService.GenerateBatchEmbeddingsAsync(
            keyframes, 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);
        }
    }
}

TranskriptionWave-:-AudioSummarizer Integration

Audio-Extraktion und Transkription sind jetzt separates Signal.

// 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, benannte Anerkennung des Unternehmens

VideoSummarizer extrahiert benannte Entitäten aus Transkripten unter Verwendung von NER-BERT--.

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();
    }
}

Beispielausgabe

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"]

Warum NER für Video wichtig ist

  • Ermöglicht Abfragen wie z.B. Videos finden, die Microsoft erwähnen
  • Links zu DocSummarizer Entity Graph
  • Bietet strukturierte Metadaten ohne LLM-Schlussfolgerung

Multi---Signal Szene Clustering

Die Verwendung von CLIP-Einbettungen allein für die Szeneerkennung funktioniert gut – - ?keyframes sind spärlich durch Design – eins pro Schusswechsel –), , aber die Aufnahmen sind dicht – . ? Mit 39 ?embeddings für 1881 .(~2% ?coverage ?), ? pure Einbettung Clustering produziert nur eine 1 ?Szene für einen 10stündigen Film - . ?

VideoSummarizer verwendet eine Multi---Signalanflug Das kombiniert gewichtete Signale für robuste Szenengrenzdetektion.

SceneClusteringWave-:-Multi---Signalarchitektur

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;
    }
}

Schlüsselinnovationen

  1. Nächstgelegene --Benachbarte, die Verbreitung einbettenDer neue Ansatz propagiert Einbettungen in nahe gelegene Aufnahmen innerhalb von Sekunden unter Verwendung der zeitlichen Nähe Gewichtung

  2. Transcript Semantic WindowsÜberlappung oder Einbettung von Drift kann verwendet werden, wenn verfügbar.

  3. Typ-Bewußtsein ausschneidenÜbergänge auflösen stark zeigen Szenegrenzen an.

  4. Adaptive ThresholdingAnstelle einer festen Schwelle wählt , die Grenzen aus dem oberen Bereich aus.

  5. Temporale Einschränkungen: enforces minimum MS K1 s Szenen und Kräfte Grenzen bei maximum 5- minute

Beispiel:

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"

Der Videosignalvertrag

VideoSummarizer erweitert den Signalvertrag von ImageSummarinizer und AudioSummaraizer

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";
}

Schlüsselsignale emittiert

Signal Quelle Quelle Beschreibung |--------|--------|-------------| | video.duration Gesamtdauer in Sekunden | video.resolution Breite ×Höhe | | video.fps NormalisierenWave Bildrate Bilddaten | shots.count Anzahl der gefundenen Schüsse | keyframes.count Eindeutige Keyframes nach der Dedup-Version | keyframes.duplicates_skipped KeyframeExtractionWave Frames mit dHash gefiltert | scene.count Szenen-ClusteringWave-Segmente für die Kohärente Szene | transcript.entities.per Name der Person von NER | transcript.entities.org TranskriptionWave - Name der Organisation | transcript.word_count TranskriptionWave - Gesamte Wörter in Transkripten


VideoPipeline-Ausgabe der RAG

Das VideoPipeline wandelt Videosignale in ContentChunk für RAG-Indizierung

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);
    }
}

Beispielausgabe für einen 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"
      }
    }
  ]
}

Leistungsmerkmale

Verarbeitende Zeit (2--Stundenfilm ,-Film 1080-Film-Film

Anmerkung: |-------|------|-------| Metadaten der FF-Probe-Datenbank ]|-Schußdetektion ®|-S~10-SUS-Suss-Szenenfilter ?|-Sinus Schlüsselrahmenextraktion - | - ~30 -- | -- 500 -- I- -- Rahmen DHash-Deduplizieren von |, ~0.5, MSC3, MCSK4, →, MSR6, Rahmen |, Batch CLIP Einbettung von |, ~60, MSC3, MCSK4, R,, L8 und | Bild-Summarizer OCR Bild-summarisator OCR Formate mit Texten Audioextraktion |, ~30, MSC3, FFmpeg | Whisper-Transkription |, ~180, MSC3, MSKK4, Sprechstunden Lautsprecher-Diarisierungs-Diariierungs-diarisations-Darialisierungs-|.60.|. |-NER-Extraktion - |-10-s - |-BERT.--NER auf Transkript - MSC5- Szenen-Clustering mit |, ~5 und |, MKS4, Evidenz-Generierung von |, ~2, MSC3 und | | Insgesamt | ~8-10, Minuten | |

Ohne Optimierungen

Optimierung von Einsparungen |--------------|---------| DHash-Deduplikation mit gefilterten MSK-Frames, die CLIP gespeicherten | |, Batch CLIP, schneller und schneller 3-5, gespeicherte |, |-Rohrleitungszusammensetzung |-Rüstungen BildSummarizer /-Rauschen Schalldämmungswellen | Einsparungen insgesamt | ~3-4, Minuten |

Speichernutzung

Speicher-Speicher-Modul-Modul |-----------|--------| KLIP-ViT--B-MSC2B-ONNX-MSSK3B-M~350MB-| Flüsterbasen aus | ECAPA--, TDNN, |, 100MB, |, ]|-BERT---NER ♪|-Bert-500-MB | Höhe | ]~1.5-GB |


Integration mit lucidRAG

VideoSummarizer registriert sich als IPipeline für automatisches Routing

// 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");

Unterstützte Erweiterungen

  • .mp4, .mkv, .avi, .mov, .wmv, .webm, .flv, .m4v, .mpeg, .mpg

Was man bekommt

  • Scene---Level RAG BrockenKohärente Segmente mit Transkripten – Schlüsselbegriffe – Lautsprecher-IDs
  • Multi‐--modale NachweiseBildliche Einbettungen von ),, Audio-Diarisierung des Lautsprechers, ),, Text, Untertitel
  • Benannte StellenMenschen – Organisationen – Standorte von transcript NER
  • Prüfungsfähige HerkunftJedes Signal hat Quellwellen, ,, Konfidenzintervall
  • Effiziente Verarbeitung10-15 Minuten für einen , 2-hour Film nicht Stunden

Was es kostet

  • GPU-Speicher für alle ONNX Modelle
  • Verarbeitende Minuten pro Stunde Film
  • Speicherplatz für intermediäre Dateien automatisch gesäubert
  • KomplexitätDas Orchestrieren von drei Pipelines erfordert Verständnis der Wellenabhängigkeiten

Schlußfolgerung

VideoSummarizer zeigt, dass Rohrleitungszusammensetzung Skalierungen

  1. Wiederverwendung spezialisierter PipelinesNicht neu erfinden ImageSummarizer oder AudioSummarinizer
  2. Filtern vor teurem BetriebKosten der dHash-Deduplikation von <1,ms,,, speichert die CLIP-Inferenz
  3. Batch GPU OperationenBeschleunigen von Bildern pro Pass
  4. Struktur vor dem Inhalt extrahieren:-Schieße , →-Szenen , →-Evidenz , nicht rohe Rahmen ,→-LLM-)
  5. Faules ModellmanagementDownloaden Sie Modelle nur, wenn sie benötigt werden.
  6. Reaktives RoutingRoutenarbeiten zu den verfügbaren Komponenten

Das Ergebnis: Ein Film aus der 2-hour wird zu einem strukturierten Signalbuch mit Szenen und Transkripten von ,.Entitäten und Embeddings, die für RAG-Abfragen wie z.B. : bereit sind.

  • Finden Sie Szenen, in denen John Smith Microsoft diskutiert
  • Videoclips mit einem Bildschirmtext über das Phoenix-Projekt anzeigen
  • Videos finden, die dieser Szene ähnlich sind

Das reduzierte RAG-Muster für Video

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)

Das Kapazitäts-System-:

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

Das ist Beschränkte Fuzziness im Maßstab

  • Probabilistische Komponenten schlagen Signale vorEinbettungen von (CLIPP-),C OCR-Hypothesen – Diarisierungsraten
  • Deterministische Scoring montiert StrukturBewertete Grenzpunkte des ,, Auswahl der Schwellen, zeitliche Einschränkungen
  • LLM synthetisiert aus BeweismaterialBegrenzter Kontext Berechenbare Herkunft

Die LLM arbeitet auf pre-.--berechnete Evidenz , nicht Rohvideo , sondern


Aufkommen

lucidRAG-Dokumentation

Verwandte Bibliotheken

ONNX-Modelle

Verwandte Artikel

Kernmuster

Reduzierte RAG-Implementierungen


Die Reihe

Teil , Muster , Fokus
1 Beschränkte Fuzziness Einteilige Komponente
2 Beschränkte Fuzzy MoM Mehrere Komponenten
3 Kontext schleppen Zeit-Speicher (MSK)
4 Bildintelligenz Wellenarchitektur,, Wellen 22, Wellen
4.1 Drei---Tier OCR Pipeline Filmreisen mit ONNX-Modellen
4.2 AudioSummarizer Forensische Audio-Diarisierung von Lautsprechern
4.3 VideoSummarizer Dieser Artikel Video-Orchestrierung: ,, Batch CLIP-,, NER

NächsterMehrdimensionaler Graph RAG mit lucidRAG®- , der alle vier Zusammenfassungen zu einem einheitlichen Wissensgraphen zusammenfasst, wobei cross-- modale Einheit die Verbindung zwischen . ,

Alle Teile folgen dem gleichen Invariant. probabilistische Komponenten schlagen vor deterministische Systeme bestehen fort..

Finding related posts...
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.