Asosiy qismga o'tish

Yuz: "aynan shu odam"

13-bob 7 kod bloki 3428 so'z

Bu bobning kodi: kod/13_yuz.py — kitob papkasidan ishga tushiring: python kod/13_yuz.py

ReID dan yuzga

12-bobda tashqi ko'rinish vektori "shu kiyimdagi odam" ni taniydi, lekin "aynan shu inson" ni emas: odam kurtkasini yechsa, u boshqa odamga aylanadi, bir xil formadagi ikki ishchi esa bitta bo'lib qoladi. "Aynan shu inson" uchun esa biometrik belgi kerak — va kamera uchun eng qulayi yuz.

Yuz tanish to'rt qadamdan iborat:

  1. Topish — rasmda yuz qayerda (box va 5 ta nuqta: ikki ko'z, burun, og'iz ikki cheti).
  2. Tekislash — shu 5 nuqta bo'yicha yuzni burib, kichraytirib, standart 112 × 112 ko'rinishga keltirish.
  3. Vektor — tekislangan yuzdan 128 o'lchamli vektor.
  4. Solishtirish — kosinus o'xshashlik (12-bob), chegara bilan: bir odammi yoki yo'q.

Bu bobda to'rttalasini MOT17 odamlarida o'lchaymiz va asosiy savolga javob beramiz: odatiy kuzatuv kamerasida yuz bilan tanish qachon ishlaydi va qachon ishlamaydi.

Modellar va litsenziya

Yuz tanish — sotiladigan mahsulot uchun eng xavfli joy: model litsenziyasi ham, qonun ham qattiq. Keng tarqalgan variantlar:

Model Vazifa Kod litsenziyasi Model og'irliklari
InsightFace (ArcFace, buffalo) topish + vektor MIT faqat notijorat tadqiqot uchun
YuNet (OpenCV Zoo) topish + 5 nuqta MIT MIT
SFace (OpenCV Zoo) vektor Apache-2.0 Apache-2.0
MediaPipe BlazeFace (Google) topish Apache-2.0 hujjatda alohida ko'rsatilmagan

InsightFace README si ochiq yozadi: kod MIT, lekin o'qitilgan modellar va ular o'qitilgan ma'lumotlar "faqat notijorat tadqiqot maqsadlari uchun". Ko'pchilik qo'llanmalar InsightFace bilan boshlanadi va shu shartni aytmaydi. Bu kitob YuNet + SFace dan foydalanadi: ikkalasi OpenCV ning o'zida (cv2.FaceDetectorYN, cv2.FaceRecognizerSF), qo'shimcha kutubxona shart emas, litsenziyasi esa tijoratga ruxsat beradi. Taqqoslash uchun MediaPipe ning yaqin masofa detektorini ham sinaymiz.

kod/yuklab_olish.pydavomi
python10 qator
# Yuz modellari (13-bob): YuNet, SFace, MediaPipe BlazeFace
import urllib.request

zoo = "https://github.com/opencv/opencv_zoo/raw/main/models"
for url in (f"{zoo}/face_detection_yunet/face_detection_yunet_2023mar.onnx",
            f"{zoo}/face_recognition_sface/face_recognition_sface_2021dec.onnx",
            "https://storage.googleapis.com/mediapipe-models/face_detector/blaze_face_short_range/float16/latest/blaze_face_short_range.tflite"):
    fayl = Path("weights") / url.rsplit("/", 1)[1]
    if not fayl.exists():
        urllib.request.urlretrieve(url, fayl)
kod/13_yuz.pyyangi fayl · 1-qator: from kitob_yordam import fig, show
python19 qator
import time
from pathlib import Path

import cv2
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import mediapipe as mp
from mediapipe.tasks import python as mpt
from mediapipe.tasks.python import vision

MOT = Path("datasets/MOT17/train/MOT17-09-FRCNN")
gt = np.loadtxt(MOT / "gt" / "gt.txt", delimiter=",")
piyoda = gt[(gt[:, 7] == 1) & (gt[:, 6] == 1)]

yunet = cv2.FaceDetectorYN.create("weights/face_detection_yunet_2023mar.onnx", "", (320, 320), 0.7, 0.3, 5000)
sface = cv2.FaceRecognizerSF.create("weights/face_recognition_sface_2021dec.onnx", "")
for f in ("face_detection_yunet_2023mar.onnx", "face_recognition_sface_2021dec.onnx", "blaze_face_short_range.tflite"):
    print(f"{f:40s} {Path('weights', f).stat().st_size / 1e6:6.2f} MB")
