Asosiy qismga o'tish

Gistogramma va kontrast: rasmni "o'qish"

11-bob 8 kod bloki 2575 so'z

Gistogramma nima aytadi

Gistogramma — har yorqinlik darajasida nechta piksel borligi. U rasmga qaramasdan ko'p narsa aytadi: rasm qorong'imi, yorug'mi, kontrasti pastmi, ikki xil fon bormi.

Bizda ataylab buzilgan rasm bor: past_kontrast.jpg — asl rasmning yorqinlik oralig'i siqib qo'yilgan.

python16 qator
asl = cv2.imread(str(RASM / "qurilish_kichik.jpg"), cv2.IMREAD_GRAYSCALE)
past = cv2.imread(str(RASM / "past_kontrast.jpg"), cv2.IMREAD_GRAYSCALE)
show(pd.DataFrame([{"rasm": n, "min": int(g.min()), "max": int(g.max()),
                    "oraliq": int(g.max()) - int(g.min()), "o'rtacha": round(float(g.mean()), 1),
                    "std (kontrast)": round(float(g.std()), 1)}
                   for n, g in [("asl", asl), ("past kontrast", past)]]))

plt.figure(figsize=(11, 3.2))
for i, (n, g) in enumerate([("asl", asl), ("past kontrast", past)], 1):
    plt.subplot(1, 2, i)
    plt.hist(g.ravel(), bins=256, range=(0, 256), color="#2c6fbb")
    plt.title(n); plt.xlim(0, 255); plt.xlabel("yorqinlik"); plt.ylabel("piksellar")
plt.tight_layout()
fig("gistogramma_asos.png", "Past kontrastli rasmda hamma piksel tor oraliqqa siqilgan — gistogramma ingichka ustun.")
ko_rsat(asl, past, nom="past_kontrast.png", sarlavhalar=["asl", "past kontrast"],
        izoh="Ikkalasida ham bir xil ma'lumot bor, lekin o'ngdagisida u tor yorqinlik oralig'iga siqilgan.")
rasmminmaxoraliqo'rtachastd (kontrast)
asl0255255111.649.2
past kontrast85204119138.420.6

Past kontrastli rasmda hamma piksel tor oraliqqa siqilgan — gistogramma ingichka ustun.

Past kontrastli rasmda hamma piksel tor oraliqqa siqilgan — gistogramma ingichka ustun.

Ikkalasida ham bir xil ma'lumot bor, lekin o'ngdagisida u tor yorqinlik oralig'iga siqilgan.

Ikkalasida ham bir xil ma'lumot bor, lekin o'ngdagisida u tor yorqinlik oralig'iga siqilgan.

