Asosiy qismga o'tish

Loyiha — hujjatdan ma'lumot ajratish

8-bob 16 kod bloki 1937 so'z

Bu bob butun kitobni birlashtiradi. 0-bobdagi hisob-faktura, 3-bobdagi tozalash, 7-bobdagi ma'lumot tayyorlash — hammasi bitta ishlaydigan quvurga aylanadi.

Vazifa: hujjat rasmidan tuzilmali ma'lumot olish.

matn1 qator
rasm  ->  {"summa": "250 000", "sana": "2026-01-15"}

Diqqat: maqsad matn emas, maydonlar. Bu farq muhim.

Arxitektura

python6 qator
1. Rasmni yuklash va tekshirish
2. Tozalash            <- 3-bob (adaptive threshold, morfologiya)
3. Matn bloklarini topish  <- 3-bob (dilate + kontur)
4. Har bir blokni o'qish   <- OCR kitobi
5. Maydonlarga ajratish    <- BU BOB
6. Tekshirish va qaytarish

5-qadam eng ko'p unutiladigan qism. OCR sizga matn beradi. Lekin "Summa: 250 000 som" degan satrdan 250000 sonini ajratish — bu alohida ish.

1-qism: hujjat yasash (sinov uchun)

python21 qator
import cv2
import numpy as np


def hisob_faktura_yasa(summa="250 000", sana="2026-01-15",
                       raqam="INV-0042"):
    rasm = np.ones((360, 640, 3), dtype=np.uint8) * 255
    F = cv2.FONT_HERSHEY_SIMPLEX

    cv2.putText(rasm, "HISOB-FAKTURA", (40, 60), F, 1.1, (0,0,0), 2, cv2.LINE_AA)
    cv2.line(rasm, (40, 80), (600, 80), (0,0,0), 2)
    cv2.putText(rasm, f"Raqam: {raqam}",   (40, 130), F, 0.7, (0,0,0), 2, cv2.LINE_AA)
    cv2.putText(rasm, f"Sana: {sana}",     (40, 180), F, 0.7, (0,0,0), 2, cv2.LINE_AA)
    cv2.putText(rasm, f"Summa: {summa} som", (40, 230), F, 0.7, (0,0,0), 2, cv2.LINE_AA)
    cv2.putText(rasm, "Toshkent shahri",   (40, 280), F, 0.7, (0,0,0), 2, cv2.LINE_AA)
    return rasm


hujjat = hisob_faktura_yasa()
cv2.imwrite("faktura.png", hujjat)
print("yaratildi:", hujjat.shape)

2-qism: tozalash quvuri

python19 qator
def tayyorla(rasm):
    """3-bobdagi usullar — bitta funksiyada."""
    kulrang = cv2.cvtColor(rasm, cv2.COLOR_BGR2GRAY) if rasm.ndim == 3 else rasm

    # Chetlarni saqlab shovqinni yumshatish
    yumshoq = cv2.bilateralFilter(kulrang, 9, 75, 75)

    # Matn OQ bo'lishi kerak — INV (3-bobdagi tuzoq)
    oq_matn = cv2.adaptiveThreshold(
        yumshoq, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
        cv2.THRESH_BINARY_INV, 31, 15)

    # Mayda shovqinni olib tashlash
    yadro = np.ones((2, 2), np.uint8)
    return cv2.morphologyEx(oq_matn, cv2.MORPH_OPEN, yadro)


tayyor = tayyorla(hujjat)
print("oq piksel foizi:", round(100 * (tayyor > 0).mean(), 2))

Natija: oq piksel foizi: 4.72

4.72% — matn hujjatning 5% ini egallaydi. Bu son foydali tekshiruv: 0 ga yaqin bo'lsa threshold juda qattiq (matn yo'qolgan), 50% ga yaqin bo'lsa juda yumshoq (shovqin qolgan).

THRESH_BINARY_INV — 3-bobdagi eng muhim saboq. findContours oq hududlarni obyekt deb biladi, shuning uchun matn oq bo'lishi shart.

3-qism: matn qatorlarini topish

