Threshold: rasmni ikki rangga bo'lish
Vazifa: qog'ozdan matnni ajratish
Threshold (chegaralash) — eng sodda amal: har piksel chegaradan katta bo'lsa 255, aks holda 0. Natija — faqat oq va qora. OCR, kontur topish, shakl tahlili — hammasi shu yerdan boshlanadi.
Qiyinchilik chegarani tanlashda. Telefonda olingan hujjatimizni ko'ramiz: bir tomoni yorug', ikkinchisi soyada.
hujjat = cv2.imread(str(RASM / "hujjat.jpg")) gray = cv2.cvtColor(hujjat, cv2.COLOR_BGR2GRAY) chap, ong = gray[:, :400], gray[:, 400:] print(f"chap yarmining o'rtacha yorqinligi: {chap.mean():.0f} | o'ng yarmi: {ong.mean():.0f}") print(f"butun rasm: min {gray.min()}, max {gray.max()}, o'rtacha {gray.mean():.0f}")
chap yarmining o'rtacha yorqinligi: 166 | o'ng yarmi: 145 butun rasm: min 25, max 255, o'rtacha 156
Ikki yarmi orasida katta farq bor. Endi bitta chegara bilan nima bo'lishini ko'ramiz.
Global threshold: bitta chegara hammaga
natijalar = {} for chegara in (100, 140, 180): _, b = cv2.threshold(gray, chegara, 255, cv2.THRESH_BINARY) natijalar[f"chegara {chegara}"] = b ko_rsat(*natijalar.values(), nom="global_threshold.png", sarlavhalar=list(natijalar), izoh="Bitta chegara: past bo'lsa soyadagi joy qorayib ketadi, yuqori bo'lsa yorug' joydagi matn yo'qoladi.") BURCHAK = np.float32([[150, 90], [720, 170], [650, 940], [90, 830]]) # varaqning 4 burchagi (4-bob) NISHON = np.float32([[0, 0], [640, 0], [640, 900], [0, 900]]) P = cv2.getPerspectiveTransform(BURCHAK, NISHON) togri = cv2.warpPerspective(gray, P, (640, 900)) # varaqni to'g'rilab olamiz toza = cv2.imread(str(RASM / "hujjat_toza.png"), cv2.IMREAD_GRAYSCALE) # asl, buzilmagan varaq _, HAQIQAT = cv2.threshold(toza, 128, 255, cv2.THRESH_BINARY_INV) # haqiqiy matn piksellari def sifat(binar): """To'g'rilangan varaqdagi natijani haqiqat bilan solishtiradi: matn piksellari bo'yicha F1.""" topilgan = binar == 0 haqiqiy = HAQIQAT > 0 tp = int((topilgan & haqiqiy).sum()) fp = int((topilgan & ~haqiqiy).sum()) fn = int((~topilgan & haqiqiy).sum()) p_ = tp / max(tp + fp, 1) r_ = tp / max(tp + fn, 1) return round(100 * p_, 1), round(100 * r_, 1), round(100 * 2 * p_ * r_ / max(p_ + r_, 1e-9), 1) qatorlar = [] for chegara in (100, 140, 180): _, b = cv2.threshold(togri, chegara, 255, cv2.THRESH_BINARY) aniq, toliq, f1 = sifat(b) qatorlar.append({"usul": f"global {chegara}", "aniqlik %": aniq, "to'liqlik %": toliq, "F1 %": f1}) show(pd.DataFrame(qatorlar))
| usul | aniqlik % | to'liqlik % | F1 % |
|---|---|---|---|
| global 100 | 98.6 | 51 | 67.2 |
| global 140 | 96.4 | 76.4 | 85.3 |
| global 180 | 84.1 | 94.2 | 88.9 |

