Image Summarizer: En begränsad fuzzy Image RAG Engine (Svenska (Swedish))

Image Summarizer: En begränsad fuzzy Image RAG Engine

Tuesday, 06 January 2026

//

28 minute read

delar 1-3 beskriven begränsad oförklarhet som ett abstraktt mönster. Detta artikel applicerar dessa mönste på en arbetsbildanalys som demonstrerar principerna i praktiken

NOTET: Ändå tända systemet. Men det finns nu en version för ', som även CLIs version. It works PRETTY well but some edges to smooth out

Läsareführer

Detta artikel tjänar flera syften. Navigera till vad du är intresserad av

  • Arkitekturmuster → Se "Vavelarkitekturen
  • OCR-detailler → Se Del 4.1: OCR Pipeline för djupa tekniska nedbrytningar
  • CLI-användning → Spring till " CLI: s apparat
  • GUI-fenskaper → Se sektionen "Desktop GUI
  • Implementering → Kodexexempler över hela , full source på GitHub

Motion Stripp

ImageSummarizer är en RAG-inspegningskanal för bilder som extraherar strukturerade metadata, ,, text, ,, captioner och visuella signaler med hjälp av architecture based wave-. Systemet skalar upp från snabb lokal analys (Florence-2 ONNXM SK3 till Vision LLMs endast när det behövs .

Nyckelprinciper

  • Ingen självständighet (modeller bestämmer aldrig utdragsvägar)
  • Inget naturliga-språks tillstånd (signaler är inskrivna , inte prosaM SK3
  • Modell föreslår signaler ; deterministisk policy bestämmer vad som förblir

ImageSummarizer visar att multimodella LLM kan användas utan att ge upp determinism. sannolikhet föreslår , determinism förblir.

Designregler

  • Modeller konsumerar aldrig andra modeller
  • Naturspråk är aldrig ett tillstånd
  • Escalation är deterministiska trösklar
  • Varje utgång bär på förtroende + provenance

Licensen : Olicenctioner frigöra Att bygga .NET


Vad det gör

Pipelinen extraherar strukturerade metadata från bilder för RAG-system.

  • Utvunnet text (dree-tier OCRM SK2 Tesseract → FlorenceMska4 ONNX mska5 Vision LLM fallbackM Ska6
  • Farbpalette (beräknat, inte gisst
  • Kvalite mätningar ( skarphet , svaghet, exponering
  • Typklassificering (Foto, Synkronis , Diagram , Meme )
  • Rödselanalys ( för animerade bilderM SK1
  • Optionellt undertecknande (Florens -2 lokal eller Vision LLM, begränsad av beräknings faktaM SK3
  • Semantiska inbäddar ( för vektorsökningen)

Det viktigaste ordet är strukturerad. Varje utgång har tillförlitlighetsvärdena , källans antagning, , och bevispunkter,. Ingen modell är den enda källan till sanning,

Tre-Tier OCR-strategi

Deep Dive: Pipelinet för OCR är tillräckligt komplex för att äga en egen artikel Del 4.1: De tre-Tier OCR Pipeline för den kompletta tekniska uppsättningen inklusive EAST, CRAFTM SK1 Real-ESRGANMska3 CLIPMske4 och optimering av filmsträngarnaM Ska5

Systemet använder en escalationsstrategi på tre nivåer för textextraktion.

Niveau Methode Tempo kostnaderna МSK4 bäst för
1 Tesseract ~50ms Gratis Rent МSK4 högt - motstridiga text
2 Florence-2 ONNX ~200ms Gratis МSK3 Stiliserade fonter , inga API-kostnader
3 Vision LLM ~1-5s $0.001-0.01 komplext

Intelligent ruttering

Detektorn för text (Öst, KRATT, ~20-30msM SK2 bestämmer den optimala vägen

  • FAST-väg: Florensen -2 bara (~100ms)
  • BALANCED-väg: FlorenceM SK1 + Tesseract-röstning
  • QUALITY-vägMulti- -frameanalys Vision LLM (~1-5 s )

stödja ONNX-modeller

  • Real-ESRGAN: ♫ 4× super ♫ ♫
  • CLIP: Semantiska inbäddar för bildsökningar (~100msM SK2 gratis)

Resultat: ~1.16GB av lokala ONNX-modeller som hanterar 85%+ av bilder utan API-kostnader


Se det i praktiken

Motion Detection & Animationsanalys

Katt på soffan

$ imagesummarizer demo-images/cat_wag.gif --pipeline caption --output text
Caption: A cat is sitting on a white couch.
Scene: indoor
Motion: MODERATE object_motion motion (partial coverage)

Rörningsfraser släpps ut endast när de stödjers av optiska flöde mätningar och ramdeltas.; annars faller systemet tillbaka till neutrala beskrivare.

Meme & Tilltving av undertitel

Anchorman-memen

$ imagesummarizer demo-images/anchorman-not-even-mad.gif --pipeline caption --output text
"I'm not even mad."
"That's amazing."
Caption: A person wearing grey turtleneck sweater with neutral expression
Scene: meme
Motion: SUBTLE general motion (localized coverage)

Den undertiteln-medveten ramduplikering detekterar textförändringar i botten 25% av bilderna , viktande ljusa pixels | ( | vit | МSK4 | gul text |) | mer kraftigt |

Frame Strip Technology

För animerade GIFs med subtitler, skapar redskapet horisontella ramsträngar för Vision LLM-analysen . Tre moder fokuserar på olika användningsområden:

Text-Omnär Strep (NEW

Den mest effektiva metoden extraherar bara textbubblade lådor, dramatiskt minskar tokenskostnader:

Text-Omnär Strep

$ imagesummarizer export-strip demo-images/anchorman-not-even-mad.gif --mode text-only
Detecting subtitle regions (bottom 30%)...
  Found 2 unique text segments
Saved text-only strip to: anchorman-not-even-mad_textonly_strip.png
  Dimensions: 253×105 (83% token reduction)
tillvägagångspunkt
Hela tallrikar (10)
OCR-stripp (2 frammänder ) m M600×185 M m~300 мSK7 mellan
Text- endast fläck 253×105 ~50 låg

Hur det fungerar: OpenCV detekterar subtitrets områden | ( | botten |30%), | tröskeln ljusa pixeler | МSK3 | vit | / | gul text |), | extraherar smala bångboxar | , | och duplicerar baserat på textförändringar ♫ . | Vision LLM får bara textregionen ♫ , | bevarar all subtitretts innehåll medan den eliminerar bakgrundspixeln ♫.

OCR-läger (tydliga ändringar i texten | | - | МSK2 | rader reducerade till |

OCR-stripp

$ imagesummarizer export-strip demo-images/anchorman-not-even-mad.gif --mode ocr
Deduplicating 93 frames (OCR mode - text changes only)...
  Reduced to 2 unique text frames
Saved ocr strip to: anchorman-not-even-mad_ocr_strip.png
  Dimensions: 600x185 (2 frames)

Motion Mode-stripp (keyframes för rörelsesuppfattningar

Motion Stripp

$ imagesummarizer export-strip demo-images/cat_wag.gif --mode motion --max-frames 6
Extracting 6 keyframes from 9 frames (motion mode)...
  Extracted 6 keyframes for motion inference
Saved motion strip to: cat_wag_motion_strip.png
  Dimensions: 3000x280 (6 frames)

Detta låter Vision LLMs läsa alla undertitel i en enda API sändning.

Varför inte bara tända det?

Det slår " bara att döma upp det med en gränsmodell " av samma anledning som ett X-ray slår på berättelsen : modellen blir aldrig tillfrågad att fylla på luckor M SK4 Den får en stängd ledger, -, mätda färger, ,, spårade rörelser,,, avvikta subtitret frammarmor, MSC8, OCR-förtroende, M SK9 och bara återskapa det som substratet redan innehåller, MSL10, när GPT, MLS11, dömer upp en bild, Msl12, det, MKL13, gissningen, MCL14, när ImageSummarizer gör, MNL15, det och MKL16, sammanfattande signaler som redan finns, MHL17.


Architecturen i vågen

Systemet använder våg---baserad bränsleledning där varje våg är en oberoende analysator som producerar utskrivna signaler vågor sätter igång i prioritära ordning. ( lägre talet kör först., och senare vågor kan läsa signaler från tidigare

Tilrättelse: Plötsligt ♫ ♫ 10 rinner innan plötslig ♫

flowchart TB
    subgraph Wave10["Wave 10: Foundational Signals"]
        W1[IdentityWave - Format, dimensions]
        W2[ColorWave - Palette, saturation]
    end

    subgraph Wave40["Wave 40: Text Detection"]
        W9[TextLikelinessWave - OpenCV EAST/CRAFT]
    end

    subgraph Wave50["Wave 50: Traditional OCR"]
        W3[OcrWave - Tesseract]
    end

    subgraph Wave51["Wave 51: ML OCR"]
        W8[MlOcrWave - Florence-2 ONNX]
    end

    subgraph Wave55["Wave 55: ML Captioning"]
        W10[Florence2Wave - Local captions]
    end

    subgraph Wave58["Wave 58: Quality Gate"]
        W5[OcrQualityWave - Escalation decision]
    end

    subgraph Wave70["Wave 70: Embeddings"]
        W7[ClipEmbeddingWave - Semantic vectors]
    end

    subgraph Wave80["Wave 80: Vision LLM"]
        W6[VisionLlmWave - Cloud fallback]
    end

    Wave10 --> Wave40 --> Wave50 --> Wave51 --> Wave55 --> Wave58 --> Wave70 --> Wave80

    style Wave10 stroke:#22c55e,stroke-width:2px
    style Wave40 stroke:#06b6d4,stroke-width:2px
    style Wave50 stroke:#f59e0b,stroke-width:2px
    style Wave51 stroke:#8b5cf6,stroke-width:2px
    style Wave55 stroke:#8b5cf6,stroke-width:2px
    style Wave58 stroke:#ef4444,stroke-width:2px
    style Wave70 stroke:#3b82f6,stroke-width:2px
    style Wave80 stroke:#8b5cf6,stroke-width:2px

Prioriterad ordning ( lägre löper först): 10 ♫ ♫ → ♫

Det här är Begränsad Fuzzy MoM tillämpat på bildanalys: flera proposer publicerar till ett gemensamt substrat (the AnalysisContext), och den slutgiltiga utgången aggregerar sina signaler

Nyckelvågor

  • Textliknande våg (NEW
  • OcrWave: Traditionell Tesseract OCR för ren text (Tier 1)
  • MlOcrWave: Florence-2 Runner lokalt på ONNX (~200msM SK3 hanterar stiliserade fonter |
  • Florence2Sål: Lokala ML-captionering när Vision LLM inte är
  • Ocrkvalitetsvåg: Spell -kontroll portaal, bestämmer stegeringsväg
  • VisionLlmWave: Cloud, -baserade Vision LLM, ,, funkar bara när tidigare vågor misslyckas eller signalerar lågt självförtroende.

Beteckning om vågsortering: De tre OCR: n våningar (Tesseract /Florence-2/Vision LLMM SK3 är de konceptuella nivåerna för eskalationenMSC4 Individuella vågor som Advanced OCR eller Quality Gate är raffineringar within those tiers , not separate escalation levels—they perform temporal stabilization and quality checks


Signalkontraktet

Varje våg producerar signaler med hjälp av en standardiserad kontrakt

public record Signal
{
    public required string Key { get; init; }      // "color.dominant", "ocr.quality.is_garbled"
    public object? Value { get; init; }             // The measured value
    public double Confidence { get; init; } = 1.0;  // 0.0-1.0 reliability score
    public required string Source { get; init; }    // "ColorWave", "VisionLlmWave"
    public DateTime Timestamp { get; init; }        // When produced
    public List<string>? Tags { get; init; }        // "visual", "ocr", "quality"
    public Dictionary<string, object>? Metadata { get; init; }  // Additional context
}

Detta är den delen 2 Signalkontraktet i praktiken .vågor pratar inte med varandra genom naturlig språk . De publicerar utskrivna signaler till den de delar med sig av i sammanhanget M SK3 och nedströmsvågor kan ställa frågor om dessa signaler

Kom ihåg att Confidence är per-signalM SK1 inte per -welle. En enda våg kan skicka ut flera signaler med olika epistemiska styrkorMska4färgvågenMske5 dess dominerande färglista har självförtroendet M Ska6 | | Mska7 beräknat Mska8 men individuella färgprocent använder självför troendet som en viktfaktor för nedåtgående sammanfattning

Tillit här betyder tillförlitlighet för nedströmsanvändning, inte matematisk säkerhet . Deterministiska signaler är återskapbara , inte felaktiga M SK3 spelttonning MSC4 kontroll kan vara deterministiskt fel på korrekta namn

Bestämning om determinism:, ", Deterministiskt, M SK2, betyder att det inte finns någon slumpmässig sampling och stabila resultat för en given lopptid och konfiguration.


OCR-signaltaksonom

För att undvika förvirringar

Signal nyckeln källan ♫ ♫ ♫ Beskrywing ♫
ocr.text Tesseract (Tier 1) råddig enkel M-frame OCR MMA5
ocr.confidence Tesseract
ocr.ml.text FlorenceM SK1 (Tier ♫ ♫ 2) ♫
ocr.ml.multiframe_text Florensen -2 (Tier 2) Multi МSK5 frambild GIF OCR föredrars för animationer
ocr.ml.confidence Florenz1 Florence3 självförtroende siffra
ocr.quality.spell_check_score OcrQualityWave
ocr.quality.is_garbled OcrQualityWave
ocr.vision.text VisionLlmWave
caption.text VisionLlmWave

En viktig skillnad: ocr.vision.text är extraktion av text (OCRM SK1 medan caption.text är scenbeschreibung (captioning). Båda kan komma från samma Vision LLM sändning, men till olika syften.

Den slutgiltiga textselektionens prioritet (höjsta till lägsta

  1. ocr.vision.text (Vision LLM OCR
  2. ocr.ml.multiframe_text (Florens
  3. ocr.ml.text (Florence
  4. ocr.text (Tesseract)

Wave-gränssnittet

Varje våg implementerar en enkel gränssnitt

public interface IAnalysisWave
{
    string Name { get; }
    int Priority { get; }           // Lower number = runs earlier (10 before 50 before 80)
    IReadOnlyList<string> Tags { get; }

    Task<IEnumerable<Signal>> AnalyzeAsync(
        string imagePath,
        AnalysisContext context,    // Shared substrate with earlier signals
        CancellationToken ct);
}

Den AnalysisContext är konsensussutrymme från en del 2.vågor kan

  • Att läsa signaler från tidigare vågor: context.GetValue<bool>("ocr.quality.is_garbled")
  • Zugriff på cachede mellanresultat: context.GetCached<Image<Rgba32>>("ocr.frames")
  • Lägg till nya signaler som nedströmsvågor kan konsumera

ColorWave: Det deterministiska fundamentet

ColorWave kör först (prioriteet 10) och räknar fakta som begränsar allt annat

public class ColorWave : IAnalysisWave
{
    public string Name => "ColorWave";
    public int Priority => 10;  // Runs first (lowest priority number)
    public IReadOnlyList<string> Tags => new[] { "visual", "color" };

    public async Task<IEnumerable<Signal>> AnalyzeAsync(
        string imagePath,
        AnalysisContext context,
        CancellationToken ct)
    {
        var signals = new List<Signal>();

        using var image = await LoadImageAsync(imagePath, ct);

        // Extract dominant colors (computed, not guessed)
        var dominantColors = _colorAnalyzer.ExtractDominantColors(image);
        signals.Add(new Signal
        {
            Key = "color.dominant_colors",
            Value = dominantColors,
            Confidence = 1.0,  // Reproducible measurement
            Source = Name,
            Tags = new List<string> { "color" }
        });

        // Individual colors for easy access
        for (int i = 0; i < Math.Min(5, dominantColors.Count); i++)
        {
            var color = dominantColors[i];
            signals.Add(new Signal
            {
                Key = $"color.dominant_{i + 1}",
                Value = color.Hex,
                Confidence = color.Percentage / 100.0,
                Source = Name,
                Metadata = new Dictionary<string, object>
                {
                    ["name"] = color.Name,
                    ["percentage"] = color.Percentage
                }
            });
        }

        // Cache the image for other waves (no need to reload)
        context.SetCached("image", image.CloneAs<Rgba32>());

        return signals;
    }
}

Vision LLM får senare dessa färger som begränsningar. Den borde inte hävda att bilden har ♫ " ♫ kraftfulla röda ♫" ♫ om ColorWave räknat ut att den dominerande färgen är blå ♫


OcrQualityWave: Escalation Gate

Det här är Begränsad otydighet strålar. OcrQualityWave är konstrainer som bestämmer om man ska skala upp till dyr Vision LLM:

public class OcrQualityWave : IAnalysisWave
{
    public string Name => "OcrQualityWave";
    public int Priority => 58;  // Runs after OCR waves
    public IReadOnlyList<string> Tags => new[] { "content", "ocr", "quality" };

    public async Task<IEnumerable<Signal>> AnalyzeAsync(
        string imagePath,
        AnalysisContext context,
        CancellationToken ct)
    {
        var signals = new List<Signal>();

        // Get OCR text from earlier waves (canonical taxonomy)
        string? ocrText =
            context.GetValue<string>("ocr.ml.multiframe_text") ??  // Florence-2 GIF
            context.GetValue<string>("ocr.ml.text") ??             // Florence-2 single
            context.GetValue<string>("ocr.text");                  // Tesseract

        if (string.IsNullOrWhiteSpace(ocrText))
        {
            signals.Add(new Signal
            {
                Key = "ocr.quality.no_text",
                Value = true,
                Confidence = 1.0,
                Source = Name
            });
            return signals;
        }

        // Tier 1: Spell check (deterministic, no LLM)
        var spellResult = _spellChecker.CheckTextQuality(ocrText);

        signals.Add(new Signal
        {
            Key = "ocr.quality.spell_check_score",
            Value = spellResult.CorrectWordsRatio,
            Confidence = 1.0,
            Source = Name,
            Metadata = new Dictionary<string, object>
            {
                ["total_words"] = spellResult.TotalWords,
                ["correct_words"] = spellResult.CorrectWords
            }
        });

        signals.Add(new Signal
        {
            Key = "ocr.quality.is_garbled",
            Value = spellResult.IsGarbled,  // < 50% correct words
            Confidence = 1.0,
            Source = Name
        });

        // This signal triggers Vision LLM escalation
        if (spellResult.IsGarbled)
        {
            signals.Add(new Signal
            {
                Key = "ocr.quality.correction_needed",
                Value = true,
                Confidence = 1.0,
                Source = Name,
                Tags = new List<string> { "action_required" },
                Metadata = new Dictionary<string, object>
                {
                    ["quality_score"] = spellResult.CorrectWordsRatio,
                    ["correction_method"] = "llm_sentinel"
                }
            });

            // Cache for Vision LLM to access
            context.SetCached("ocr.garbled_text", ocrText);
        }

        return signals;
    }
}

Beslutet om escalation är deterministisk: om stavningskontrollsvärdet ♫ < ♫ ♫50%, ♫ avger en signal som utlöser syn LLM ♫

Escalation i aktion

Arse Biscuits

$ imagesummarizer demo-images/arse_biscuits.gif --pipeline caption --output text
OCR: "ARSE BISCUITS"
Caption: An elderly man dressed as bishop with text reading "arse biscuits"
Scene: meme

OCR fick texten


VisionLlmWave: Begränsade författare

Vision LLM-vågen sätter igång endast när tidigare signaler visar att den är möjlig.

public class VisionLlmWave : IAnalysisWave
{
    public string Name => "VisionLlmWave";
    public int Priority => 50;  // Runs after quality assessment
    public IReadOnlyList<string> Tags => new[] { "content", "vision", "llm" };

    public async Task<IEnumerable<Signal>> AnalyzeAsync(
        string imagePath,
        AnalysisContext context,
        CancellationToken ct)
    {
        var signals = new List<Signal>();

        if (!Config.EnableVisionLlm)
        {
            signals.Add(new Signal
            {
                Key = "vision.llm.disabled",
                Value = true,
                Confidence = 1.0,
                Source = Name
            });
            return signals;
        }

        // Check if OCR was unreliable (garbled text)
        var ocrGarbled = context.GetValue<bool>("ocr.quality.is_garbled");
        var textLikeliness = context.GetValue<double>("content.text_likeliness");
        var ocrConfidence = context.GetValue<double>("ocr.ml.confidence",
            context.GetValue<double>("ocr.confidence"));

        // Only escalate when: OCR failed OR (text likely but low OCR confidence)
        // Models never decide paths; deterministic signals do (no autonomy)
        bool shouldEscalate = ocrGarbled ||
                              (textLikeliness > 0.7 && ocrConfidence < 0.5);

        if (shouldEscalate)
        {
            var llmText = await ExtractTextAsync(imagePath, ct);

            if (!string.IsNullOrEmpty(llmText))
            {
                // Emit OCR signal (Vision LLM tier)
                signals.Add(new Signal
                {
                    Key = "ocr.vision.text",  // Vision LLM OCR extraction
                    Value = llmText,
                    Confidence = 0.95,  // High but not 1.0 - still probabilistic
                    Source = Name,
                    Tags = new List<string> { "ocr", "vision", "llm" },
                    Metadata = new Dictionary<string, object>
                    {
                        ["ocr_was_garbled"] = ocrGarbled,
                        ["escalation_reason"] = ocrGarbled ? "quality_gate_failed" : "low_confidence_high_likeliness",
                        ["text_likeliness"] = textLikeliness,
                        ["prior_ocr_confidence"] = ocrConfidence
                    }
                });

                // Optionally emit caption (separate signal)
                var llmCaption = await GenerateCaptionAsync(imagePath, ct);
                if (!string.IsNullOrEmpty(llmCaption))
                {
                    signals.Add(new Signal
                    {
                        Key = "caption.text",  // Descriptive caption (not OCR)
                        Value = llmCaption,
                        Confidence = 0.90,
                        Source = Name,
                        Tags = new List<string> { "caption", "description" }
                    });
                }
            }
        }

        return signals;
    }
}

Den viktigaste insikten Vision LLM text har förtroende 0.95, inte 1.0. Det är bättre än förruttnade OCR, men det är fortfarande sannolikhetsmässigt, .. Den nedgående aggregationen vet detta. att ha en värde som är't 1.0.)


Ledgern : Begränsad syntes

Den ImageLedger samlar in signaler i strukturerade sektioner för nedströmsförbrukning Kontext dragning tillämpat på bildanalys:

public class ImageLedger
{
    public ImageIdentity Identity { get; set; } = new();
    public ColorLedger Colors { get; set; } = new();
    public TextLedger Text { get; set; } = new();
    public MotionLedger? Motion { get; set; }
    public QualityLedger Quality { get; set; } = new();
    public VisionLedger Vision { get; set; } = new();

    public static ImageLedger FromProfile(DynamicImageProfile profile)
    {
        var ledger = new ImageLedger();

        // Text: Priority order - corrected > voting > temporal > raw
        ledger.Text = new TextLedger
        {
            ExtractedText =
                profile.GetValue<string>("ocr.final.corrected_text") ??  // Tier 2/3 corrections
                profile.GetValue<string>("ocr.voting.consensus_text") ?? // Temporal voting
                profile.GetValue<string>("ocr.full_text") ??             // Raw OCR
                string.Empty,
            Confidence = profile.GetValue<double>("ocr.voting.confidence"),
            SpellCheckScore = profile.GetValue<double>("ocr.quality.spell_check_score"),
            IsGarbled = profile.GetValue<bool>("ocr.quality.is_garbled")
        };

        // Colors: Computed facts, not guessed
        ledger.Colors = new ColorLedger
        {
            DominantColors = profile.GetValue<List<DominantColor>>("color.dominant_colors") ?? new(),
            IsGrayscale = profile.GetValue<bool>("color.is_grayscale"),
            MeanSaturation = profile.GetValue<double>("color.mean_saturation")
        };

        return ledger;
    }

    public string ToLlmSummary()
    {
        var parts = new List<string>();

        parts.Add($"Format: {Identity.Format}, {Identity.Width}x{Identity.Height}");

        if (Colors.DominantColors.Count > 0)
        {
            var colorList = string.Join(", ",
                Colors.DominantColors.Take(5).Select(c => $"{c.Name}({c.Percentage:F0}%)"));
            parts.Add($"Colors: {colorList}");
        }

        if (!string.IsNullOrWhiteSpace(Text.ExtractedText))
        {
            var preview = Text.ExtractedText.Length > 100
                ? Text.ExtractedText[..100] + "..."
                : Text.ExtractedText;
            parts.Add($"Text (OCR, {Text.Confidence:F0}% confident): \"{preview}\"");
        }

        return string.Join("\n", parts);
    }
}

Ledgern är ankar i CFCD termer . Den för vidare vad som överlevt urvalen, och LLM-synthesen måste respektera dessa fakta


Beslutet om att kliva

Ni har sett eskalationslogik på två ställen. OcrQualityWave utsläpper signaler om kvalitet; EscalationService gäller policy över de signalerna . Det här är avsiktligt separerande:

  • Wave-lokal escalation: Varje våg släpper ut fakta om dess domain.
  • Service---nivåns upphöjning: EscalationService aggregerar signaler och applicerar globala trösklar

Den EscalationService sammanfogar det hela. Den implementerar delen 1 mönster substrat → förespråkare → konstrainer:

public class EscalationService
{
    private bool ShouldAutoEscalate(ImageProfile profile)
    {
        // Escalate if type detection confidence is low
        if (profile.TypeConfidence < _config.ConfidenceThreshold)
            return true;

        // Escalate if image is blurry
        if (profile.LaplacianVariance < _config.BlurThreshold)
            return true;

        // Escalate if high text content
        if (profile.TextLikeliness >= _config.TextLikelinessThreshold)
            return true;

        // Escalate for complex diagrams or charts
        if (profile.DetectedType is ImageType.Diagram or ImageType.Chart)
            return true;

        return false;
    }
}

Varje escalationsval är deterministisk: samma ingångar , samma tröskeln , samma beslut M SK3 Inga LLM-bedömande i eskalationslogiken


Vision LLM Prompt: Evidensbegränsningar

När Vision LLM körs, får den beräknade fakta som begränsningar.

private static string BuildVisionPrompt(ImageProfile profile)
{
    var prompt = new StringBuilder();

    prompt.AppendLine("CRITICAL CONSTRAINTS:");
    prompt.AppendLine("- Only describe what is visually present in the image");
    prompt.AppendLine("- Only reference metadata values provided below");
    prompt.AppendLine("- Do NOT infer, assume, or guess information not visible");
    prompt.AppendLine();

    prompt.AppendLine("METADATA SIGNALS (computed from image analysis):");

    if (profile.DominantColors?.Any() == true)
    {
        prompt.Append("Dominant Colors: ");
        var colorDescriptions = profile.DominantColors
            .Take(3)
            .Select(c => $"{c.Name} ({c.Percentage:F0}%)");
        prompt.AppendLine(string.Join(", ", colorDescriptions));

        if (profile.IsMostlyGrayscale)
            prompt.AppendLine("  → Image is mostly grayscale");
    }

    prompt.AppendLine($"Sharpness: {profile.LaplacianVariance:F0} (Laplacian variance)");
    if (profile.LaplacianVariance < 100)
        prompt.AppendLine("  → Image is blurry or soft-focused");

    prompt.AppendLine($"Detected Type: {profile.DetectedType} (confidence: {profile.TypeConfidence:P0})");

    prompt.AppendLine();
    prompt.AppendLine("Use these metadata signals to guide your description.");
    prompt.AppendLine("Your description should be grounded in observable facts only.");

    return prompt.ToString();
}

Vision LLM borde inte hävda "kraftiga färger " om vi räknade gråskalan - om det gör det M SK3 motsägelserna är upptäckbara Mska4 Det borde inte behaupta ♫ " skarpa detaljer Mske6 om man räknade låga Laplacian-varianser deterministisk substrat begränsar den probabilistiska utgången.

Dessa begränsningar minskar hallucinationen men inte eliminerar den.


Utgångsteksprioriteet

När man extraherar den slutgiltiga texten

static string? GetExtractedText(DynamicImageProfile profile)
{
    // Priority chain using canonical signal names (see OCR Signal Taxonomy above)
    //   1. Vision LLM OCR (best for complex/garbled)
    //   2. Florence-2 multi-frame GIF (temporal stability)
    //   3. Florence-2 single-frame (stylized fonts)
    //   4. Tesseract (baseline)

    var visionText = profile.GetValue<string>("ocr.vision.text");
    if (!string.IsNullOrEmpty(visionText))
  ocally (confidence 0.85-0.90, no cost)
- **Tesseract voting**: Reliable for clean text (confidence varies, deterministic)
- **Raw Tesseract**: Baseline fallback (confidence < 0.7 for stylized fonts)

The priority order encodes this knowledge. Florence-2 sitting between Vision LLM and Tesseract provides a "sweet spot" for most images—better than traditional OCR, cheaper than cloud Vision LLMs.

Note: this function selects *one* source, but the ledger exposes *all* sources with their confidence scores. Downstream consumers can-and should-inspect provenance when the domain requires it. The priority order is a sensible default, not a straitjacket.

---

## Selection and Conflict Resolution

The priority chain above is the current implementation-a simple fallback. But the architecture supports adding rejection rules as config-driven policy. Here's the pattern for contradiction detection (not yet implemented, but the signals exist to support it):

```csharp
// Pattern: Contradiction detection as policy rules
public static class SelectionPolicy
{
    public static string? SelectTextWithConstraints(DynamicImageProfile profile)
    {
        var visionText = profile.GetValue<string>("vision.llm.text");
        if (!string.IsNullOrEmpty(visionText))
        {
            // Rule: Reject if Vision claims text but deterministic signals say no text
            var textLikeliness = profile.GetValue<double>("content.text_likeliness");
            if (textLikeliness < _config.TextLikelinessThreshold && visionText.Length > 50)
            {
                // Contradiction detected - log and fall through
                profile.AddSignal(new Signal
                {
                    Key = "selection.vision_rejected",
                    Value = "text_likeliness_contradiction",
                    Confidence = 1.0,
                    Source = "SelectionPolicy",
                    Metadata = new Dictionary<string, object>
                    {
                        ["text_likeliness"] = textLikeliness,
                        ["vision_text_length"] = visionText.Length,
                        ["threshold"] = _config.TextLikelinessThreshold
                    }
                });
                // Fall through to OCR sources
            }
            else
            {
                return visionText;
            }
        }

        // Continue with priority chain...
        return profile.GetValue<string>("ocr.voting.consensus_text")
            ?? profile.GetValue<string>("ocr.full_text");
    }
}

Samma mönster gäller andra typer av signaler:

  • Färger motsägelser: Refusera undertiteln som påstår " kraftfulla röda punkter color.is_grayscale är sant
  • Sharpness motsägelse: Refrigera captionen som påstår " skarpa detaljer quality.sharpness < tröskel
  • Typ motsägelse: Refusera undertiteln som påstår " en person content.type är Diagramm med hög trovärdighet

De viktigaste egenskaperna hos den valskivan:

  • Prioritetskedjan: Varje källa har en definierad fallback ordning
  • Kvalite-grind uppströms: OCR accepteras när det deterministiska spåret säger att den är inte förruttrad (< 50% rätt ord spolar uppvärmning
  • Längsvinkel: motsägelsreglerna är config-driven och versionerade som alla andra regler
  • Revideringsspår: Motsägelser släpper ut signaler med observerade värden och trösklar

Det är här som "determinism förblir kvar " blir mekaniskt sant M SK2 LLM föreslår ; deterministiska regler som bestämmer om man ska acceptera


JSON-pingelinkonfiguration

Pipeliner är fullt konfigurierbara via JSON, vilket gör att vågsstrukturen är tydlig och kontrollbar :

{
  "name": "advancedocr",
  "displayName": "Advanced OCR (Default)",
  "description": "Multi-frame temporal OCR with stabilization and voting",
  "estimatedDurationSeconds": 2.5,
  "accuracyImprovement": 25,
  "phases": [
    {
      "id": "color",
      "name": "Color Analysis",
      "priority": 100,
      "waveType": "ColorWave",
      "enabled": true
    },
    {
      "id": "simple-ocr",
      "name": "Simple OCR",
      "priority": 60,
      "waveType": "OcrWave",
      "earlyExitThreshold": 0.98
    },
    {
      "id": "advanced-ocr",
      "name": "Advanced Multi-Frame OCR",
      "priority": 59,
      "waveType": "AdvancedOcrWave",
      "dependsOn": ["simple-ocr"],
      "parameters": {
        "maxFrames": 30,
        "ssimThreshold": 0.95,
        "enableVoting": true
      }
    },
    {
      "id": "quality",
      "name": "OCR Quality Assessment",
      "priority": 58,
      "waveType": "OcrQualityWave",
      "dependsOn": ["advanced-ocr"]
    }
  ]
}

tidiga utgångsgränser låter dyra vågor hoppa över när billiga strömmar redan har uppnått hög trovärdighet.


Det automatiserade Pipelinet: Intelligent ruttering

Den auto pipeline implementerar smart ruttering baserat på bilder

Image Analysis (OpenCV ~5-20ms)
    │
    ├── Is animated (>1 frame)?
    │   └── ANIMATED route
    │       ├── Has subtitle regions? → Text-only strip extraction
    │       ├── Minimal text? → FAST (Florence-2 only)
    │       └── Motion significant? → Motion analysis
    │
    ├── Has text regions (OpenCV detection)?
    │   ├── High contrast, clean text → FAST route (Florence-2, ~100ms)
    │   ├── Moderate confidence → BALANCED route (Florence-2 + Tesseract, ~300ms)
    │   └── Low confidence → QUALITY route (Multi-frame + Vision LLM, ~1-5s)
    │
    ├── Is chart/diagram (type detection)?
    │   └── QUALITY route → Vision LLM caption
    │
    └── Default → FAST route (Florence-2 caption)

Route-presterande

Router Triglar när Bearbetande Timm МSK4 kostnaderna
FAST Enkel text, hög kontrastMSG3 standardfonts MSG4 FlorenzMSC5 bara МСG6 MSG7ms msG8 lågt lokal MSG10 msG11
BALNED Normal text, måttlig självförtroende
KVALITY Diagrammar , Diagramer МSK3 Stiliserade fonts , lågt självförtroende ♫ Multi - ram + Vision LLM ♫ ♫
ANIMATED GIF-bilder med undertitel Text - enkel stripp МSK4 film 5 6 7 mellan 8 API 9 10

Ett verkligt exempel: Automatisk Route Selection

$ imagesummarizer anchorman-not-even-mad.gif --pipeline auto --output visual
[Route selection...]
  Image: 300×185, 93 frames
  Text detection: 15 regions found (bottom 30%)
  Subtitle pattern: DETECTED
  → Selected ANIMATED route (text-only filmstrip)

[Processing...]
  MlOcrWave: Extracted 10 frames → 2 unique text segments
  Text-only strip: 253×105 (83% token reduction)
  VisionLlmWave: Processing filmstrip...

[Results - 2.3s total]
Text: "I'm not even mad." + "That's amazing."
Caption: A person wearing grey turtleneck sweater with neutral expression
Scene: meme
Motion: SUBTLE general motion

Rutingstillståndet är deterministiskt och registrerad i signaler för audit.

{
  "routing": {
    "selected_route": "ANIMATED",
    "reason": "subtitle_pattern_detected",
    "text_regions": 15,
    "frames": 93,
    "decision_time_ms": 18
  }
}

The CLI: Using It

Grundanvändning

# Use auto pipeline (smart routing - recommended)
imagesummarizer meme.gif --pipeline auto

# Fast local caption with Florence-2 ONNX (~200ms)
imagesummarizer photo.jpg --pipeline florence2

# Best quality: Florence-2 + Vision LLM
imagesummarizer complex-diagram.png --pipeline florence2+llm

# Extract text only (three-tier OCR)
imagesummarizer screenshot.png --pipeline advancedocr

# Motion analysis for GIFs
imagesummarizer animation.gif --pipeline motion

# Process a directory with visual output
imagesummarizer ./photos/ --output visual

Signalinsamlingar

Säg bara de signaler du behöver genom att använda förutfattade samlingar-

# Minimal metadata (fast)
imagesummarizer image.png --signals "@minimal"

# Alt text for accessibility
imagesummarizer image.png --signals "@alttext"

# Motion analysis
imagesummarizer animation.gif --signals "@motion"

# Full analysis
imagesummarizer image.png --signals "@full"

# Custom wildcard patterns
imagesummarizer image.png --signals "color.dominant*, ocr.text, motion.*"
Kollektion Signaler
@minimal Identitet*, kvalitetM SK1 skarphet Bara snabbprofil
@alttext undertiteln . text* tillgänglighet
@motion rörelse*,identitet.framemöjlighet, _koll, ♫ ♫ ♫ Animationsanalys ♫
@full Alla signaler Hela analysen
@tool Optimiserad undergrupp MCP/automation

Den riktiga JSON utgången

$ imagesummarizer princess-bride.gif --output json
{
  "image": "princess-bride.gif",
  "duration_ms": 1838,
  "waves_executed": ["ColorWave", "OcrWave", "AdvancedOcrWave", "VisionLlmWave"],
  "text": {
    "value": "You keep using that word.\nI do not think it means what you think it means.",
    "source": "ocr.voting.consensus_text",
    "confidence": 0.95
  },
  "escalation": {
    "triggered": true,
    "reason": "text_likeliness_above_threshold",
    "threshold": 0.4,
    "observed": 0.67
  },
  "signals": {
    "color.dominant_1": { "value": "#1a1a2e", "confidence": 1.0 },
    "ocr.quality.spell_check_score": { "value": 0.82, "confidence": 1.0 },
    "ocr.quality.is_garbled": { "value": false, "confidence": 1.0 },
    "motion.type": { "value": "static", "confidence": 0.95 }
  }
}

Varje fält har provenance. Den escalation blockshows varför Vision LLM kallades ".".

rörelseanalys

Alan Shrug

$ imagesummarizer demo-images/alanshrug_opt.gif --pipeline motion
Motion: SUBTLE general motion (localized coverage)
Direction: up-down
Magnitude: 0.23

Interaktiva Modus

$ imagesummarizer
ImageSummarizer Interactive Mode
Pipeline: advancedocr | Output: auto | LLM: auto
Commands: /help, /pipeline, /output, /llm, /model, /ollama, /models, /quit

Enter image path (or drag & drop): F:\Gifs\meme.gif
Processing...
I'm not even mad. That's amazing.

Enter image path: /llm true
Vision LLM: enabled

Enter image path: /model minicpm-v:8b
Vision model: minicpm-v:8b

Desktop GUI

För visuella utforskningar, erbjuder datorapplikationen

  • Drag & drop-gränssnitt: Släpp bilder till direkt analys
  • Livsignallogg: Uhrvågor sätter igång i realtid -tid med självförtroende färg
  • Modellens statusindikatorer: trafikljussystem | | (🟢 | Ready |
  • En animerad GIF-version: Se filmspår generering och ram extraktion
  • Signalinspektor: klicka på vilket signal som helst för att se hela provenansen och metadata
  • Pipeline-selektor: byter mellan automatiska /blomma2/kvalitéM SK3rörelsemodi
  • Export options: Kopiera signaler som JSON , spara filmsträngar

Det skrivbords GUI demonstrerar arkitekturen visuellt— du kan se exakt vilka vågor som gick igång , vilka signaler de skickade ut , och hur rutteringsbesluten fattades

CLI visar alla komplexiteten som enkla alternativ.


Var mönsterna syns

del mönster Implementering av ImageSummarizer
1 Begränsad otydighet ColorWave bearbetar fakta; VisionLlmWave respekterar dem
2 Begränsad Fuzzy MoM Flera vågor publiceras i analys Kontexten ; Bandorchesterkoordinater
3 Kontext dragning ImageLedger samlar upp salienta egenskaperM SK1 Signaldatabas-cacheresultat

Samma mönster sannolikhet föreslår , determinism förblir.


Vad du får

  • RAG-ready utgång: Strukturerad JSON med självförtroende poäng
  • Lokalt-first bearbetande: FlorenceM SK1 ONNX kör lokalt (~200ms), inga API-kostnader för de flesta bilder
  • Intelligent ruttering: Automatisk pipeline väljer den optimala vägen
  • Symboleffektivitet: Text
  • Rättsliga beslut: Varje eskalation har ett tydligt skäl med ursprung
  • Modell-agnostisk: Swap Ollama till OpenAI eller Anthropic utan att ändra arkitektur
  • Cachéd av innehåll: Samma bild
  • Desktop GUI: Drag
  • MCP-server-läger: Integrera med vilken LLM som supporterar Model Context Protocol som helst.
  • Signal-baserad API: Säg bara vad du behöver med hjälp av wildcard mönster eller samlingar

Vad det kostar

  • Kognitiv överhead: Du måste förstå signalkontraktet , vågprioriteer , och eskaleringslogi innan du skriver en enda våg M SK3 Det bestraffar sluppig tankesätt MSC4
  • Specdisciplin: Varje signalnyckled behöver en klar definition . Varje självförtroendes poäng behöver en resonemang
  • Per-vågkomplexitet: Varje våg har sin egen konfiguration , randföreställningar , och felläger M SK3 Debugging sker på vågnivån MSC4 inte på ledningsnivån
  • Försökande yta: Fler komponenter innebär fler tester . Förflytta kontexten , bekräfta signalerna M SK3 kontrollera upphöjdhetens vägar
  • Tillgångsinvesteringar: Du definierar vågor , signaler M SK2 och ledgerstruktur innan du ser resultaten . Belöningen kommer senare

Det här är inte den snabbaste vägen. Den är den tillförlitliga vägen . Det är värt det om du behöver en kontrollbar bild förståelse på skala


Misslyckandensmodi och hur det löser dem

Missbruksmöjlighet Vad händer Hur det går
Skrämmande GIF Framsjitter , kompressionsartefakter Tillfällig stabilisering МSK3 SSIM-duplikation
OCR återvänder skräp Tesseract misslyckas med stiliserade fonter Spell-kontrollgrann detekterar < МSK4 korrekt uppskrider till Florenz LLM om syn än är dålig
Höga API-kostnader Alltför många cloud Vision LLM-sändningar Florensen -2 ONNX löser 80%+ lokalt ♫ ~200 ms МSK5 text - bara fläckar reducerar token 30× för GIFs
Synhallucinationer LLM säger att texten som inte är ' finns där ♫ ♫ Signaler gör det möjligt att upptäcka motsägelser ♫vision.llm.text mot content.text_likeliness
Pipelineförändringar över tid Nya vågor lagts till , tröskelnjusterade innehåll МSK3 hash-caching + full provenance in every signal + version tracking
Modellen ger inget tillbaka Vision LLM upptimening eller tom respons Fallback-skedja : Vision LML Florence МSK4 Tesseract-röstning

Varje felläger har en deterministisk respons. Ingen tyst nedbrytning.


Den större bilden : Multi-Modal Graph RAG

En viktig kontext: ImageSummarizer är bildinspegningsrör för LucidRAG ekosystem.

Detta artikel fokuserar på att extrahera strukturerade signaler från bilder.

  • DocSummarizer - Strukturerad dokumentanalys
  • DataSummarizer - Tabulardataprofilering (CSVM SK2 databaser)
  • ImageSummarizer (den här artikelnM SK1 - Bild- och animationsanalys

När de kopplas ihop med LucidRAG ( "Kom snart" !), dessa tre banor möjliggör multi-modal graf RAG:

Document → DocSummarizer → Structured signals
    ↓
Images → ImageSummarizer → Structured signals
    ↓
Data → DataSummarizer → Structured signals
    ↓
    ↓ (all signals)
    ↓
LucidRAG Graph Builder → Multi-modal knowledge graph
    ↓
Query → Multi-modal retrieval + constrained generation

Varför är detta viktigt?: Traditionella RAG behandlar bilder som osynliga fläckar som får captioning första-klass signaler med tiffade kopplingar till texten, data , och andra bilder. Samma deterministiska principer

Patternskalan är: : om du kan extrahera strukturerade signaler från bilderna.DocSummarizer), och data (DataSummarizer), kan du bygga en kunskapsgraf där varje nod har provenance och varje rand har självförtroende poäng

Kom snart: Hela LucidRAG-integrationen som visar hur dessa banor bildar sig i flera grafier.


Slutsatsen

Architekturen har en struktur: varje våg är oberoende , varje signal är inskriven M SK2 varje upphöjning är deterministiska . Florenz MSC4 erbjuder snabb lokal analys Mska5 Vision LLM hanterar komplexa fall M Ska6 men inga av dem fungerar utan begränsningarMska7 deterministiska signaler angrippar alltid utgången

Sedan den första artikelpubliceringen, ,, har systemet utvecklats signifikant.

  • Florence-2 integration av ONNX minskar API-kostnader och latens (~200ms lokal vs ~1-5s moln
  • Text- endast filmspår att uppnå 30×-tokenreduktion för GIF-undertitel
  • Pipeline väljer den optimala vägensättningen baserat på bilders egenskaper
  • Signalinsamlingar att förenkla vanliga användarfall (@alttext, @motionM SK3
  • Desktop GUI tillhandahåller drag-and-drop-analys med livesignalvisualisering

Bara den OCR-rörsverket, —, med sina tre nivåer: -, skalning av nivåerna: ,, multi: M SK3, skärmdumpning och optimering av filmvägar: Mska5, text: M Ska6, bara extraktion av fläcken: Mka7, har blivit tillräckligt komplicerad för att äga sin egen detaljerade artikel. Vision OCR-integration vägledning för den kompletta tekniska sammansättningen.

Om du kan göra detta för bilder— den mest stökiga inmatningstypen , med OCR-ljud , stiliserade fonter M SK3 animerade bilder Mska4 och hallucinationer M Ska5 antagliga titningar M ska6 kan du göra det för vilken sannolikhetskomponent som helst

Att ' är begränsad Fuzziness i praktiken


resurser

Repository

CLI-verktyg

Kernbiblioteket


Serien

del mönster ♫ ♫ ♫ Axel ♫
1 Begränsad otydighet Enkel komponent
2 Begränsad Fuzzy MoM Flera komponenter
3 Kontext dragning tid / minne
4 Image Intelligence (den här artikeln) Wave architecture, mönster
4.1 Det tre---Tier-OCR-röret OCR

Nästa: Del 5 visar hur ImageSummarizer DocSummarizer, och DataSummarizer komponera till multi-modal graf RAG med LucidRAG.

Alla delar följer samma invariant sannolikhetskomponenter föreslår ; deterministiska system förblir kvar.

Finding related posts...
logo

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