آموزش API تحلیل تصویر؛ ارسال عکس به مدل‌های چندوجهی درواره

در این راهنمای فنی یاد می‌گیرید چگونه تصویر را با URL، Base64 یا فایل آپلودی به مدل‌های چندوجهی درواره ارسال کنید، چند عکس را هم‌زمان تحلیل کنید و یک API عملی با Python و FastAPI بسازید.

Share
آموزش API تحلیل تصویر؛ ارسال عکس به مدل‌های چندوجهی درواره

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

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

در یک درخواست چندوجهی، برنامه معمولاً دو نوع ورودی ارسال می‌کند:

  • دستور متنی
  • تصویر به‌صورت URL یا Base64

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

در این مقاله، نحوه ارسال تصویر به مدل‌های Vision از طریق API درواره را با cURL، Python و FastAPI بررسی می‌کنیم. همچنین درباره چند تصویر، مدیریت اندازه فایل، خروجی JSON، اعتبارسنجی، Retry و معماری مناسب محیط عملیاتی صحبت خواهیم کرد.

مدل چندوجهی چیست؟

مدل چندوجهی می‌تواند بیش از یک نوع داده را دریافت یا تولید کند. این Modalities ممکن است شامل موارد زیر باشند:

  • متن
  • تصویر
  • صدا
  • ویدئو
  • فایل
  • داده ساختاریافته

مدلی که ورودی متن و تصویر را هم‌زمان می‌پذیرد، می‌تواند رابطه میان دستور کاربر و محتوای بصری را درک کند.

برای مثال، می‌توانید تصویر یک داشبورد را همراه با این دستور ارسال کنید:

سه تغییر مهمی را که در این نمودار مشاهده می‌شود توضیح بده. فقط مشاهدات قابل‌تأیید از تصویر را بنویس و درباره علت تغییرات حدس نزن.

تفاوت Vision API با OCR چیست؟

OCR یا Optical Character Recognition عمدتاً برای استخراج نوشته از تصویر استفاده می‌شود. مدل چندوجهی علاوه بر خواندن متن، می‌تواند ساختار و محتوای کلی تصویر را نیز تحلیل کند.

قابلیتOCRمدل چندوجهی
استخراج متنبلهبله
تشخیص ساختار صفحهمحدودمعمولاً بهتر
توضیح تصویرخیربله
تحلیل نمودارمحدودبله
پاسخ به سوال درباره تصویرخیربله
ارتباط میان متن و تصویرمحدودبله
استدلال درباره چند تصویرخیردر مدل‌های پشتیبانی‌شده
خروجی ساختاریافتهبه ابزار بستگی دارددر مدل‌های پشتیبانی‌شده

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

کاربردهای API تحلیل تصویر

استخراج اطلاعات از سند

  • فاکتور
  • رسید
  • فرم
  • جدول
  • کارت محصول
  • گزارش تصویری
  • برگه سفارش

تحلیل عکس محصول

  • تشخیص دسته محصول
  • استخراج رنگ و ویژگی ظاهری
  • تولید توضیحات اولیه
  • مقایسه چند محصول
  • شناسایی ایراد قابل‌مشاهده

تحلیل اسکرین‌شات

  • توضیح رابط کاربری
  • استخراج پیام خطا
  • شناسایی عناصر صفحه
  • تهیه گزارش باگ
  • مقایسه طراحی با نمونه مرجع

تحلیل نمودار

  • خواندن عنوان و محورها
  • استخراج روندهای قابل‌مشاهده
  • مقایسه دسته‌ها
  • خلاصه‌سازی نمودار
  • تولید Alt Text

جست‌وجوی بصری

  • تولید برچسب برای تصاویر
  • ساخت توضیح قابل‌جست‌وجو
  • دسته‌بندی کاتالوگ
  • استخراج ویژگی‌های ظاهری

کنترل محتوای تولیدشده

  • بررسی وجود عناصر موردنیاز
  • کنترل نسبت تصویر
  • شناسایی متن ناخواسته
  • مقایسه خروجی با Brief
  • انتخاب بهترین تصویر از چند گزینه

پیش‌نیازهای اتصال به API درواره

برای ارسال درخواست به API نیاز دارید:

  • حساب درواره
  • موجودی کافی
  • API Key
  • Model ID مدل دارای قابلیت ورودی تصویر
  • Base URL درواره
  • تصویر قابل‌دسترسی یا فایل محلی

Base URL درواره:

https://api.darvareh.ir/v1

برای انتخاب مدل چندوجهی، صفحه مدل‌های درواره را بررسی کنید. در این آموزش به‌جای نام ثابت مدل از YOUR_MODEL_ID استفاده می‌کنیم.

همه مدل‌ها ورودی تصویر ندارند. پیش از ارسال درخواست، مطمئن شوید مدل انتخابی از Image Input یا Vision پشتیبانی می‌کند.

ساخت API Key

بعد از ثبت‌نام در درواره، یک کلید API از پنل کاربری ایجاد کنید.

در Linux و macOS:

export DARVAREH_API_KEY="YOUR_API_KEY"
export DARVAREH_MODEL_ID="YOUR_MODEL_ID"

در PowerShell:

$env:DARVAREH_API_KEY="YOUR_API_KEY"
$env:DARVAREH_MODEL_ID="YOUR_MODEL_ID"

در فایل .env:

DARVAREH_API_KEY=YOUR_API_KEY
DARVAREH_MODEL_ID=YOUR_MODEL_ID

کلید API را در فرانت‌اند، کد JavaScript مرورگر، اپلیکیشن عمومی یا مخزن Git قرار ندهید. درخواست باید از سرور برنامه شما ارسال شود.

ساختار درخواست چندوجهی