Bitta chegara: past bo'lsa soyadagi joy qorayib ketadi, yuqori bo'lsa yorug' joydagi matn yo'qoladi.
Natijani haqiqat bilan solishtirdik: hujjat_toza.png — o'sha varaqning buzilmagan asli, undagi matn
piksellari bizning "to'g'ri javob"imiz. Aniqlik — topilgan siyohning qanchasi haqiqatan matn; to'liqlik —
haqiqiy matnning qanchasi topilgan; F1 — ikkalasining muvozanati.
Jadval muammoni ko'rsatadi: chegara past bo'lsa to'liqlik tushadi (soyadagi matn yo'qoladi), chegara yuqori bo'lsa aniqlik tushadi (fon ham siyoh deb belgilanadi). Yagona chegara notekis yorug'likda ishlamaydi — chunki "qorong'i" tushunchasi rasmning har joyida boshqacha.
Otsu: chegarani ma'lumotning o'zi tanlaydi
Otsu usuli gistogrammaga qarab, ikki guruhni (fon va obyekt) eng yaxshi ajratadigan chegarani o'zi topadi:
chegara, otsu = cv2.threshold(togri, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) print("Otsu tanlagan chegara:", int(chegara), "| F1:", sifat(otsu)[2], "%") _, qolda = cv2.threshold(togri, 140, 255, cv2.THRESH_BINARY) ko_rsat(otsu, qolda, nom="otsu.png", sarlavhalar=[f"Otsu (chegara {int(chegara)})", "qo'lda 140"], izoh="Otsu chegarani o'zi topadi, lekin u ham bitta chegara — notekis yorug'lik muammosini yechmaydi.")
Otsu tanlagan chegara: 168 | F1: 91.2 %

Otsu chegarani o'zi topadi, lekin u ham bitta chegara — notekis yorug'lik muammosini yechmaydi.
Otsu qulay: qo'lda son tanlash shart emas, va turli rasmlar uchun moslashadi. Lekin u global — butun rasm uchun bitta son. Bizning hujjatda muammo saqlanib qoladi.
Otsu qachon yaxshi ishlaydi: bir tekis yoritilgan skaner tasviri, konveyerdagi detal, mikroskop tasviri — ya'ni fon bir xil bo'lganda.
Adaptive threshold: har joyga o'z chegarasi
Adaptive usul har piksel uchun atrofidagi piksellarga qarab chegara hisoblaydi. Shuning uchun soya muammo emas:
mean_t = cv2.adaptiveThreshold(togri, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 31, 10) gauss_t = cv2.adaptiveThreshold(togri, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 31, 10) ko_rsat(otsu, mean_t, gauss_t, nom="adaptive.png", sarlavhalar=["Otsu (global)", "adaptive MEAN", "adaptive GAUSSIAN"], izoh="Adaptive usulda soyadagi matn ham ajraladi: to'liqlik 99.6% (Otsu da 90.9%).") show(pd.DataFrame([{"usul": n, "aniqlik %": sifat(b)[0], "to'liqlik %": sifat(b)[1], "F1 %": sifat(b)[2]} for n, b in [("global 140", qolda), ("Otsu", otsu), ("adaptive MEAN", mean_t), ("adaptive GAUSSIAN", gauss_t)]]))
| usul | aniqlik % | to'liqlik % | F1 % |
|---|---|---|---|
| global 140 | 96.4 | 76.4 | 85.3 |
| Otsu | 91.5 | 90.9 | 91.2 |
| adaptive MEAN | 77.4 | 100 | 87.3 |
| adaptive GAUSSIAN | 89.2 | 99.6 | 94.1 |