std (standart og'ish) — kontrastning eng oddiy o'lchovi. Katta bo'lsa piksellar yorqinlik bo'yicha keng tarqalgan, kichik bo'lsa hammasi bir xilga yaqin.

calcHist: OpenCV usuli

python11 qator
gist = cv2.calcHist([asl], [0], None, [256], [0, 256]).ravel()
print(f"gistogramma uzunligi: {len(gist)} | yig'indi: {int(gist.sum())} = {asl.size} piksel")
print(f"eng ko'p uchraydigan yorqinlik: {int(gist.argmax())} ({int(gist.max())} piksel)")

rangli = cv2.imread(str(RASM / "qurilish_kichik.jpg"))
plt.figure(figsize=(7, 3.2))
for kanal, rang in enumerate(["b", "g", "r"]):
    h = cv2.calcHist([rangli], [kanal], None, [256], [0, 256]).ravel()
    plt.plot(h, color={"b": "#1f77b4", "g": "#2ca02c", "r": "#d62728"}[rang], label=rang.upper())
plt.legend(); plt.xlim(0, 255); plt.title("BGR kanallar gistogrammasi"); plt.tight_layout()
fig("bgr_gistogramma.png", "Uch kanal alohida: qaysi rang ustun ekanini ko'rsatadi.")
Chiqish
gistogramma uzunligi: 256 | yig'indi: 518400 = 518400 piksel
eng ko'p uchraydigan yorqinlik: 102 (9273 piksel)

Uch kanal alohida: qaysi rang ustun ekanini ko'rsatadi.

Uch kanal alohida: qaysi rang ustun ekanini ko'rsatadi.

cv2.calcHist parametrlari: [rasm], [kanal], maska, [binlar soni], [oraliq]. Hammasi ro'yxat ichida — bu C++ API sidan qolgan, birinchi marta chalkashtiradi.

Maska foydali: rasmning faqat bir qismining gistogrammasini olish mumkin.

python10 qator
maska = np.zeros(asl.shape, np.uint8)
maska[250:, :] = 255                                        # faqat pastki qism
butun = cv2.calcHist([asl], [0], None, [256], [0, 256]).ravel()
pastki = cv2.calcHist([asl], [0], maska, [256], [0, 256]).ravel()
print(f"butun rasm o'rtachasi: {asl.mean():.1f} | pastki qism: {asl[250:].mean():.1f}")
plt.figure(figsize=(7, 3.2))
plt.plot(butun / butun.sum(), label="butun rasm")
plt.plot(pastki / pastki.sum(), label="pastki qism (maska)")
plt.legend(); plt.xlim(0, 255); plt.title("Maskali gistogramma"); plt.tight_layout()
fig("maskali_gistogramma.png", "Pastki qismning gistogrammasi o'ngga siljigan — u butun rasmdan yorug'roq.")
Chiqish
butun rasm o'rtachasi: 111.6 | pastki qism: 121.5

Pastki qismning gistogrammasi o'ngga siljigan — u butun rasmdan yorug'roq.

Pastki qismning gistogrammasi o'ngga siljigan — u butun rasmdan yorug'roq.

O'lchov kutilganning teskarisini ko'rsatdi: butun rasm o'rtachasi 111.6, pastki qism esa 121.5 — ya'ni pastki qism yorug'roq. Osmon yuqorida bo'lgani uchun "past = qorong'i" deb o'ylagan edik, lekin bu rasmda osmon bulutli va kulrang, pastdagi qurilish maydoni esa ochiq rangli. Maskali gistogramma aynan shuning uchun kerak — taxminni tekshirib beradi.

equalizeHist: kontrastni cho'zish

equalizeHist gistogrammani butun 0–255 oralig'iga yoyadi:

python11 qator
teng = cv2.equalizeHist(past)
def entropiya(g):
    h = cv2.calcHist([g], [0], None, [256], [0, 256]).ravel()
    p = h[h > 0] / h.sum()
    return round(float(-(p * np.log2(p)).sum()), 2)

show(pd.DataFrame([{"rasm": n, "min": int(g.min()), "max": int(g.max()),
                    "std": round(float(g.std()), 1), "entropiya (bit)": entropiya(g)}
                   for n, g in [("asl", asl), ("past kontrast", past), ("equalizeHist", teng)]]))
ko_rsat(past, teng, asl, nom="equalize.png", sarlavhalar=["past kontrast", "equalizeHist", "asl (solishtirish uchun)"],
        izoh="equalizeHist oraliqni to'liq ochdi, lekin natija aslga aynan teng emas.")
rasmminmaxstdentropiya (bit)
asl025549.27.15
past kontrast8520420.65.92
equalizeHist025573.55.86

equalizeHist oraliqni to'liq ochdi, lekin natija aslga aynan teng emas.

equalizeHist oraliqni to'liq ochdi, lekin natija aslga aynan teng emas.

Entropiya — rasmda qancha ma'lumot borligini bit bilan o'lchaydi (maksimum 8). Aslda 7.15 bit, kontrast siqilgandan keyin 5.92 — ma'lumot haqiqatan yo'qolgan.

Endi jadvaldagi eng muhim qatorga qarang. equalizeHist dan keyin std 20.6 dan 73.5 ga chiqdi, ya'ni rasm ko'zga ancha kontrastliroq ko'rinadi. Lekin entropiya 5.92 dan 5.86 ga tushdi — pasaydi!

Bu qarama-qarshilik emas, aynan haqiqat: equalizeHist yo'qolgan ma'lumotni qaytarmaydi. U bor ma'lumotni kengroq yoyadi, xolos; yoyish paytida esa ba'zi darajalar birlashib, bir oz ma'lumot yana yo'qoladi. Ko'rinish yaxshilandi, ma'lumot yaxshilanmadi.

Saboq: "chiroyli ko'rinadi" bilan "ma'lumot ko'paydi" — ikki xil narsa. Agar tuzatishdan keyin OCR yoki detektor ishlatsangiz, ularga ikkinchisi kerak.

CLAHE: mahalliy tenglashtirish

equalizeHist butun rasmga bitta o'zgartirish qo'llaydi — 6-bobdagi global threshold muammosining aynan o'zi. CLAHE (Contrast Limited Adaptive Histogram Equalization) rasmni kataklarga bo'lib, har biriga alohida ishlov beradi:

python7 qator
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
c = clahe.apply(past)
show(pd.DataFrame([{"usul": n, "std": round(float(g.std()), 1), "entropiya": entropiya(g)}
                   for n, g in [("past kontrast", past), ("equalizeHist", teng), ("CLAHE 2.0", c),
                                ("asl", asl)]]))
ko_rsat(past, teng, c, nom="clahe.png", sarlavhalar=["past kontrast", "equalizeHist (global)", "CLAHE (mahalliy)"],
        izoh="CLAHE soyadagi tafsilotni ham ochadi va osmonni equalizeHist kabi 'yondirmaydi'.")
usulstdentropiya
past kontrast20.65.92
equalizeHist73.55.86
CLAHE 2.037.37.09
asl49.27.15

CLAHE soyadagi tafsilotni ham ochadi va osmonni equalizeHist kabi 'yondirmaydi'.

CLAHE soyadagi tafsilotni ham ochadi va osmonni equalizeHist kabi 'yondirmaydi'.

CLAHE entropiyani 7.09 ga ko'tardi — aslning 7.15 iga deyarli teng, equalizeHist ning 5.86 idan ancha yuqori. Ya'ni mahalliy tenglashtirish nafaqat chiroyliroq ko'rinadi, balki ma'lumotni ham haqiqatan tiklaydi.

clipLimit — kontrastni qanchalik kuchaytirishga ruxsat. Kattalashtirilsa shovqin ham kuchayadi:

python7 qator
show(pd.DataFrame([{"clipLimit": cl, "std": round(float(cv2.createCLAHE(clipLimit=cl, tileGridSize=(8, 8)).apply(past).std()), 1),
                    "entropiya": entropiya(cv2.createCLAHE(clipLimit=cl, tileGridSize=(8, 8)).apply(past))}
                   for cl in (1.0, 2.0, 4.0, 10.0, 40.0)]))
ko_rsat(cv2.createCLAHE(clipLimit=1.0, tileGridSize=(8, 8)).apply(past),
        cv2.createCLAHE(clipLimit=40.0, tileGridSize=(8, 8)).apply(past), nom="clip_limit.png",
        sarlavhalar=["clipLimit 1.0 — ehtiyotkor", "clipLimit 40 — shovqin kuchaygan"],
        izoh="Chegara yo'q qilinsa CLAHE tekis joylardagi mayda shovqinni ham kontrastga aylantiradi.")
clipLimitstdentropiya
130.36.67
237.37.09
446.57.5
10597.84
40707.98

Chegara yo'q qilinsa CLAHE tekis joylardagi mayda shovqinni ham kontrastga aylantiradi.

Chegara yo'q qilinsa CLAHE tekis joylardagi mayda shovqinni ham kontrastga aylantiradi.

Bu jadvalda tuzoq bor: clipLimit=40 da entropiya 7.98 — aslning 7.15 idan ham yuqori. Rasmda asl nusxadagidan ko'p ma'lumot paydo bo'lishi mumkin emas, demak bu "ma'lumot" — shovqin. Entropiya yolg'iz o'zi sifat o'lchovi emas.

Amaliy boshlang'ich: clipLimit=2.0, tileGridSize=(8, 8) — o'lchovlarimizda u aslga eng yaqini.

Rangli rasmda: har kanalga alohida qilmang

Bu eng ko'p uchraydigan xato:

python15 qator
past_rangli = cv2.imread(str(RASM / "past_kontrast.jpg"))
notogri = cv2.merge([cv2.equalizeHist(k) for k in cv2.split(past_rangli)])       # BGR ga alohida — XATO
lab = cv2.cvtColor(past_rangli, cv2.COLOR_BGR2LAB)
L, A, B = cv2.split(lab)
togri = cv2.cvtColor(cv2.merge([clahe.apply(L), A, B]), cv2.COLOR_LAB2BGR)       # faqat L ga — TO'G'RI
def rang_ogishi(im):
    """Piksellarning o'rtacha rang to'yinganligi: qanchalik 'qichqiriq' rang."""
    return round(float(cv2.cvtColor(im, cv2.COLOR_BGR2HSV)[:, :, 1].mean()), 1)
show(pd.DataFrame([{"usul": n, "to'yinganlik (S o'rtacha)": rang_ogishi(im)}
                   for n, im in [("past kontrast", past_rangli), ("BGR ga alohida equalizeHist", notogri),
                                 ("LAB ning L kanaliga CLAHE", togri),
                                 ("asl", cv2.imread(str(RASM / "qurilish_kichik.jpg")))]]))
ko_rsat(past_rangli, notogri, togri, nom="rangli_kontrast.png",
        sarlavhalar=["past kontrast", "BGR ga alohida — ranglar buzildi", "L kanaliga CLAHE — to'g'ri"],
        izoh="Har kanalni alohida tenglashtirsangiz kanallar nisbati o'zgaradi va rang siljiydi.")
usulto'yinganlik (S o'rtacha)
past kontrast19.1
BGR ga alohida equalizeHist76.3
LAB ning L kanaliga CLAHE20.3
asl55.5