درخواست معمول Chat Completions شامل یک آرایه messages است. محتوای پیام کاربر نیز به‌جای یک رشته ساده، آرایه‌ای از بخش‌های مختلف خواهد بود:

{
  "role": "user",
  "content": [
    {
      "type": "text",
      "text": "این تصویر را توضیح بده."
    },
    {
      "type": "image_url",
      "image_url": {
        "url": "https://example.com/image.jpg"
      }
    }
  ]
}

ترتیب پیشنهادی این است که ابتدا دستور متنی و سپس تصویر قرار بگیرد. اگر چند تصویر دارید، هرکدام را در یک بخش image_url جداگانه ارسال کنید.

روش‌های ارسال تصویر

دو روش اصلی وجود دارد:

ارسال URL تصویر

اگر تصویر از طریق یک آدرس عمومی HTTPS در دسترس است، URL آن را مستقیماً ارسال کنید.

مزایا:

  • حجم درخواست API کمتر است.
  • تبدیل Base64 لازم نیست.
  • برای فایل‌های ذخیره‌شده در Object Storage مناسب است.

محدودیت‌ها:

  • URL باید برای سرویس قابل‌دسترسی باشد.
  • لینک نباید به صفحه HTML اشاره کند.
  • لینک‌های موقت ممکن است پیش از پردازش منقضی شوند.
  • بعضی سرورها دسترسی خودکار را مسدود می‌کنند.

ارسال Base64

فایل محلی را به Base64 تبدیل و در قالب Data URL ارسال می‌کنید.

نمونه:

data:image/jpeg;base64,/9j/4AAQSkZJRgABAQ...

مزایا:

  • برای تصاویر محلی یا خصوصی کاربرد دارد.
  • به URL عمومی نیاز ندارد.
  • تصویر همراه خود درخواست منتقل می‌شود.

محدودیت‌ها:

  • حجم Payload بیشتر می‌شود.
  • Base64 حجم داده را تقریباً به نسبت چهار به سه افزایش می‌دهد.
  • فایل‌های بزرگ می‌توانند باعث افزایش زمان درخواست شوند.
  • محدودیت اندازه Body سرور باید در نظر گرفته شود.

ارسال تصویر با URL و cURL

curl https://api.darvareh.ir/v1/chat/completions \
  -H "Authorization: Bearer $DARVAREH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "'"$DARVAREH_MODEL_ID"'",
    "messages": [
      {
        "role": "system",
        "content": "فقط براساس محتوای قابل مشاهده در تصویر پاسخ بده. اگر چیزی مشخص نیست، آن را نامشخص اعلام کن."
      },
      {
        "role": "user",
        "content": [
          {
            "type": "text",
            "text": "این تصویر را به فارسی توصیف کن و عناصر اصلی آن را فهرست کن."
          },
          {
            "type": "image_url",
            "image_url": {
              "url": "https://example.com/sample.jpg"
            }
          }
        ]
      }
    ],
    "temperature": 0.2
  }'

در این درخواست:

  • مدل از متغیر محیطی خوانده می‌شود.
  • پیام System رفتار مدل را محدود می‌کند.
  • متن و تصویر در یک پیام کاربر قرار دارند.
  • دمای پایین برای پاسخ‌های تحلیلی انتخاب شده است.

ارسال تصویر Base64 با Python

نصب کتابخانه‌ها:

pip install openai python-dotenv pillow

فایل .env:

DARVAREH_API_KEY=YOUR_API_KEY
DARVAREH_MODEL_ID=YOUR_MODEL_ID

کد:

import base64
import mimetypes
import os
from pathlib import Path

from dotenv import load_dotenv
from openai import OpenAI

load_dotenv()

api_key = os.getenv("DARVAREH_API_KEY")
model_id = os.getenv("DARVAREH_MODEL_ID")

if not api_key:
    raise RuntimeError("DARVAREH_API_KEY is not configured")

if not model_id:
    raise RuntimeError("DARVAREH_MODEL_ID is not configured")

client = OpenAI(
    api_key=api_key,
    base_url="https://api.darvareh.ir/v1",
)


def image_to_data_url(image_path: Path) -> str:
    mime_type, _ = mimetypes.guess_type(image_path.name)

    allowed_types = {
        "image/jpeg",
        "image/png",
        "image/webp",
    }

    if mime_type not in allowed_types:
        raise ValueError(
            f"Unsupported image type: {mime_type}"
        )

    image_bytes = image_path.read_bytes()
    encoded = base64.b64encode(image_bytes).decode("ascii")

    return f"data:{mime_type};base64,{encoded}"


image_path = Path("dashboard.png")
image_data_url = image_to_data_url(image_path)

response = client.chat.completions.create(
    model=model_id,
    messages=[
        {
            "role": "system",
            "content": (
                "تو یک تحلیلگر تصویر هستی. فقط مواردی را "
                "بیان کن که از تصویر قابل مشاهده‌اند و "
                "درباره علت‌ها حدس نزن."
            ),
        },
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": (
                        "عنوان، محورهای نمودار و سه روند اصلی "
                        "قابل مشاهده را به فارسی توضیح بده."
                    ),
                },
                {
                    "type": "image_url",
                    "image_url": {
                        "url": image_data_url,
                    },
                },
            ],
        },
    ],
    temperature=0.1,
)

print(response.choices[0].message.content)

چرا از mimetypes استفاده کردیم؟

نوع MIME باید با فرمت واقعی فایل هماهنگ باشد:

JPEG  → image/jpeg
PNG   → image/png
WebP  → image/webp

استفاده همیشگی از image/jpeg برای فایل PNG یا WebP می‌تواند باعث خطای پردازش شود.

در محیط عملیاتی بهتر است نوع فایل را فقط از پسوند حدس نزنید و محتوای واقعی فایل را نیز اعتبارسنجی کنید.

