مقاله آموزشی

استخراج داده از فاکتور و سند فارسی با مدل‌های ویژن

نویسنده: تیم نِت اَرز ۱۰ دقیقه مطالعه ۱۱ بازدید

هر ماه یک دسته فاکتور کاغذی یا عکس گرفته‌شده با موبایل روی میز حسابداری می‌آید و کسی باید ردیف‌به‌ردیف آن‌ها را در اکسل یا نرم‌افزار مالی تایپ کند. این کار هم وقت می‌برد و هم خطا تولید می‌کند. مدل‌های ویژن امروز می‌توانند بخش بزرگی از این کار را انجام دهند — اما نه به شکلی که تبلیغ‌ها می‌گویند. این مقاله نشان می‌دهد چطور از یک عکس فاکتور فارسی، خروجی JSON ساختاریافته بگیرید، کجاها مدل اشتباه می‌کند و چه بازبینی‌ای لازم است تا نتیجه قابل اتکا باشد.

تفاوت این روش با OCR معمولی چیست؟

OCR کلاسیک کارش تبدیل تصویر به متن است؛ خروجی‌اش یک رشتهٔ طولانی از کاراکترهاست و بعد شما باید با قاعده‌نویسی و عبارت باقاعده بفهمید کدام عدد «جمع کل» است و کدام «مالیات». هر فاکتور با طرح متفاوت، یعنی یک دور قاعده‌نویسی تازه.

مدل ویژن یک لایه بالاتر کار می‌کند: تصویر را می‌بیند و معنای اجزا را می‌فهمد. به همین دلیل می‌توانید به‌جای «متن را بده»، بگویید «شمارهٔ فاکتور، تاریخ، نام فروشنده و ردیف اقلام را در این ساختار به من بده». خروجی مستقیماً همان چیزی است که در دیتابیس می‌نشیند. مزیت اصلی این است که با تغییر طرح فاکتور، معمولاً لازم نیست چیزی را بازنویسی کنید.

در مقابل، یک ضعف واقعی هم دارد: مدل ویژن گاهی عددی را که خوب نمی‌بیند «حدس» می‌زند، در حالی که OCR در همان جا احتمالاً کاراکتر مخدوش تحویل می‌داد. حدسِ قابل‌قبول اما اشتباه، از خطای آشکار خطرناک‌تر است. بخش بازبینی همین مقاله دقیقاً برای همین نوشته شده است.

کیفیت تصویر؛ جایی که نصف دقت برنده یا بازنده می‌شود

پیش از هر بهینه‌سازی پرامپت، ورودی را درست کنید. تفاوت یک عکس خوب و یک عکس بد در نتیجه، بیشتر از تفاوت دو مدل است.

عاملوضعیت مطلوباثر روی نتیجه
وضوحمتن فاکتور روی صفحه کاملاً خوانا باشد؛ اگر خودتان با زوم نمی‌خوانید، مدل هم نمی‌خواندمستقیم‌ترین اثر را روی خطای ارقام دارد
زاویهدوربین موازی با کاغذ؛ عکس از زاویهٔ مورب، جدول را کج می‌کندباعث جابه‌جایی مقادیر بین ستون‌ها می‌شود
نورنور یکنواخت بدون سایهٔ دست و بدون بازتابسایه روی یک ستون یعنی همان ستون خالی برمی‌گردد
کامل بودن کادرهر چهار گوشهٔ سند داخل کادرقطع شدن لبه یعنی حذف خاموش یک ردیف
حجم فایلفشرده‌سازی معقول؛ تصویر بیش از حد فشرده، ارقام را می‌جودخطاهایی مثل خواندن ۵ به‌جای ۶

یک توصیهٔ عملی: اگر منبع شما اسکنر است، حالت سیاه‌وسفید کم‌کیفیت را خاموش کنید. اگر منبع موبایل کاربران است، در فرم آپلود راهنمای تصویری بگذارید؛ همین یک تغییر ساده معمولاً بیشتر از هر تنظیم فنی، نرخ خطا را پایین می‌آورد.

کد: از عکس تا JSON ساختاریافته

