Asosiy qismga o'tish

Yuz aniqlash: Haar kaskadi va YuNet

16-bob 8 kod bloki 3260 so'z

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.FaceDetectorYN orqali 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.

python20 qator
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.")
Chiqish
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.

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

python27 qator
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")
Chiqish
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:

python7 qator
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))
scaleFactorminNeighborstopilgantopish %bo'lagiga, ms
1.0534642.211.1
1.0553733.910.8
1.0582724.810.8
1.133330.35.9
1.1524225.8
1.181816.55.8
1.331412.82.5
1.35109.22.5
1.3854.62.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

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

python8 qator
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))
detektortopilgantopish %bo'lagiga, ms
Haar (default)24226
YuNet (ishonch 0.5)7871.61.4
YuNet (ishonch 0.7)60551.4
YuNet (ishonch 0.9)2623.91.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?

python14 qator
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 balandligibo'laklarHaar topish %YuNet topish %
kichik (< 200 px)16050
o'rta (200–400)5715.849.1
katta (>= 400)3641.766.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.

python22 qator
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.")
Chiqish
kalit nuqtali namunalar: 4

YuNet ning 5 nuqtasi: ko'zlar, burun, og'iz burchaklari. Yuzni to'g'rilash uchun shular kerak.

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:

python29 qator
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.")
Chiqish
kadr o'lchami: 1920x1080 | bu kadrda belgilangan odamlar: 4
eslatma: butun kadrda Haar har piksel pozitsiyasini tekshiradi — shuning uchun sekin
detektortopilgan yuzlarkadrga, ms
Haar1104.1
YuNet344.1

Qizil — Haar, yashil — YuNet. Butun kadrda ishlatilganda farq yaqqol ko'rinadi.

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

  1. haarcascade_frontalface_alt2.xml ni default bilan solishtiring. Topish darajasi va tezlik qanday farq qiladi?
  2. Odam bo'laklarining yuqori uchdan bir qismini (bosh sohasi) kesib olib, detektorlarni qayta o'lchang. Topish darajasi oshdimi? Nega?
  3. 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)?
  4. YuNet ning kalit nuqtalaridan foydalanib, yuzni to'g'rilaydigan (ko'zlar gorizontal bo'ladigan) funksiya yozing. 4-bobdagi getRotationMatrix2D kerak bo'ladi.

Bu bobni bepul o'qidingiz.

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

Telegramda yozish Narxlar