ارسال تصویر از URL با Python

import os

from dotenv import load_dotenv
from openai import OpenAI

load_dotenv()

client = OpenAI(
    api_key=os.environ["DARVAREH_API_KEY"],
    base_url="https://api.darvareh.ir/v1",
)

model_id = os.environ["DARVAREH_MODEL_ID"]

image_url = "https://example.com/product-image.jpg"

response = client.chat.completions.create(
    model=model_id,
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": (
                        "این محصول را براساس ویژگی‌های قابل مشاهده "
                        "توصیف کن. رنگ، شکل، جنس ظاهری و اجزای اصلی "
                        "را جداگانه بنویس. ویژگی نامشخص را حدس نزن."
                    ),
                },
                {
                    "type": "image_url",
                    "image_url": {
                        "url": image_url,
                    },
                },
            ],
        }
    ],
    temperature=0.2,
)

print(response.choices[0].message.content)

URL تصویر باید چه ویژگی‌هایی داشته باشد؟

URL مناسب باید:

  • با HTTPS قابل‌دسترسی باشد.
  • مستقیماً فایل تصویر را برگرداند.
  • نیازمند ورود کاربر نباشد.
  • مدت اعتبار کافی داشته باشد.
  • Content-Type صحیح داشته باشد.
  • توسط محدودیت جغرافیایی یا شبکه مسدود نشده باشد.
  • به صفحه نمایش تصویر در سایت اشاره نکند.

نامناسب:

https://example.com/gallery/image/123

این URL ممکن است یک صفحه HTML باشد.

مناسب‌تر:

https://cdn.example.com/images/123.jpg

برای فایل خصوصی می‌توانید از Signed URL کوتاه‌مدت با مدت اعتبار کافی استفاده کنید.

ارسال چند تصویر در یک درخواست

برای مقایسه دو تصویر، آن‌ها را در یک پیام قرار دهید:

response = client.chat.completions.create(
    model=model_id,
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": (
                        "تصویر اول و دوم را مقایسه کن. "
                        "فقط تفاوت‌های قابل مشاهده در چیدمان، "
                        "رنگ، نوشته و اجزای رابط کاربری را بنویس."
                    ),
                },
                {
                    "type": "text",
                    "text": "تصویر اول:",
                },
                {
                    "type": "image_url",
                    "image_url": {
                        "url": "https://example.com/before.png",
                    },
                },
                {
                    "type": "text",
                    "text": "تصویر دوم:",
                },
                {
                    "type": "image_url",
                    "image_url": {
                        "url": "https://example.com/after.png",
                    },
                },
            ],
        }
    ],
    temperature=0.1,
)

print(response.choices[0].message.content)

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

تعداد تصاویر قابل ارسال و محدودیت Payload به مدل انتخاب‌شده بستگی دارد. برای تعداد زیاد تصویر، بهتر است درخواست‌ها را دسته‌بندی کنید.

مقایسه چند تصویر محصول

پرامپت مناسب:

سه تصویر محصول با برچسب A، B و C ارسال شده‌اند.

فقط براساس ظاهر تصاویر این موارد را مقایسه کن:
- رنگ
- فرم کلی
- اجزای قابل مشاهده
- نوع بسته‌بندی
- تفاوت اندازه فقط در صورتی که مقیاس مشترک وجود دارد

قواعد:
- درباره کیفیت ساخت، قیمت یا دوام حدس نزن.
- اگر زاویه تصاویر متفاوت است، این محدودیت را ذکر کن.
- خروجی را در قالب جدول Markdown بنویس.

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

استخراج اطلاعات ساختاریافته از تصویر

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

نمونه خروجی موردنظر:

{
  "document_type": "invoice",
  "invoice_number": "INV-1024",
  "invoice_date": "2026-07-20",
  "currency": "IRR",
  "total_amount": 12500000,
  "line_items": [
    {
      "description": "نام کالا",
      "quantity": 2,
      "unit_price": 5000000
    }
  ],
  "warnings": []
}

پرامپت:

اطلاعات این فاکتور را استخراج کن.

خروجی فقط JSON معتبر با ساختار زیر باشد:
{
  "document_type": "invoice یا unknown",
  "invoice_number": "string یا null",
  "invoice_date": "string یا null",
  "currency": "string یا null",
  "total_amount": "number یا null",
  "line_items": [
    {
      "description": "string",
      "quantity": "number یا null",
      "unit_price": "number یا null",
      "total_price": "number یا null"
    }
  ],
  "warnings": ["string"]
}

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

اگر مدل انتخابی از Structured Output یا JSON Schema پشتیبانی می‌کند، از همان قابلیت برای محدود کردن شکل پاسخ استفاده کنید.

اعتبارسنجی خروجی با Pydantic

نصب:

pip install pydantic

مدل داده:

from pydantic import BaseModel, Field


class InvoiceLineItem(BaseModel):
    description: str
    quantity: float | None = None
    unit_price: float | None = None
    total_price: float | None = None


class InvoiceResult(BaseModel):
    document_type: str
    invoice_number: str | None = None
    invoice_date: str | None = None
    currency: str | None = None
    total_amount: float | None = None
    line_items: list[InvoiceLineItem] = Field(
        default_factory=list
    )
    warnings: list[str] = Field(
        default_factory=list
    )

اعتبارسنجی:

import json

raw_content = response.choices[0].message.content
parsed_json = json.loads(raw_content)

invoice = InvoiceResult.model_validate(parsed_json)

print(invoice.model_dump_json(
    ensure_ascii=False,
    indent=2,
))

اعتبارسنجی ساختار به معنی تأیید صحت اطلاعات استخراج‌شده نیست. مقدارها را باید با تصویر اصلی یا قواعد کسب‌وکار تطبیق دهید.

