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
Wednesday, 07 January 2026
جزء 4: استخبارات الصورة قدم معمارية موجات ImageSummarizer ونماذج أوسع نظام فرعي OCR—ثلاثة طوابق من إستخراج النص , توجيه ذكي | , | و تحسين مسار الأفلام الذي يحقق |30× | تخفيض الرموز لGIF الرسوم المتحركة
لماذا مقالة مختلفة? خط أنابيب التصوير بالرنين المغناطيسي تطور من "Tesseract مع Vision LLM fallback" إلى نظام ثلاثي الأبعاد متطور -tier مع MLMSC3based OCRM SK4 multiMST5frame votingMSSK6 textMS-only strip extractionMSR8 and costMSL9aware routingMSV10 ItMSP11s complex enough to warrant its own detailed breakdownMSS12
المقالات المترابطة:
التصوير بالرنين المغناطيسي على الصور الحقيقية
المقاربة التقليدية: "Run Tesseract , إذا فشلت في استخدام Vision LLMM SK3
المشكلة: هذا إما يفتقد النص المصمم ( تفشل تيسيراكت) أو يكلف أكثر من ذلك ( دائماً تستخدم الـ Vision LLM
الحل: أضف مستوى متوسط (FlorenceM SK2 ONNX) يتعامل مع الخطوط المصممة بشكل محليMSC4 يتدرج إلى Vision LLM فقط عندما تفشل كل الطرق المحليةMスク5
يقوم النظام بتشغيل الأمواج في ترتيب أولي (أعلى رقم = تنفيذ متأخر):
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)
MSC0 الأولوية MSC1 السرعة msc2 التكلفة msc3 الأفضل ل мсc4 الحدود psc5 |----------|-------|-------|----------|------------| | 50 | ~50ms | مجانية | نص نقي, إختلاف كبيرM SK5 шрифтات قياسية | | | Schriftات مصممة |, | منخفضة الجودة |
الإشارات المنبعثة:
ocr.text - نص تم استخراجهocr.confidence - نمره الثقة المتوسطة لـ TesseractMSC0 الأولوية MSC1 السرعة msc2 التكلفة msc3 الأفضل ل мсc4 الحدود psc5 |----------|-------|-------|----------|------------| | 51 | ~200ms | مجانية | Schriftات مصممةM SK4 الميمات, النص الديكوري MPSK6 الرسوم البيانية المعقدةMPK7 النص المدور PMK8
الإشارات المنبعثة:
ocr.ml.text - واحدM SK1 إطار فلورانس-2 رسم بيانيocr.ml.multiframe_text - متعددةM SK1 نص إطار GIF (من المفضلة للانيميشنات)ocr.ml.confidence - نمط درجة الثقةMSC0 الأولوية MSC1 السرعة msc2 التكلفة msc3 الأفضل ل psc4 القيود scs5
| ---------- | ------- | ------- | ---------- | ------------ |
|---|
الإشارات المنبعثة:
ocr.vision.text - إستخراج النص لـ Vision LLM OCRocr.vision.confidence - الثقة في الـ LLMcaption.text - عنوان وصفي إختياري ( منفصل عن الرسم البيانيقبل الغوص في ثلاث طبقات من التصوير بالرنين المغناطيسي نماذج ML محددة التي تعمل على النظام. جميع النماذج تعمل محلياً من خلال ONNX Runtime— لا مكالمات APIM SK2 لا إعتمادات السحاب , لا تكاليف
* تحذير صغير: مقدمي تنفيذ GPU يمكن أن تضع في الإعتبار اللامحدودية الضئيلة-point nondeterminismM SK2 العقد السلكي (confidence thresholdsMSC4 routing logicMST5 remains fully deterministicMSM6
ملاحظة: الحجم تقريبية ومتغيرة حسب النموذج/كميّنةM SK2 بحجمات تحميل تقليدية تظهر في الأسفلMSC3
| نمط | تقريبا. الحجم | الهدف | السرعة | نوع النموذج | |||
|---|---|---|---|---|---|---|---|
| الشرق | ~100MB | الكشف عن النص في المشهد | ♫~20ms ♫ | ♫ الكشف عن texte ♫ | |||
| CRAFT | ~150MB | الرمز-إكتشاف النص في المنطقة ms | إختشاف tekst | ||||
| فلورانس-2 | ~250MB | نغمة صوتية + تُقَمّر S | ~200ms ♫ | الرؤية ♫ | -لغة ♫ | ||
| Real-ESRGAN | ~60MB | ||||||
| قفص | ~600MB | បង្កប់ الدلالية | ♫~100ms ♫ | ♫បង្កប់ متعددة الأشكال ♫ |
المساحة الكلية للقرص: ~1.0-1.5GB إعتماداً على الطرازات المختارة.
كاشف دقيق وفعال للكتابة النصية في المشهد - يجد مناطق نصية في المناظر الطبيعية
// 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)]
كيف تعمل:
لماذا ديناميكية?
< 0.5 → escalate)التفاصيل التقنية:
// 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
مخرج مثالي:
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%)
Character-الكشف عن مستوى النص - يتفوق على الرسم المنحنيM SK1 الفني , والكتابة الملونة.
// CRAFT finds character-level regions, then groups into words
var result = await textDetector.RunCraftDetectionAsync(imagePath);
// Better than EAST for: decorative fonts, curved text, logos
كيف تعمل:
عندما يتم استخدام "CRAFT":
التفاصيل التقنية:
// 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
اقصى الشرق مقابل CRAFT مقارنة:
| الميزة | شرق | كرات S | |||
|---|---|---|---|---|---|
| مستوى إكتشاف | كلمة/خط | حرف | |||
| السرعة | MSC2ms MSC3 \MSC4ms | ||||
| الأفضل لـ | |||||
| النص المنحني | محدود | ممتاز S | |||
| حجم النماذج | MSC2MB MSC3 | 150 | MB | MSC5 |
تحسن الصور ذات الجودة المنخفضة - قبل التصوير بالرسوم المتحركة - 4× رفع الخطوط المضيئةM SK2 نص صغير.
// 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
}
عندما يتم استخدامه:
مثال:
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)
التفاصيل التقنية:
// 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)
الإقتصاد الرمزي:
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
دمجات متعددة الأشكال لبحث الصور الدلالية - يعرض الصور والكتابة في الفضاء الفيكتوري المشترك
// 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);
كيف تعمل:
إستخدام الحالات:
التفاصيل التقنية:
// 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
مثال:
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
رؤية قسم 2 للتفصيلات الكاملة عن فلورانس-2 ONNX OCR و اللوحاتM SK2
جميع النماذج يتم تحميلها تلقائياً عند الاستخدام الأول:
$ 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
الانهيار اللطيف:
// 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
كل فشل نموذج أونكس يتم تسجيله مع طريق التراجع , متأكداً من أن النظام لن يتعطل أبداً بسبب النماذج المفقودة .
ملاحظة الأسعار: أمثلة من التكلفة في الأسفل تستخدم رسماً لتسعير (~$0.005/ صورة لـ Vision LLM). تكاليف API الحقيقية تختلف حسب المورد والنموذجM SK3 الإدراك الأساسيMSC4 معالجة محلية تقضي على معظم مكالمات API
بدون نماذج أونكس (خط الأساس):
Every image → Send to Vision LLM
Cost: ~$0.005/image (example pricing)
Time: ~2s network + inference
100 images = ~$0.50, ~200s
مع نماذج ONNX (localM SK1first):
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
الإدخار: ~95% تخفيض من التكلفة, |~85% أسرعM SK4 التوجيه الإفتراضي.
نماذج ONNX تحول النظام من "probabilistic down all the way down" to "deterministic foundation + probabilistic escalation only when neededM SK4
الخط الاساسي. سريع, محددM SK2 يعمل بشكل جيد للكتابة النظيفة .
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;
}
}
إشارات مفاتيح:
ocr.full_text - النص المستخرجocr.early_exit - إشارة لتخطى مستوى 2/3 إذا كان الثقة مرتفعةمايكروسوفت's فلورانس-2 هو نموذج لرؤية - النموذج اللغوي الذي يتفوق على بكتابة كثافة و OCRM SK3 نسخة ONNX تعمل محلياً بدون أي تكاليف API .
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;
}
}
بالنسبة للGIF المتحركة , فلورانس-2 العمليات إلى 10 الأجزاء المستخرجة بشكل متوازن
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;
}
مثال: 93-صورة GIF | | → |10 | फ्रेमات العينة |
Frame 1-45: "I'm not even mad."
Frame 46-93: "That's amazing."
إكتشاف النص OpenCV (~5-20ms) يحدد أي مسار يجب أخذه
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;
}
}
| الطريق | يحدد متى | المعالجة | الوقت | التكلفة | ||||
|---|---|---|---|---|---|---|---|---|
| أسرع | ثقة عالية (>0.8), نص معياري MSC2 فلورانسMSc3 فقط MSC4 МСC5ms msC6 مجانية msC7 | |||||||
| متوازن | الثقة المتواضعة (0.5-0.8) | فلورانس-2 ♫+ التصويت Tesseract | 5 | 6 | 7 | مجاني | 8 | |
| الجودة | ثقة ضعيفة (<0.5), معقدة | متعددة - إطار + رؤية اللوحة اللاسلكية | ~1-5 | $0.001-0.01 | ||||
| تم تصويره | GIF مع نمط عنوان فرعي | النص-شريحة فيلمية فقط |
تحسين التطور لترجمات GIF فقط المناطق النصية, لا إطارات كاملة.
المقاربة التقليدية لـ 93-frame GIF with subtitles
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
استخرج فقط مربعات الكتابة النصية, إزالة पिكسल्स الخلفية :
2 text regions × 250×50 × ~25 tokens/region = 50 tokens
Cost: ~$0.0005
Time: ~2 seconds
Token reduction: 30×
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;
}
}
الدخل: anchorman-not-even-mad.gif (93 frames, 300×185)
المعالجة:
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
ناتج: النص-شريحة واحدة فقط (450×49)