ایدهٔ اصلی این است که به‌جای امید بستن به اینکه مدل «خودش JSON تمیز بدهد»، ساختار خروجی را از قبل تعریف کنید. سرویس API نِت اَرز همان قالب OpenAI را می‌فهمد؛ پس با کتابخانهٔ رسمی و فقط با عوض کردن آدرس پایه کار می‌کند:

import base64, json
from openai import OpenAI

client = OpenAI(
    base_url="https://netarz.ir/api/ai/v1",
    api_key="sk-ntz-v1-...",
)

with open("invoice.jpg", "rb") as f:
    b64 = base64.b64encode(f.read()).decode()

schema = {
    "type": "object",
    "additionalProperties": False,
    "required": ["invoice_number", "issue_date", "seller", "items", "total"],
    "properties": {
        "invoice_number": {"type": ["string", "null"]},
        "issue_date": {"type": ["string", "null"]},
        "seller": {"type": ["string", "null"]},
        "total": {"type": ["number", "null"]},
        "items": {
            "type": "array",
            "items": {
                "type": "object",
                "additionalProperties": False,
                "required": ["title", "quantity", "unit_price", "line_total"],
                "properties": {
                    "title": {"type": "string"},
                    "quantity": {"type": ["number", "null"]},
                    "unit_price": {"type": ["number", "null"]},
                    "line_total": {"type": ["number", "null"]},
                },
            },
        },
    },
}

instruction = (
    "Read this Persian invoice image and extract the fields. "
    "Convert every Persian or Arabic digit to Western digits. "
    "Remove thousand separators. Keep dates exactly as printed. "
    "If a field is not visible, return null instead of guessing."
)

response = client.chat.completions.create(
    model="gpt-4.1-mini",
    messages=[{
        "role": "user",
        "content": [
            {"type": "text", "text": instruction},
            {"type": "image_url",
             "image_url": {"url": f"data:image/jpeg;base64,{b64}"}},
        ],
    }],
    response_format={
        "type": "json_schema",
        "json_schema": {"name": "invoice", "strict": True, "schema": schema},
    },
)

data = json.loads(response.choices[0].message.content)
print(data)

سه نکته دربارهٔ همین کد. اول، strict باعث می‌شود خروجی حتماً با ساختار بخواند و دیگر لازم نباشد نگران متن اضافه قبل و بعد JSON باشید؛ توضیح کامل این مکانیزم در راهنمای خروجی JSON قابل اعتماد آمده است. دوم، مقدار null را در شِما مجاز گذاشته‌ایم تا مدل به‌جای ساختن یک عدد الکی، خالی بودن را اعلام کند. سوم، مدل ویژن سبک برای فاکتورهای تمیز کافی است؛ سراغ مدل سنگین‌تر وقتی بروید که ورودی واقعاً بد باشد. مدل‌های OpenAI هم‌اکنون فعال‌اند و نام‌های دقیق و قیمت هر کدام در فهرست مدل‌ها آمده است.

تله‌های فارسی که نتیجه را خراب می‌کنند

  • ارقام فارسی و عربی: سند فارسی ممکن است ۱۲۳ فارسی، ١٢٣ عربی یا 123 لاتین داشته باشد و گاهی هر سه در یک صفحه. حتی اگر در پرامپت خواسته‌اید تبدیل شوند، در سمت کد هم نرمال‌سازی کنید.
  • جداکنندهٔ هزار: ویرگول فارسی «٬» و ویرگول لاتین هر دو دیده می‌شوند. اگر پاکشان نکنید، تبدیل به عدد شکست می‌خورد.
  • تاریخ شمسی: ۱۴۰۵/۰۶/۰۹ را نباید مستقیم در ستون تاریخ میلادی ذخیره کنید. از مدل بخواهید تاریخ را عیناً همان‌طور که چاپ شده برگرداند و تبدیل تقویم را در کد خودتان با کتابخانهٔ تاریخ شمسی انجام دهید؛ محاسبهٔ تقویم کار مدل نیست.
  • ریال یا تومان: فاکتورهای ایرانی هر دو واحد را به کار می‌برند و گاهی در یک سند مخلوط می‌شوند. یک فیلد جدا برای واحد پول در نظر بگیرید و در بازبینی به آن دقت کنید.
  • راست‌به‌چپ بودن جدول: در بعضی طرح‌ها ستون‌ها از راست شروع می‌شوند و مدل ممکن است «مقدار» و «قیمت واحد» را جابه‌جا بفهمد. در پرامپت، عنوان ستون‌ها را به‌صراحت نام ببرید.
  • حروف مشابه: ی و ي، ک و ك در نام‌ها و شمارهٔ اقتصادی تفاوت ایجاد می‌کنند. اگر روی این مقادیر تطبیق می‌دهید، حتماً نرمال‌سازی کنید.