پاک کردن Markdown Code Fence از JSON

بعضی مدل‌ها ممکن است JSON را داخل Code Fence برگردانند:

```json
{
  "document_type": "invoice"
}
```

تابع ساده:

def strip_json_fence(content: str) -> str:
    content = content.strip()

    if content.startswith("```json"):
        content = content[len("```json"):]

    if content.startswith("```"):
        content = content[3:]

    if content.endswith("```"):
        content = content[:-3]

    return content.strip()

استفاده:

clean_content = strip_json_fence(raw_content)
parsed_json = json.loads(clean_content)

در محیط عملیاتی بهتر است از مدل دارای پشتیبانی واقعی Structured Output استفاده کنید تا احتمال این مشکلات کاهش پیدا کند.

آماده‌سازی تصویر پیش از ارسال

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

برای مثال:

  • برای تشخیص کلی تصویر، وضوح متوسط ممکن است کافی باشد.
  • برای استخراج نوشته کوچک، وضوح بالاتر لازم است.
  • برای نمودار، برچسب محورها باید خوانا باشند.
  • برای سند چندبخشی، برش هر بخش ممکن است نتیجه را بهتر کند.
  • برای جدول طولانی، ارسال چند Crop با هم‌پوشانی کنترل‌شده مفید است.

تغییر اندازه تصویر با Pillow

from pathlib import Path

from PIL import Image, ImageOps

input_path = Path("large-document.jpg")
output_path = Path("optimized-document.jpg")

max_size = (2048, 2048)

with Image.open(input_path) as image:
    image = ImageOps.exif_transpose(image)
    image = image.convert("RGB")
    image.thumbnail(
        max_size,
        Image.Resampling.LANCZOS,
    )

    image.save(
        output_path,
        format="JPEG",
        quality=90,
        optimize=True,
        progressive=True,
    )

print("Saved:", output_path)

ImageOps.exif_transpose جهت تصویر را براساس اطلاعات EXIF اصلاح می‌کند. بدون آن ممکن است تصویر گرفته‌شده با موبایل در سمت سرور چرخیده نمایش داده شود.

حفظ نوشته‌های کوچک

برای سند دارای نوشته، فشرده‌سازی شدید JPEG می‌تواند لبه حروف را خراب کند. راهکارها:

  • کیفیت JPEG را بیش از حد کاهش ندهید.
  • در صورت مناسب بودن، PNG استفاده کنید.
  • بخش دارای نوشته را Crop کنید.
  • کنتراست تصویر را پیش از ارسال بررسی کنید.
  • تصویر تار را با بزرگ کردن ساده «دقیق» فرض نکنید.
  • برای سند چندصفحه‌ای، هر صفحه را جدا پردازش کنید.

برش تصویر با Python

from pathlib import Path

from PIL import Image, ImageOps

input_path = Path("invoice.jpg")
output_path = Path("invoice-total-section.jpg")

with Image.open(input_path) as image:
    image = ImageOps.exif_transpose(image)
    image = image.convert("RGB")

    width, height = image.size

    crop_box = (
        int(width * 0.45),
        int(height * 0.60),
        width,
        height,
    )

    cropped = image.crop(crop_box)

    cropped.save(
        output_path,
        quality=94,
        optimize=True,
    )

print("Saved:", output_path)

مختصات Crop باید براساس ساختار واقعی سند تعیین شوند. برای قالب‌های ثابت می‌توان ناحیه‌ها را از قبل تعریف کرد.

ساخت API آپلود تصویر با FastAPI

در این مثال، یک Endpoint ایجاد می‌کنیم که تصویر را دریافت و برای تحلیل به مدل چندوجهی ارسال می‌کند.

نصب:

pip install fastapi uvicorn python-multipart openai python-dotenv pillow

فایل app.py:

import base64
import io
import os

from dotenv import load_dotenv
from fastapi import FastAPI, File, HTTPException, UploadFile
from openai import OpenAI
from PIL import Image, ImageOps, UnidentifiedImageError

load_dotenv()

api_key = os.getenv("DARVAREH_API_KEY")
model_id = os.getenv("DARVAREH_MODEL_ID")

if not api_key:
    raise RuntimeError("DARVAREH_API_KEY is not configured")

if not model_id:
    raise RuntimeError("DARVAREH_MODEL_ID is not configured")

client = OpenAI(
    api_key=api_key,
    base_url="https://api.darvareh.ir/v1",
)

app = FastAPI(
    title="Darvareh Vision API Example",
)

MAX_UPLOAD_BYTES = 8 * 1024 * 1024

ALLOWED_CONTENT_TYPES = {
    "image/jpeg",
    "image/png",
    "image/webp",
}


def optimize_image(
    image_bytes: bytes,
) -> tuple[bytes, str]:
    try:
        with Image.open(io.BytesIO(image_bytes)) as image:
            image.load()
            image = ImageOps.exif_transpose(image)
            image = image.convert("RGB")

            image.thumbnail(
                (2048, 2048),
                Image.Resampling.LANCZOS,
            )

            output = io.BytesIO()

            image.save(
                output,
                format="JPEG",
                quality=90,
                optimize=True,
            )

            return output.getvalue(), "image/jpeg"

    except UnidentifiedImageError as error:
        raise ValueError(
            "Uploaded file is not a valid image"
        ) from error


def bytes_to_data_url(
    image_bytes: bytes,
    mime_type: str,
) -> str:
    encoded = base64.b64encode(
        image_bytes
    ).decode("ascii")

    return (
        f"data:{mime_type};base64,{encoded}"
    )