Chiqish
face_detection_yunet_2023mar.onnx          0.23 MB
face_recognition_sface_2021dec.onnx       38.70 MB
blaze_face_short_range.tflite              0.23 MB

ArcFace g'oyasi: burchak bilan ajratish

Yuz vektori modelini qanday o'qitish kerak? Oddiy klassifikator (har odam — bitta sinf, softmax) tanish uchun yomon: u faqat "to'g'ri sinf eng katta ball olsin" ni talab qiladi, lekin bir odamning turli rasmlari bir-biriga yaqin, turli odamlarniki esa uzoq bo'lishini talab qilmaydi.

2018-yilda Deng va hamkasblari "ArcFace: Additive Angular Margin Loss for Deep Face Recognition" (arXiv 1801.07698) maqolasida buni burchak orqali hal qildilar. Vektor x va har bir odamning "markaz" vektori W normallashtiriladi, shunda ular orasidagi ko'paytma faqat burchak kosinusi bo'ladi: cos θ. Keyin to'g'ri odam burchagiga qo'shimcha m qo'shiladi:

θ_y — vektor va to'g'ri odam markazi orasidagi burchak, s — masshtab (maqolada 64), m — burchak zaxirasi (0.5 radian ≈ 29°). Ma'nosi: to'g'ri odamga "shunchaki eng yaqin" bo'lish yetmaydi — undan qolganlariga qaraganda kamida m burchak yaqinroq bo'lish kerak. Natijada har bir odam vektorlari sfera ustida ixcham "orolcha" ga yig'iladi va orolchalar orasida bo'sh joy qoladi. Tanishda esa aynan shu kosinus o'xshashlik o'lchanadi.

Bu kitobdagi SFace ("SFace: Sigmoid-Constrained Hypersphere Loss for Robust Face Recognition", arXiv 2205.12010) — shu oilaning davomi: vektorlar yana sfera ustida, lekin sinf ichidagi va sinflar orasidagi masofalar sigmoid egri chiziqlar bilan alohida boshqariladi, shovqinli o'qitish ma'lumotiga chidamliroq. Tarmog'i — telefonlar uchun mo'ljallangan MobileFaceNet.

Kuzatuv kamerasida yuz qanchalik ko'rinadi

MOT17-09 ning har 5-kadridan ko'rinishi ≥ 0.8 bo'lgan odamlarni olamiz va ularning box i ichida yuz qidiramiz. Yuz tananing yuqori 40% ida bo'lishi shart. Topilgan har bir yuzni 5 nuqta bo'yicha tekislab, SFace vektorini hisoblaymiz:

kod/13_yuz.pydavomi
python31 qator
tanlov = piyoda[(piyoda[:, 8] >= 0.8) & (piyoda[:, 0] % 5 == 0)]
tanlov = tanlov[(tanlov[:, 2] >= 0) & (tanlov[:, 3] >= 0) & (tanlov[:, 2] + tanlov[:, 4] <= 1920) & (tanlov[:, 3] + tanlov[:, 5] <= 1080)]
sozlama = vision.FaceDetectorOptions(base_options=mpt.BaseOptions(model_asset_path="weights/blaze_face_short_range.tflite"),
                                     min_detection_confidence=0.5)
yuzlar, odamlar, mp_topdi, vaqt = [], 0, 0, []
with vision.FaceDetector.create_from_options(sozlama) as mp_detektor:
    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)
            odam = kadr[y:y + h, x:x + w]
            odamlar += 1
            t0 = time.perf_counter()
            yunet.setInputSize((w, h))
            _, F = yunet.detect(odam)                            # har qator: box (4), 5 nuqta (10), ishonch
            vaqt.append(time.perf_counter() - t0)
            if F is not None:
                f = F[np.argmax(F[:, -1])]
                if f[1] + f[3] / 2 < 0.4 * h:                    # yuz markazi tananing yuqori 40% ida
                    tekis = sface.alignCrop(odam, f)             # 5 nuqta bo'yicha 112x112 ga tekislash
                    v = sface.feature(tekis).ravel()
                    yuzlar.append({"kadr": k, "odam": int(g[1]), "vektor": v / np.linalg.norm(v),
                                   "kenglik": float(f[2]), "ishonch": float(f[-1]), "tekis": tekis})
            natija = mp_detektor.detect(mp.Image(image_format=mp.ImageFormat.SRGB, data=cv2.cvtColor(odam, cv2.COLOR_BGR2RGB)))
            mp_topdi += len(natija.detections) > 0
