🔍 پیاده‌سازی OCR با Tesseract

تکمیل متد PerformOcrAsync در XImageFileContentExtractor برای استخراج متن از تصاویر
👨‍💻 توسعه‌دهنده: هادی خزاعی اصل
🏢 شرکت: فن آوران ساحر علم
📅 تاریخ: شنبه ۱۲ مهر ۱۴۰۵
📦 پروژه: xAiApi

📦 گام ۱: نصب پکیج NuGet Tesseract

برای استفاده از Tesseract در پروژه .NET، پکیج زیر را نصب کنید:

# در Package Manager Console:
Install-Package Tesseract

# یا در .NET CLI:
dotnet add package Tesseract
💡 نکته: پکیج Tesseract به صورت خودکار Native DLL های مورد نیاز را نیز دانلود و در پوشه output کپی می‌کند.

📥 گام ۲: دانلود فایل‌های Trained Data

Tesseract برای تشخیص متن به فایل‌های traineddata نیاز دارد. این فایل‌ها مدل‌های زبانی هستند که برای هر زبان جداگانه آموزش دیده‌اند.

۲.۱. دانلود فایل‌ها:

از لینک زیر فایل‌های مورد نیاز را دانلود کنید:

https://github.com/tesseract-ocr/tessdata

۲.۲. فایل‌های مورد نیاز:

فایل زبان حجم تقریبی کاربرد
fas.traineddata فارسی ~۱۰ MB تشخیص متن فارسی
eng.traineddata انگلیسی ~۴ MB تشخیص متن انگلیسی
ara.traineddata عربی ~۵ MB تشخیص متن عربی (اختیاری)

۲.۳. ساختار پوشه‌ها:

فایل‌های دانلود شده را در مسیر زیر قرار دهید:

xAiApi/
└── tessdata/
    ├── fas.traineddata
    ├── eng.traineddata
    └── ara.traineddata (اختیاری)
⚠️ نکته مهم: مسیر tessdata باید در زمان اجرا در دسترس باشد. می‌توانید آن را در appsettings.json پیکربندی کنید.

⚙️ گام ۳: پیکربندی در appsettings.json

تنظیمات Tesseract را در فایل appsettings.json اضافه کنید:

{
  "AiApiConfiguration": {
    "FileExtraction": {
      "OCR": {
        "TessDataPath": "tessdata",
        "DefaultLanguage": "fas+eng",
        "EngineMode": "LstmOnly"
      }
    },
    "Models": [ ... ],
    "Prompts": [ ... ]
  }
}

توضیح پارامترها:

پارامتر توضیح مقادیر مجاز
TessDataPath مسیر پوشه فایل‌های traineddata مسیر نسبی یا مطلق
DefaultLanguage زبان‌های پیش‌فرض برای OCR fas, eng, ara (ترکیب با +)
EngineMode حالت موتور Tesseract TesseractOnly, LstmOnly, TesseractAndLstm, Default

🔄 گام ۴: بازنویسی کلاس XImageFileContentExtractor

اکنون کلاس XImageFileContentExtractor را بازنویسی می‌کنیم تا از Tesseract برای OCR استفاده کند:

MODIFY xAiApi/Providers/Extractors/XImageFileContentExtractor.cs
using System;
using System.IO;
using System.Linq;
using System.Threading;
using Tesseract;
using xAiModels.Models;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using xAiApi.Interfaces.Extractors;

namespace xAiApi.Providers.Extractors
{
    /// <summary>
    /// Extracts content from image files using Tesseract OCR ...
    /// </summary>
    public class XImageFileContentExtractor : IXImageFileContentExtractor
    {
        /// <summary>
        /// Supported MIME Types ...
        /// </summary>
        private static readonly string[] SupportedMimeTypes =
        [
            "image/png",
            "image/jpeg",
            "image/jpg",
            "image/gif",
            "image/webp",
            "image/bmp"
        ];

        /// <summary>
        /// مسیر پوشه tessdata ...
        /// </summary>
        private readonly string _tessDataPath;

        /// <summary>
        /// زبان پیش‌فرض برای OCR ...
        /// </summary>
        private readonly string _defaultLanguage;

        /// <summary>
        /// حالت موتور Tesseract ...
        /// </summary>
        private readonly OcrEngineMode _engineMode;

        /// <summary>
        /// Logger ...
        /// </summary>
        private readonly ILogger<XImageFileContentExtractor> _logger;