نرمال‌سازی ارقام در پایتون چند خط بیشتر نیست و ارزشش را دارد که همیشه روی خروجی اجرا شود:

DIGITS = str.maketrans("۰۱۲۳۴۵۶۷۸۹٠١٢٣٤٥٦٧٨٩", "01234567890123456789")

def to_number(value):
    if value is None:
        return None
    text = str(value).translate(DIGITS)
    for ch in ("٬", ",", " ", "‌"):
        text = text.replace(ch, "")
    text = text.replace("٫", ".")
    try:
        return float(text)
    except ValueError:
        return None

جدول‌ها و ردیف‌های چندسطری

سخت‌ترین بخش هر فاکتور، جدول اقلام است. سه مشکل رایج دارد. اول، شرح کالا که در دو یا سه خط شکسته شده و مدل ممکن است آن را دو ردیف جدا ببیند. دوم، ردیف‌هایی که ستون قیمت واحد ندارند و فقط جمع سطر دارند. سوم، فاکتور چندصفحه‌ای که جدولش از صفحه‌ای به صفحهٔ بعد ادامه پیدا می‌کند.

راه‌حل‌های عملی: در پرامپت صریح بنویسید که هر قلم کالا یک ردیف است حتی اگر شرحش چند خط باشد. برای اسناد چندصفحه‌ای، هر صفحه را جداگانه بفرستید و ردیف‌ها را در کد خودتان به هم بچسبانید — فرستادن چند تصویر در یک درخواست، دقت را پایین می‌آورد و اشکال‌زدایی را سخت می‌کند. و همیشه یک بررسی ساده اضافه کنید: جمع line_total ردیف‌ها باید با total بخواند. اگر اختلاف داشت، یعنی یا ردیفی جا افتاده یا عددی اشتباه خوانده شده؛ همین یک شرط، بخش بزرگی از خطاها را قبل از ورود به دیتابیس می‌گیرد.

دقت واقعی چقدر است و بازبینی انسانی کجا لازم می‌شود؟

هیچ عدد ثابتی وجود ندارد که بشود صادقانه اعلام کرد؛ دقت به کیفیت تصویر، یکدست بودن طرح فاکتورها و پیچیدگی جدول بستگی دارد. چیزی که می‌شود با اطمینان گفت این است: روی اسناد تمیز و یکدست، نتیجه معمولاً خیلی خوب است و روی عکس‌های موبایلی کج و کم‌نور، خطا محسوس می‌شود. بنابراین طراحی درست این است که سامانه را «کمک‌کار اپراتور» بدانید، نه جایگزین او.

یک گردش کار قابل اتکا معمولاً این شکل است:

  1. استخراج خودکار با شِمای مشخص انجام می‌شود.
  2. بررسی‌های ماشینی اجرا می‌شوند: جمع ردیف‌ها با کل بخواند، تاریخ معتبر باشد، مقادیر null علامت بخورند، شمارهٔ فاکتور تکراری نباشد.
  3. هر سندی که در بررسی‌ها مردود شود یا فیلد خالی داشته باشد، به صف بازبینی انسانی می‌رود.
  4. اپراتور فقط همان صف را می‌بیند، نه همهٔ اسناد را. کاری که قبلاً تایپ کامل بود، تبدیل به تأیید و اصلاح چند فیلد می‌شود.
  5. اصلاحات را ثبت کنید تا بفهمید بیشترین خطا روی کدام فیلد و کدام نوع سند است؛ همان‌جا باید پرامپت یا فرایند عکس‌برداری را بهبود دهید.