kengliklar = np.array([y["kenglik"] for y in yuzlar])
print(f"tekshirilgan odamlar: {odamlar} (bo'yi mediana {np.median(tanlov[:, 5]):.0f} px)")
print(f"YuNet yuz topdi:      {len(yuzlar)} ({len(yuzlar) / odamlar:.0%}), bitta odamga {np.median(vaqt) * 1000:.1f} ms")
print(f"MediaPipe yuz topdi:  {mp_topdi} ({mp_topdi / odamlar:.0%})")
print(f"yuz kengligi: mediana {np.median(kengliklar):.0f} px, 10% i {np.percentile(kengliklar, 10):.0f} px dan kichik, "
      f"10% i {np.percentile(kengliklar, 90):.0f} px dan katta")
Chiqish
tekshirilgan odamlar: 443 (bo'yi mediana 315 px)
YuNet yuz topdi:      246 (56%), bitta odamga 1.3 ms
MediaPipe yuz topdi:  163 (37%)
yuz kengligi: mediana 29 px, 10% i 18 px dan kichik, 10% i 57 px dan katta

Tanlangan odamlar yaxshi ko'rinadi — to'silmagan, bo'yi yuzlab piksel. Shunga qaramay yuz ularning faqat bir qismida topiladi: qolganlari kameraga yoni yoki orqasi bilan turibdi, yoki yuzi juda kichik. MediaPipe ning short_range modeli yanada kam topadi — u telefon oldidagi, 2 metrgacha bo'lgan yuzlar uchun mo'ljallangan.

Yuz kattaligi qanchalik muhimligini ko'z bilan ko'raylik — kichik va katta yuzning tekislangan 112 × 112 ko'rinishi:

kod/13_yuz.pydavomi
python8 qator
tartib = np.argsort(kengliklar)
tanlangan = [tartib[int(q * (len(tartib) - 1))] for q in (0.05, 0.3, 0.6, 0.97)]
_, axes = plt.subplots(1, 4, figsize=(8, 2.4))
for ax, i in zip(axes, tanlangan):
    ax.imshow(yuzlar[i]["tekis"][:, :, ::-1])
    ax.set_title(f"kadrdagi kengligi {yuzlar[i]['kenglik']:.0f} px", fontsize=9)
    ax.axis("off")
fig("yuz_olchami.png", "SFace ga beriladigan tekislangan yuzlar. Kichik yuzlar 112x112 ga kattalashtirilganda ma'lumot qo'shilmaydi — faqat xiralik.")

SFace ga beriladigan tekislangan yuzlar. Kichik yuzlar 112x112 ga kattalashtirilganda ma'lumot qo'shilmaydi — faqat xiralik.

SFace ga beriladigan tekislangan yuzlar. Kichik yuzlar 112x112 ga kattalashtirilganda ma'lumot qo'shilmaydi — faqat xiralik.

Yuz vektori: bir xil va turli odamlar

12-bobdagi kabi: bir odamning kamida 60 kadr (2 soniya) oralig'idagi yuzlari "bir xil" juftlar, turli odamlarniki — "turli". OpenCV ning yuz tanish namunasi SFace uchun kosinus chegarasini 0.363 deb oladi (samples/dnn/face_detect.py):

kod/13_yuz.pydavomi
python26 qator
idlar = np.array([y["odam"] for y in yuzlar])
kadrlar = np.array([y["kadr"] for y in yuzlar])
E = np.array([y["vektor"] for y in yuzlar])
S = E @ E.T
bir_xil = (idlar[:, None] == idlar[None]) & (np.abs(kadrlar[:, None] - kadrlar[None]) >= 60)
turli = idlar[:, None] != idlar[None]
CHEGARA = 0.363

togri = soni = 0
for q in range(len(E)):
    if bir_xil[q].any():
        s = np.where(bir_xil[q] | turli[q], S[q], -2)
        togri += idlar[s.argmax()] == idlar[q]
        soni += 1
print(f"yuzi topilgan odamlar: {len(np.unique(idlar))} | rank-1: {togri / soni:.2f}")
print(f"bir xil odam: o'rtacha o'xshashlik {S[bir_xil].mean():.3f}, chegaradan o'tadi {(S[bir_xil] >= CHEGARA).mean():.0%}")
print(f"turli odamlar: o'rtacha o'xshashlik {S[turli].mean():.3f}, chegaradan xato o'tadi {(S[turli] >= CHEGARA).mean():.1%}")

_, ax = plt.subplots(figsize=(7, 3.2))
ax.hist(S[turli], bins=60, range=(-0.3, 1), density=True, alpha=0.6, label="turli odamlar")
ax.hist(S[bir_xil], bins=60, range=(-0.3, 1), density=True, alpha=0.6, label="bir xil odam")
ax.axvline(CHEGARA, color="black", ls="--", lw=1)
ax.text(CHEGARA + 0.01, ax.get_ylim()[1] * 0.9, "chegara 0.363", fontsize=9)
ax.set_xlabel("kosinus o'xshashlik")
ax.legend()
fig("yuz_oxshashlik.png", "SFace vektorlari: turli odamlar (ko'k) va bir xil odam (to'q sariq) o'xshashliklari taqsimoti.")
Chiqish
yuzi topilgan odamlar: 21 | rank-1: 0.64
bir xil odam: o'rtacha o'xshashlik 0.453, chegaradan o'tadi 64%
turli odamlar: o'rtacha o'xshashlik 0.109, chegaradan xato o'tadi 2.2%

SFace vektorlari: turli odamlar (ko'k) va bir xil odam (to'q sariq) o'xshashliklari taqsimoti.

SFace vektorlari: turli odamlar (ko'k) va bir xil odam (to'q sariq) o'xshashliklari taqsimoti.

Ikki tog' ajralgan, lekin "bir xil odam" taqsimotining o'zida ikkita cho'qqi bor: biri 0.6–0.7 atrofida, ikkinchisi 0.2 atrofida — chegaradan chapda, ya'ni tizim o'sha odamni tanimagan juftliklar. Sababini yuz kattaligi bo'yicha bo'lib ko'ramiz — juftlikning ikkala yuzi ham shu oraliqda bo'lsin:

kod/13_yuz.pydavomi
python8 qator
qatorlar = []
for past, yuqori in ((0, 30), (30, 45), (45, 1000)):
    m = (kengliklar >= past) & (kengliklar < yuqori)
    bx, tr = bir_xil & m[:, None] & m[None], turli & m[:, None] & m[None]
    qatorlar.append({"yuz kengligi, px": f"{past}–{yuqori}" if yuqori < 1000 else f"{past} va katta", "yuzlar": int(m.sum()),
                     "bir xil: o'rtacha": S[bx].mean(), "bir xil: tanildi (>= 0.363)": (S[bx] >= CHEGARA).mean(),
                     "turli: xato qabul (>= 0.363)": (S[tr] >= CHEGARA).mean()})
show(pd.DataFrame(qatorlar).round(3))
yuz kengligi, pxyuzlarbir xil: o'rtachabir xil: tanildi (>= 0.363)turli: xato qabul (>= 0.363)
0–301290.3740.4770.036
30–45670.4470.6430.025
45 va katta500.61710.038

Bu jadval — bobning asosiy natijasi. Yuz 45 pikseldan keng bo'lsa, bir xil odamning deyarli har bir juftligi tanildi. 30 pikseldan kichik yuzlarda esa har ikkinchisi tanilmadi. Kuzatuv kamerasining odatiy yuzi — mediana 29 px — aynan chegarada. Xulosa loyihalash uchun: yuz bilan tanish kerak bo'lsa, kamera shu maqsad uchun alohida o'rnatiladi — kirish eshigida, ko'z balandligida, yuz 45+ piksel chiqadigan masofada. Umumiy kuzatuv kamerasi esa tracking uchun. Kamera va masofa hisobi (piksel/metr) — III qismda.

Bazaga yozish va tanish

Real tizim: xodimlar bazasi. Har bir xodimning bitta sifatli yuzi bazaga yoziladi (enrollment), keyin kameradagi har bir yuz baza bilan solishtiriladi: eng o'xshash xodim chegaradan o'tsa — "tanildi", bo'lmasa — "noma'lum". Muhim sinov: bazada yo'q odamlar ham keladi (mehmonlar), va ular hech kimga "tanilib" qolmasligi kerak.

MOT17-09 dagi odamlarning yarmini (juft raqamlilar) "xodim" deb bazaga yozamiz — har biridan eng katta yuzini. Qolgan yarmi — "mehmonlar". So'ng boshqa hamma yuzlarni (bazadagi rasmdan kamida 60 kadr uzoqda) tanib ko'ramiz:

kod/13_yuz.pydavomi
python25 qator
xodimlar = sorted({i for i in np.unique(idlar) if i % 2 == 0})
baza = {}
for i in xodimlar:
    j = max(np.nonzero(idlar == i)[0], key=lambda n: kengliklar[n])      # eng katta yuz
    baza[i] = j
B = np.array([E[j] for j in baza.values()])
baza_id = np.array(list(baza))

natija = []
for q in range(len(E)):
    oz = idlar[q]
    if oz in baza and abs(kadrlar[q] - kadrlar[baza[oz]]) < 60:
        continue                                                   # bazadagi rasmning o'ziga yaqin kadrlarni sanamaymiz
    s = B @ E[q]
    j = int(s.argmax())
    javob = baza_id[j] if s[j] >= CHEGARA else None
    if oz in baza:
        holat = "to'g'ri tanildi" if javob == oz else "noma'lum dedi" if javob is None else "BOSHQA xodim deb tanidi"
    else:
        holat = "to'g'ri: noma'lum" if javob is None else "MEHMONNI xodim deb tanidi"
    natija.append({"kim": "xodim" if oz in baza else "mehmon", "holat": holat, "katta yuz": kengliklar[q] >= 45})
natija = pd.DataFrame(natija)
print(f"bazada: {len(baza)} xodim | tekshirilgan yuzlar: {len(natija)}")
show(natija.groupby(["kim", "holat"]).size().rename("yuzlar").reset_index())
show(natija[natija["katta yuz"]].groupby(["kim", "holat"]).size().rename("yuzlar (faqat >= 45 px)").reset_index())
Chiqish
bazada: 8 xodim | tekshirilgan yuzlar: 189
kimholatyuzlar
mehmonMEHMONNI xodim deb tanidi30
mehmonto'g'ri: noma'lum107
xodimBOSHQA xodim deb tanidi8
xodimnoma'lum dedi8
xodimto'g'ri tanildi36
kimholatyuzlar (faqat >= 45 px)
mehmonMEHMONNI xodim deb tanidi9
mehmonto'g'ri: noma'lum11
xodimto'g'ri tanildi5

Mehmonlarning sezilarli qismi xodim deb tanildi — katta yuzlarda ham. Buning sababi oddiy ehtimollik. 0.363 chegarasi 1:1 tekshiruv uchun: "bu ikki rasm bitta odammi?". Bazada esa 1:N qidiruv: mehmon yuzi har bir xodim bilan solishtiriladi, va har biri — xato qabul qilishning yana bir imkoniyati. Bitta juftlikda xato qabul ehtimoli p bo'lsa, N ta xodimli bazada:

Yuqoridagi jadvaldagi p ≈ 0.025–0.036 va N = 8 uchun bu 18–25% — o'lchangan natijaga juda yaqin. 100 xodimli ofisda xuddi shu chegara bilan deyarli har bir mehmon kimgadir "o'xshab qoladi". Shuning uchun baza bilan qidiruvda chegara ancha baland qo'yiladi, qaror esa bitta kadr bo'yicha emas, trek davomidagi bir necha katta va aniq yuz bo'yicha qabul qilinadi.

Ikki xil xato bor va ular teng emas. "Noma'lum dedi" — xodim qayta o'tishi yoki boshqa kadrda tanilishi mumkin, bu noqulaylik. "Boshqa xodim deb tanidi" va "mehmonni xodim deb tanidi" esa jurnalda yolg'on yozuv — davomatda ham, xavfsizlikda ham bu eng qimmat xato. Chegarani ko'tarish ikkinchi xil xatolarni kamaytiradi va birinchisini ko'paytiradi. Qaysi murosani tanlash — mijozning vazifasi, lekin uni aynan shu jadval bilan, uning o'z kadrlarida o'lchab tanlash kerak.

Tracking bilan birlashtirish

Har kadrda har bir odamning yuzini tanish shart emas va imkonsiz ham — yuz ko'pincha ko'rinmaydi. To'g'ri tuzilma asl suhbatdagi sxema:

  1. Detector + tracker (11-bob) har bir odamga vaqtinchalik ID beradi.
  2. Trek davomida yuz yaxshi ko'rinib qolgan kadrlarda (yuz ≥ 45 px, yuqori ishonch) bir necha marta tanish qilinadi, ovozlar yig'iladi.
  3. Qaror qabul qilingach, ism butun trek ga — oldingi va keyingi kadrlarga ham — yoziladi. Yuz ko'rinmaydigan paytni tracker o'zi olib yuradi.
  4. Trek yo'qolsa, ism 12-bobdagi galereyaga o'tadi; qaytgan odamning yuzi yana ko'rinsa, u ishonchli qaytadan tanilaydi — rang bo'yicha taxmin emas.

Kirishdagi karta yoki turniket bo'lsa, yuz ham shart emas: karta urilgan paytda eshik zonasida paydo bo'lgan trek o'sha odamning ismini oladi. Ko'p real ofis va zavodlar aynan shunday ishlaydi — hech qanday biometrika saqlamasdan.

Qonun va rozilik

Yuz vektori — biometrik ma'lumot. O'zbekistonda buni "Shaxsga doir ma'lumotlar to'g'risida"gi Qonun (O'RQ-547, 2019-yil 2-iyul, 2019-yil 1-oktabrdan kuchga kirgan) tartibga soladi: biometrik ma'lumotlar shaxsga doir ma'lumotlar qatoriga kiradi, 2021-yilda qo'shilgan 27¹-modda esa O'zbekiston fuqarolarining shaxsga doir ma'lumotlari O'zbekiston hududidagi serverlarda to'planishi va saqlanishini talab qiladi (qonun matni — lex.uz). Amalda bu degani:

  • odamdan (xodimdan) yozma rozilik olinadi va nima uchun, qancha muddat saqlanishi aytiladi;
  • yuz vektorlari bazasi mahalliy serverda, xorijiy bulutda emas;
  • kerak bo'lmagan ma'lumot o'chiriladi, kamera kadrlari muddati belgilanadi.

Talaba loyihasi uchun: faqat rozi bo'lgan kursdoshlar yoki ochiq datasetlar (bu bobdagi MOT17 — CC BY-NC-SA 3.0). Tasodifiy odamlar videosini yuz bilan tanishga ishlatmang. Biznes tomoni va huquqiy tafsilotlar — IV qismda; aniq loyiha uchun huquqshunos bilan maslahatlashing.

Asosiy fikr

  • Yuz tanish: topish → 5 nuqta bo'yicha tekislash → vektor → kosinus va chegara. ArcFace oilasi vektorlarni sfera ustida burchak zaxirasi bilan ajratishga o'rgatadi.
  • InsightFace ning tayyor modellari faqat notijorat tadqiqot uchun. OpenCV dagi YuNet (MIT) va SFace (Apache-2.0) tijoratga ruxsat beradi va qo'shimcha kutubxonasiz ishlaydi.
  • Kuzatuv kamerasida yuz ko'pincha ko'rinmaydi yoki juda kichik: bu videoda yuz 45 px dan kichik bo'lsa, tanish ishonchsiz. Yuz bilan tanish uchun alohida, yaqin kamera kerak; odam tanilgach, ismni tracker olib yuradi.

Mashqlar

  1. CHEGARA ni 0.3, 0.45 va 0.55 qilib, "bazaga yozish va tanish" jadvalini qayta hisoblang. Mehmonlarni xodim deb tanish qaysi chegarada nolga tushadi va buning evaziga xodimlarning qancha qismi "noma'lum" bo'ladi?
  2. Bazaga har xodimdan bitta emas, uchta yuz yozing (eng kattalaridan) va solishtirishda eng yaxshisini oling. Natija yaxshilanadimi?
  3. yolo11n-pose.pt bilan boshning qaysi tomonga qaraganini (burun va quloqlar nuqtalari) aniqlang va faqat kameraga qarab turgan yuzlarni tanishga yuboring. Xato qabul qilish kamayadimi?

Bu bobni bepul o'qidingiz.

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

Telegramda yozish Narxlar