        /// <summary>
        /// Constructor با پیکربندی پیش‌فرض ...
        /// </summary>
        public XImageFileContentExtractor(
            ILogger<XImageFileContentExtractor> logger,
            string tessDataPath = "tessdata",
            string defaultLanguage = "fas+eng",
            OcrEngineMode engineMode = OcrEngineMode.LstmOnly
        )
        {
            _logger = logger;
            _tessDataPath = tessDataPath;
            _defaultLanguage = defaultLanguage;
            _engineMode = engineMode;
        }

        /// <summary>
        /// Check if this extractor supports the specified MIME type ...
        /// </summary>
        public bool CanExtract(string mimeType)
        {
            return SupportedMimeTypes.Contains(
                mimeType?.ToLowerInvariant() ?? string.Empty
            );
        }

        /// <summary>
        /// Extract text content from file stream ...
        /// </summary>
        public async Task<string> ExtractAsync(
            Stream fileStream,
            string mimeType,
            CancellationToken cancellationToken = default
        )
        {
            var result = await ExtractRichAsync(
                fileStream,
                "image",
                mimeType,
                cancellationToken
            );
            return result.Text;
        }

        /// <summary>
        /// Extract content from stream as Rich Result ...
        /// </summary>
        public async Task<XFileExtractionResult> ExtractRichAsync(
            Stream fileStream,
            string fileName,
            string mimeType,
            CancellationToken cancellationToken = default
        )
        {
            var result = new XFileExtractionResult
            {
                FileName = fileName,
                MimeType = mimeType
            };

            // خواندن بایت‌های تصویر
            using var memoryStream = new MemoryStream();
            await fileStream.CopyToAsync(memoryStream, cancellationToken);
            var imageBytes = memoryStream.ToArray();

            // افزودن تصویر به عنوان DataContent (برای Vision Models)
            result.Images.Add(new XExtractedImage
            {
                Bytes = imageBytes,
                MimeType = mimeType,
                Description = $"Attached image: {fileName}"
            });

            // انجام OCR برای استخراج متن
            try
            {
                var ocrText = await PerformOcrAsync(imageBytes, cancellationToken);
                if (!string.IsNullOrWhiteSpace(ocrText))
                {
                    result.Text = ocrText;
                    _logger.LogInformation(
                        "OCR completed successfully for file: {FileName}, extracted {Length} characters",
                        fileName,
                        ocrText.Length
                    );
                }
                else
                {
                    _logger.LogWarning("OCR completed but no text was extracted from file: {FileName}", fileName);
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "OCR failed for file: {FileName}", fileName);
                result.ErrorMessage = $"OCR failed: {ex.Message}";
                // تصویر همچنان برای Vision Models در دسترس است
            }

            return result;
        }

        /// <summary>
        /// انجام OCR روی تصویر با استفاده از Tesseract ...
        /// </summary>
        private async Task<string> PerformOcrAsync(
            byte[] imageBytes,
            CancellationToken cancellationToken
        )
        {
            return await Task.Run(() =>
            {
                // بررسی وجود مسیر tessdata
                if (!Directory.Exists(_tessDataPath))
                {
                    throw new DirectoryNotFoundException(
                        $"TessData directory not found at: {_tessDataPath}. " +
                        "Please download traineddata files from https://github.com/tesseract-ocr/tessdata"
                    );
                }

                // ایجاد Tesseract Engine
                using var engine = new TesseractEngine(
                    datapath: _tessDataPath,
                    language: _defaultLanguage,
                    mode: _engineMode
                );

                // بارگذاری تصویر از byte array
                using var pix = Pix.LoadFromMemory(imageBytes);

                // تنظیم CancellationToken
                cancellationToken.ThrowIfCancellationRequested();

                // انجام OCR
                using var page = engine.Process(pix);

                // استخراج متن
                var text = page.GetText();

                // پاکسازی متن (حذف فاصله‌های اضافی)
                text = text?.Trim();

                return text ?? string.Empty;
            }, cancellationToken);
        }
    }
}

🔧 گام ۵: جزئیات پیاده‌سازی متد PerformOcrAsync

۵.۱. مراحل کار متد:

۱. بررسی مسیر tessdata

  • اطمینان از وجود پوشه tessdata
  • پرتاب خطا در صورت عدم وجود

۲. ایجاد Tesseract Engine

  • بارگذاری مدل زبانی
  • تنظیم حالت موتور (LSTM)

۳. بارگذاری تصویر

  • تبدیل byte[] به Pix object
  • پشتیبانی از فرمت‌های مختلف

