This is a viewer only at the moment see the article on how this works.
To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk
This is a preview from the server running through my markdig pipeline
Eingeschränkte Fuzzy OCR Die Drei---Tier OCR Pipeline
Wednesday, 07 January 2026
Teil Bildintelligenz führte die ImageSummarizer-Wellenarchitektur und die breiteren Muster ein OCR-Teilsystem3 Ebenen der Textextraktion – Intelligentes Routing – , und die Optimierung des Filmstreifens, die eine Reduktion von Zeichen für animierte GIFs erreicht
Warum ein separater Artikel? Die OCR-Pipeline entwickelte sich aus dem Tesseract mit Vision LLM Fallback" zu einem ausgeklügelten Drei---Tier-System mit ML.- basiertem OCR.MS K4. multi...-.frame Voting,.-.only Strip Extraction, und kostete das -.aware Routing ..Es ist komplex genug, um seinen eigenen detaillierten Zusammenbruch zu gewährleisten.MSC12.
Verwandte Artikel:
- Teil Beschränktes Fuzziness-Muster Das Grundmuster
- Teil Beschränkte Fuzzy MoM Mehrere Modellkoordination
- Teil - Kontext-Dragging Speicher und Zeit
- Teil Bildintelligenz Überblick über die Vollwellenarchitektur
- DocSummarizer Dokumentanalyse mit ähnlichen Mustern
- DatenZusammenfassung Datenprofilierung mit Determinismus – erster Ansatz
Das Problem der Textextraktion ist hart
OCR auf real---Weltbildern schlägt in vorhersehbarer Weise fehl
- Stylisierte SchriftartenAusgebildeter Tesseract auf Standard-Schriften, fehlschlägt bei dekorativem Text
- Lärmige GIFÄnderungen der Rahmenkomprimierungsartefakte,, Jitter,, Untertitel
- Niedriger KontrastDunkler Text auf dunklen Hintergründen
- Rotierter TextNicht--Horizontale Textwinkel
- Gemischter InhaltScreenshots mit mehreren Textregionen
- API-KostenVision LLM-Aufrufe sind teuer.
Traditioneller Ansatz, wenn es nicht mit Vision LLM-"
ProblemstellungDieses entweder verpasst stilisierten Text -( ,Tesseract fehlschlägt ,) , oder kostet zu viel , ( , immer verwenden Vision LLM , ). .
LösungFügen Sie eine mittlere Ebene hinzu.(...Florenz... ONNX-), die lokal mit stilisierten Schriftarten umgeht, die zu Vision LLM eskalieren, wenn beide lokalen Methoden fehlschlagen.
Die Architektur von Three--.Tier OCR
Das System läuft Wellen in Prioritätsreihenfolge, höhere Zahl und spätere Ausführung.
Wave Priority Order:
40: TextLikelinessWave → Heuristic text detection
50: OcrWave → Tesseract OCR (if text-likely)
51: MlOcrWave → Florence-2 ML OCR (if Tesseract low confidence)
55: Florence2Wave → Florence-2 captions (optional)
80: VisionLlmWave → Vision LLM (escalation)
Tier: 1:-Tesseract - Traditionelle OCR-)-Teiger
Vorrangige Geschwindigkeit Kosten am besten für die Begrenzungen am besten |----------|-------|-------|----------|------------| | 50 Reduzierter Text Hochkontrastierte Standardschriften Stylisierte Schriftarten Niedrige Qualität Rotierter Text
Signale emittiert
ocr.textAusgezogener Textocr.confidenceMittelwert für das Vertrauen von Tesseract
Tierart: Florence, ONNX, OCRM, OLRM, ),
Vorrangige Geschwindigkeit Kosten am besten für die Begrenzungen am besten |----------|-------|-------|----------|------------| | 51 |-M~200-Ms-|-M.S.|-Mese-Memes-Memes-,-Dekoraltext Komplizierte Charts - ,-rotierter Text
Signale emittiert
ocr.ml.textBESEITIGUNG UND ENTWICKLUNGocr.ml.multiframe_textMehrsprachiger GIF-Text von -, bevorzugt für Animationenocr.ml.confidenceModell des Vertrauenspunkts
Tier: Vision LLM: Cloud Fallback: )
| Vorrangige Kosten für die Geschwindigkeit von | , Kosten für | , am besten für Einschränkungen von | , |
|---|---|---|---|
| 80 ~1-5 ist ein sehr komplexes Bild, das deterministische Signale respektieren muss. |
Signale emittiert
ocr.vision.textTextextraktion von Vision LLM OCRocr.vision.confidenceDas LLM-Konfidenzprofil ist typischerweisecaption.textOptionale, beschreibende Bildunterschrift, Trennung von OCR-)
Die lokalen ML Modelle von ONNX Arsenal-:
Vor dem Eintauchen in die drei OCR-Ebenen decken die deterministische ML-Modelle alle Modelle laufen lokal über ONNX Runtime.—.no API ruft keine Cloud-Abhängigkeiten an.
Warum ONNX-??
- Wird lokal ausgeführtKeine API-Schlüssel, keine Netzwerklatenz, keine wiederkehrenden Kosten
- DeterministischGleicher Eingang gleiche Ausgabe gleiche Ausgabe keine Probenahme*
- SchnellBeschleunigte Hardware - Beschleunigte Schlussfolgerung
- TragbarFunktioniert unter Windows, ,, Linux und MacOS
- Automatisch heruntergeladenErste Download-Modelle automatisch ausführen
* Geringfügige Vorbehalte Die GPU-Exekutionsanbieter können vernachlässigbares Schweben einführen-Punkt-NichtdeterminismusDer Signalkontrakt Die Vertrauensschwellen des Signalvertrages,...)... bleibt vollständig deterministisch
Die fünf Modelle von ONNX
AnmerkungDie Größen sind ungefähr und variieren je nach Variante.
| Das Modell | , ca. ., Größe | , Zweck Geschwindigkeit | , Typ | , |
|---|---|---|---|---|
| OST Texterkennung von Szenentexten | , ~20, | , | ||
| HANDWERK ~150MB Zeichen Zeichen - Region Texterkennung | -Auswertung ~30-Auskopplung | -Auslesung | -Ausschnitt | |
| Florenz ,-2 Bildunterschrift mit der Überschrift | Bildunterschriften mit der Bezeichnung "MSC6 Vision" | |||
| REAL---ESRGAN Bildvergrößerung Bildverbesserung | ||||
| KLEIN ~600MB Semantische Einbettungen | , ~100, Multimodale Einbettung | , MSC1MB |
Gesamter SpeicherplatzJe nach gewählter Modellvariante ist der ~1.0-1.5GB zu verwenden.
Erkennung von Szenentexten
Effizienter und präziser Szenen-Textdetektor Textregionen in natürlichen Szenen finden sich bei .
// EAST detects text bounding boxes with confidence scores
var result = await textDetector.RunEastDetectionAsync(imagePath);
// Output: List of BoundingBox with coordinates + confidence
// Example: [BoundingBox(x1:50, y1:100, x2:300, y2:150, confidence:0.92)]
Wie es funktioniert:
- Deep Learning Modell auf Szene Text Datensätze trainiert
- Outputs score map -(-Vertrauen - )-Geometriekarte - (-Box-Koordinaten -)
- Griffe rotierter Text ,-Multi---Skala-Text
- Verwendet nicht , um überlappende Boxen zusammenzufassen
Warum deterministisch?
- Keine Zufälligkeit in Bezug auf die Inferenz
- Gleiches Bild - gleiche Begrenzung Kästen
- Vertrauenswerte sind reproduzierbar
- Eskalationsschwellen sind konfig, -, angetrieben, (, und ., g..,,
< 0.5 → escalate)
Technische Details:
// EAST preprocessing (from implementation)
- Input size: 320×320 (must be multiple of 32)
- Format: BGR with mean subtraction [123.68, 116.78, 103.94]
- Output stride: 4 (downsampled 4×)
- Score threshold: 0.5
- NMS IoU threshold: 0.4
Beispielausgabe:
Input: meme.png (800×600)
EAST detection: 15 text regions found
Region 1: (50, 480, 750, 580) - confidence 0.87 [bottom subtitle area]
Region 2: (100, 50, 300, 90) - confidence 0.62 [top text]
Region 3: ...
Route decision: ANIMATED (subtitle pattern in bottom 30%)
Bewusstwerdung der Charakterregion
Zeichen---Level-Texterkennung -' zeichnet sich bei gewölbten ,' künstlerisch- und stilisierten Texten aus
// CRAFT finds character-level regions, then groups into words
var result = await textDetector.RunCraftDetectionAsync(imagePath);
// Better than EAST for: decorative fonts, curved text, logos
Wie es funktioniert:
- Erkennt individuelle Charakterregionen ( mehr körnig als EAST -)
- Verwendet Affinitätspunktzahl, um Zeichen in Wörter zu gruppieren
- Flood---Fill-Algorithmus findet angeschlossene Textkomponenten
- Griffe gebogenen Text, die EAST verpasst
Wenn CRAFT verwendet wird:
- EAST ist nicht verfügbar oder fehlgeschlagen
- Bild hat künstlerische ,/ , dekorative Schriftarten , ( , auto , - , erkannt
- Benutzer wählt explizit CRAFT-Detektor
Technische Details:
// CRAFT preprocessing
- Max dimension: 1280px (maintains aspect ratio)
- Format: RGB normalized with ImageNet stats
- Mean: [0.485, 0.456, 0.406]
- Std: [0.229, 0.224, 0.225]
- Output stride: 2 (downsampled 2×)
- Threshold: 0.4 for character regions
Vergleich zwischen EAST und CRAFT:
]|-Feature - EAST - |-CRAFT - | - |---------|------|-------| |-Detektionslevel Word-/-line Zeichen Geschwindigkeit |, Geschwindigkeit ~20,ms |,~30,ms Am besten für Standard-Texte, Untertitel und dekorative Schriftarten, Logos, Gebogener Text Begrenzter Text Ausgezeichneter Text Modellgröße: |, 100, MKS3, MKK4, MRK5,
,3. , Real , - , ESRGAN , : , Super , - , Auflösung Hochskalierung
Verbessert Bildqualität vor OCR -, Hochskalierung für verschwommene /, kleiner Text.
// Upscale low-quality image before running OCR
if (quality.Sharpness < 30) // Laplacian variance threshold
{
var upscaled = await esrganService.UpscaleAsync(imagePath, scale: 4);
// Now run OCR on the enhanced image
}
Wenn es verwendet wird:
- Schärfe des Bildes, <, MSC1, MCS2, Lapplacian Varianz, MSR3,
- Textregionen erkannt, aber sehr klein
- OCR Vertrauen niedrig, aber Text Regionen vorhanden
- Benutzer fordert ausdrücklich upscaling
Beispiel:
Input: 100×75 screenshot with tiny text
Laplacian variance: 18 (very blurry)
ESRGAN: Upscale to 400×300 (~500ms)
New Laplacian variance: 87 (sharp)
OCR: Tesseract confidence: 0.92 (vs 0.42 before upscaling)
Text: "Click here to continue" (vs garbled before)
Technische Details:
// Real-ESRGAN processing
- Input: Any size (processed in 128×128 tiles if large)
- Output: 4× scaled (200×150 → 800×600)
- Model: x4plus variant (general photos)
- Processing: ~500ms for 800×600 image
- Memory: ~2GB peak (tiles reduce this)
Token Economics:
Scenario: Screenshot with tiny text
Option 1: Send low-res to Vision LLM
Image: 100×75 = ~20 tokens
LLM can't read tiny text → fails
Cost: $0.0002 (wasted)
Option 2: Upscale with ESRGAN, use Tesseract
ESRGAN: Free (local), 500ms
Tesseract: Free (local), 50ms
Success: 92% confidence
Cost: $0
Result: ESRGAN + local OCR beats Vision LLM for low-res images
KLIPP-:-Semantische Einbettungen
Multimodale Einbettungen für die semantische Bildsuche Bild und Text in den gemeinsamen Vektorraum projizieren
// Generate embedding for semantic search
var embedding = await clipService.GenerateEmbeddingAsync(imagePath);
// Returns: float[512] vector
// Later: semantic search across thousands of images
var similarImages = await vectorDb.SearchAsync(queryEmbedding, topK: 10);
Wie es funktioniert:
- KLIP-ViT---B-/32-visueller Encoder
- Projekte Bilder zu .512--dimensionalen Vektoren
- Ausgebildet, um sich an Textbeschreibungen anzupassen
- Aktiviert Bilder wie diese finden, ohne Schlüsselwörter
Anwendungsfälle:
- Semantische Bildsuche in RAG-Systemen
- Duplikate-Erkennung - auch wenn bearbeitet -/Croppped-)
- Content-basiertes Clustering
- Ähnliche Bildempfehlungen
Technische Details:
// CLIP visual encoder
- Model: ViT-B/32 (Vision Transformer)
- Input: 224×224 RGB (center crop + resize)
- Output: 512-dimensional embedding
- Normalized: L2 norm = 1.0
- Speed: ~100ms per image
Beispiel:
Input images:
cat_on_couch.jpg → [0.23, -0.51, 0.88, ...]
dog_on_couch.jpg → [0.19, -0.48, 0.91, ...]
car_photo.jpg → [-0.67, 0.33, -0.12, ...]
Query: "animals on furniture"
Text embedding → [0.21, -0.50, 0.89, ...]
Cosine similarity:
cat_on_couch: 0.94 (very similar!)
dog_on_couch: 0.91 (similar)
car_photo: 0.12 (not similar)
Result: Returns cat and dog images
Mehr Infos-2:Aussehen-Aussprechendes Modell aus dem Tierversuch(Auslesendes Modell
Siehe Tier Abschnitt für vollständige Details über Florence.-2.ONNX OCR und Untertitelung.
Automatisches System herunterladen
Alle Modelle werden automatisch beim ersten Einsatz heruntergeladen.
$ imagesummarizer image.png --pipeline auto
[First run]
Downloading EAST scene text detector (~100MB)...
Progress: ████████████████████ 100% (102.4 MB)
Downloading Florence-2 base model (~250MB)...
Progress: ████████████████████ 100% (248.7 MB)
Downloading CLIP ViT-B/32 visual (~350MB)...
Progress: ████████████████████ 100% (347.2 MB)
Models saved to: ~/.mostlylucid/models/
Total disk space: 1.16 GB
[Subsequent runs]
All models cached, analysis starts immediately
Anmutige Erniedrigung:
// If ONNX model download fails, system falls back gracefully
EAST unavailable → Try CRAFT → Fall back to Tesseract PSM
Real-ESRGAN unavailable → Skip upscaling, use original image
CLIP unavailable → Skip embeddings, OCR still works
Florence-2 unavailable → Use Tesseract → Vision LLM escalation
Jeder ONNX-Modellausfall wird mit Fallback-Pfad protokolliert, um sicherzustellen, dass das System aufgrund fehlender Modelle nie abstürzt.
Warum das so wichtig ist
PreisfestsetzungKostenbeispiele unten verwenden illustrative Preisgestaltung Bild für Vision LLM-).-Bild Tatsächliche API-Kosten variieren je nach Anbieter und Modell Die Kerninsights—-lokale Verarbeitung eliminiert die meisten API-Aufrufe—-Holds unabhängig von spezifischen Preisen.-
Ohne ONNX-Modelle ](-Basislinie ®):-
Every image → Send to Vision LLM
Cost: ~$0.005/image (example pricing)
Time: ~2s network + inference
100 images = ~$0.50, ~200s
Mit ONNX Modellen ,(, lokal , -, zuerst , ):,
85 images → EAST + Florence-2 (local)
Cost: $0
Time: ~200ms
10 images → EAST + Tesseract (local)
Cost: $0
Time: ~50ms
5 images → EAST + Vision LLM (escalation)
Cost: ~$0.025 (5 × $0.005)
Time: ~2s each
100 images = ~$0.025, ~30s total
EinsparungenKostensenkung bei , schneller Deterministisches Routing.
Die ONNX-Modelle verwandeln das System von der probabilistischen ganzen Strecke nach unten in die deterministische Stiftung +.
Tierart: OCR mit Tesseract
Die Baseline-Version von . , Fast , , , deterministisch , , , funktioniert hervorragend für saubere Text ,. ,
public class OcrWave : IAnalysisWave
{
public string Name => "OcrWave";
public int Priority => 60; // After color/identity
public async Task<IEnumerable<Signal>> AnalyzeAsync(
string imagePath,
AnalysisContext context,
CancellationToken ct)
{
var signals = new List<Signal>();
// Get preprocessed image from cache
var image = context.GetCached<Image<Rgba32>>("image");
// Run Tesseract OCR
using var engine = new TesseractEngine(@"./tessdata", "eng", EngineMode.Default);
using var page = engine.Process(image);
var text = page.GetText();
var confidence = page.GetMeanConfidence();
signals.Add(new Signal
{
Key = "ocr.text", // Tesseract OCR result
Value = text,
Confidence = confidence,
Source = Name,
Tags = new List<string> { "ocr", "text" },
Metadata = new Dictionary<string, object>
{
["engine"] = "tesseract",
["mean_confidence"] = confidence,
["word_count"] = text.Split(' ').Length
}
});
signals.Add(new Signal
{
Key = "ocr.confidence",
Value = confidence,
Confidence = 1.0,
Source = Name
});
return signals;
}
}
Schlüsselsignale:
ocr.full_textDer extrahierte Textocr.early_exitSignal, um Tier zu überspringen, wenn das Vertrauen hoch ist- Vertrauenswürdigkeit treibt Eskalationsentscheidungen an
Tierart: Florence, ONNX
Microsoft®' s Florence--2 ist ein Visionsmodell, das sich durch dichte Bildunterschriften und OCRá. auszeichnet Die ONNX-Version läuft lokal ohne API-Kosten.
Warum Florenz?
- Besser als Tesseract für stilisierte SchriftartenVerwendet dekorativen Text , memes , M, , Logos
- Schneller als Vision LLM,: , , ~200 , ms vs ,1-5 ,
- Frei:-Ausführung läuft lokal ,-Auslegung kein API-Schlüssel erforderlich
- Multimodales VerständnisKann Text im Kontext extrahieren
Durchführung
public class MlOcrWave : IAnalysisWave
{
private readonly Florence2OnnxModel _model;
public string Name => "MlOcrWave";
public int Priority => 51; // Runs AFTER Tesseract (priority 50)
public async Task<IEnumerable<Signal>> AnalyzeAsync(
string imagePath,
AnalysisContext context,
CancellationToken ct)
{
var signals = new List<Signal>();
// Check if Tesseract already succeeded with high confidence
var tesseractConfidence = context.GetValue<double>("ocr.confidence");
if (tesseractConfidence >= 0.95)
{
signals.Add(new Signal
{
Key = "ocr.ml.skipped", // Consistent namespace: ocr.ml.*
Value = true,
Confidence = 1.0,
Source = Name,
Metadata = new Dictionary<string, object>
{
["reason"] = "tesseract_high_confidence",
["tesseract_confidence"] = tesseractConfidence
}
});
return signals;
}
// Run Florence-2 OCR
var result = await _model.ExtractTextAsync(imagePath, ct);
signals.Add(new Signal
{
Key = "ocr.ml.text", // Florence-2 ML OCR text
Value = result.Text,
Confidence = result.Confidence,
Source = Name,
Tags = new List<string> { "ocr", "text", "ml" },
Metadata = new Dictionary<string, object>
{
["model"] = "florence2-base",
["inference_time_ms"] = result.InferenceTime,
["token_count"] = result.TokenCount
}
});
// For animated GIFs, extract all unique frames
if (context.GetValue<int>("identity.frame_count") > 1)
{
var frameResults = await ExtractMultiFrameTextAsync(
imagePath,
maxFrames: 10,
ct);
signals.Add(new Signal
{
Key = "ocr.ml.multiframe_text",
Value = frameResults.CombinedText,
Confidence = frameResults.AverageConfidence,
Source = Name,
Metadata = new Dictionary<string, object>
{
["frames_processed"] = frameResults.FrameCount,
["unique_text_segments"] = frameResults.UniqueSegments,
["deduplication_method"] = "levenshtein_85"
}
});
}
return signals;
}
}
Multi---Frame GIF-Verarbeitung
Für animierte GIFs-Verfahren bis zu parallel gemusterten Frames
private async Task<MultiFrameResult> ExtractMultiFrameTextAsync(
string imagePath,
int maxFrames,
CancellationToken ct)
{
// Load GIF and extract frames
using var image = await Image.LoadAsync<Rgba32>(imagePath, ct);
var frames = new List<Image<Rgba32>>();
int frameCount = image.Frames.Count;
int step = Math.Max(1, frameCount / maxFrames);
for (int i = 0; i < frameCount; i += step)
{
frames.Add(image.Frames.CloneFrame(i));
}
// Process all frames in parallel (bounded concurrency to avoid thrashing)
var semaphore = new SemaphoreSlim(4); // Max 4 concurrent inferences
var tasks = frames.Select(async frame =>
{
await semaphore.WaitAsync(ct);
try
{
var result = await _model.ExtractTextAsync(frame, ct);
return result;
}
finally
{
semaphore.Release();
}
});
var results = await Task.WhenAll(tasks);
semaphore.Dispose();
// Deduplicate using Levenshtein distance
var uniqueTexts = DeduplicateByLevenshtein(
results.Select(r => r.Text).ToList(),
threshold: 0.85);
return new MultiFrameResult
{
CombinedText = string.Join("\n", uniqueTexts),
FrameCount = frames.Count,
UniqueSegments = uniqueTexts.Count,
AverageConfidence = results.Average(r => r.Confidence)
};
}
private List<string> DeduplicateByLevenshtein(
List<string> texts,
double threshold)
{
var unique = new List<string>();
foreach (var text in texts)
{
bool isDuplicate = false;
foreach (var existing in unique)
{
var distance = LevenshteinDistance(text, existing);
var maxLen = Math.Max(text.Length, existing.Length);
var similarity = 1.0 - (distance / (double)maxLen);
if (similarity >= threshold)
{
isDuplicate = true;
break;
}
}
if (!isDuplicate)
{
unique.Add(text);
}
}
return unique;
}
BeispielGesammelte Frames mit einzigartigen Textergebnissen
Frame 1-45: "I'm not even mad."
Frame 46-93: "That's amazing."
Die Routing-Entscheidung
OpenCV Text-Erkennung (~5-20-ms-)- legt fest, welcher Pfad zu nehmen ist.
public class TextDetectionService
{
public TextDetectionResult DetectText(Image<Rgba32> image)
{
// Use OpenCV EAST text detector
var (regions, confidence) = RunEastDetector(image);
return new TextDetectionResult
{
HasText = regions.Count > 0,
RegionCount = regions.Count,
Confidence = confidence,
Route = SelectRoute(regions, confidence, image)
};
}
private ProcessingRoute SelectRoute(
List<TextRegion> regions,
double confidence,
Image<Rgba32> image)
{
// No text detected
if (regions.Count == 0)
return ProcessingRoute.NoOcr;
// Animated GIF with subtitle pattern
if (image.Frames.Count > 1 && HasSubtitlePattern(regions))
return ProcessingRoute.AnimatedFilmstrip;
// High confidence, standard text
if (confidence >= 0.8 && HasStandardTextCharacteristics(regions))
return ProcessingRoute.Fast; // Florence-2 only
// Moderate confidence
if (confidence >= 0.5)
return ProcessingRoute.Balanced; // Florence-2 + Tesseract voting
// Low confidence, complex image
return ProcessingRoute.Quality; // Full pipeline + Vision LLM
}
private bool HasSubtitlePattern(List<TextRegion> regions)
{
// Subtitles are typically in bottom 30% of frame
var bottomRegions = regions.Where(r =>
r.BoundingBox.Y > r.ImageHeight * 0.7);
return bottomRegions.Count() >= regions.Count * 0.5;
}
}
Streckenleistung
| -Routenauslöser bei der Verarbeitung von | -Zeiten Kosten | |||
|---|---|---|---|---|
| FAST Hohes Vertrauen Standard-Texte Nur für | Ausreichende Informationen | |||
| BILANZIERT Mittelmäßiges Vertrauen - (0.5-0.8) - | - Florenz - -2 - Tesseract-Abstimmungen | |||
| QUALITÄT , | , Niedriges Vertrauen , (<0.5), , Komplex , | , Multi-- frame ,+ , Vision LLM , | , ~1-5, | , -$0.001-0.01 , |
| TIERE GIF mit Untertitelmuster | , Text Nur-Filmstreifen | , ~2-3 und | , MSC6, MCSK7, |
Nur Streifenextraktion
Die bahnbrechende Optimierung für den GIF-Untertitel-Extrakt Nur die TextregionenNicht voll gerahmt
Das Problem
Traditioneller Ansatz für einen GIF-Rahmen mit Untertiteln
Option 1: Process every frame
93 frames × 300×185 × ~150 tokens/frame = 13,950 tokens
Cost: ~$0.14 @ $0.01/1K tokens
Time: ~27 seconds
Option 2: Sample 10 frames
10 frames × 300×185 × ~150 tokens/frame = 1,500 tokens
Cost: ~$0.015
Time: ~3 seconds
Problem: Might miss subtitle changes
Die Lösung – nur Streifen
Extrahieren Sie nur den Text, der die Boxen umgrenzt und Hintergrundpixel eliminiert
2 text regions × 250×50 × ~25 tokens/region = 50 tokens
Cost: ~$0.0005
Time: ~2 seconds
Token reduction: 30×
Durchführung
public class FilmstripService
{
public async Task<TextOnlyStrip> CreateTextOnlyStripAsync(
string imagePath,
CancellationToken ct)
{
using var gif = await Image.LoadAsync<Rgba32>(imagePath, ct);
// 1. Detect subtitle region (bottom 30% of frames)
var subtitleRegion = DetectSubtitleRegion(gif);
// 2. Extract frames with text changes
var uniqueFrames = ExtractUniqueTextFrames(gif, subtitleRegion);
// 3. Extract tight bounding boxes around text
var textRegions = ExtractTextBoundingBoxes(uniqueFrames);
// 4. Create horizontal strip of text-only regions
var strip = CreateHorizontalStrip(textRegions);
return new TextOnlyStrip
{
Image = strip,
RegionCount = textRegions.Count,
TotalTokens = EstimateTokens(strip),
OriginalTokens = EstimateTokens(gif),
Reduction = CalculateReduction(strip, gif)
};
}
private Rectangle DetectSubtitleRegion(Image<Rgba32> gif)
{
// Analyze bottom 30% of frame for text patterns
int subtitleHeight = (int)(gif.Height * 0.3);
int subtitleY = gif.Height - subtitleHeight;
return new Rectangle(0, subtitleY, gif.Width, subtitleHeight);
}
private List<Image<Rgba32>> ExtractUniqueTextFrames(
Image<Rgba32> gif,
Rectangle subtitleRegion)
{
var uniqueFrames = new List<Image<Rgba32>>();
Image<Rgba32>? previousFrame = null;
for (int i = 0; i < gif.Frames.Count; i++)
{
var frame = gif.Frames.CloneFrame(i);
var subtitleCrop = frame.Clone(ctx =>
ctx.Crop(subtitleRegion));
// Compare with previous frame
if (previousFrame == null ||
HasTextChanged(subtitleCrop, previousFrame, threshold: 0.05))
{
uniqueFrames.Add(subtitleCrop);
previousFrame = subtitleCrop;
}
}
return uniqueFrames;
}
private bool HasTextChanged(
Image<Rgba32> current,
Image<Rgba32> previous,
double threshold)
{
// Threshold bright pixels (white/yellow text on dark background)
var currentBright = CountBrightPixels(current);
var previousBright = CountBrightPixels(previous);
// Calculate Jaccard similarity of bright pixels
var intersection = currentBright.Intersect(previousBright).Count();
var union = currentBright.Union(previousBright).Count();
var similarity = union > 0 ? intersection / (double)union : 1.0;
// Text changed if similarity drops below threshold
return similarity < (1.0 - threshold);
}
// Helper type for bounding box + crop
private record TextCrop
{
public required Image<Rgba32> CroppedImage { get; init; }
public required Rectangle Bounds { get; init; }
}
private List<TextCrop> ExtractTextBoundingBoxes(
List<Image<Rgba32>> frames)
{
var textCrops = new List<TextCrop>();
foreach (var frame in frames)
{
// Threshold to get text mask
var mask = ThresholdBrightPixels(frame, minValue: 200);
// Find connected components (text regions)
var components = FindConnectedComponents(mask);
// Get tight bounding box around all components
var bbox = GetTightBoundingBox(components);
// Add padding
bbox.Inflate(5, 5);
// Clone the region (dispose properly in production!)
var cropped = frame.Clone(ctx => ctx.Crop(bbox));
textCrops.Add(new TextCrop
{
CroppedImage = cropped,
Bounds = bbox
});
}
return textCrops;
}
private Image<Rgba32> CreateHorizontalStrip(
List<TextCrop> textCrops)
{
// Calculate strip dimensions
int totalWidth = textCrops.Sum(c => c.Bounds.Width);
int maxHeight = textCrops.Max(c => c.Bounds.Height);
// Create blank canvas
var strip = new Image<Rgba32>(totalWidth, maxHeight);
// Paste text regions horizontally
int xOffset = 0;
foreach (var crop in textCrops)
{
strip.Mutate(ctx => ctx.DrawImage(
crop.CroppedImage,
new Point(xOffset, 0),
opacity: 1.0f));
xOffset += crop.Bounds.Width;
// Dispose crop after use (important!)
crop.CroppedImage.Dispose();
}
return strip;
}
}
Visuelles Beispiel
Eingabe: anchorman-not-even-mad.gif (93 frames, 300×185)
Verarbeitung:
1. Detect subtitle region: bottom 30% (300×55)
2. Extract unique frames: 93 frames → 2 text changes
3. Extract tight bounding boxes:
- Frame 1-45: "I'm not even mad." → 252×49 bbox
- Frame 46-93: "That's amazing." → 198×49 bbox
4. Create horizontal strip: 450×49 total
AusgabeNur Streifen