Har kanalni alohida tenglashtirsangiz kanallar nisbati o'zgaradi va rang siljiydi.

Har kanalni alohida tenglashtirsangiz kanallar nisbati o'zgaradi va rang siljiydi.

Sabab: rang uch kanal nisbatidan hosil bo'ladi. Har birini alohida cho'zsangiz nisbat buziladi. To'g'ri yo'l — yorqinlikni rangdan ajratadigan fazoga o'tish (LAB yoki YCrCb), faqat yorqinlik kanalini o'zgartirish, keyin qaytarish.

Jadvaldagi sonlar buni tasdiqlaydi. Asl rasmning to'yinganligi 55.5; past kontrastli nusxada u 19.1 ga tushgan (rasmni buzganda ranglar ham yuvilib ketgan). BGR kanallariga alohida equalizeHist qo'llanganda to'yinganlik 76.3 ga chiqdi — aslning 55.5 idan ham yuqori. Ya'ni u rangni tiklamadi, yo'q rangni o'ylab topdi.

L kanaliga CLAHE esa to'yinganlikni 20.3 da qoldirdi. Bu ham 55.5 emas — va bu to'g'ri xulq: yorqinlikni tuzatish yo'qolgan rangni qaytarmaydi, chunki u ma'lumot rasmda yo'q. Yaxshi usul yo'q narsani o'ylab topmaydi.

