Filtrlash: shovqinni olib tashlash
Shovqin qayerdan keladi
Kam yorug'likda olingan surat, arzon kamera, uzoq masofadan yuborilgan video — hammasida shovqin bor. Shovqin keyingi qadamlarni buzadi: chekka topish soxta chiziqlar ko'radi, kontur mayda bo'laklarga bo'linadi, OCR harflarni chalkashtiradi.
Ikki xil shovqin bilan ishlaymiz: gauss (har pikselga kichik tasodifiy qo'shimcha) va tuz-qalampir (ba'zi piksellar butunlay oq yoki qora). Bizda ikkalasi ham bor:
toza = cv2.imread(str(RASM / "shakllar.png")) shovqinli = cv2.imread(str(RASM / "shovqinli.png")) farq = np.abs(toza.astype(np.int16) - shovqinli.astype(np.int16)) print(f"o'rtacha farq: {farq.mean():.1f} | eng katta farq: {farq.max()}") print(f"butunlay oq yoki qora bo'lib qolgan piksellar: {100 * ((shovqinli == 0) | (shovqinli == 255)).all(axis=2).mean():.1f}%") ko_rsat(toza, shovqinli, nom="shovqin.png", sarlavhalar=["toza (asl)", "shovqinli"], izoh="Gauss shovqini — umumiy 'g'adir-budurlik'; tuz-qalampir — yakka oq va qora nuqtalar.")
o'rtacha farq: 16.6 | eng katta farq: 245 butunlay oq yoki qora bo'lib qolgan piksellar: 5.7%

Gauss shovqini — umumiy 'g'adir-budurlik'; tuz-qalampir — yakka oq va qora nuqtalar.
Bizda haqiqat bor: shakllar.png — asl, toza rasm. Demak har filtrning natijasini u bilan solishtirib,
qaysi biri yaxshiroq ekanini o'lchash mumkin, "menimcha chiroyliroq" demasdan.
To'rtta filtr
import time filtrlar = { "blur (o'rtacha)": lambda im: cv2.blur(im, (5, 5)), "GaussianBlur": lambda im: cv2.GaussianBlur(im, (5, 5), 0), "medianBlur": lambda im: cv2.medianBlur(im, 5), "bilateralFilter": lambda im: cv2.bilateralFilter(im, 9, 75, 75), } natijalar, qatorlar = {}, [] for nom, f in filtrlar.items(): t0 = time.perf_counter() for _ in range(10): chiqdi = f(shovqinli) ms = (time.perf_counter() - t0) / 10 * 1000 natijalar[nom] = chiqdi xato = float(np.abs(toza.astype(np.int16) - chiqdi.astype(np.int16)).mean()) psnr = cv2.PSNR(toza, chiqdi) qatorlar.append({"filtr": nom, "vaqt, ms": round(ms, 2), "asldan farq": round(xato, 2), "PSNR, dB": round(psnr, 1)}) qatorlar.append({"filtr": "(filtrsiz)", "vaqt, ms": 0, "asldan farq": round(float(np.abs(toza.astype(np.int16) - shovqinli.astype(np.int16)).mean()), 2), "PSNR, dB": round(cv2.PSNR(toza, shovqinli), 1)}) show(pd.DataFrame(qatorlar)) ko_rsat(*natijalar.values(), nom="filtrlar.png", sarlavhalar=list(natijalar), kenglik=15, izoh="To'rt filtr, bitta shovqinli rasm. Farqni chekkalarga va yakka nuqtalarga qarab baholang.")
| filtr | vaqt, ms | asldan farq | PSNR, dB |
|---|---|---|---|
| blur (o'rtacha) | 0.18 | 8.53 | 26.1 |
| GaussianBlur | 0.23 | 8.67 | 25.6 |
| medianBlur | 1.31 | 4.01 | 33.1 |
| bilateralFilter | 17.11 | 7.82 | 17.8 |
| (filtrsiz) | 0 | 16.56 | 17 |

To'rt filtr, bitta shovqinli rasm. Farqni chekkalarga va yakka nuqtalarga qarab baholang.
PSNR (peak signal-to-noise ratio) — asl bilan qanchalik mos kelishini dB da o'lchaydi: katta bo'lsa yaxshiroq. Har 6 dB taxminan "ikki barobar aniqroq" degani.
Jadvaldan ko'rinadi: medianBlur tuz-qalampir shovqinida yutadi — PSNR 33.1 dB, boshqalari 26 dB atrofida.
Sababi oddiy: u atrofdagi 25 pikselni tartiblab, o'rtadagisini oladi. Yakka oq nuqta (255) tartiblanganda
chetga tushadi va tanlanmaydi. O'rtacha oluvchi filtrlar (blur, GaussianBlur) esa o'sha 255 ni hisobga
qo'shadi va nuqtani "yoyib" yuboradi — dog' kichrayadi, lekin yo'qolmaydi.
Endi jadvaldagi eng qiziq qatorga qarang: bilateralFilter — 17.8 dB, filtrsiz rasm esa 17.0 dB. Ya'ni eng qimmat filtr (17.7 ms, boshqalaridan o'n barobar sekin) deyarli hech narsa qilmadi. Bu xato emas, uning tabiati: bilateral filtr chekkani saqlaydi, tuz-qalampirdagi har bir yakka nuqta esa uning uchun o'tkir chekka — demak u saqlanadi.
Saboq: filtrni "yaxshi" yoki "yomon" deb emas, shovqin turiga mos yoki mos emas deb tanlang. Keyingi bo'limda bilateral o'z ishini qiladigan holatni ko'ramiz.
Yadro (kernel) o'lchami nimani o'zgartiradi
qatorlar = [] for k in (3, 5, 9, 15): g = cv2.GaussianBlur(shovqinli, (k, k), 0) m = cv2.medianBlur(shovqinli, k) qatorlar.append({"yadro": f"{k}x{k}", "Gauss PSNR": round(cv2.PSNR(toza, g), 1), "Median PSNR": round(cv2.PSNR(toza, m), 1)}) show(pd.DataFrame(qatorlar)) ko_rsat(cv2.GaussianBlur(shovqinli, (3, 3), 0), cv2.GaussianBlur(shovqinli, (15, 15), 0), nom="yadro_olchami.png", sarlavhalar=["Gauss 3x3 — shovqin qoldi", "Gauss 15x15 — chekkalar yo'qoldi"], izoh="Yadro kattalashsa shovqin kamayadi, lekin tafsilot ham yo'qoladi. Har doim murosa.")
| yadro | Gauss PSNR | Median PSNR |
|---|---|---|
| 3x3 | 24.2 | 30.3 |
| 5x5 | 25.6 | 33.1 |
| 9x9 | 26.6 | 33.9 |
| 15x15 | 26.2 | 32.1 |

