OCR uchun ma'lumot tayyorlash
Bu bob OCR ni o'rgatmaydi. OCR arxitekturasi, Tesseract, EasyOCR, CER/WER — bularning hammasi alohida kitobda: OCR — Skanerlangan Hujjatlardan Matnga (10 bob).
Bu bob boshqa savolga javob beradi: OCR modelini o'qitish uchun MA'LUMOTNI qayerdan olasiz?
Muammo: belgilangan ma'lumot yo'q
O'zbek tilida OCR modelini o'qitmoqchisiz. Sizga kerak:
rasm ("Hisob-faktura" yozuvi) -> matn "Hisob-faktura"
Va bunday juftlik o'n minglab kerak.
Qo'lda yig'sangiz: 10 000 ta rasmni kesib, har biriga matn yozish. Bir kishi kuniga 500 ta qilsa — 20 kun. Va xato ko'p bo'ladi.
Yechim: sintetik ma'lumot. Matnni o'zimiz yozamiz, rasmni o'zimiz yasaymiz. Belgilash bepul — chunki matnni allaqachon bilamiz.
Sintetik satr yaratish
import cv2 import numpy as np SHRIFTLAR = [cv2.FONT_HERSHEY_SIMPLEX, cv2.FONT_HERSHEY_DUPLEX, cv2.FONT_HERSHEY_COMPLEX, cv2.FONT_HERSHEY_TRIPLEX] def satr_yasa(matn, kenglik=320, balandlik=64, shrift=None, olcham=None): """Matndan rasm yasaydi. Javob allaqachon ma'lum — bu MATN.""" rasm = np.ones((balandlik, kenglik), dtype=np.uint8) * 255 shrift = shrift if shrift is not None else SHRIFTLAR[0] olcham = olcham if olcham is not None else 0.9 (mk, mb), _ = cv2.getTextSize(matn, shrift, olcham, 2) x = max(4, (kenglik - mk) // 2) y = (balandlik + mb) // 2 cv2.putText(rasm, matn, (x, y), shrift, olcham, 0, 2, cv2.LINE_AA) return rasm r = satr_yasa("Hisob-faktura") print("o'lcham:", r.shape, "| oq foizi:", round(100 * (r > 128).mean(), 1))
Natija: o'lcham: (64, 320) | oq foizi: 92.0
92% oq — matn rasmning atigi 8% ini egallaydi. Bu normal: hujjatda bo'sh joy ko'p. Agar bu son 50% ga yaqin bo'lsa — shrift juda katta.
cv2.LINE_AA — antialiasing. Busiz harflar "zinapoyasimon"
bo'ladi va haqiqiy skanerga o'xshamaydi.
Muhim: sintetik ma'lumot HAQIQIYga o'xshashi kerak
Toza oq fonda ideal harflar bilan o'qitilgan model haqiqiy skanerda ishlamaydi. Chunki haqiqiy hujjatda:
- shovqin va dog'lar bor
- qog'oz oq emas, sarg'ish
- matn biroz qiyshiq
- yorug'lik notekis
- fokus har doim ham aniq emas
Bu farq domain gap deyiladi. Uni yopish uchun sintetik rasmni buzamiz.
Augmentatsiya — ataylab buzish
import random def shovqin_qosh(rasm, kuch=12): shovqin = np.random.normal(0, kuch, rasm.shape) return np.clip(rasm.astype(np.float32) + shovqin, 0, 255).astype(np.uint8) def qiyshaytir(rasm, daraja=None): daraja = daraja if daraja is not None else random.uniform(-2.5, 2.5) b, k = rasm.shape M = cv2.getRotationMatrix2D((k / 2, b / 2), daraja, 1.0) return cv2.warpAffine(rasm, M, (k, b), borderMode=cv2.BORDER_REPLICATE) def xiralashtir(rasm, kuch=None): kuch = kuch if kuch is not None else random.choice([1, 3]) return cv2.GaussianBlur(rasm, (kuch, kuch), 0) def qogoz_rangi(rasm, kuch=None): """Oq fonni sarg'ish qiladi — eski qog'ozga o'xshaydi.""" kuch = kuch if kuch is not None else random.randint(0, 35) return np.clip(rasm.astype(np.int16) - kuch, 0, 255).astype(np.uint8) def notekis_yoruglik(rasm, kuch=40): """Chapdan o'ngga soya — telefon surati kabi.""" b, k = rasm.shape gradient = np.linspace(0, kuch, k, dtype=np.float32) return np.clip(rasm.astype(np.float32) - gradient, 0, 255).astype(np.uint8)
Har biri bitta haqiqiy muammoni taqlid qiladi. Birga qo'llasak — haqiqiy skanerga yaqin natija chiqadi.
To'liq quvur
def augmentatsiya(rasm): """Tasodifiy buzishlar — har safar boshqacha.""" if random.random() < 0.7: rasm = qiyshaytir(rasm) if random.random() < 0.5: rasm = xiralashtir(rasm) if random.random() < 0.6: rasm = qogoz_rangi(rasm) if random.random() < 0.4: rasm = notekis_yoruglik(rasm) if random.random() < 0.8: rasm = shovqin_qosh(rasm) return rasm def dataset_yasa(matnlar, nusxa=5, seed=42): """Har bir matndan `nusxa` ta TURLI rasm yasaydi.""" random.seed(seed) np.random.seed(seed) juftliklar = [] for matn in matnlar: for _ in range(nusxa): toza = satr_yasa(matn, shrift=random.choice(SHRIFTLAR), olcham=random.uniform(0.7, 1.0)) juftliklar.append((augmentatsiya(toza), matn)) return juftliklar MATNLAR = ["Hisob-faktura", "Summa: 250 000 som", "Sana: 2026-01-15", "Jami: 1 250 000", "Toshkent shahri", "INN 123456789"] dataset = dataset_yasa(MATNLAR, nusxa=5) print(f"juftlik: {len(dataset)}") print(f"birinchi: o'lcham {dataset[0][0].shape}, matn '{dataset[0][1]}'") # Har bir rasm HAQIQATAN turlichami? birinchi_matn = [r for r, m in dataset if m == MATNLAR[0]] farqlar = [int(np.abs(birinchi_matn[0].astype(int) - x.astype(int)).mean()) for x in birinchi_matn[1:]] print(f"bir xil matnning nusxalari orasidagi farq: {farqlar}")
Natija:
juftlik: 30 birinchi: o'lcham (64, 320), matn 'Hisob-faktura' bir xil matnning nusxalari orasidagi farq: [41, 49, 18, 28]
Farqlar 18 dan 49 gacha — ya'ni bir xil matnning har bir
nusxasi haqiqatan boshqacha. Agar bu ro'yxat [0, 0, 0, 0]
bo'lsa, augmentatsiya ishlamayapti.
Oxirgi tekshiruv muhim. Agar farq 0 bo'lsa — augmentatsiya ishlamayapti va siz bir xil rasmni 5 marta saqlayapsiz. Model uchun bu foydasiz.
Nusxa soni — qancha kerak
| Vaziyat | Tavsiya |
|---|---|
| Kichik lug'at (raqam, sana) | matn boshiga 20–50 nusxa |
| Umumiy matn | 5–10 nusxa, lekin KO'P turli matn |
| Haqiqiy ma'lumot ham bor | 3–5 nusxa yetarli |
Muhimi: turli MATN turli NUSXAdan foydaliroq.
1000 ta matn x 5 nusxa (5000) — 100 ta matn x 50 nusxa (5000) dan ancha yaxshi. Model turli so'zlarni ko'rishi kerak, bir so'zning turli buzilishini emas.
Haqiqiy ma'lumot bilan aralashtirish
Sintetik ma'lumot boshlash uchun. Eng yaxshi natija aralashmadan chiqadi:
80% sintetik + 20% haqiqiy (qo'lda belgilangan)
Nega ishlaydi: sintetik ma'lumot hajm beradi, haqiqiy ma'lumot esa modelni haqiqiy taqsimotga tortadi.
Amaliy maslahat: avval 100% sintetik bilan o'qiting. Keyin haqiqiy 200–500 ta rasm yig'ib, fine-tuning qiling (6-bob).
O'zbek tili uchun alohida e'tibor
1. Apostrof muammosi
O'zbek lotinida to'rt xil belgi ishlatiladi va ular har xil Unicode kodlar:
apostroflar = ["'", "‘", "’", "ʻ", "ʼ"] for a in apostroflar: print(f" U+{ord(a):04X} {a!r}")
Natija:
U+0027 "'" oddiy apostrof U+2018 '‘' chap tirnoq U+2019 '’' o'ng tirnoq <- Word AVTOMATIK shunga almashtiradi U+02BB 'ʻ' o'zbek rasmiy belgisi U+02BC 'ʼ' modifier apostrof
U+2019 eng xavflisi: Microsoft Word oddiy apostrofni
avtomatik shunga o'zgartiradi. Word da tayyorlangan matnni
datasetga qo'shsangiz — bilmagan holda ikki xil belgi aralashadi.
o' va oʻ — ko'zga bir xil, kompyuterga boshqa. Datasetda
ikkalasi bo'lsa, model chalkashadi.
Yechim: normallashtirish.
def apostrof_normallashtir(matn): for a in ["‘", "’", "ʻ", "ʼ", "`"]: matn = matn.replace(a, "'") return matn print(apostrof_normallashtir("O‘zbekiston oʻzbek"))
Butun dataset bo'ylab bitta variant ishlating.
2. Kirill va lotin aralashuvi
Eski hujjatlar kirillda, yangilari lotinda. Ikkalasi kerak bo'lsa — alifboni birlashtiring, lekin model murakkablashadi.
Iloji bo'lsa ikkita alohida model yaxshiroq.
3. Alifbo ro'yxati
LOTIN = "abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVXYZ" RAQAM = "0123456789" BELGI = " .,:-/()'" ALIFBO = LOTIN + RAQAM + BELGI print(f"alifbo hajmi: {len(ALIFBO)}") print(f"takror bormi: {len(ALIFBO) != len(set(ALIFBO))}")
Diqqat: o'zbek lotinida c, w deyarli ishlatilmaydi, lekin
sh, ch, ng — bu ikki harfli birikmalar. CTC uchun ular
alohida belgi emas, oddiy harflar ketma-ketligi.
Keyingi qadam
Endi sizda o'qitish uchun ma'lumot bor. Model qurish va o'qitish uchun OCR kitobiga o'ting — u yerda CRNN, CTC va baholash batafsil yoritilgan.
Bu bobning vazifasi shu edi: ma'lumotsiz model yo'q.
Bu bobni bepul o'qidingiz.
Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.