Gistogramma va kontrast: rasmni "o'qish"
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.
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.")
| rasm | min | max | oraliq | o'rtacha | std (kontrast) |
|---|---|---|---|---|---|
| asl | 0 | 255 | 255 | 111.6 | 49.2 |
| past kontrast | 85 | 204 | 119 | 138.4 | 20.6 |

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.
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
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.")
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.
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.
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.")
butun rasm o'rtachasi: 111.6 | pastki qism: 121.5

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:
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.")
| rasm | min | max | std | entropiya (bit) |
|---|---|---|---|---|
| asl | 0 | 255 | 49.2 | 7.15 |
| past kontrast | 85 | 204 | 20.6 | 5.92 |
| equalizeHist | 0 | 255 | 73.5 | 5.86 |

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:
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'.")
| usul | std | entropiya |
|---|---|---|
| past kontrast | 20.6 | 5.92 |
| equalizeHist | 73.5 | 5.86 |
| CLAHE 2.0 | 37.3 | 7.09 |
| asl | 49.2 | 7.15 |

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:
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.")
| clipLimit | std | entropiya |
|---|---|---|
| 1 | 30.3 | 6.67 |
| 2 | 37.3 | 7.09 |
| 4 | 46.5 | 7.5 |
| 10 | 59 | 7.84 |
| 40 | 70 | 7.98 |

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:
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.")
| usul | to'yinganlik (S o'rtacha) |
|---|---|
| past kontrast | 19.1 |
| BGR ga alohida equalizeHist | 76.3 |
| LAB ning L kanaliga CLAHE | 20.3 |
| asl | 55.5 |

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:
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()]))
| rasm | CORREL (1 = bir xil) | BHATTACHARYYA (0 = bir xil) |
|---|---|---|
| asl | 1 | 0 |
| past kontrast | 0.22 | 0.706 |
| tuzatilgan | 0.221 | 0.691 |
| shakllar (boshqa rasm) | 0.033 | 0.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
hujjat.jpgning chap va o'ng yarmining gistogrammasini alohida chizing. 6-bobdagi "notekis yorug'lik" muammosi gistogrammada qanday ko'rinadi?- CLAHE ni
tileGridSizeni (2,2), (8,8), (32,32) qilib sinang. Katak juda kichik bo'lsa nima buziladi? past_kontrast.jpgni CLAHE bilan tuzatib, keyin 8-bobdagiavto_cannyni qo'llang. Tuzatishdan oldin va keyin topilgan chekka piksellari foizini solishtiring. Tuzatish yordam berdimi?compareHistniqurilish.jpgning 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.