و یک هشدار مهم: اسناد مالی و هویتی دادهٔ حساس‌اند. پیش از فرستادن، مشخص کنید چه چیزی لازم است و چه چیزی نیست، و سیاست نگه‌داری داده را برای خودتان روشن کنید. برای اسنادی که به قانون یا مالیات مربوط می‌شوند، خروجی خودکار بدون تأیید انسانی مبنای تصمیم نباشد.

هزینه، مقیاس و شروع کار

در استخراج تصویری، بخش عمدهٔ هزینه از توکن‌های ورودی می‌آید؛ چون تصویر پیش از پردازش به تعدادی توکن تبدیل می‌شود و هرچه تصویر بزرگ‌تر باشد، این عدد بیشتر است. سه راهکار ساده هزینه را پایین نگه می‌دارد: تصویر را به اندازهٔ لازم برای خوانا بودن متن کوچک کنید و نه بیشتر، برای فاکتورهای تمیز از مدل سبک استفاده کنید و مدل قوی را فقط برای اسنادی بگذارید که بررسی ماشینی ردشان کرده، و خروجی را کوتاه نگه دارید — از مدل توضیح نخواهید، فقط داده بخواهید. روش دقیق محاسبه در توکن چیست و هزینهٔ API چطور حساب می‌شود آمده و برای دقیق‌تر کردن دستورها راهنمای پرامپت‌نویسی کمک می‌کند.

برای شروع، در صفحهٔ سرویس API یک کلید بسازید، اعتبار کوچکی شارژ کنید و اول بیست فاکتور واقعی را پردازش کنید؛ نتیجه را دستی با اصل سند مقایسه کنید و ببینید خطا کجاست. همین آزمایش کوچک، بهتر از هر تخمینی به شما می‌گوید که این مسیر برای اسناد شما جواب می‌دهد یا نه. جزئیات فنی مسیرها در مستندات فنی و مقایسهٔ مسیر API با اشتراک در تفاوت API و اشتراک آمده است. قیمت لحظه‌ای شارژ اعتبار در صفحهٔ محصول درج شده و مالیات بر ارزش افزوده هنگام پرداخت جداگانه نمایش داده می‌شود.

پرسش‌های پرتکرار

آیا مدل ویژن جای OCR را می‌گیرد؟

برای استخراج فیلدهای مشخص از سندهای متنوع، معمولاً بله و دردسر قاعده‌نویسی را حذف می‌کند. اما اگر فقط به متن خام سند نیاز دارید و طرح اسناد کاملاً ثابت است، OCR سنتی ارزان‌تر و قابل پیش‌بینی‌تر باقی می‌ماند.

تاریخ شمسی را چطور مدیریت کنم؟

از مدل بخواهید تاریخ را دقیقاً همان‌طور که روی سند چاپ شده برگرداند و تبدیل شمسی به میلادی را در کد خودتان با یک کتابخانهٔ تاریخ انجام دهید. محاسبهٔ تقویم کار مدل نیست و سپردنش به مدل، منبع خطاهای پنهان است.

چطور بفهمم خروجی قابل اعتماد است؟

بررسی‌های ماشینی بگذارید: جمع ردیف‌ها با مبلغ کل بخواند، فیلدهای خالی علامت بخورند و شمارهٔ فاکتور تکراری نباشد. هر سندی که این بررسی‌ها را رد کند باید به صف بازبینی انسانی برود، نه مستقیم به دیتابیس.

برای فاکتور چندصفحه‌ای چه کنم؟

هر صفحه را جداگانه بفرستید و ردیف‌ها را در کد خودتان به هم بچسبانید. فرستادن چند تصویر در یک درخواست هم دقت را پایین می‌آورد و هم پیدا کردن اینکه خطا از کدام صفحه آمده را سخت می‌کند.

محصولات مرتبط با این مقاله

مطالب مشابه

نظر خوانندگان

هنوز نظری ثبت نشده. اگر این مقاله پرسشتان را جواب داد یا جای چیزی در آن خالی ماند، همین‌جا بنویسید.

نظرتان را بنویسید

این مقاله چقدر به کارتان آمد؟ (اختیاری)

نظرها را پیش از انتشار بررسی می‌کنیم. نقد صریح مشکلی ندارد؛ تبلیغ و توهین منتشر نمی‌شود.