:::gotcha Kontrast tuzatish — bepul emas equalizeHist va CLAHE shovqinni ham kuchaytiradi. Agar keyin chekka topish yoki OCR qilsangiz, avval filtrlang (7-bob), keyin kontrastni tuzating. Teskari tartibda shovqin kuchayib qoladi. :::

Ikki rasmni gistogramma bo'yicha solishtirish

compareHist ikki gistogrammaning o'xshashligini beradi — rasm qidirish va sahna o'zgarishini aniqlashda ishlatiladi:

python12 qator
def hsv_gist(im):
    h = cv2.calcHist([cv2.cvtColor(im, cv2.COLOR_BGR2HSV)], [0, 1], None, [50, 60], [0, 180, 0, 256])
    return cv2.normalize(h, h).flatten()

rasmlar = {"asl": cv2.imread(str(RASM / "qurilish_kichik.jpg")),
           "past kontrast": past_rangli,
           "tuzatilgan": togri,
           "shakllar (boshqa rasm)": cv2.imread(str(RASM / "shakllar.png"))}
baza = hsv_gist(rasmlar["asl"])
show(pd.DataFrame([{"rasm": n, "CORREL (1 = bir xil)": round(float(cv2.compareHist(baza, hsv_gist(im), cv2.HISTCMP_CORREL)), 3),
                    "BHATTACHARYYA (0 = bir xil)": round(float(cv2.compareHist(baza, hsv_gist(im), cv2.HISTCMP_BHATTACHARYYA)), 3)}
                   for n, im in rasmlar.items()]))