python22 qator
def qatorlarni_top(tayyor, min_kenglik=40, min_balandlik=10):
    """Harflarni gorizontal ulab, qatorlarga ajratadi."""
    yadro = cv2.getStructuringElement(cv2.MORPH_RECT, (25, 3))
    ulangan = cv2.dilate(tayyor, yadro, iterations=1)

    konturlar, _ = cv2.findContours(
        ulangan, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

    qutilar = []
    for k in konturlar:
        x, y, w, h = cv2.boundingRect(k)
        if w >= min_kenglik and h >= min_balandlik:
            qutilar.append((x, y, w, h))

    # Yuqoridan pastga tartiblash — o'qish tartibi
    return sorted(qutilar, key=lambda q: q[1])


qatorlar = qatorlarni_top(tayyor)
print(f"topilgan qator: {len(qatorlar)}")
for x, y, w, h in qatorlar:
    print(f"  x={x:>3} y={y:>3} w={w:>3} h={h:>2}")

Natija:

python6 qator
topilgan qator: 5
  x= 32 y= 36 w=300 h=28     <- HISOB-FAKTURA (sarlavha, balandroq)
  x= 30 y=114 w=224 h=25     <- Raqam
  x= 30 y=164 w=244 h=20     <- Sana
  x= 30 y=214 w=276 h=20     <- Summa
  x= 29 y=263 w=198 h=21     <- Toshkent shahri

Aynan 5 ta qator — hujjatdagidek. Sarlavha h=28, qolganlari h=20-25: shrift kattaligi farqi ko'rinib turibdi. Bu ma'lumotdan sarlavhani ajratish uchun foydalanish mumkin.

sorted(..., key=lambda q: q[1]) — y bo'yicha tartiblash. Kontur topish tartibi tasodifiy, o'qish esa yuqoridan pastga bo'lishi kerak.

4-qism: OCR o'rniga — vaqtinchalik "soxta" o'qish

Haqiqiy OCR uchun OCR kitobiga qarang. Bu yerda quvurni sinash uchun oddiy funksiya ishlatamiz:

python11 qator
def matn_oqi(rasm_boragi):
    """HAQIQIY loyihada bu yerda Tesseract yoki CRNN turadi.

    Hozircha quvurni sinash uchun bo'sh joy qoldiramiz —
    OCR ni ulaganingizda faqat SHU funksiyani almashtirasiz.
    """
    try:
        import pytesseract
        return pytesseract.image_to_string(rasm_boragi, lang="uzb").strip()
    except ImportError:
        return None

Bu tuzilish muhim: OCR — bitta almashtiriladigan funksiya. Tesseract, EasyOCR, o'z CRNN modelingiz — quvurning qolgani o'zgarmaydi.

5-qism: maydonlarni ajratish

Bu — bobning asosiy qismi. Matndan tuzilmali ma'lumot.

python26 qator
import re

NAQSHLAR = {
    "raqam": r"raqam\s*[:\-]?\s*([A-Z]{2,4}[-\s]?\d+)",
    "sana":  r"sana\s*[:\-]?\s*(\d{4}[-./]\d{2}[-./]\d{2})",
    "summa": r"summa\s*[:\-]?\s*([\d\s]+?)\s*(?:som|so'm|сум)",
}


def maydonlarni_ajrat(matn):
    """Matndan maydonlarni topadi. Topilmasa — None."""
    natija = {}
    past = matn.lower()
    for nom, naqsh in NAQSHLAR.items():
        m = re.search(naqsh, past, re.IGNORECASE)
        natija[nom] = m.group(1).strip() if m else None
    return natija


sinov = """HISOB-FAKTURA
Raqam: INV-0042
Sana: 2026-01-15
Summa: 250 000 som
Toshkent shahri"""

print(maydonlarni_ajrat(sinov))

Natija:

matn1 qator
{'raqam': 'inv-0042', 'sana': '2026-01-15', 'summa': '250 000'}

Uchala maydon ham topildi. raqam kichik harfda — chunki matn.lower() qilganmiz. Kerak bo'lsa .upper() qiling.

Nega regex, LLM emas? Hujjat formati barqaror bo'lsa, regex: tez, bepul, aniq va tushuntirib beriladi. LLM faqat format xilma-xil bo'lganda kerak.

6-qism: tozalash va tekshirish

OCR xato qiladi. Ajratilgan qiymatni tekshirish kerak.

python33 qator
from datetime import datetime


def summani_tozala(xom):
    """'250 000' -> 250000. Xato bo'lsa None."""
    if not xom:
        return None
    faqat_raqam = re.sub(r"[^\d]", "", xom)
    if not faqat_raqam:
        return None
    son = int(faqat_raqam)
    # Aql bovar qiladigan oraliq — OCR "0" ni "8" deb o'qishi mumkin
    return son if 100 <= son <= 10_000_000_000 else None


def sanani_tekshir(xom):
    """Sana haqiqiymi? '2026-13-45' -> None."""
    if not xom:
        return None
    for fmt in ("%Y-%m-%d", "%Y.%m.%d", "%Y/%m/%d"):
        try:
            d = datetime.strptime(xom, fmt)
            if 2000 <= d.year <= 2100:
                return d.strftime("%Y-%m-%d")
        except ValueError:
            continue
    return None


print("summa :", summani_tozala("250 000"))
print("buzuq :", summani_tozala("2"))
print("sana  :", sanani_tekshir("2026-01-15"))
print("buzuq :", sanani_tekshir("2026-13-45"))

Natija:

matn4 qator
summa : 250000
buzuq : None
sana  : 2026-01-15
buzuq : None

Ikkala buzuq qiymat ham None — bazaga kirmaydi. Mana shu tekshiruv tizimni ishonchli qiladi.

2026-13-45 — 13-oy, 45-kun. OCR bunday narsa qaytarishi mumkin. Tekshirmasangiz, baza ichiga axlat kiradi.

Oraliq tekshiruvi ham muhim: OCR "5" ni "6" deb o'qishi mumkin, lekin 250000 o'rniga 2 qaytarsa — bu aniq xato.

7-qism: to'liq quvur

python37 qator
def hujjatni_qayta_ishla(yol_yoki_rasm):
    """Rasmdan tuzilmali ma'lumotgacha — to'liq quvur."""
    if isinstance(yol_yoki_rasm, str):
        rasm = cv2.imread(yol_yoki_rasm)
        if rasm is None:
            return {"xato": f"fayl o'qilmadi: {yol_yoki_rasm}"}
    else:
        rasm = yol_yoki_rasm

    tayyor = tayyorla(rasm)
    qatorlar = qatorlarni_top(tayyor)

    if not qatorlar:
        return {"xato": "matn topilmadi — rasm juda xira bo'lishi mumkin"}

    # Har bir qatorni o'qish
    matnlar = []
    for x, y, w, h in qatorlar:
        boragi = rasm[max(0, y-4):y+h+4, max(0, x-4):x+w+4]
        oqilgan = matn_oqi(boragi)
        if oqilgan:
            matnlar.append(oqilgan)

    if not matnlar:
        return {"xato": "OCR ishlamadi — pytesseract o'rnatilganmi?",
                "qatorlar_topildi": len(qatorlar)}

    xom_matn = "\n".join(matnlar)
    maydonlar = maydonlarni_ajrat(xom_matn)

    return {
        "matn": xom_matn,
        "raqam": maydonlar["raqam"],
        "sana": sanani_tekshir(maydonlar["sana"]),
        "summa": summani_tozala(maydonlar["summa"]),
        "qatorlar": len(qatorlar),
    }

Har bir qadamda xato holati qaytariladi — traceback emas. Foydalanuvchi nima bo'lganini bilishi kerak.

max(0, y-4) — bo'rtma (padding). Kontur harfning aynan chegarasini beradi, OCR esa biroz bo'sh joy bilan yaxshiroq ishlaydi. max(0, ...) esa manfiy indeksdan saqlaydi.

Sinash

python4 qator
natija = hujjatni_qayta_ishla("faktura.png")
for kalit, qiymat in natija.items():
    if kalit != "matn":
        print(f"  {kalit}: {qiymat}")

pytesseract o'rnatilmagan kompyuterda natija:

yaml2 qator
  xato: OCR ishlamadi — pytesseract o'rnatilganmi?
  qatorlar_topildi: 5

pytesseract o'rnatilmagan bo'lsa, xato maydoni chiqadi — lekin qatorlar_topildi soni ko'rinadi. Ya'ni quvurning CV qismi ishlayapti, faqat OCR yo'q.

Bu — yaxshi xato xabari namunasi: nima ishladi va nima ishlamadi — ikkalasi ham aytiladi.

Nimani yaxshilash mumkin

1. Qiyshiq hujjat. 3-bobdagi togrila() funksiyasini tayyorla() boshiga qo'shing.

2. Jadval. Hozirgi kod qatorlarni topadi. Jadval uchun vertikal va gorizontal chiziqlarni alohida topish kerak:

python7 qator
def chiziqlarni_top(tayyor):
    """Jadval chiziqlarini ajratadi."""
    gor_yadro = cv2.getStructuringElement(cv2.MORPH_RECT, (40, 1))
    ver_yadro = cv2.getStructuringElement(cv2.MORPH_RECT, (1, 40))
    gorizontal = cv2.morphologyEx(tayyor, cv2.MORPH_OPEN, gor_yadro)
    vertikal = cv2.morphologyEx(tayyor, cv2.MORPH_OPEN, ver_yadro)
    return gorizontal, vertikal

Uzun va yupqa yadro faqat uzun chiziqlarni qoldiradi — matn yo'qoladi. Bu jadval katakchalarini topishning asosi.

3. Format o'zgarsa. Har bir yetkazib beruvchining fakturasi boshqacha. Ikki yo'l:

  • har biri uchun alohida NAQSHLAR to'plami (aniq, lekin qo'lda)
  • VLM ga rasmni berib, JSON so'rash (moslashuvchan, lekin sekin va pullik)

Amalda: 80% hujjat uchun regex, qolgani uchun VLM.

Yakun

Siz qurgan narsa:

matn1 qator
rasm -> tozalash -> qatorlar -> OCR -> maydonlar -> tekshirish -> JSON

Har bir qadam alohida funksiya va alohida sinaladi. OCR ni almashtirsangiz, qolgani o'zgarmaydi.

Bu — haqiqiy hujjat AI tizimining skeleti. Qolgani — sifatni oshirish va maydonlarni ko'paytirish.


Bu bobni bepul o'qidingiz.

Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.

Telegramda yozish Narxlar