Asosiy qismga o'tish

Threshold: rasmni ikki rangga bo'lish

6-bob 6 kod bloki 1679 so'z

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.

python5 qator
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}")
Chiqish
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

python32 qator
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))
usulaniqlik %to'liqlik %F1 %
global 10098.65167.2
global 14096.476.485.3
global 18084.194.288.9

Bitta chegara: past bo'lsa soyadagi joy qorayib ketadi, yuqori bo'lsa yorug' joydagi matn yo'qoladi.

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:

python5 qator
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.")
Chiqish
Otsu tanlagan chegara: 168 | F1: 91.2 %

Otsu chegarani o'zi topadi, lekin u ham bitta chegara — notekis yorug'lik muammosini yechmaydi.

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:

python8 qator
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)]]))
usulaniqlik %to'liqlik %F1 %
global 14096.476.485.3
Otsu91.590.991.2
adaptive MEAN77.410087.3
adaptive GAUSSIAN89.299.694.1

Adaptive usulda soyadagi matn ham ajraladi: to'liqlik 99.6% (Otsu da 90.9%).

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
python7 qator
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))
blockSizeCaniqlik %to'liqlik %F1 %
11591.897.894.7
11159493.893.9
3158599.891.8
311591.399.395.2
6157510085.7
611584.199.991.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

python6 qator
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).

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

  1. blockSize ni 3 dan 101 gacha o'zgartirib, F1 ni grafikda chizing. Eng yaxshi qiymat qayerda va undan keyin nega pasayadi?
  2. 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)?
  3. THRESH_TRUNC va THRESH_TOZERO qanday holatlarda kerak bo'lishi mumkin? Bitta misol o'ylab toping.
  4. 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.

Telegramda yozish Narxlar