@app.post("/analyze-image")
async def analyze_image(
    file: UploadFile = File(...),
):
    if file.content_type not in ALLOWED_CONTENT_TYPES:
        raise HTTPException(
            status_code=415,
            detail="Unsupported image format",
        )

    file_bytes = await file.read(
        MAX_UPLOAD_BYTES + 1
    )

    if len(file_bytes) > MAX_UPLOAD_BYTES:
        raise HTTPException(
            status_code=413,
            detail="Image is too large",
        )

    try:
        optimized_bytes, mime_type = optimize_image(
            file_bytes
        )
    except ValueError as error:
        raise HTTPException(
            status_code=400,
            detail=str(error),
        ) from error

    data_url = bytes_to_data_url(
        optimized_bytes,
        mime_type,
    )

    try:
        response = client.chat.completions.create(
            model=model_id,
            messages=[
                {
                    "role": "system",
                    "content": (
                        "فقط محتوای قابل مشاهده را توصیف کن. "
                        "اگر بخشی نامشخص است، آن را حدس نزن."
                    ),
                },
                {
                    "role": "user",
                    "content": [
                        {
                            "type": "text",
                            "text": (
                                "این تصویر را به فارسی تحلیل کن. "
                                "نوع تصویر، عناصر اصلی، نوشته‌های "
                                "خوانا و محدودیت‌های مشاهده را بنویس."
                            ),
                        },
                        {
                            "type": "image_url",
                            "image_url": {
                                "url": data_url,
                            },
                        },
                    ],
                },
            ],
            temperature=0.1,
        )

    except Exception as error:
        raise HTTPException(
            status_code=502,
            detail="Vision model request failed",
        ) from error

    return {
        "filename": file.filename,
        "analysis": (
            response.choices[0]
            .message.content
        ),
    }

اجرای سرور:

uvicorn app:app --reload

آزمایش با cURL:

curl -X POST \
  http://127.0.0.1:8000/analyze-image \
  -H "Accept: application/json" \
  -F "file=@dashboard.png"

در محیط Production از --reload استفاده نکنید.

جدا کردن Endpoint دریافت فایل از پردازش مدل

در سامانه پرترافیک بهتر است پردازش سنگین را مستقیماً داخل درخواست آپلود انجام ندهید.

معماری پیشنهادی:

  1. کاربر فایل را بارگذاری می‌کند.
  2. سرور نوع و اندازه فایل را بررسی می‌کند.
  3. فایل در فضای ذخیره‌سازی قرار می‌گیرد.
  4. یک Job ایجاد می‌شود.
  5. Worker تصویر را به مدل ارسال می‌کند.
  6. نتیجه در پایگاه داده ذخیره می‌شود.
  7. وضعیت Job از طریق Polling یا Webhook اعلام می‌شود.

وضعیت‌های مفید:

queued
processing
completed
failed
needs_review

این معماری برای تحلیل دسته‌ای اسناد یا پردازش تصاویر زیاد مناسب‌تر است.

ارسال چند فایل با FastAPI

from fastapi import File, HTTPException, UploadFile


@app.post("/compare-images")
async def compare_images(
    files: list[UploadFile] = File(...),
):
    if len(files) < 2:
        raise HTTPException(
            status_code=400,
            detail="At least two images are required",
        )

    if len(files) > 4:
        raise HTTPException(
            status_code=400,
            detail="No more than four images are allowed",
        )

    content = [
        {
            "type": "text",
            "text": (
                "تصاویر را به‌ترتیب مقایسه کن. "
                "فقط تفاوت‌های قابل مشاهده را بنویس."
            ),
        }
    ]

    for index, file in enumerate(files, start=1):
        if file.content_type not in ALLOWED_CONTENT_TYPES:
            raise HTTPException(
                status_code=415,
                detail=(
                    f"Unsupported format for image {index}"
                ),
            )

        file_bytes = await file.read(
            MAX_UPLOAD_BYTES + 1
        )

        if len(file_bytes) > MAX_UPLOAD_BYTES:
            raise HTTPException(
                status_code=413,
                detail=f"Image {index} is too large",
            )

        optimized_bytes, mime_type = optimize_image(
            file_bytes
        )

        data_url = bytes_to_data_url(
            optimized_bytes,
            mime_type,
        )

        content.extend(
            [
                {
                    "type": "text",
                    "text": f"تصویر {index}:",
                },
                {
                    "type": "image_url",
                    "image_url": {
                        "url": data_url,
                    },
                },
            ]
        )

    response = client.chat.completions.create(
        model=model_id,
        messages=[
            {
                "role": "user",
                "content": content,
            }
        ],
        temperature=0.1,
    )

    return {
        "comparison": (
            response.choices[0]
            .message.content
        ),
    }

حداکثر چهار تصویر در این مثال، محدودیت برنامه نمونه است و نه الزام عمومی API. محدودیت واقعی مدل را باید جداگانه بررسی کنید.

تحلیل اسکرین‌شات خطای نرم‌افزار

پرامپت مناسب:

این تصویر اسکرین‌شات یک خطای نرم‌افزاری است.

وظایف:
1. متن خطا را دقیق استخراج کن.
2. نام نرم‌افزار یا بخش رابط را فقط در صورت مشاهده بنویس.
3. اجزای مهم رابط کاربری را فهرست کن.
4. اطلاعاتی را که برای تشخیص قطعی کافی نیست مشخص کن.
5. چند مرحله عیب‌یابی کم‌خطر و قابل‌بازگشت پیشنهاد بده.

قواعد:
- متن ناخوانا را حدس نزن.
- اطلاعات حساس احتمالی را در پاسخ تکرار نکن.
- بین مشاهده تصویر و استنباط تفاوت قائل شو.

اگر اسکرین‌شات حاوی API Key، رمز، ایمیل یا اطلاعات شخصی است، قبل از ارسال آن بخش‌ها را محو یا حذف کنید.

استخراج جدول از تصویر

جدول داخل تصویر را استخراج کن.