۴. انجام OCR

  • پردازش تصویر
  • استخراج متن

۵. پاکسازی متن

  • حذف فاصله‌های اضافی
  • بررسی CancellationToken

۵.۲. ویژگی‌های کلیدی پیاده‌سازی:

ویژگی توضیح
پشتیبانی چند زبانه استفاده از fas+eng برای تشخیص همزمان فارسی و انگلیسی
حالت LSTM استفاده از موتور LSTM برای دقت بالاتر در متون فارسی
مدیریت خطا بررسی وجود tessdata و مدیریت استثناها
Logging ثبت موفقیت/شکست OCR با جزئیات
Cancellation Support پشتیبانی از لغو عملیات در میانه کار
Resource Management استفاده از using برای آزادسازی منابع

💡 گام ۶: نکات مهم و عیب‌یابی

۶.۱. مشکلات رایج و راه‌حل‌ها:

مشکل علت راه‌حل
DirectoryNotFoundException پوشه tessdata یافت نشد دانلود traineddata و قرار دادن در مسیر صحیح
TesseractException فایل traineddata خراب یا ناسازگار دانلود مجدد از منبع رسمی
دقت پایین OCR کیفیت پایین تصویر پیش‌پردازش تصویر (افزایش کنتراست، resize)
تشخیص نادرست فارسی ترکیب نامناسب زبان‌ها استفاده از fas به تنهایی یا fas+eng
کندی عملکرد تصاویر بزرگ تغییر EngineMode به TesseractOnly

۶.۲. بهینه‌سازی عملکرد:

🚀 افزایش سرعت

  • استفاده از OcrEngineMode.TesseractOnly
  • کاهش DPI تصویر (مثلاً 150 به جای 300)
  • استفاده از یک زبان به جای چند زبان

🎯 افزایش دقت

  • استفاده از OcrEngineMode.LstmOnly
  • افزایش DPI تصویر (300 یا بالاتر)
  • پیش‌پردازش تصویر (binarization)

۶.۳. پیش‌پردازش تصویر (اختیاری):

برای افزایش دقت OCR، می‌توانید قبل از پردازش، تصویر را پیش‌پردازش کنید:

// مثال: افزایش کنتراست و تبدیل به سیاه و سفید
using var pix = Pix.LoadFromMemory(imageBytes);
using var enhanced = pix.ConvertTo1(); // تبدیل به 1-bit
using var page = engine.Process(enhanced);

۶.۴. ثبت در Startup.cs:

اکنون XImageFileContentExtractor را با پیکربندی صحیح ثبت کنید:

public void ConfigureServices(IServiceCollection services)
{
    // ... سایر ثبت‌ها

    // ثبت XImageFileContentExtractor با پیکربندی
    services.AddSingleton<IXFileContentExtractor>(sp =>
    {
        var logger = sp.GetRequiredService<ILogger<XImageFileContentExtractor>>();
        var configuration = sp.GetRequiredService<IConfiguration>();
        
        var tessDataPath = configuration.GetValue<string>(
            "AiApiConfiguration:FileExtraction:OCR:TessDataPath"
        ) ?? "tessdata";
        
        var defaultLanguage = configuration.GetValue<string>(
            "AiApiConfiguration:FileExtraction:OCR:DefaultLanguage"
        ) ?? "fas+eng";
        
        return new XImageFileContentExtractor(
            logger: logger,
            tessDataPath: tessDataPath,
            defaultLanguage: defaultLanguage,
            engineMode: OcrEngineMode.LstmOnly
        );
    });

    // ... سایر ثبت‌ها
}
✅ نتیجه نهایی:
  • 🔍 OCR کامل: استخراج متن از تصاویر با دقت بالا
  • 🌐 چند زبانه: پشتیبانی از فارسی، انگلیسی و عربی
  • ⚡ بهینه: استفاده از موتور LSTM برای دقت بالاتر
  • 🛡️ مقاوم: مدیریت خطا و logging کامل
  • 🔄 قابل لغو: پشتیبانی از CancellationToken
  • 📦 منعطف: پیکربندی از طریق appsettings.json
💡 نکته تکمیلی:

اگر نیاز به دقت بالاتر برای متون فارسی دارید، می‌توانید از مدل‌های آموزش‌دیده‌شده خاص فارسی استفاده کنید یا از ترکیب Tesseract با مدل‌های deep learning (مانند CRNN) بهره ببرید.