إقتصاد الرموز:
30× تقليل بينما يحفظ كل نص الترجمة
عندما تفشل تيسيراكت و فلورانس-2 أو تنتج نتائج ضعيفة-نتائج الثقةM SK2 تتدرج إلى Vision LLM MSC3GPT -4oMスク5 Claude MSSK6 SonnetMSL7 Gemini Pro VisionMST8 أو نماذج أولاما مثل minicpmMSSK9vMS).
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;
}
}
التسارع هو محدد: علامة إختبار اللغز < 50% | | → تتصاعد. لا الحكم الإحتمالي
عندما يتم تحفيز التسارع للGIF المتحركة , إستخدم النص - فقط الشريط МSK2
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();
}
}
عندما تنتهي جميع الطبقات, يستعمل اختيار النص النهائي ترتيب أولوية صارمة:
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;
}
كل مستوى لديه خصائص معروفة:
| المصدر | مفاتيح الإشارات | الأفضل لـ | الثقة | التكلفة | السرعة psk6 | |
|---|---|---|---|---|---|---|
| Vision LLM OCR | ocr.vision.text |
الرسوم البيانية المعقدةM SK1 النص المدور, مشوهة | 0.95 ♫ | $0.001-0.01 | ||
| فلورانسM SK1 (GIF) | ocr.ml.multiframe_text |
الرسوم المتحركة الGIFs مع الأشرطة الفرعية | 0.85-0.92 | |||
فلورانسM SK1 (single) ocr.ml.text |
Schriftات مصممة, الميماتM SK2 النص الديكوري | 0.85-0.90 | مجاني | ~200ms | |||
| Tesseract | ocr.text |
نص معياري نقي, متباينة عالية | إختلافات MSC3 مجانية MSC4 | MSC5 | ms |
100 الصور, جميعها باستخدام Vision LLM
100 images × $0.005/image = $0.50
Total time: 100 × 2s = 200 seconds
توزيع الخطوط (نموذجي):
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)
الطبقة الوسطى (Florence-2) تعالج 85% الصور بتكلفة صفرةM SK3
هنا ' هو التدفق الكامل لGIF الميم مع الأشرطة
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)
إذا فشلت فلورانس-2 (الثقة < ♫0.5), ستستمر التدفقs
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
نظام الطبقة الثلاثة - قابلة للتركيب بالكامل :
{
"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
}
}
}
| الفشل | الكشف | الاستجابة | |||
|---|---|---|---|---|---|
| فشلت تيسيراكت | الثقة < 0.7 أو إختبار الحروف < \0.5 | الهروب إلى فلورانس | -2 | ||
| فلورانس-2 فشل | الثقة < 0.5 أو إختبار الحروف < | 0.5 | الهروب إلى رؤية LLM | ||
| إنقطاع رؤية LLM | الطلب يتجاوز 30s S | العودة إلى أفضل نتيجة صوتية موجودة | |||
| فشلت جميع الطبقات | جميع النتائج فارغة أو مشوهة | أرجع السطر الفارغ بثقة | |||
| حد تكلفة API | الميزانية اليومية تجاوزت | لا يستطيعون الرؤية LLM, يستخدمون فلورانسM SK3 فقط | |||
| النموذج غير متوفر | فلورانسM SK1Vision LLM offline | Skip tier, continue to next |
كل فشل هو محدد ومسجل مع provenance الكامل.
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
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
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
لقد أثبتت أنابيب الأوCR الثلاثية الـ - أن Cost-aware routing و محلي- المعالجة الأولى يمكنه تحسين الأداء والاقتصاد بشكل كبير دون أن يهدئ الجودة.
الملاحظات الرئيسية:
مقياس النمط: تحليل محلي ديميائي → نموذج ML محلي → تتصاعد السحاب, كل الطبقة مع الخصائص المعروفة والتجارة بالتكاليف-offsM SK2
هذا هو الغموض المقيد الذي يطبق على الـ OCR : الإشارات التنقيبية ( اختبار اللاتغة \ , الكشف عن النص | ) نماذج تقليدية إفتراضية ♫ ( | فلورانس |-2, | الرؤية | LLM ٬ ), | والانتاج النهائي يجمع المصادر حسب الجودة ٬.
| جزء | نمط | تركيز |
|---|---|---|
| 1 | الغموض المقيد | عنصر واحد |
| 2 | MoM مقيدة | أجزاء متعددة |
| 3 | سحب السياق | الوقت / الذاكرة |
| 4 | ذكاء الصورة | فن العمارة الموجية, الأنماط |
| 4.1 | الأنبوب الثلاثي -Tier OCR (هذه المقالة) | OCR, نماذج ONNX, شرائح الأفلام |
التالي: الجزء 5 سيظهر كيف ImageSummarizer DocSummarizer, و المزيج للبيانات compose into multi-modal graph RAG with LucidRAG.
كل الأجزاء تتبع نفس المتغير: مكونات الإحتمالية تقترح ; أن الأنظمة الثابتة تستمر.
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.