Adaptive usulda soyadagi matn ham ajraladi: to'liqlik 99.6% (Otsu da 90.9%).
Raqamlar: global 140 — F1 85.3%, Otsu — 91.2%, adaptive GAUSSIAN — 94.1%. Adaptive usul soyadagi matnni deyarli to'liq topadi (to'liqlik 99.6%), global usul esa uning chorak qismini yo'qotadi.
Diqqat qiling: adaptive MEAN ning F1 i 87.3% — Otsu dan ham past. Uning to'liqligi 100%, lekin aniqligi
77.4%: u matnni to'liq topadi, ammo fonni ham siyoh deb belgilaydi. Shuning uchun "adaptive" degan so'zning
o'zi yetarli emas — GAUSSIAN varianti va parametrlar muhim.
Ikkita muhim parametr:
| Parametr | Ma'nosi | Qanday tanlanadi |
|---|---|---|
blockSize |
Atrof qanchalik katta (toq son) | Harf balandligidan kattaroq. Kichik bo'lsa harf ichi buziladi |
C |
Chegaradan ayiriladigan son | Kattaroq C — kamroq shovqin, lekin ingichka chiziqlar yo'qoladi |
qatorlar = [] for blok in (11, 31, 61): for C in (5, 15): b = cv2.adaptiveThreshold(togri, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, blok, C) aniq, toliq, f1 = sifat(b) qatorlar.append({"blockSize": blok, "C": C, "aniqlik %": aniq, "to'liqlik %": toliq, "F1 %": f1}) show(pd.DataFrame(qatorlar))
| blockSize | C | aniqlik % | to'liqlik % | F1 % |
|---|---|---|---|---|
| 11 | 5 | 91.8 | 97.8 | 94.7 |
| 11 | 15 | 94 | 93.8 | 93.9 |
| 31 | 5 | 85 | 99.8 | 91.8 |
| 31 | 15 | 91.3 | 99.3 | 95.2 |
| 61 | 5 | 75 | 100 | 85.7 |
| 61 | 15 | 84.1 | 99.9 | 91.3 |
Eng yaxshi F1 beradigan juftlikni jadvaldan tanlang. Amalda blockSize ni harf balandligidan 2–3 barobar
katta olish yaxshi boshlang'ich nuqta.
:::gotcha blockSize juft bo'lmasin
blockSize toq bo'lishi shart (11, 31, 61). Juft son bersangiz OpenCV xato beradi. Sabab: markaz
piksel aniq bo'lishi kerak.
:::
THRESH turlari
turlar = {"BINARY": cv2.THRESH_BINARY, "BINARY_INV": cv2.THRESH_BINARY_INV, "TRUNC": cv2.THRESH_TRUNC, "TOZERO": cv2.THRESH_TOZERO} shakllar = cv2.imread(str(RASM / "shakllar.png"), cv2.IMREAD_GRAYSCALE) rasmlar = [cv2.threshold(shakllar, 150, 255, t)[1] for t in turlar.values()] ko_rsat(*rasmlar, nom="thresh_turlari.png", sarlavhalar=list(turlar), kenglik=14, izoh="BINARY_INV — teskari (matn oq bo'lib chiqadi, kontur topishda shu kerak bo'ladi).")

BINARY_INV — teskari (matn oq bo'lib chiqadi, kontur topishda shu kerak bo'ladi).
THRESH_BINARY_INV ayniqsa muhim: OpenCV da kontur topish oq obyektni qora fonda qidiradi. Hujjatda
esa matn qora. Shuning uchun kontur qidirishdan oldin teskari qilinadi.
Xulosa
| Tushuncha | Asosiy fikr |
|---|---|
| Global threshold | Bitta chegara; faqat bir tekis yoritilgan rasmda ishlaydi |
| Otsu | Chegarani gistogrammadan o'zi tanlaydi, lekin baribir global |
| Adaptive | Har piksel uchun atrofiga qarab chegara — notekis yorug'lik uchun |
blockSize |
Toq son, harf balandligidan katta |
C |
Kattaroq — kamroq shovqin, lekin ingichka chiziq yo'qoladi |
BINARY_INV |
Kontur qidirishdan oldin: obyekt oq bo'lishi kerak |
| Tekshiruv | Natijani haqiqat bilan solishtiring (F1). Bu hujjatda: global 85.3%, Otsu 91.2%, adaptive GAUSSIAN 94.1% |
Talaba ishi
blockSizeni 3 dan 101 gacha o'zgartirib, F1 ni grafikda chizing. Eng yaxshi qiymat qayerda va undan keyin nega pasayadi?sifat()funksiyasini o'zgartiring: F1 o'rniga faqat aniqlik ga qarab eng yaxshi parametrni tanlang. Tanlov o'zgardimi? Qaysi holatda aniqlik to'liqlikdan muhimroq (masalan, OCR ga berishdan oldin)?THRESH_TRUNCvaTHRESH_TOZEROqanday holatlarda kerak bo'lishi mumkin? Bitta misol o'ylab toping.- Otsu ni faqat rasmning o'ng yarmiga (yorug' qismiga) qo'llang va chegarani butun rasmnikiga solishtiring. Farq qancha? Bu adaptive usul nima qilishini tushuntiradimi?
Bu bobni bepul o'qidingiz.
Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.