خروجی فقط JSON معتبر باشد:
{
  "columns": ["string"],
  "rows": [
    ["string یا null"]
  ],
  "warnings": ["string"]
}

قواعد:
- ترتیب سطر و ستون حفظ شود.
- سلول ناخوانا null باشد.
- عددها همان‌طور که در تصویر هستند استخراج شوند.
- واحدها حذف نشوند.
- Header چندسطحی در warnings توضیح داده شود.
- هیچ مقدار محاسبه یا تکمیل نشود.

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

تحلیل نمودار با مدل Vision

پرامپت:

نمودار داخل تصویر را تحلیل کن.

ابتدا این اطلاعات را استخراج کن:
- عنوان
- نوع نمودار
- محور افقی
- محور عمودی
- واحدها
- راهنمای رنگ‌ها
- بازه زمانی
- منبع داده در صورت مشاهده

سپس:
- سه روند یا مقایسه قابل مشاهده را توضیح بده.
- از بیان علت تغییرات خودداری کن.
- اگر اعداد دقیق خوانا نیستند، مقدار تقریبی نساز.
- محدودیت‌های تصویر را در پایان بنویس.

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

تشخیص کیفیت تصویر پیش از تحلیل

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

from pathlib import Path

from PIL import Image, ImageStat


def inspect_image(image_path: Path) -> dict:
    with Image.open(image_path) as image:
        image = ImageOps.exif_transpose(image)
        grayscale = image.convert("L")
        stats = ImageStat.Stat(grayscale)

        width, height = image.size
        mean_brightness = stats.mean[0]
        contrast_stddev = stats.stddev[0]

        return {
            "width": width,
            "height": height,
            "format": image.format,
            "mean_brightness": round(
                mean_brightness,
                2,
            ),
            "contrast_stddev": round(
                contrast_stddev,
                2,
            ),
        }

این شاخص‌ها به‌تنهایی کیفیت تصویر را تعیین نمی‌کنند، اما می‌توانند تصاویر بسیار کوچک، تاریک یا کم‌کنتراست را برای بررسی بیشتر علامت‌گذاری کنند.

Retry برای خطاهای موقت

نصب Tenacity:

pip install tenacity

کد:

from tenacity import (
    retry,
    retry_if_exception_type,
    stop_after_attempt,
    wait_random_exponential,
)


@retry(
    retry=retry_if_exception_type(Exception),
    wait=wait_random_exponential(
        min=1,
        max=20,
    ),
    stop=stop_after_attempt(3),
    reraise=True,
)
def call_vision_model(messages):
    return client.chat.completions.create(
        model=model_id,
        messages=messages,
        temperature=0.1,
    )

در پروژه واقعی نباید تمام خطاها Retry شوند. خطاهایی مانند مدل نامعتبر، فایل پشتیبانی‌نشده یا کلید اشتباه با تکرار حل نمی‌شوند. Retry را برای خطاهای موقت و براساس کد وضعیت محدود کنید.

Timeout مناسب

برای تصویر، زمان پردازش ممکن است بیشتر از درخواست متنی باشد. Timeout باید مشخص باشد تا Worker برای همیشه منتظر نماند.

from openai import OpenAI

client = OpenAI(
    api_key=api_key,
    base_url="https://api.darvareh.ir/v1",
    timeout=60.0,
    max_retries=2,
)

مقدار مناسب به اندازه تصویر، مدل، الگوی ترافیک و SLA برنامه بستگی دارد.

ثبت Log بدون ذخیره تصویر

برای عیب‌یابی می‌توانید این اطلاعات را ثبت کنید:

  • شناسه درخواست
  • شناسه مدل
  • نوع MIME
  • اندازه فایل
  • ابعاد تصویر
  • تعداد تصاویر
  • مدت پاسخ
  • وضعیت درخواست
  • نوع خطا
  • میزان مصرف اعلام‌شده

از ثبت Base64، تصویر کامل، API Key و محتوای محرمانه در Log خودداری کنید.

نمونه متادیتا:

{
  "request_id": "req_123",
  "model": "YOUR_MODEL_ID",
  "image_count": 1,
  "mime_type": "image/jpeg",
  "file_size_bytes": 845210,
  "width": 1600,
  "height": 1200,
  "latency_ms": 3240,
  "status": "completed"
}

کش کردن نتیجه تحلیل تصویر

اگر یک تصویر و دستور یکسان چند بار پردازش می‌شوند، می‌توانید نتیجه را Cache کنید.

کلید Cache می‌تواند از این اجزا ساخته شود:

image_hash + prompt_version + model_id + output_schema_version

هش تصویر:

import hashlib


def sha256_bytes(data: bytes) -> str:
    return hashlib.sha256(data).hexdigest()

کلید کامل:

def build_cache_key(
    image_bytes: bytes,
    prompt_version: str,
    model_id: str,
    schema_version: str,
) -> str:
    image_hash = sha256_bytes(image_bytes)

    raw_key = (
        f"{image_hash}:"
        f"{prompt_version}:"
        f"{model_id}:"
        f"{schema_version}"
    )

    return hashlib.sha256(
        raw_key.encode("utf-8")
    ).hexdigest()

اگر Prompt، مدل یا Schema تغییر کرد، نتیجه قبلی نباید بدون بررسی مجدد استفاده شود.

نسخه‌بندی پرامپت

پرامپت را داخل کدهای پراکنده نگه ندارید. برای آن شناسه نسخه تعریف کنید:

VISION_PROMPT_VERSION = "invoice-extraction-v1.3"

متادیتای نتیجه:

{
  "model_id": "YOUR_MODEL_ID",
  "prompt_version": "invoice-extraction-v1.3",
  "schema_version": "invoice-v2",
  "review_status": "needs_review"
}