Yadro kattalashsa shovqin kamayadi, lekin tafsilot ham yo'qoladi. Har doim murosa.
Yadro toq bo'lishi kerak (3, 5, 7...), chunki markaz piksel aniq bo'lishi shart. Kattalashtirsangiz: shovqin kamayadi, chekkalar xiralashadi, hisob sekinlashadi.
Bilateral: chekkani saqlaydigan filtr
Oddiy blur hamma narsani birdek xiralashtiradi. Bilateral filtr ikki narsani hisobga oladi: piksellar qanchalik yaqin (masofa) va qanchalik o'xshash (rang). Rangi keskin farq qiladigan qo'shni — ya'ni chekka — o'rtachalashga qo'shilmaydi.
img = cv2.imread(str(RASM / "qurilish_kichik.jpg")) g = cv2.GaussianBlur(img, (9, 9), 0) b = cv2.bilateralFilter(img, 9, 90, 90) def keskinlik(im): return round(float(cv2.Laplacian(cv2.cvtColor(im, cv2.COLOR_BGR2GRAY), cv2.CV_64F).var())) print(f"keskinlik — asl: {keskinlik(img)}, Gauss: {keskinlik(g)}, bilateral: {keskinlik(b)}") ko_rsat(img[180:400, 280:620], g[180:400, 280:620], b[180:400, 280:620], nom="bilateral.png", sarlavhalar=["asl", "GaussianBlur 9x9", "bilateralFilter"], izoh="Bilateral tekis joylarni silliqlaydi, chekkalarni esa saqlaydi — shuning uchun keskinlik yuqori qoladi.")
keskinlik — asl: 2038, Gauss: 16, bilateral: 1231

Bilateral tekis joylarni silliqlaydi, chekkalarni esa saqlaydi — shuning uchun keskinlik yuqori qoladi.
Narxi — tezlik: bilateral yuqoridagi jadvalda eng sekin filtr edi. Video uchun har kadrda ishlatish qimmat.
:::tip Qaysi filtrni qachon
Tuz-qalampir (yakka nuqtalar) → medianBlur. Umumiy g'adir-budurlik → GaussianBlur.
Chekka muhim, teksturani tozalash kerak (portret, skan) → bilateralFilter. Tezlik hammasidan muhim →
blur. Shovqin yo'q bo'lsa — hech qanday filtr ishlatmang.
:::
Filtr chekkada nima qiladi
Yadro rasm chekkasiga yetganda tashqarida piksel yo'q. OpenCV ularni "o'ylab topadi":
kichik = cv2.resize(toza, (200, 150)) turlar = {"REFLECT (default)": cv2.BORDER_REFLECT, "CONSTANT (qora)": cv2.BORDER_CONSTANT, "REPLICATE": cv2.BORDER_REPLICATE} rasmlar = [cv2.GaussianBlur(kichik, (31, 31), 0, borderType=t) for t in turlar.values()] ko_rsat(*rasmlar, nom="filtr_chegara.png", sarlavhalar=list(turlar), izoh="Katta yadroda farq ko'rinadi: CONSTANT chekkani qoraytiradi, REFLECT tabiiyroq.")

Katta yadroda farq ko'rinadi: CONSTANT chekkani qoraytiradi, REFLECT tabiiyroq.
Kichik yadroda farq sezilmaydi, katta yadroda chekka bo'ylab qora yo'l paydo bo'lishi mumkin — sababi shu.
Xulosa
| Tushuncha | Asosiy fikr |
|---|---|
| PSNR | Asl bilan moslikni dB da o'lchaydi; katta — yaxshi |
blur |
Eng tez, eng qo'pol |
GaussianBlur |
Umumiy shovqin uchun standart tanlov |
medianBlur |
Tuz-qalampirni butunlay olib tashlaydi |
bilateralFilter |
Chekkani saqlaydi, lekin sekin |
| Yadro | Toq son; katta — tozaroq, lekin xiraroq |
| Chegara | Katta yadroda borderType sezilarli bo'ladi |
Talaba ishi
medianBlurni faqat gauss shovqini bor rasmda sinang (tuz-qalampirsiz). U hali ham yutadimi? Nega?bilateralFilterningsigmaColorni 10, 50, 150 qilib ko'ring. Qaysi qiymatdan keyin u oddiy blur ga o'xshab qoladi?- Ikki filtrni ketma-ket qo'llang (avval median, keyin gauss). PSNR yaxshilandimi? Tartibni almashtirsangiz-chi?
qurilish.jpgga sun'iy tuz-qalampir shovqini qo'shing (5% piksel) va to'rt filtrni qayta o'lchang. Haqiqiy fotosuratda tartib o'zgardimi?
Bu bobni bepul o'qidingiz.
Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.