Yuz aniqlash: Haar kaskadi va YuNet
Ikki avlod
Yuz aniqlash — kompyuter ko'rishning eng eski amaliy vazifasi. OpenCV da uning ikki avlodi bor va ikkalasi ham paket bilan birga keladi:
- Haar kaskadi (2001) — Viola-Jones algoritmi. Neyron tarmoqsiz, juda tez, lekin faqat to'g'ri qaragan yuzni topadi.
- YuNet (2021) — kichik neyron tarmoq.
cv2.FaceDetectorYNorqali ishlaydi, qiyshiq va yon yuzlarni ham topadi, ustiga 5 ta kalit nuqta beradi.
Bu bobda ikkalasini bir xil ma'lumotda o'lchab solishtiramiz.
Sinov ma'lumoti
MOT17 — piyodalar kuzatuvi uchun ochiq to'plam. Unda har kadrdagi har bir odamning qutisi qo'lda belgilangan. Biz shu qutilardan odamlarni qirqib olamiz: har bo'lakda aniq bitta odam bor, demak detektor yuz topishi kerak.
MOT = DATA.parent.parent / "YOLO_Tracking_Kitob" / "datasets" / "MOT17" / "train" / "MOT17-09-FRCNN" OGIRLIK = DATA.parent.parent / "YOLO_Tracking_Kitob" / "weights" / "face_detection_yunet_2023mar.onnx" gt = np.loadtxt(MOT / "gt" / "gt.txt", delimiter=",") piyoda = gt[(gt[:, 7] == 1) & (gt[:, 6] == 1)] # sinf 1 = odam, 6-ustun = e'tiborga olish tanlov = piyoda[(piyoda[:, 8] >= 0.8) & (piyoda[:, 0] % 20 == 0)] # ko'rinishi 0.8 dan yuqori, har 20-kadr tanlov = tanlov[(tanlov[:, 2] >= 0) & (tanlov[:, 3] >= 0) & (tanlov[:, 2] + tanlov[:, 4] <= 1920) & (tanlov[:, 3] + tanlov[:, 5] <= 1080)] print(f"gt.txt qatorlari: {len(gt)} | piyodalar: {len(piyoda)} | sinovga tanlangan: {len(tanlov)}") print(f"kadrlar: {len(np.unique(tanlov[:, 0]))} ta") o_w, o_h = tanlov[:, 4], tanlov[:, 5] print(f"odam qutisi o'lchami — o'rtacha {o_w.mean():.0f}x{o_h.mean():.0f} px, " f"eng kichigi {o_w.min():.0f}x{o_h.min():.0f}") kadr = cv2.imread(str(MOT / "img1" / f"{int(tanlov[0, 0]):06d}.jpg")) namuna = kadr.copy() for g in tanlov[tanlov[:, 0] == tanlov[0, 0]]: x, y, w, h = g[2:6].astype(int) cv2.rectangle(namuna, (x, y), (x + w, y + h), (0, 255, 0), 2) ko_rsat(cv2.resize(namuna, (960, 540)), nom="mot_kadr.png", kenglik=11, izoh="MOT17 kadri: yashil qutilar — qo'lda belgilangan odamlar. Har biridan yuz qidiramiz.")
gt.txt qatorlari: 10411 | piyodalar: 5325 | sinovga tanlangan: 109 kadrlar: 26 ta odam qutisi o'lchami — o'rtacha 149x365 px, eng kichigi 52x129