این اطلاعات برای مقایسه نتایج، بازتولید خطا و مهاجرت مدل مفید هستند.

URL بهتر است یا Base64؟

معیارURLBase64
حجم Requestکمتربیشتر
نیاز به دسترسی عمومیبله یا Signed URLخیر
مناسب فایل محلینیازمند آپلودبله
مدیریت زمان انقضالازملازم نیست
سادگی برای فایل کوچکمتوسطبالا
مناسب پردازش دسته‌ایبلهدر حجم بالا نامناسب‌تر
وابستگی به Storageداردندارد
مدت ارسالمعمولاً کمترممکن است بیشتر باشد

برای اپلیکیشن Production، ذخیره فایل در فضای اختصاصی و استفاده از Signed URL معمولاً معماری مقیاس‌پذیرتری است. برای نمونه‌سازی یا فایل کوچک محلی، Base64 ساده‌تر است.

هزینه تحلیل تصویر چگونه محاسبه می‌شود؟

روش محاسبه هزینه میان مدل‌ها یکسان نیست. عواملی که ممکن است روی مصرف اثر بگذارند عبارت‌اند از:

  • مدل انتخابی
  • ابعاد تصویر
  • سطح جزئیات
  • تعداد تصاویر
  • طول Prompt
  • طول پاسخ
  • تعداد درخواست‌ها
  • پردازش مجدد
  • پارامترهای اختصاصی مدل

قیمت را داخل کد ثابت نکنید. برای مشاهده قیمت و قابلیت‌های به‌روز هر مدل به صفحه مدل‌های درواره مراجعه کنید.

راه‌های کاهش هزینه و زمان پاسخ

  • تصویر را فقط تا اندازه موردنیاز نگه دارید.
  • حاشیه‌های غیرضروری را Crop کنید.
  • چند تصویر نامرتبط را در یک درخواست قرار ندهید.
  • پاسخ را با Schema کوتاه محدود کنید.
  • نتیجه ورودی‌های تکراری را Cache کنید.
  • پیش از ارسال، فرمت و اندازه را اعتبارسنجی کنید.
  • برای کار ساده از مدل بیش از حد بزرگ استفاده نکنید.
  • درخواست ناموفق دائمی را Retry نکنید.
  • برای پردازش دسته‌ای از صف استفاده کنید.
  • فقط Crop مرتبط با سوال را ارسال کنید.

مدل Vision ممکن است چه اشتباهاتی داشته باشد؟

خواندن اشتباه متن کوچک

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

حدس زدن بخش پنهان

مدل ممکن است براساس الگوهای رایج چیزی را که دیده نمی‌شود تکمیل کند.

اشتباه در شمارش

شمارش تعداد زیاد اشیا همیشه دقیق نیست.

اشتباه در موقعیت

رابطه چپ، راست، جلو و عقب ممکن است در تصاویر پیچیده اشتباه شود.

برداشت نادرست از نمودار

محورها، رنگ‌ها یا اعداد نزدیک ممکن است اشتباه تفسیر شوند.

اشتباه در چند تصویر مشابه

اگر تصاویر برچسب واضح نداشته باشند، مدل ممکن است آن‌ها را جابه‌جا کند.

استنباط ویژگی غیرقابل‌مشاهده

کیفیت، اصالت، وزن، قیمت و عملکرد داخلی معمولاً از ظاهر تصویر قابل‌اثبات نیستند.

روش کاهش پاسخ‌های حدسی

در System Prompt بنویسید:

فقط براساس اطلاعات قابل مشاهده پاسخ بده.
اگر متن، عدد یا ویژگی مشخص نیست، مقدار را نامشخص اعلام کن.
بین «مشاهده مستقیم» و «استنباط» تفاوت قائل شو.
هیچ مقدار گمشده‌ای را تکمیل نکن.

در خروجی ساختاریافته نیز فیلدهایی برای اطمینان و هشدار تعریف کنید:

{
  "value": "مقدار استخراج‌شده",
  "confidence": "high",
  "evidence": "محل مشاهده در تصویر",
  "needs_review": false
}

امتیاز اطمینان تولیدشده توسط مدل، اندازه‌گیری آماری تضمین‌شده نیست؛ فقط می‌تواند برای اولویت‌بندی بازبینی استفاده شود.

طراحی Human-in-the-Loop

برای کاربردهای مهم، نتیجه باید از مسیر بازبینی عبور کند:

  1. مدل تصویر را تحلیل می‌کند.
  2. فیلدهای نامشخص علامت‌گذاری می‌شوند.
  3. قواعد برنامه خروجی را کنترل می‌کنند.
  4. موارد کم‌اطمینان به صف بازبینی می‌روند.
  5. اپراتور مقدار را با تصویر مقایسه می‌کند.
  6. نتیجه تأییدشده ذخیره می‌شود.
  7. اصلاحات برای ارزیابی کیفیت سیستم ثبت می‌شوند.

ارزیابی کیفیت سیستم تحلیل تصویر

یک مجموعه ارزیابی بسازید که شامل نمونه‌های واقعی و پاسخ صحیح باشد.

معیارهای مناسب:

  • دقت نوع سند
  • دقت استخراج فیلدها
  • نرخ مقدارهای ساختگی
  • نرخ JSON نامعتبر
  • درصد موارد نیازمند بازبینی
  • زمان پاسخ
  • هزینه هر سند موفق
  • دقت روی تصویر تار
  • دقت روی زبان فارسی
  • دقت روی فرمت‌های مختلف

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

اشتباهات رایج در استفاده از Vision API

انتخاب مدل بدون قابلیت تصویر

هر مدل متنی لزوماً Image Input ندارد.

ارسال صفحه وب به‌جای URL مستقیم تصویر

مدل باید به فایل واقعی تصویر دسترسی داشته باشد.