rasmCORREL (1 = bir xil)BHATTACHARYYA (0 = bir xil)
asl10
past kontrast0.220.706
tuzatilgan0.2210.691
shakllar (boshqa rasm)0.0330.972

Butunlay boshqa rasm (shakllar.png) uchun CORREL 0.033 — deyarli nol, o'zi bilan o'zi uchun aniq 1.0. Shu ikki qutb orasida usul ishlaydi.

Lekin o'rtadagi qatorlarga qarang: o'sha sahnaning past kontrastli nusxasi uchun CORREL atigi 0.22. Bir xil bino, bir xil kran, bir xil kadr — lekin gistogramma bo'yicha "boshqa rasm"ga o'xshab qoldi. Tuzatilgandan keyin ham 0.221, ya'ni deyarli o'zgarmadi.

Demak compareHist sahnani emas, rang taqsimotini solishtiradi. Yorug'lik yoki ekspozitsiya o'zgarsa u aldaydi. Shuning uchun u "kadr keskin o'zgardimi?" degan savolga yaxshi javob beradi (14-bobda videoda shunday ishlatamiz), "bu ikkalasi bir joymi?" degan savolga esa yo'q — bunga 13-bobdagi kalit nuqtalar kerak.

Xulosa

Tushuncha Asosiy fikr
Gistogramma Har yorqinlikda nechta piksel; rasmni ko'rmasdan tahlil qilish
calcHist Parametrlar ro'yxat ichida: [rasm], [kanal], maska, [bin], [oraliq]
std Kontrastning eng oddiy o'lchovi
Entropiya Rasmdagi ma'lumot miqdori, bit (maksimum 8)
equalizeHist Global; std ni oshiradi, lekin entropiyani oshirmaydi (5.92 → 5.86)
CLAHE Mahalliy; entropiyani aslga qaytaradi (5.92 → 7.09, asl 7.15)
clipLimit Katta bo'lsa shovqin ham "ma'lumot" bo'lib ko'rinadi (40 da entropiya 7.98)
Rangli rasm BGR kanallariga alohida qilmang — LAB ning L kanaliga qiling
compareHist Rang taqsimotini solishtiradi, sahnani emas: bir xil sahna uchun 0.22 chiqdi

Talaba ishi

  1. hujjat.jpg ning chap va o'ng yarmining gistogrammasini alohida chizing. 6-bobdagi "notekis yorug'lik" muammosi gistogrammada qanday ko'rinadi?
  2. CLAHE ni tileGridSize ni (2,2), (8,8), (32,32) qilib sinang. Katak juda kichik bo'lsa nima buziladi?
  3. past_kontrast.jpg ni CLAHE bilan tuzatib, keyin 8-bobdagi avto_canny ni qo'llang. Tuzatishdan oldin va keyin topilgan chekka piksellari foizini solishtiring. Tuzatish yordam berdimi?
  4. compareHist ni qurilish.jpg ning turli qismlari orasida hisoblang (masalan, 4 ta chorak). Qaysi ikki chorak eng o'xshash va bu rasmni ko'rganingizda mantiqiymi?

Bu bobni bepul o'qidingiz.

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

Telegramda yozish Narxlar