MOT17 kadri: yashil qutilar — qo'lda belgilangan odamlar. Har biridan yuz qidiramiz.
Diqqat: bizda yuz qutilari yo'q, odam qutilari bor. Shuning uchun aniqlikni piksel bilan emas, topish darajasi bilan o'lchaymiz: nechta odam bo'lagida detektor yuz topdi?
Bu to'g'ri o'lchov, chunki bo'lakda aniq bitta odam bor — demak ko'pi bilan bitta yuz bo'lishi mumkin.
Haar kaskadi
import time HAAR = cv2.data.haarcascades + "haarcascade_frontalface_default.xml" haar = cv2.CascadeClassifier(HAAR) print("kaskad yuklandimi:", not haar.empty()) def haar_topish(bolaklar, scaleFactor=1.1, minNeighbors=5, minSize=(20, 20)): topildi, vaqtlar = 0, [] for b in bolaklar: g = cv2.cvtColor(b, cv2.COLOR_BGR2GRAY) t0 = time.perf_counter() yuzlar = haar.detectMultiScale(g, scaleFactor=scaleFactor, minNeighbors=minNeighbors, minSize=minSize) vaqtlar.append(time.perf_counter() - t0) if len(yuzlar) > 0: topildi += 1 return topildi, float(np.mean(vaqtlar)) * 1000 bolaklar = [] for k in np.unique(tanlov[:, 0]).astype(int): kadr = cv2.imread(str(MOT / "img1" / f"{k:06d}.jpg")) for g in tanlov[tanlov[:, 0] == k]: x, y, w, h = g[2:6].astype(int) bolaklar.append(kadr[y:y + h, x:x + w]) print(f"kesib olingan odam bo'laklari: {len(bolaklar)}") haar_topildi, haar_ms_bolak = haar_topish(bolaklar) print(f"Haar: {haar_topildi} / {len(bolaklar)} bo'lakda yuz topdi " f"({100 * haar_topildi / len(bolaklar):.1f}%), bo'lagiga {haar_ms_bolak:.1f} ms")
kaskad yuklandimi: True kesib olingan odam bo'laklari: 109 Haar: 24 / 109 bo'lakda yuz topdi (22.0%), bo'lagiga 6.0 ms
Haar parametrlari
Haar ning uchta parametri bor va ular natijani keskin o'zgartiradi:
qatorlar = [] for sf in (1.05, 1.1, 1.3): for mn in (3, 5, 8): t, t_ms = haar_topish(bolaklar, scaleFactor=sf, minNeighbors=mn) qatorlar.append({"scaleFactor": sf, "minNeighbors": mn, "topilgan": t, "topish %": round(100 * t / len(bolaklar), 1), "bo'lagiga, ms": round(t_ms, 1)}) show(pd.DataFrame(qatorlar))
| scaleFactor | minNeighbors | topilgan | topish % | bo'lagiga, ms |
|---|---|---|---|---|
| 1.05 | 3 | 46 | 42.2 | 11.1 |
| 1.05 | 5 | 37 | 33.9 | 10.8 |
| 1.05 | 8 | 27 | 24.8 | 10.8 |
| 1.1 | 3 | 33 | 30.3 | 5.9 |
| 1.1 | 5 | 24 | 22 | 5.8 |
| 1.1 | 8 | 18 | 16.5 | 5.8 |
| 1.3 | 3 | 14 | 12.8 | 2.5 |
| 1.3 | 5 | 10 | 9.2 | 2.5 |
| 1.3 | 8 | 5 | 4.6 | 2.5 |
| Parametr | Ma'nosi | Ta'siri |
|---|---|---|
scaleFactor |
Piramidada har qadamda rasm qancha kichrayadi | 1.05 — sekin va sinchkov, 1.3 — tez va qo'pol |
minNeighbors |
Nechta qo'shni oyna "ha" desa qabul qilinadi | Katta — kam soxta, lekin kam topish |
minSize |
Eng kichik yuz o'lchami | Kichik yuzlarni qidirmaslik tezlikni oshiradi |
Jadval sof murosani ko'rsatadi. Eng yaxshi natija scaleFactor=1.05, minNeighbors=3 da — 42.2%,
lekin bo'lagiga 10.9 ms. Eng tez sozlama (1.3, 8) 2.4 ms oladi, ammo atigi 4.6% topadi —
to'qqiz barobar yomon.
Ya'ni Haar da tezlik to'g'ridan-to'g'ri aniqlik hisobiga olinadi. Standart 1.1/5 sozlamasi
o'rtada turadi: 22.0%, 5.8 ms.
YuNet
print("model fayl:", OGIRLIK.name, f"{OGIRLIK.stat().st_size / 1e6:.2f} MB") yunet = cv2.FaceDetectorYN.create(str(OGIRLIK), "", (320, 320), 0.7, 0.3, 5000) def yunet_topish(bolaklar, ishonch=0.7): d = cv2.FaceDetectorYN.create(str(OGIRLIK), "", (320, 320), ishonch, 0.3, 5000) topildi, vaqtlar, ballar = 0, [], [] for b in bolaklar: h, w = b.shape[:2] d.setInputSize((w, h)) t0 = time.perf_counter() _, F = d.detect(b) vaqtlar.append(time.perf_counter() - t0) if F is not None and len(F) > 0: topildi += 1 ballar.append(float(F[0][-1])) return topildi, float(np.mean(vaqtlar)) * 1000, ballar t_y, ms_y, ballar = yunet_topish(bolaklar) print(f"YuNet: {t_y} / {len(bolaklar)} bo'lakda yuz topdi ({100 * t_y / len(bolaklar):.1f}%), " f"bo'lagiga {ms_y:.1f} ms") print(f"ishonch ballari — o'rtacha {np.mean(ballar):.3f}, eng kichigi {min(ballar):.3f}")
model fayl: face_detection_yunet_2023mar.onnx 0.23 MB YuNet: 60 / 109 bo'lakda yuz topdi (55.0%), bo'lagiga 1.4 ms ishonch ballari — o'rtacha 0.862, eng kichigi 0.704
Yonma-yon solishtirish
qatorlar = [{"detektor": "Haar (default)", "topilgan": haar_topildi, "topish %": round(100 * haar_topildi / len(bolaklar), 1), "bo'lagiga, ms": round(haar_ms_bolak, 1)}] for ishonch in (0.5, 0.7, 0.9): t_i, ms_i, _ = yunet_topish(bolaklar, ishonch) qatorlar.append({"detektor": f"YuNet (ishonch {ishonch})", "topilgan": t_i, "topish %": round(100 * t_i / len(bolaklar), 1), "bo'lagiga, ms": round(ms_i, 1)}) show(pd.DataFrame(qatorlar))
| detektor | topilgan | topish % | bo'lagiga, ms |
|---|---|---|---|
| Haar (default) | 24 | 22 | 6 |
| YuNet (ishonch 0.5) | 78 | 71.6 | 1.4 |
| YuNet (ishonch 0.7) | 60 | 55 | 1.4 |
| YuNet (ishonch 0.9) | 26 | 23.9 | 1.4 |
Natija bir tomonlama. YuNet ishonch chegarasi 0.5 da 71.6% topdi — Haar ning eng yaxshi sozlamasidan (42.2%) ham ancha yuqori. Ustiga 1.3 ms, ya'ni Haar ning standart sozlamasidan (5.8 ms) to'rt barobar, eng sinchkov sozlamasidan (10.9 ms) sakkiz barobar tez.
Bu kam uchraydigan holat: yangi usul ham aniqroq, ham tezroq. Odatda birini ikkinchisiga almashtirish kerak bo'ladi.
Ishonch chegarasi esa tabiiy boshqaruv dastagi beradi: 0.9 da YuNet 23.9% ga tushadi — deyarli Haar darajasiga. Demak "qat'iy ishonch" kerak bo'lsa aniqlikni yo'qotasiz, va bu tanlov sizniki.
Nima uchun topilmaydi
Topish darajasi 100% emas va bu kutilgan. Sabablarni o'lchaymiz — odam bo'lagining o'lchami muhimmi?
olchamlar = np.array([b.shape[0] for b in bolaklar]) guruhlar = [("kichik (< 200 px)", olchamlar < 200), ("o'rta (200–400)", (olchamlar >= 200) & (olchamlar < 400)), ("katta (>= 400)", olchamlar >= 400)] qatorlar = [] for nom, m in guruhlar: guruh = [b for b, bor in zip(bolaklar, m) if bor] if not guruh: continue h_t, _ = haar_topish(guruh) y_t, _, _ = yunet_topish(guruh) qatorlar.append({"odam balandligi": nom, "bo'laklar": len(guruh), "Haar topish %": round(100 * h_t / len(guruh), 1), "YuNet topish %": round(100 * y_t / len(guruh), 1)}) show(pd.DataFrame(qatorlar))
| odam balandligi | bo'laklar | Haar topish % | YuNet topish % |
|---|---|---|---|
| kichik (< 200 px) | 16 | 0 | 50 |
| o'rta (200–400) | 57 | 15.8 | 49.1 |
| katta (>= 400) | 36 | 41.7 | 66.7 |
Ha, o'lcham hal qiluvchi. Ikkala detektor ham katta bo'laklarda yaxshiroq ishlaydi, lekin pasayish tezligi boshqa:
- Haar: 41.7% → 15.8% → 0%. Kichik odamlarda u butunlay ishlamaydi.
- YuNet: 66.7% → 49.1% → 50%. Pasayadi, lekin yo'qolmaydi.
Nega? 365 px balandlikdagi odamda yuz taxminan 40–50 px, 150 px lik odamda esa atigi 15–20 px. Haar kaskadi 20x20 dan kichik oynada ishlamaydi va bunday o'lchamda yuzning tuzilishi ko'rinmaydi.
Amaliy xulosa: agar odamlar uzoqda bo'lsa, detektorni almashtirishdan oldin kamerani yaqinroq qo'ying yoki rezolyutsiyani oshiring. Hech qanday algoritm yo'q pikseldan yuz yasay olmaydi.
Qolgan sabablar: odam kameraga orqasi bilan turgan (MOT17 da ko'p), yuz qo'l yoki boshqa odam bilan berkitilgan, harakat tufayli xiralashgan.
Kalit nuqtalar va yuzni to'g'rilash
YuNet har yuz uchun 5 ta nuqta beradi: ikki ko'z, burun, og'izning ikki cheti. Ular yuzni to'g'rilash (alignment) uchun ishlatiladi — bu yuzni tanib olishning (recognition) majburiy qadami.
yunet.setInputSize((320, 320)) namunalar, chizmalar = [], [] for b in bolaklar: h, w = b.shape[:2] if h < 200: continue yunet.setInputSize((w, h)) _, F = yunet.detect(b) if F is None or len(F) == 0: continue f = F[0] x, y, bw, bh = f[:4].astype(int) ch = b.copy() cv2.rectangle(ch, (x, y), (x + bw, y + bh), (0, 255, 0), 2) for i, rang in enumerate([(255, 0, 0), (0, 0, 255), (0, 255, 255), (255, 0, 255), (0, 128, 255)]): cv2.circle(ch, (int(f[4 + 2 * i]), int(f[5 + 2 * i])), 3, rang, -1) chizmalar.append(cv2.resize(ch, (140, 320))) if len(chizmalar) >= 4: break print(f"kalit nuqtali namunalar: {len(chizmalar)}") ko_rsat(*chizmalar, nom="yunet_nuqtalar.png", kenglik=10, izoh="YuNet ning 5 nuqtasi: ko'zlar, burun, og'iz burchaklari. Yuzni to'g'rilash uchun shular kerak.")
kalit nuqtali namunalar: 4

YuNet ning 5 nuqtasi: ko'zlar, burun, og'iz burchaklari. Yuzni to'g'rilash uchun shular kerak.
Ikki ko'z orasidagi burchakdan yuzning qiyshiqligini hisoblab, 4-bobdagi warpAffine bilan to'g'rilash
mumkin. Yuz tanish tizimlari har doim shunday boshlanadi.
Butun kadrda ishlatish
Yuqorida biz odam bo'laklarida ishladik. Amalda detektor butun kadrga qo'llanadi:
kadr = cv2.imread(str(MOT / "img1" / f"{int(np.unique(tanlov[:, 0])[2]):06d}.jpg")) h, w = kadr.shape[:2] t0 = time.perf_counter() g = cv2.cvtColor(kadr, cv2.COLOR_BGR2GRAY) haar_yuzlar = haar.detectMultiScale(g, 1.1, 5, minSize=(20, 20)) haar_ms = (time.perf_counter() - t0) * 1000 d = cv2.FaceDetectorYN.create(str(OGIRLIK), "", (w, h), 0.7, 0.3, 5000) t0 = time.perf_counter() _, F = d.detect(kadr) yunet_ms = (time.perf_counter() - t0) * 1000 n_yunet = 0 if F is None else len(F) odamlar_shu_kadrda = int((tanlov[:, 0] == np.unique(tanlov[:, 0])[2]).sum()) show(pd.DataFrame([ {"detektor": "Haar", "topilgan yuzlar": len(haar_yuzlar), "kadrga, ms": round(haar_ms, 1)}, {"detektor": "YuNet", "topilgan yuzlar": n_yunet, "kadrga, ms": round(yunet_ms, 1)}, ])) print(f"kadr o'lchami: {w}x{h} | bu kadrda belgilangan odamlar: {odamlar_shu_kadrda}") print("eslatma: butun kadrda Haar har piksel pozitsiyasini tekshiradi — shuning uchun sekin") belgi = kadr.copy() for (x, y, bw, bh) in haar_yuzlar: cv2.rectangle(belgi, (x, y), (x + bw, y + bh), (0, 0, 255), 3) if F is not None: for f in F: x, y, bw, bh = f[:4].astype(int) cv2.rectangle(belgi, (x, y), (x + bw, y + bh), (0, 255, 0), 3) ko_rsat(cv2.resize(belgi, (960, 540)), nom="butun_kadr_yuz.png", kenglik=11, izoh="Qizil — Haar, yashil — YuNet. Butun kadrda ishlatilganda farq yaqqol ko'rinadi.")
kadr o'lchami: 1920x1080 | bu kadrda belgilangan odamlar: 4 eslatma: butun kadrda Haar har piksel pozitsiyasini tekshiradi — shuning uchun sekin
| detektor | topilgan yuzlar | kadrga, ms |
|---|---|---|
| Haar | 1 | 104.1 |
| YuNet | 3 | 44.1 |

Qizil — Haar, yashil — YuNet. Butun kadrda ishlatilganda farq yaqqol ko'rinadi.
Bu yerda farq yanada kattaroq. 1920x1080 kadrda Haar 111 ms sarfladi va 1 ta yuz topdi; YuNet 46.1 ms da 3 ta topdi. Kadrda 4 ta belgilangan odam bor.
Haar ning sekinligi tushunarli: u yuzni butun rasm bo'ylab, har o'lchamda, har pozitsiyada qidiradi. Bo'lakda ishlaganda bo'lak kichik edi, butun kadrda esa ish hajmi o'n barobar oshdi. YuNet esa rasmni bir marta tarmoqdan o'tkazadi — uning vaqti rasm o'lchamiga ancha yumshoq bog'liq.
111 ms — 14-bobdagi 33 ms byudjetdan uch barobar ko'p. Ya'ni Haar ni 1080p videoda real vaqtda ishlatib bo'lmaydi; YuNet esa 46 ms bilan chegarada turibdi va kadrni kichraytirsa sig'adi.
:::gotcha setInputSize ni unutmang
FaceDetectorYN har chaqiriqdan oldin kirish o'lchamini bilishi kerak. Rasm o'lchami o'zgarsa
setInputSize((w, h)) ni qayta chaqirmasangiz, natija noto'g'ri koordinatalar beradi yoki xato
chiqadi. Bu — YuNet bilan ishlashdagi birinchi tuzoq.
:::
Qaysi birini tanlash
| Mezon | Haar | YuNet |
|---|---|---|
| Qo'shimcha fayl | Kerak emas (paket ichida) | 232 KB ONNX model |
| Qiyshiq / yon yuz | Topmaydi | Topadi |
| Kalit nuqtalar | Yo'q | 5 ta |
| Ishonch balli | Yo'q | Bor — chegara qo'yish mumkin |
| Sozlash | 3 parametr, qo'lda | Bitta ishonch chegarasi |
Amaliy tavsiya: yangi loyihada YuNet dan boshlang. Haar ni faqat model faylini qo'shish mumkin bo'lmagan yoki juda cheklangan qurilmada ishlaganda tanlang.
Etik va huquqiy eslatma
Yuz — biometrik ma'lumot. O'zbekiston Respublikasining "Shaxsga doir ma'lumotlar to'g'risida"gi qonuni bo'yicha biometrik ma'lumotlarni qayta ishlash alohida talablarga bo'ysunadi, va ko'p hollarda shaxsning roziligi talab qilinadi.
Amaliy qoidalar:
- Yuz rasmlarini saqlashdan oldin nima uchun kerakligini yozib qo'ying; kerak bo'lmasa saqlamang.
- Yuzni tanish (kim ekanini aniqlash) va yuzni aniqlash (yuz bor-yo'qligi) — huquqiy jihatdan butunlay boshqa narsalar. Ikkinchisi ancha kam cheklovga ega.
- Sanash yoki anonim statistika kerak bo'lsa, yuz vektorini emas, faqat sonni saqlang.
- Ochiq joyda kamera o'rnatilsa, ogohlantiruvchi belgi qo'yish talab qilinadi.
Bu mavzu YOLO kitobining 24-bobida batafsil yozilgan.
Xulosa
| Tushuncha | Asosiy fikr |
|---|---|
| Haar kaskadi | 2001 yil, neyron tarmoqsiz; faqat to'g'ri qaragan yuz |
scaleFactor |
1.05 sinchkov va sekin, 1.3 tez va qo'pol |
minNeighbors |
Katta — kam soxta, lekin kam topish |
| YuNet | cv2.FaceDetectorYN, 232 KB model, 5 kalit nuqta va ishonch balli |
setInputSize |
Har rasm o'lchami uchun qayta chaqirilishi shart |
| Aniqlik | O'lchovda YuNet 71.6%, Haar eng yaxshi holatda 42.2% |
| Tezlik | Bo'lakda YuNet 1.3 ms, Haar 5.8 ms; 1080p kadrda 46 ms va 111 ms |
| O'lcham | 200 px dan kichik odamda Haar 0%, YuNet 50% |
| Etika | Yuz — biometrik ma'lumot; roziliq va saqlash muddatini o'ylang |
Talaba ishi
haarcascade_frontalface_alt2.xmlnidefaultbilan solishtiring. Topish darajasi va tezlik qanday farq qiladi?- Odam bo'laklarining yuqori uchdan bir qismini (bosh sohasi) kesib olib, detektorlarni qayta o'lchang. Topish darajasi oshdimi? Nega?
- YuNet ning ishonch chegarasini 0.3 dan 0.95 gacha o'zgartirib, topilgan yuzlar sonini grafikda chizing. Qaysi nuqtada soxta natijalar boshlanadi (bir bo'lakda 2+ yuz topilsa — soxta)?
- YuNet ning kalit nuqtalaridan foydalanib, yuzni to'g'rilaydigan (ko'zlar gorizontal bo'ladigan)
funksiya yozing. 4-bobdagi
getRotationMatrix2Dkerak bo'ladi.
Bu bobni bepul o'qidingiz.
Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.