ОCR з обмеженими розбіжними параметрами - Трійні МSK1 Швидкі OCR труби (Українська (Ukrainian))

ОCR з обмеженими розбіжними параметрами - Трійні МSK1 Швидкі OCR труби

Wednesday, 07 January 2026

//

29 minute read

Частина 4: Інтелект зображення представила архітектуру хвиль ImageSummarizer і ширші моделі. Субсистема OCR—три шари видобутку тексту , інтелектове маршрутизування МSK2 і оптимізація стрічків фільмів, яка досягає МSK3 зниження символів для анімованих GIFs M SK4

Чому окремий artykuł? Трубопровод OCR розвинувся від "Tesseract з Vision LLM fallback " до складної тривимірної МSK2тиєрної системи з ML-засадою OCRM SK4 мультиплікаторного М SK5швидкісного голосування M SK6тексту -взяття лише смугів MSC8 та вартості MNK9розумівого маршрутизування МСК10 Він МSK11 достатньо складний, щоб гарантувати свій власний деталізований розрив МСК12

Подібні статті:


Проблема: Видобуток текстів важкий

ОCR на реальних зображеннях

  • У стилізованих шрифтах: Tesseract тренується на стандартних шрифтахM SK1 не працює на декоративному тексті
  • Шумливі GIF: Артефакти стиснення кадру, джиттерM SK2 зміни субтитра
  • Низкий контраст: Темний текст на темних фонах
  • Викручений текст: Не МSK1 горизонтальні кути тексту
  • Змішаний контент: Картили з багатьма текстовими областями
  • Ціна API: Зйомка LLM дорого коштує

Традиційний підхід: M SK1Run Tesseract, якщо він не використовує Vision LLM

