Threshold va morfologiya — hujjatni tozalash
Skanerlangan hujjat hech qachon toza bo'lmaydi: soya, shovqin, notekis yorug'lik. OCR ga berishdan oldin tozalash kerak.
Threshold — oq va qora
G'oya oddiy: chegaradan qorong'i piksel → qora, yorug' → oq.
import cv2 kulrang = cv2.imread("hujjat.png", cv2.IMREAD_GRAYSCALE) _, oq_qora = cv2.threshold(kulrang, 128, 255, cv2.THRESH_BINARY) cv2.imwrite("oq_qora.png", oq_qora)
Muammo: bitta chegara YETMAYDI
Slayderni suring — chap tomon yorug', o'ng tomon soyada:
Ko'rdingizmi? Bitta chegara ikkala tomonga to'g'ri kelmaydi. Chegarani pasaytirsangiz — o'ngdagi matn yo'qoladi. Oshirsangiz — soya ham qora bo'ladi.
Bu haqiqiy muammo: telefonda suratga olingan hujjatda doim notekis yorug'lik bo'ladi.
Yechim 1: Otsu — chegarani AVTOMATIK topish
chegara, natija = cv2.threshold( kulrang, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) print(f"Otsu topgan chegara: {chegara}")
Bizning sinov rasmida 0.0 chiqadi. Sabab: hujjat.png sof
oq (255) va sof qora (0) dan iborat — oraliq qiymatlar yo'q.
Bunday rasmda "eng yaxshi chegara" tushunchasi ma'nosiz.
Haqiqiy skanerlangan hujjatda 100–180 oralig'ida son chiqadi. Sinovni haqiqiy surat bilan takrorlang.
Otsu gistogrammaga qaraydi va qora/oq guruhlarni eng yaxshi ajratadigan chegarani topadi. Qo'lda tanlashdan yaxshiroq.
Lekin u ham BITTA chegara beradi — notekis yorug'lik muammosini hal qilmaydi.
Yechim 2: Adaptive threshold — HAR HUDUD uchun alohida
adaptiv = cv2.adaptiveThreshold( kulrang, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, # qo'shnilarning vaznli o'rtachasi cv2.THRESH_BINARY, blockSize=31, # hudud o'lchami (TOQ son) C=10, # o'rtachadan ayiriladigan qiymat )
Bu har bir piksel uchun atrofidagi 31x31 hududga qarab chegara hisoblaydi. Soyadagi hudud uchun past chegara, yorug' hudud uchun baland.
blockSize toq bo'lishi SHART — markaz piksel kerak.
Juft son bersangiz xato chiqadi.
Qanday tanlash:
blockSize— matn balandligidan kattaroq (odatda 15–51)C— kattalashtirilsa natija oqroq, shovqin kamayadi
Morfologiya — shakl bilan ishlash
Threshold dan keyin mayda nuqtalar (shovqin) qoladi. Yoki harflar uzilib qoladi. Morfologiya shuni tuzatadi.
Ikki asosiy amal:
import numpy as np yadro = np.ones((3, 3), np.uint8) eroziya = cv2.erode(oq_qora, yadro, iterations=1) # qora KICHRAYADI dilatsiya = cv2.dilate(oq_qora, yadro, iterations=1) # qora KATTALASHADI
Eroziya — obyektni yupqalashtiradi, mayda nuqtalarni yo'qotadi. Dilatsiya — obyektni qalinlashtiradi, uzilgan joyni ulaydi.
Ikkalasini ketma-ket qo'llash foydali:
# OPENING = eroziya -> dilatsiya : SHOVQINNI o'chiradi tozalangan = cv2.morphologyEx(oq_qora, cv2.MORPH_OPEN, yadro) # CLOSING = dilatsiya -> eroziya : TESHIKLARNI to'ldiradi butun = cv2.morphologyEx(oq_qora, cv2.MORPH_CLOSE, yadro)
Opening — mayda oq nuqtalar yo'qoladi, katta shakllar qoladi. Closing — harf ichidagi uzilishlar to'ladi.
Hujjat uchun odatiy tartib: adaptive threshold → opening (shovqin ketadi) → agar harflar uzilgan bo'lsa closing.
Kontur — shakllarni topish
Kontur — bu bir xil rangdagi hududning chegarasi.
konturlar, _ = cv2.findContours( oq_qora, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) print(f"topilgan kontur: {len(konturlar)}")
DIQQAT: matn OQ bo'lishi kerak
findContours oq hududlarni obyekt deb biladi, qorani fon deb.
Bizning hujjatda esa matn qora, fon oq — teskari. Shuning
uchun oddiy THRESH_BINARY bilan quyidagi natija chiqadi:
_, oddiy = cv2.threshold(kulrang, 128, 255, cv2.THRESH_BINARY) k1, _ = cv2.findContours(oddiy, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) print(f"BINARY -> {len(k1)} kontur") _, teskari = cv2.threshold(kulrang, 128, 255, cv2.THRESH_BINARY_INV) k2, _ = cv2.findContours(teskari, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) print(f"BINARY_INV -> {len(k2)} kontur")
Natija:
BINARY -> 1 kontur BINARY_INV -> 45 kontur
BINARY bilan bitta kontur — u butun FON. Harflar emas.
BINARY_INV bilan 45 ta — mana harflar.
Bu xatoni deyarli hamma qiladi. Kontur topishdan oldin doim so'rang: obyektim oqmi?
RETR_EXTERNAL — faqat tashqi konturlar. Harf ichidagi
teshiklar ("o" harfining ichi) alohida kontur sifatida
qaytarilmaydi. Hujjat uchun odatda shu kerak.
CHAIN_APPROX_SIMPLE — to'g'ri chiziqni faqat ikki nuqta bilan
saqlaydi. Xotira tejaydi.
Kontur atrofiga to'rtburchak
Bu — hujjatdan matn bloklarini ajratishning asosi:
for k in konturlar: x, y, w, h = cv2.boundingRect(k) if w * h < 100: # juda kichik — shovqin continue print(f"blok: x={x} y={y} kenglik={w} balandlik={h}")
w * h < 100 filtri muhim. Busiz yuzlab mayda shovqin
konturlari chiqadi.
Amaliy: matn bloklarini topish
Alohida harflar emas, butun qatorlarni topish uchun hiyla bor: avval harflarni gorizontal yo'nalishda "yopishtiramiz".
# Matn OQ bo'lishi kerak — shuning uchun INV _, teskari = cv2.threshold(kulrang, 128, 255, cv2.THRESH_BINARY_INV) # Keng va past yadro — harflarni yonma-yon ulaydi qator_yadro = cv2.getStructuringElement(cv2.MORPH_RECT, (25, 3)) qatorlar_rasm = cv2.dilate(teskari, qator_yadro, iterations=1) qatorlar, _ = cv2.findContours( qatorlar_rasm, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) for k in sorted(qatorlar, key=lambda c: cv2.boundingRect(c)[1]): x, y, w, h = cv2.boundingRect(k) if w * h >= 100: print(f"qator: x={x} y={y} w={w} h={h}")
Natija:
qator: x=32 y=33 w=324 h=30 qator: x=29 y=111 w=311 h=22 qator: x=29 y=171 w=275 h=22
45 ta harf -> 3 ta qator. Aynan hujjatdagi uchta qator: sarlavha, summa, sana.
(25, 3) — keng va past. Gorizontal yo'nalishda kuchli
yoyadi, vertikalda deyarli yo'q. Natijada bir qatordagi harflar
birlashadi, lekin qatorlar bir-biriga yopishmaydi.
Bu — hujjat tahlilining asosiy usuli. Yadro o'lchamini o'zgartirib, so'z / qator / paragraf darajasini boshqarasiz.
Qiyshiq hujjatni to'g'rilash
Telefonda olingan surat deyarli doim qiyshiq bo'ladi.
def burchakni_top(oq_qora): """Matn bloklarining o'rtacha burilish burchagini topadi.""" koords = np.column_stack(np.where(oq_qora > 0)) burchak = cv2.minAreaRect(koords)[-1] if burchak < -45: burchak = 90 + burchak return -burchak def togrila(rasm, burchak): b, k = rasm.shape[:2] markaz = (k // 2, b // 2) M = cv2.getRotationMatrix2D(markaz, burchak, 1.0) return cv2.warpAffine(rasm, M, (k, b), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REPLICATE)
minAreaRect barcha matn piksellarini o'rab turgan eng kichik
burchakli to'rtburchakni topadi. Uning burchagi — hujjatning
qiyshiqligi.
Nega bu OCR uchun muhim: ko'p OCR modellari matn gorizontal deb hisoblaydi. 5 daraja qiyshiqlik ham aniqlikni sezilarli tushiradi.
To'liq quvur
def hujjatni_tayyorla(yol): rasm = cv2.imread(yol) if rasm is None: raise FileNotFoundError(yol) kulrang = cv2.cvtColor(rasm, cv2.COLOR_BGR2GRAY) # 1. Shovqinni yumshatish (chetlarni saqlagan holda) yumshoq = cv2.bilateralFilter(kulrang, 9, 75, 75) # 2. Adaptive threshold — notekis yorug'likka chidamli oq_qora = cv2.adaptiveThreshold( yumshoq, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 31, 10) # 3. Mayda shovqinni olib tashlash yadro = np.ones((2, 2), np.uint8) tozalangan = cv2.morphologyEx(oq_qora, cv2.MORPH_OPEN, yadro) return tozalangan
bilateralFilter nega oddiy blur emas? Oddiy xiralashtirish
chetlarni ham yuvadi — harflar noaniq bo'ladi. Bilateral filtr
tekis hududlarni yumshatadi, lekin chetlarni saqlaydi.
Sekinroq, lekin matn uchun ancha yaxshi.
Bu bobni bepul o'qidingiz.
Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.