MIME اشتباه

نوع فایل و Data URL باید هماهنگ باشند.

ارسال فایل بسیار بزرگ

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

فشرده‌سازی بیش از حد

نوشته کوچک و جزئیات مهم از بین می‌روند.

قرار دادن تصویر بدون دستور مشخص

مدل باید بداند چه اطلاعاتی و با چه قالبی استخراج شوند.

اعتماد کامل به پاسخ

خروجی مدل باید با تصویر و قواعد کسب‌وکار بررسی شود.

اجرای مستقیم خروجی کد

اگر مدل از روی اسکرین‌شات کد تولید می‌کند، کد را پیش از اجرا بازبینی و آزمایش کنید.

ذخیره Base64 در Log

این کار Logها را بزرگ و ممکن است اطلاعات تصویر را بدون نیاز نگهداری کند.

ارسال چند تصویر بدون برچسب

هر تصویر را با عبارت «تصویر اول»، «نسخه قبل» یا شناسه مشخص معرفی کنید.

ترکیب استخراج و نتیجه‌گیری در یک مرحله

ابتدا داده قابل‌مشاهده را استخراج و سپس در مرحله جداگانه تحلیل کنید.

چک‌لیست نهایی پیاده‌سازی Vision API

پیش از انتشار بررسی کنید:

  • مدل انتخابی از ورودی تصویر پشتیبانی می‌کند.
  • Base URL صحیح درواره تنظیم شده است.
  • API Key فقط در سرور نگهداری می‌شود.
  • فرمت‌های مجاز مشخص شده‌اند.
  • محدودیت اندازه فایل وجود دارد.
  • نوع واقعی فایل بررسی می‌شود.
  • جهت EXIF اصلاح می‌شود.
  • تصویر بیش از حد فشرده نمی‌شود.
  • Prompt هدف دقیق دارد.
  • مدل به عدم حدس زدن ملزم شده است.
  • پاسخ ساختاریافته اعتبارسنجی می‌شود.
  • فیلدهای نامشخص مقدار null می‌گیرند.
  • چند تصویر دارای برچسب مشخص هستند.
  • Timeout تنظیم شده است.
  • Retry فقط برای خطاهای موقت انجام می‌شود.
  • Base64 در Log ذخیره نمی‌شود.
  • نتیجه تکراری Cache می‌شود.
  • Prompt و Schema نسخه‌بندی شده‌اند.
  • موارد مهم بازبینی انسانی دارند.
  • خروجی با Dataset واقعی ارزیابی شده است.
  • قیمت و محدودیت مدل از صفحه به‌روز بررسی شده‌اند.

سوالات متداول

چگونه عکس را به API هوش مصنوعی ارسال کنیم؟

تصویر را می‌توانید از طریق URL عمومی یا Data URL حاوی Base64 داخل بخش image_url پیام کاربر ارسال کنید.

آیا API درواره از تحلیل تصویر پشتیبانی می‌کند؟

درواره مدل‌های مختلفی ارائه می‌کند. برای تحلیل تصویر باید مدلی را انتخاب کنید که در صفحه مدل‌ها دارای قابلیت Image Input یا Vision باشد.

URL بهتر است یا Base64؟

برای فایل ذخیره‌شده و پردازش مقیاس‌پذیر، URL یا Signed URL مناسب‌تر است. برای فایل کوچک محلی و نمونه‌سازی، Base64 ساده‌تر خواهد بود.

آیا می‌توان چند تصویر را هم‌زمان ارسال کرد؟

در مدل‌های پشتیبانی‌شده، بله. هر تصویر را در بخش مستقل image_url قرار دهید و با متن مشخص کنید که هرکدام چه نقشی دارند.

آیا مدل Vision می‌تواند متن فارسی را بخواند؟

بسیاری از مدل‌های چندوجهی می‌توانند متن فارسی را پردازش کنند، اما نتیجه به مدل، وضوح، فونت، کنتراست و اندازه نوشته بستگی دارد.

آیا می‌توان PDF را مستقیماً به مدل Vision ارسال کرد؟

پشتیبانی مستقیم از PDF به مدل و Endpoint بستگی دارد. روش عمومی این است که صفحات PDF را به تصویر تبدیل و هر صفحه را جداگانه پردازش کنید.

چرا مدل متن تصویر را اشتباه می‌خواند؟

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

چگونه خروجی JSON معتبر دریافت کنیم؟

از مدل پشتیبانی‌کننده Structured Output استفاده کنید یا ساختار JSON را دقیقاً در Prompt مشخص و سپس خروجی را با Pydantic یا JSON Schema اعتبارسنجی کنید.

آیا نتیجه تحلیل تصویر همیشه دقیق است؟

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

هزینه تحلیل عکس چقدر است؟

هزینه به مدل، ابعاد و تعداد تصاویر، سطح جزئیات، طول ورودی و پاسخ بستگی دارد. قیمت‌های به‌روز در صفحه مدل‌های درواره قرار دارند.

جمع‌بندی

API تحلیل تصویر امکان اضافه کردن قابلیت‌های چندوجهی به نرم‌افزار، وب‌سایت و فرایندهای سازمانی را فراهم می‌کند. تصویر را می‌توان با URL یا Base64 در کنار دستور متنی به مدل ارسال کرد و پاسخ متنی یا ساختاریافته دریافت کرد.

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

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

برای شروع، در درواره ثبت‌نام کنید، یک مدل دارای قابلیت Vision را از صفحه مدل‌ها انتخاب کنید و اولین درخواست چندوجهی خود را به https://api.darvareh.ir/v1 ارسال کنید.

مقالات مرتبط

برای مطالعه شرایط استفاده و محدودیت‌های مسئولیت، صفحه «سلب مسئولیت» را مشاهده کنید.

Read more