Проблема: Це або не вписує стильизованого тексту (Tesseract зазнає невдачі МSK2 або занадто дорого коштує

Рішення: Додайте середній рівень ( Флоренція МSK2 ONNX M SK3 що обробляє стильизовані шрифти локально , ескалізація до Vision LLM тільки тоді, коли обидві локальні методи не спрацюють MSC5


Архітектура OCR для трьох систем

Система запускає хвилі в порядку пріоритету (вище число = пізніше виконанняM SK2

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)

Уровни 1: Тесеракт МSK1Традиційне OCRM SK2

МSK0 Prioryte Speed МSK2 Cost Best For СМС4 Limitations СМС5
50 МSK1м МSK2 Бесплатно Чистий текстM SK4 високий контраст, стандартні шрифти MSК6 Сタイлізовані шрифтиМСК7 низька якістьMСК8 обертений текст МСК9

Випущені сигнали:

  • ocr.text - Вичерпаний текст
  • ocr.confidence - середній показник довіри Тесеракту

Уровни 2: ФлоренціяM SK1 Оксфорд МSK2 МЛ ОCR)

МSK0 Prioryte Speed МSK2 Cost Best For СМС4 Limitations СМС5
51 МSK1ms МSK2 Бесплатно Стилизовані шрифтиM SK4 меми, декоративний текст MSК6 Створені діаграмиМСК7 обертений текст МСК8

Випущені сигнали:

  • ocr.ml.text - Одноразовий -фразмер ФлоренціяM SK2 ОCR
  • ocr.ml.multiframe_text - Мультифріальний GIF-текст МSK1 для анімації
  • ocr.ml.confidence - Модель рівня довіри

Уровни 3: Vision LLM (Cloud FallbackM SK2

Приорітієнт МSK1 Швидкість МSK2 Koszt Найкраще для M Заперечення МСК5
80 МSK0 ~1-5s МSK2 \ $0.001-0.01 \ \ МSK4 \ Все , \ особливо складні сцени \МSK6 \ Потрібно поважати детерміністичні сигнали \

Випущені сигнали:

  • ocr.vision.text - Видобуток тексту Vision LLM OCR
  • ocr.vision.confidence - Віра в LLM
  • caption.text - Включна дескриптивна заголовка відокремлена від OCR

Моделі локального ML - Arsenal ONNX:

Перед тим, як зануритися в три шари OCR, детерміністичні моделі МЛ що заsilaють систему. Всі моделі запускаються локально через ONNX Runtime — без API дзвінків , без залежності від хмари MSC3 без витрат M SK4

Чому ONNX?

  • Працює локально: Без API ключів , без затримки в мережі МSK2 без повторюваних витрат
  • Детермінізм: Схожий вхід МSK1 той самий виход МSK2 без зразків / температурна випадковість*
  • Найшвидше: МашинаM SK1швидшена МSK2CPU/GPU ), оптимізована гіпотеза
  • Переnosимий: працює на Windows, Linux, macOS
  • Авто- завантажений: Спочатку завантажують моделі автоматично

* Невелика обережність:fournувачи обчислень ҐПУ можуть ввести занедбаний плавнийМSK1punktовий недетермінізмМСК2 Сигналовий контракт MСК3похибка до довіруМ СК4 логіка маршрутизуванняM СК5 залишається повністю детерміністичноюМSК6

П 'ять моделей ONNX

Примітка: Размери приблизні та відрізняються залежно від варіації / кількісність МSK2 Зазвичайні розміри завантаження показані нижче M SK3

МСК0 Модель Приблизно МSK2 розмір Задача СМСК4 Швидкість СМСК5 Тип моделі МСК6
Схід МSK1MB МSK2 Виявление тексту на сцені ♫ ♫ МSK4ms ♫ ♫ Викриття тексту ♫
CRAFT МSK1MB МSK2 KarakterM SK3викриття тексту в регіоні ♫ ♫ МSK5ms ♫ ♫ Викриття tekstu ♫
Флоренція-2 МSK0 ~250MB ОCR МSK3 субтитри м. МSK4 ♫ ♫ МSK5 ♫ ms ♫
Реальний-ESRGAN МSK0 ~60MB МSK2 \ 4× супер - розгортання роздільної здатності мSK5 ~500ms M покращення зображення МСК8
Кліп МSK1MB МSK2 Семантичні вбудовані модулі ♫ ♫ МSK4ms ♫ ♫ Многоmodaльне вбудоване модуля ♫

Весь простір диска: МSK1ГБ в залежності від обраних варіантів моделіM SK2


1. Східно-ВостокіM SK1 Виявление сценарного тексту

Надзвичайний та ефективний детектор тексту сцени - знаходить текстові ділянки у природніх сценахM SK1

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

Як це працює:

  • Модель глибокого навчання, тренована на сценах текстових наборів даних
  • Карта результатів: (впевненістьM SK1 МSK2 геометрична карта (координації коробки)
  • Ручки з ротаційним текстом, багаторазовийM SK1текст в масштабі
  • Використовуючи Non-Maximum Suppression (NMSM SK2, щоб об 'єднати коробки, що перетинаються

Чому детермінативний?

  • Жодної випадковості в припущенні (замерзлі масиM SK1
  • Те ж саме зображення → ті ж коробки з вигином
  • Результати впевненості повторюються
  • Пороги ескаляції: config-driven M SK1e.gMSC3 < 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%)

МSK0 CRAFT: Знайомість регіону

Karakter-викриття тексту рівня - відмінно від вигнутихM SK1 художніх, і стильизованого текстуMSC3

// CRAFT finds character-level regions, then groups into words
var result = await textDetector.RunCraftDetectionAsync(imagePath);

// Better than EAST for: decorative fonts, curved text, logos

Як це працює:

  • Виявляє окремі ділянки символів (сервісніше, ніж на Східному фронтіM SK1
  • Використовуючи рахунок близькості для групування символів у слова
  • Алгоритм заповнення flood- знаходить пов 'язані текстові компоненти
  • Рухає вигнутим текстом, який пропустив Схід

Коли CRAFT вживається:

  1. EAST недоступна або зазнала невдачі
  2. У зображенні є художні шрифти/рекоративні шрифти M SK1авто-швидкі шрифти
  3. Пользователь явно вибирає детектор 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

Восточні країни в порівнянні з Крафтом:

Функція МSK1 Східно-Східний МSK2 Крестовий
рівень розпізнавання МSK1 слово МSK2 лінія символ ХМSK4
Швидкість МSK1 ~20ms МSK3 ♫ ♫ МSK4 ♫
Найкраще для МSK1 Стандартний текстM SK2 субтитры МSK3 Декоративні шрифти , логотипи
Закручений текст МSK1 Ограничений МSK2 Чудово
розмір моделі МSK1 МSK2 МБ ♫ ♫ МSK4 Мб ♫

МSK0 Реальний-ESRGAN: СуперM SK3 Підвищення роздільної здатності

Удосконалює зображення низької якості - МСК0 - перед OCR МSK0 4× розміщування для нерозбірливості М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
}

Коли він' працює:

  • Швидкість зображення < МSK1 ♫ ♫ МSK2 ♫ Лаплаційська варіантність ♫
  • Текстові ділянки виявлені, але дуже малі (< 20 висотаpxM SK2
  • Упевненість в OCR низька, але текстові регіони присутні
  • Пользователь явно просить розміщування

Наприклад,:

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

4. CLIP: Семантичні додатки

Мультиmodaльне вбудова для семантичного пошуку зображення - проектує зображення та текст у спільний векторний простір

// 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 ViT-BM SK1 (350MB
  • Проектує зображення на вектори 512-вимірності
  • Обучений узгоджуватися з текстовими описами
  • Дасть змогу "найти зображення, подібні до цьогоM SK1 без słów kluczowych

Використовуйте випадки:

  • Семантичний пошук зображення в системах RAG
  • Похибне виявлення ( навіть якщо відредаговано
  • Конtent-це кластерування
  • Схожі вказівки на зображення

Технические деталі:

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

5. ФлоренціяM SK1 Зйомка МSK2 Модель мови МSK3 Закритий шаром 2

Розгляньте секцію рівня 2 для більш детального опису Флоренції -2 ONNX OCR та субтитра М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

Кожен провал моделі ONNX записується шляхом зворотнього зв 'язку,, що гарантує, що система ніколи не скасується через бракуючих моделей.


Чому це важливо

Цінна: Ниже представлені приклади затрат використовують ілюстративну ціну (~$0.005/об 'єкт для Vision LLM МSK2 Взагалі-то витрати на API відрізняються залежно від постачальника та моделі

Без моделей ONNX (початкова лініяM SK1

Every image → Send to Vision LLM
  Cost: ~$0.005/image (example pricing)
  Time: ~2s network + inference
  100 images = ~$0.50, ~200s

З моделями ONNX МSK0 локальний-першийM SK2

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

ЗаощадженняМSK0 ~95% зниження витрат МSK2 \ ~85% швидше , детерміністичне маршрутизування.

Моделі ONNX перетворюють систему з "припущення всередину " до МSK2детерміністичного фундаменту МSK3 вероятностної ескалації тільки тоді, коли це необхідно


Уровни 1: Tesseract OCR

Базова лінія. SzвидкаM SK1 детерміністична , чудово працює для чистого тексту.

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, якщо рівень довіри високий
  • Результати впевненості призводять до прийняття рішень про ескалацію

Зона 2: ФлоренціяM SK1 NX

Microsoft's ФлоренціяM SK1 це візуальна модель - мова, яка вражає у щільній субтитрації та OCR МSK3 Версия ONNX працює локально без жодних затрат на API

Чому Флоренція

  • Краще, ніж Tesseract для стилізованих шрифтів: Ручки з декоративним текстом
  • Швидше, ніж Vision LLMМSK0 ~200ms проти МSK2s
  • Безкоштовна: Працює локально , не потребує API ключу
  • Мультиmodaльне розуміння: Можуть витягати текст у контексті (e.gM SK3 мовні бульбашки МSK4

Втілення

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

Мульти-МСК0 - обробка фреймів GIF

Для анімованих GIF, ФлоренціяM SK1 процеси до 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;
}

Наприклад,: МSK1фразма GIF МSK2 10 зразки кадрів | | МSK4 | 2 | унікальні результати тексту

Frame 1-45:  "I'm not even mad."
Frame 46-93: "That's amazing."

Вибір маршруту

Розпізнавання тексту OpenCV (~5-20ms) визначає шлях, який слід пройтиM SK2

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

Досконалість маршруту

Маршрут МSK1 Триггери, коли МSK2 Обробка Час
Швидше Сильне впевненість МSK1 стандартний текст МSK2 ФлоренціяM SK3 тільки MSК5ms МСК6 Вільно СМСК7
BALANCED МSK0 Помірна впевненість МSK1 Флоренція -2 ♫ ♫ МSK4 ♫ Голосування за тезерактом ♫
QUALITY Низка впевненість
ANIMATED GIF з шаблоном субтитров МSK1 Текст МSK2 тільки стрічка для фільмів

Teks-Витягування лише стрічок

Оптимізація прориву для субтитров GIF: ekstrakt лише текстові ділянки, не повні кадри

Проблема

Традиційний підхід для графічного GIF з субтитрами 93-

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

Рішення: ТекстM SK1Только смуги

Витягніть лише літери з текстом, використовуючи фонові пікселіM SK1

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

Виход: ТекстM SK1один бік МSK2

Текст-Еще один приклад смуги

Економіка символів:

  • Цілковиті кадри (10 зразок, МSK1 МSK2 × \10 | | МSK5 | ~1500 | टोकони
  • ОCR стрічка (2 кадриM SK1 МSK2 | | × ♫ | МSK4 ♫
  • Teks- тільки смужкаМSK0 450×49 МSK2 ~50 टोकони

30× скорочення зберігаючи весь текст субтитра.


Уровни 3: Уявлення LLM ескаляція

Коли і Тесеракт, і Флоренція-2 зазнають невдачі або виробляють низькі результатиМSK1напевненістьM SK2 ескалуються до Vision LLM MSC3GPT-4oMスク5 Claude 3.5 SonnetMska7 Gemini Pro VisionM Ska8 чи моделі Оллами, такі як minicpmM СК9vMСК10

Двері якості

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

Ескаляція - детерміністична: бал перевірки заклинання

Взірок LLM з Filmstrip

Коли активується ескаляція для анімованих GIF, використовуйте текст

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

Priority Chain

Коли всі рівні закінчуються, кінцевий вибір тексту використовує жорсткий порядок пріоритетуM SK1

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

Кожен рівень має відомі характеристики:

Źródło Сигнальний ключ МSK2 Найкраще для МSK3 Упевненості M Koszt МСК5 Швидкість МПС6
Vision LLM OCR ocr.vision.text Комплексні діаграмиM SK1 обертений текстМSK2 розбитий МSK3 0.95 \ $0.001-0.01 \ МSK0 ФлоренціяМСК1 MСК2 GIFМ СК3 МСК4 ocr.ml.multiframe_text Анімовані GIF з субтитрами
МSK0 ФлоренціяМСК1 MСК2одне числоМ СК3 МСК4 ocr.ml.text Стилізовані шрифтиM SK1 меми, декоративний текст МSK4 ♫ ♫ МSK5 ♫ Бесплатно ♫
Тесеракт МSK1 ocr.text Чистий стандартний текст МSK1 високий контраст МSK2 Варіанти Вільні

Розробка витрат

До системи Tier Three

100 зображення, всі, використовуючи Vision LLM

100 images × $0.005/image = $0.50
Total time: 100 × 2s = 200 seconds

Після Системи Трійного Тиера

Розподіл маршрутів (типічнийM SK1

  • 60 зображення МSK1 Швидший маршрут МSK2 Флоренція -2 тільки
  • 25 зображення МSK1 Балансована траса МSK2 Флоренція -2 မ်SK4 Тессеракт एमSK5 безкоштовна
  • 10 зображення МSK1 Квалитечна траса МSK2 Візор ЛЛМ , MSК4 MSК5 СМСК6
  • МSK0 зображення → ANIMATED маршрут МSK2filmstrip , MSК4 МСК5s)
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)

Середній рівень (ФлоренціяM SK1 обробляє МSK2 зображення за нульову ціною.


Поєднати все разом

Тут – МСК0 – це повний потік для 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 провалилась M SK1впевненість

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

Konfiguracja

Система на трьох рівнях МСК0 є повністю конфігурована

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

Моди невдачі

Непрацездатність МSK1 Виявление Відповідь МSK3
Теsseract не працює Упевненість МSK1 МSK2 Або перевірка правопису < ♫ ♫ МSK4 ♫ Перестрибнути до Флоренції ♫
Флоренція-2 не працює Упевненість МSK1 МSK2 Або перевірка писемності < \ 0.5 \ МSK5 Переход до візуального лінгвісу LLM |
Часовий вихід Vision LLM Запрос перевершує 30s МSK2 Повертається до найкращого результату OCR
Усі рівні провалюються Всі результати - пусті або зруйновані
Ограничення вартості API Щодняшній бюджет перевищений МSK1 Неможливе зорове бачення ЛЛМ МSK2 використовувати Флоренцію
Модель не доступна ФлоренціяM SK1Vision LLM недоступна Перейти на рівень, перейти до наступного МSK4

Кожна невдача є детерміністичною і записується з повною продукцією.


Порівняння з іншими підходами

Традиційний: Tesseract M SK1 Ручний зворотній спуск

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

Хмара-ПершийM SK1 Повсякчас використовувати 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

Три МСК0 Швартовий МSK1 локальний - Перший з розумним ескаляцією

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

Завершення

Трійна труба OCR на рівні - доводить, що вартість-свідоме маршрутування і локальна-перша обробка може значно покращити як ефективність, так і економіку, не втрачаючи якості.

Найголовні ідеї:

  1. Флоренція-2 Онікс - це чудове місце: Краще, ніж Tesseract для стилизованих шрифтів , швидше і дешевше, ніж Vision LLM
  2. Teks- тільки смуги досягають M SK1 зниження символу: Витягувати коробки для згинівM SK1 не повні кадри
  3. Рутування є детерміністичним: виявлення відкритого CV МSK1 пороги довіри МSK2 без підрахунку
  4. Ескаляція піддається контролю: Кожен рівень випускає сигнали про provenance
  5. Невдача - граційна: Prioryte chain ensure fallback to best available source

Скала шаблону: локальна детерміністична analiza → локальний модель МЛ МSK1 ескаляція хмар, кожен рівень з відомою характеристикою та торгівлею витратами

Це - Конstrained Fuzziness, пристосований до OCR: детермінативних сигналів ( перевірка звуку МSK2 розпізнавання тексту \ ) обмежуючи ймовірністичні моделі | ( Флоренція \ МSK5 | Vision LLM | МSK6 | а кінцевий результат об 'єднує джерела за допомогою якості |. |


Ресурс

Документація LucidRAG

Наrzędziя CLI

Дослідження

Подібні статті


Серіал

частина МSK1 візерунок МSK2 фокус
1 Стримана неясність Едина компонента МSK1
2 Обмежений фузій МСМ Багато компонентів МSK1
3 Перетягування контексту МSK0 Час / пам 'ять
4 Інтелект зображення Архітектура хвиль
4.1 Трійна труба -Tier OCR ОCR, Моделі ONNXМSK1 стрічки для фільмів

Далі: Частина МSK1 покаже, як ImageSummarizer DocSummarizer, і DataSummarizer з 'єднати до мультиплікового графу RAG з LucidRAG.

Усі частини йдуть за однаковим інваріантом: ймовірнісні компоненти запропонують; детерміністичні системи тривають.

Finding related posts...
logo

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