Token Economics:
- Vollständiger Rahmen Beprobt):Bemustert300×185Bewertet ×Bemusterte 10Bestempelungen
- OCR-Streifen Rahmen ):, 300×185, MSC3, MSV4, MKS5, MKK6, Zeichen
- Nur Textstreifen:" , , 450×49" . , =„ , " ,~50" - Zeichen
Reduktion während der Erhaltung aller Untertitel text.
Tierische Eskalation des Sehvermögens LLM
Wenn sowohl Tesseract als auch Florence-2 versagen oder niedrige Ergebnisse erzielen, eskalieren die Ergebnisse des Vertrauens von , zu einem Vision LLM (.GPT--4.o.,. Claude Sonnet ,.Gemini Pro Vision oder Ollama Modelle wie minicpm-.v)..
Das Qualitätstor
public class OcrQualityWave : IAnalysisWave
{
private readonly SpellChecker _spellChecker;
public string Name => "OcrQualityWave";
public int Priority => 58; // After Florence-2 and Tesseract
public async Task<IEnumerable<Signal>> AnalyzeAsync(
string imagePath,
AnalysisContext context,
CancellationToken ct)
{
var signals = new List<Signal>();
// Get best OCR result from earlier waves (priority order)
string? ocrText =
context.GetValue<string>("ocr.ml.text") ?? // Florence-2 (priority 51)
context.GetValue<string>("ocr.text"); // Tesseract (priority 50)
if (string.IsNullOrWhiteSpace(ocrText))
{
signals.Add(new Signal
{
Key = "ocr.quality.no_text",
Value = true,
Confidence = 1.0,
Source = Name
});
return signals;
}
// Run spell check (deterministic quality assessment)
var spellResult = _spellChecker.CheckTextQuality(ocrText);
// Additional quality signals to avoid false positives
var alphanumRatio = CalculateAlphanumericRatio(ocrText); // Letters/digits vs junk
var avgTokenLength = CalculateAverageTokenLength(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,
["garbled_words"] = spellResult.GarbledWords,
["alphanum_ratio"] = alphanumRatio,
["avg_token_length"] = avgTokenLength
}
});
// Deterministic escalation threshold
// NOTE: Spellcheck alone can false-trigger on proper nouns, memes, brand names.
// Use additional signals (alphanum ratio, token length) to reduce false escalations.
bool isGarbled = spellResult.CorrectWordsRatio < 0.5 &&
alphanumRatio > 0.7; // Mostly valid characters, just not in dictionary
signals.Add(new Signal
{
Key = "ocr.quality.is_garbled",
Value = isGarbled,
Confidence = 1.0,
Source = Name
});
// Signal Vision LLM escalation
if (isGarbled)
{
signals.Add(new Signal
{
Key = "ocr.quality.escalation_required",
Value = true,
Confidence = 1.0,
Source = Name,
Tags = new List<string> { "action_required", "escalation" },
Metadata = new Dictionary<string, object>
{
["reason"] = "spell_check_below_threshold",
["quality_score"] = spellResult.CorrectWordsRatio,
["threshold"] = 0.5,
["target_tier"] = "vision_llm"
}
});
// Cache garbled text for Vision LLM to access
context.SetCached("ocr.garbled_text", ocrText);
}
return signals;
}
}
Eskalation ist deterministischRechtschreibprüfungspunktzahl: <, 50%, →, Eskalation: MSC4, kein probabilistisches Urteil
Vision LLM mit Filmstreifen
Wenn die Eskalation für animierte GIFs ausgelöst wird, verwenden Sie den Text.
public class VisionLlmWave : IAnalysisWave
{
private readonly IVisionLlmClient _client;
public string Name => "VisionLlmWave";
public int Priority => 50;
public async Task<IEnumerable<Signal>> AnalyzeAsync(
string imagePath,
AnalysisContext context,
CancellationToken ct)
{
var signals = new List<Signal>();
// Check if escalation is required
var escalationRequired = context.GetValue<bool>(
"ocr.quality.escalation_required");
if (!escalationRequired)
{
signals.Add(new Signal
{
Key = "vision.llm.skipped",
Value = true,
Confidence = 1.0,
Source = Name,
Metadata = new Dictionary<string, object>
{
["reason"] = "no_escalation_required"
}
});
return signals;
}
// For animated GIFs, use text-only strip
string imageToProcess = imagePath;
bool usedFilmstrip = false;
if (context.GetValue<int>("identity.frame_count") > 1)
{
var filmstrip = await CreateTextOnlyStripAsync(imagePath, ct);
imageToProcess = filmstrip.Path;
usedFilmstrip = true;
signals.Add(new Signal
{
Key = "vision.filmstrip.created",
Value = true,
Confidence = 1.0,
Source = Name,
Metadata = new Dictionary<string, object>
{
["mode"] = "text_only",
["region_count"] = filmstrip.RegionCount,
["token_reduction"] = filmstrip.Reduction,
["original_tokens"] = filmstrip.OriginalTokens,
["final_tokens"] = filmstrip.TotalTokens
}
});
}
// Build constrained prompt
var prompt = BuildConstrainedPrompt(context);
// Call Vision LLM
var result = await _client.ExtractTextAsync(
imageToProcess,
prompt,
ct);
// Emit OCR text signal (Vision LLM tier)
signals.Add(new Signal
{
Key = "ocr.vision.text", // Vision LLM OCR result
Value = result.Text,
Confidence = 0.95, // High but not 1.0 - still probabilistic
Source = Name,
Tags = new List<string> { "ocr", "vision", "llm" },
Metadata = new Dictionary<string, object>
{
["model"] = result.Model,
["used_filmstrip"] = usedFilmstrip,
["inference_time_ms"] = result.InferenceTime,
["token_count"] = result.TokenCount,
["cost_usd"] = result.Cost
}
});
// Optionally emit caption if requested (separate from OCR)
if (result.Caption != null)
{
signals.Add(new Signal
{
Key = "caption.text", // Descriptive caption, not OCR
Value = result.Caption,
Confidence = 0.90,
Source = Name,
Tags = new List<string> { "caption", "description" }
});
}
return signals;
}
private string BuildConstrainedPrompt(AnalysisContext context)
{
var sb = new StringBuilder();
sb.AppendLine("Extract all text from this image.");
sb.AppendLine();
sb.AppendLine("CONSTRAINTS:");
sb.AppendLine("- Only extract text that is actually visible");
sb.AppendLine("- Preserve formatting and line breaks");
sb.AppendLine("- If no text is present, return empty string");
sb.AppendLine();
// Add context from earlier waves
var garbledText = context.GetCached<string>("ocr.garbled_text");
if (!string.IsNullOrEmpty(garbledText))
{
sb.AppendLine("CONTEXT:");
sb.AppendLine("Traditional OCR detected garbled text:");
sb.AppendLine($" \"{garbledText}\"");
sb.AppendLine("Use this as a hint for stylized or unusual fonts.");
sb.AppendLine();
}
sb.AppendLine("Return only the extracted text, no commentary.");
return sb.ToString();
}
}
Die Prioritätskette
Wenn alle Ebenen abgeschlossen sind, verwendet die endgültige Textauswahl eine strenge Prioritätsreihenfolge.
public static string? GetFinalText(DynamicImageProfile profile)
{
// Priority chain (highest to lowest quality)
// NOTE: This selects ONE source, but the ledger exposes ALL sources
// with confidence scores for downstream inspection
// 1. Vision LLM OCR (best for complex/garbled text)
var visionText = profile.GetValue<string>("ocr.vision.text");
if (!string.IsNullOrEmpty(visionText))
return visionText;
// 2. Florence-2 multi-frame GIF OCR (best for animations)
var florenceMultiText = profile.GetValue<string>("ocr.ml.multiframe_text");
if (!string.IsNullOrEmpty(florenceMultiText))
return florenceMultiText;
// 3. Florence-2 single-frame ML OCR (good for stylized fonts)
var florenceText = profile.GetValue<string>("ocr.ml.text");
if (!string.IsNullOrEmpty(florenceText))
return florenceText;
// 4. Tesseract OCR (reliable for clean standard text)
var tesseractText = profile.GetValue<string>("ocr.text");
if (!string.IsNullOrEmpty(tesseractText))
return tesseractText;
// 5. Fallback (empty)
return string.Empty;
}
Jede Ebene weist bekannte Merkmale auf:
Quelle: Signalschlüssel |, der am besten für das Vertrauen von | geeignet ist.
|--------|------------|----------|------------|------|-------|
Vision LLM OCR ocr.vision.text Komplexe Charts, rotierter Text, rotierter Text, gestochener Text...
,| , Florenz , -2 , ( , GIF, ) , | , ocr.ml.multiframe_text Animierte GIFs mit Untertiteln
,| , Florenz , -2 , ( , Single , ) , T| , ocr.ml.text Stylisierte Schriftarten, M,, Memes,, dekorativer Text, |, 0.85-0.90, MSC5, kostenlos
.| . Tesseract ocr.text Bereinigen Sie Standard-Texte mit hohem Kontrast
Kostenanalyse
Vor dem Drei---Tier-System
Alle Bilder, die Vision LLM-: verwenden
100 images × $0.005/image = $0.50
Total time: 100 × 2s = 200 seconds
Nach dem Drei---Tier-System
Streckenverteilung
- Bildmaterial zur FAST-Route nur für -2, kostenlos für,, ohne ~100, ohne )
- Fotos von der Route →BALANCED Route (AFlorenz-2B+A Tesseract,A kostenlos ,A~300Ams)
- Bildmaterial zur QUALITÄTS-Route Bildmaterial zum LLM-,, Bildmaterial zu $0.005,, Bildmaterialien zu MSC6
- Bildmaterial der →Ausgestrahlten Route
Cost:
60 × $0 = $0
25 × $0 = $0
10 × $0.005 = $0.05
5 × $0.002 = $0.01
Total: $0.06
Time:
60 × 0.1s = 6s
25 × 0.3s = 7.5s
10 × 2s = 20s
5 × 2.5s = 12.5s
Total: 46 seconds
Savings:
Cost: 88% reduction ($0.50 → $0.06)
Time: 77% reduction (200s → 46s)
Die mittlere Ebene der Bilder mit null Kosten
Alles zusammenstellen
Hier ist der volle Fluss für einen Mem GIF mit Untertiteln.
1. Load image: anchorman-not-even-mad.gif (93 frames)
2. IdentityWave (priority 10):
→ identity.frame_count = 93
→ identity.format = "gif"
→ identity.is_animated = true
3. TextLikelinessWave (priority 40, ~10ms):
→ Heuristic text detection: 15 regions in bottom 30%
→ Subtitle pattern: DETECTED
→ text.likeliness = 0.85
4. OcrWave (priority 50, ~60ms):
→ Run Tesseract OCR on first frame
→ ocr.text = "I'm not emn mad." (garbled)
→ ocr.confidence = 0.62
5. MlOcrWave (priority 51, ~180ms):
→ Tesseract confidence < 0.95, run Florence-2
→ Sample 10 frames (animated GIF)
→ Run Florence-2 on each frame (parallel)
→ Deduplicate: 10 results → 2 unique texts
→ ocr.ml.multiframe_text = "I'm not even mad.\nThat's amazing."
→ ocr.ml.confidence = 0.91
6. OcrQualityWave (priority 58, ~5ms):
→ Check Florence-2 result
→ Spell check: 6/6 words correct (100%)
→ ocr.quality.is_garbled = false
→ ocr.quality.escalation_required = false
7. VisionLlmWave (priority 80, SKIPPED):
→ No escalation required (Florence-2 succeeded)
Final output:
Text: "I'm not even mad.\nThat's amazing."
Source: ocr.ml.multiframe_text
Confidence: 0.91
Cost: $0 (local processing)
Time: ~250ms total (Tesseract + Florence-2)
Wenn Florenz, -2, gescheitert war, würde der Fluss fortfahren.
6. OcrQualityWave:
→ Spell check: 2/6 words correct (33%)
→ ocr.quality.is_garbled = true
→ ocr.quality.escalation_required = true
7. VisionLlmWave:
→ Create text-only filmstrip (2 regions, 450×49)
→ Send to Vision LLM: "Extract all text from this strip"
→ vision.llm.text = "I'm not even mad.\nThat's amazing."
→ Confidence: 0.95
→ Cost: ~$0.002 (30× token reduction vs full frames)
→ Time: ~2.3s
Einrichtung
Das dreistufige System ist vollständig konfigurierbar.
{
"DocSummarizer": {
"Ocr": {
"Tesseract": {
"Enabled": true,
"DataPath": "/usr/share/tesseract-ocr/4.00/tessdata",
"Languages": ["eng"],
"EarlyExitThreshold": 0.95
},
"Florence2": {
"Enabled": true,
"ModelPath": "models/florence2-base",
"ConfidenceThreshold": 0.85,
"MaxFrames": 10,
"DeduplicationMethod": "levenshtein",
"LevenshteinThreshold": 0.85
},
"Quality": {
"SpellCheckThreshold": 0.5,
"EscalationEnabled": true
}
},
"VisionLlm": {
"Enabled": true,
"Provider": "ollama",
"OllamaUrl": "http://localhost:11434",
"Model": "minicpm-v:8b",
"MaxRetries": 3,
"TimeoutSeconds": 30
},
"Filmstrip": {
"TextOnlyMode": true,
"SubtitleRegionPercent": 0.3,
"BrightPixelThreshold": 200,
"TextChangeThreshold": 0.05
},
"Routing": {
"FastRouteConfidence": 0.8,
"BalancedRouteConfidence": 0.5,
"TextDetectionEnabled": true
}
}
}
Ausfallmodi
Fehler bei der Erkennung der MSK-Antwort |---------|-----------|----------| | Tesseract scheitert Das Vertrauen in das Vertrauen von <, 0.7 oder Rechtschreibprüfung <, 0.5, MSC5, Eskalat nach Florenz -2 und | | Florence--2-Ausfälle Konfidenz- oder Rechtschreibprüfung | Vision LLM Timeout Die Anfrage übersteigt die Anforderung von 30. Zurück zum besten verfügbaren OCR Ergebnis | Alle Stufen scheitern Alle Ergebnisse leer oder garbled . .| . Gibt leere Zeichenkette mit Vertrauen zurück . | API-Kostengrenze Tägliches Budget überstiegen |. Deaktivieren von Vision LLM.,. Verwenden Sie nur Florence.-2. | Modell nicht verfügbar Überspringen tierÜberspringen levelÜberspringen weiter zum nächsten
Jeder Fehler ist deterministisch und mit voller Provenienz protokolliert
Vergleich mit anderen Ansätzen
Traditioneller ,: , Tesseract , + , manueller Rückfall
For each image:
1. Run Tesseract
2. If looks wrong, manually fix or skip
Problems:
- No middle tier (binary: works or doesn't)
- Manual intervention required
- No cost optimization
Cloud®--First-:-Verwenden Sie immer Vision LLM
For each image:
1. Send to GPT-4o/Claude
2. Pay $0.005-0.01 per image
Problems:
- Expensive (85% of images could be free)
- Slow (network latency)
- Still hallucinates without constraints
Drei---Tier-:-Lokale-MS-K2-Erst mit intelligenter Eskalation
For each image:
1. OpenCV text detection (5-20ms, free)
2. Route to appropriate tier
3. Florence-2 handles 85% locally (200ms, free)
4. Vision LLM only for complex cases (2-5s, $0.001-0.01)
Benefits:
- 88% cost reduction
- 77% faster (most images process locally)
- Deterministic escalation (auditable)
- Filmstrip optimization (30× token reduction)
- Constrained by deterministic signals
Schlußfolgerung
Die drei ‘-'tier OCR Pipeline beweist, dass Kosten-Nutzen-Routing und local---Erstverarbeitung kann Leistung und Wirtschaft ohne Qualitätseinbußen dramatisch verbessern
Wichtigste Erkenntnisse
- Florence--2 ONNX ist der süße FleckBesser als Tesseract für stilisierte Schriftarten – schneller und billiger als Vision LLMs
- Nur Streifen erreichen eine Reduktion des ZeichensNicht vollständige Rahmen ausziehbare Umrandungsboxen
- Routing ist deterministischOpenCV-Erkennung
- Eskalation ist prüfbarJede Stufe sendet Signale mit Provenienz aus
- Versagen ist anmutigPrioritätskette sorgt für Rückfall zur besten verfügbaren Quelle
Die Musterskalen lokale deterministische Analyse -→ , lokales ML-Modell - → , WolkeneskalationJede Stufe mit bekannten Eigenschaften und Kostenhandel
Dies ist eingeschränkte Fuzziness auf OCR-: , deterministische Signale angewendet , ( , spell check ,, , Textdetektion , ) , Constrain probabilistic Modelle und die letzten Ausgabe Aggregate Quellen nach Qualität , die . ,
Aufkommen
LucidRAG-Dokumentation
- ImageSummarizer Bibliothek Code der Quelle
- Vision OCR Integration --Routing-Filmtrips,-Token-Ökonomie
- Leitfaden für die Architektur Eskalation der Wellen (MSK)
- Pipeline-Dokumentation Ausgewogene Strecken
- Referenzsignale Vollständiger Signalkatalog
CLI-Werkzeuge
- ImageSummarizer CLI Befehlszeilen-Werkzeug
- KLI README Verwendung und Konfiguration
- Demo-Bilder Muster GIFs und Rahmenstreifen
Forschungspapiere
- Gedruckt von Florenz Microsoft Bildverarbeitung von '
- OST-Textdetektor Effiziente Szenentexterkennung
- CRAFT-Papier Bewusstwerdung der Charakterregion
- REAL---ESRGAN Praktische Super-Auflösung
- CLIP-Papier Lernen übertragbare visuelle Modelle
Verwandte Artikel
- Teil Bildintelligenz Überblick über die Wave-Architektur
- DocSummarizer Dokumentanalyse-Pipeline
- DatenZusammenfassung Datenprofilierungsansatz
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 ,-Muster | - |
| 4.1 | Die Drei---Tier OCR Pipeline Dieser Artikel | OCR-,-ONNX-Modelle - Filmetrips |
NächsterTeil, der zeigt, wie ImageSummarizer, , DocSummarizerL 347 vom 20.12.2013, S. 671. DatenZusammenfassung Komponieren Sie in Multi-Modal Graph RAG mit LucidRAG-.
Alle Teile folgen dem gleichen Invariant. probabilistische Komponenten schlagen vor deterministische Systeme bestehen fort..