Skip to content

Face Detection & Angle Calculation - Fundamentals

Mục tiêu: Giải thích từ cơ bản đến nâng cao về face detection, machine learning, và toán học tính góc khuôn mặt trong dự án portal.

1. Face Detection là gì?

1.1. Định nghĩa

Face Detection (phát hiện khuôn mặt) là quá trình tự động phát hiện và định vị khuôn mặt trong ảnh/video.

Đầu vào: Khung hình ảnh/Video
Quá trình: Thuật toán phát hiện
Đầu ra: Hộp giới hạn (bounding box) + Điểm tin cậy (confidence score)

Ví dụ:
┌─────────────────────┐
│                     │
│    ┌─────────┐      │ ← Ảnh
│    │ (・_・)  │      │
│    └─────────┘      │
│         ↑           │
│Khuôn mặt đã phát hiện│
│    x,y,w,h (hộp)    │
└─────────────────────┘

1.2. Lịch sử phát triển

Dòng thời gian:

2001: Thuật toán Viola-Jones (Haar Cascades)
      → Nhanh, nhưng không chính xác
      → Được dùng trong OpenCV sớm

2012: Cách mạng Học sâu (AlexNet)
      → Mạng nơ-ron tích chập (CNN)

2015-2018: Phát hiện khuôn mặt hiện đại
      → MTCNN, FaceNet, RetinaFace
      → Độ chính xác cao, thời gian thực

2019: MediaPipe FaceMesh (Google)
      → 468 điểm mốc thời gian thực
      → Chạy trên thiết bị mobile
      → Được dùng trong dự án Portal ✅

Nguồn: Google MediaPipe (2019)

1.3. Phát hiện khuôn mặt vs Nhận dạng khuôn mặt vs Điểm mốc khuôn mặt

┌──────────────────────────────────────────────────────┐
│                                                      │
│  Phát hiện khuôn mặt (Bước 1)                        │
│  ┌─────────┐                                         │
│  │ (・_・)  │  → "Có khuôn mặt tại x,y,w,h"          │
│  └─────────┘                                         │
│                                                      │
│  Điểm mốc khuôn mặt (Bước 2)                         │
│  ┌─────────┐                                         │
│  │ •  •    │  → "468 điểm trên khuôn mặt"            │
│  │   •     │     (mắt, mũi, miệng, ...)              │
│  │  • • •  │                                         │
│  └─────────┘                                         │
│                                                      │
│  Nhận dạng khuôn mặt (Bước 3)                        │
│  ┌─────────┐                                         │
│  │ (・_・)  │  → "Đây là người X"                     │
│  └─────────┘     (khớp danh tính)                    │
│                                                      │
└──────────────────────────────────────────────────────┘

Dự án Portal sử dụng:
✅ Phát hiện khuôn mặt: MediaPipe (phát hiện khuôn mặt)
✅ Điểm mốc khuôn mặt: MediaPipe (468 điểm)
✅ Nhận dạng khuôn mặt: API Backend (so sánh với CCCD)

2. Machine Learning trong Face Detection

2.1. CNN (Convolutional Neural Network)

MediaPipe FaceMesh sử dụng CNN để phát hiện khuôn mặt và điểm mốc.

Kiến trúc CNN (Đơn giản hóa):

Ảnh đầu vào (640x480 pixel)

┌─────────────────────────────────────┐
│  Các lớp tích chập                  │
│  (Trích xuất đặc trưng)             │
│                                     │
│  Conv1 → ReLU → MaxPool             │
│  Conv2 → ReLU → MaxPool             │
│  Conv3 → ReLU → MaxPool             │
│  ...                                │
│                                     │
│  Đặc trưng: cạnh, hình dạng, kết cấu│
└─────────────────────────────────────┘

┌─────────────────────────────────────┐
│  Các lớp kết nối đầy đủ             │
│  (Phân loại/Hồi quy)                │
│                                     │
│  FC1 → ReLU                         │
│  FC2 → ReLU                         │
│  Đầu ra: 468 điểm mốc (x,y,z)       │
└─────────────────────────────────────┘

Nguồn: Deep Learning Book - Goodfellow et al. (2016)

2.2. MediaPipe FaceMesh Architecture

┌────────────────────────────────────────────────────┐
│                                                    │
│  MediaPipe FaceMesh Pipeline                       │
│                                                    │
│  ┌──────────────┐    ┌──────────────┐              │
│  │              │    │              │              │
│  │  BlazeFace   │───▶│  FaceMesh    │              │
│  │  Detector    │    │  Model       │              │
│  │              │    │              │              │
│  └──────────────┘    └──────────────┘              │
│        ↓                    ↓                      │
│  Face Bounding Box    468 Landmarks (x,y,z)        │
│                                                    │
└────────────────────────────────────────────────────┘

BlazeFace:
- Bộ phát hiện khuôn mặt nhẹ
- Chạy ở 200+ FPS trên mobile
- Đầu ra: Hộp giới hạn khuôn mặt

FaceMesh:
- Lấy vùng khuôn mặt đã cắt
- Dự đoán 468 điểm mốc 3D
- Chạy ở 30-60 FPS

Nguồn: MediaPipe FaceMesh Paper (2019)

2.3. Tại sao 468 landmarks?

468 điểm = Đủ chi tiết cho:

1. Đường viền khuôn mặt: 72 điểm
   → Hình oval, đường hàm, thái dương

2. Mắt: 142 điểm (71 mỗi mắt)
   → Mí mắt, tròng đen, đồng tử
   → Phát hiện nháy mắt
   → Hướng nhìn

3. Lông mày: 66 điểm (33 mỗi lông mày)
   → Nhận dạng biểu cảm

4. Mũi: 55 điểm
   → Hướng khuôn mặt
   → Tính toán góc ✅

5. Miệng: 80 điểm
   → Đồng bộ môi
   → Biểu cảm (cười, nhăn mặt)

6. Hình oval khuôn mặt: 53 điểm
   → Ước tính tư thế đầu ✅

Đánh đổi:

  • Nhiều điểm mốc hơn = Chính xác hơn, nhưng chậm hơn
  • 468 = Điểm tối ưu cho thời gian thực + độ chính xác

3. Toán học: Tính góc khuôn mặt (Yaw, Pitch, Roll)

3.1. Hệ tọa độ 3D

Hệ tọa độ Camera (Quy tắc bàn tay phải):

      Y (lên)



      └────────▶ X (phải)



  Z (độ sâu, vào màn hình)

Điểm mốc khuôn mặt:
- x: 0 (trái) → 1 (phải)
- y: 0 (trên) → 1 (dưới)
- z: âm (gần) → dương (xa)

Nguồn: Computer Vision: Algorithms and Applications - Szeliski (2010)

3.2. Euler Angles (Yaw, Pitch, Roll)

Định nghĩa:

        Pitch

      ╱───────╲
     │  (・_・) │
      ╲───────╱

       Yaw

    Roll →

Yaw (quay đầu trái/phải):

  • Quay quanh trục Y (dọc)
  • Quay đầu trái/phải
  • Phạm vi: -180° đến +180°
  • Ví dụ: Lắc đầu "không"

Pitch (ngước/cúi đầu):

  • Quay quanh trục X (ngang)
  • Ngước/cúi đầu
  • Phạm vi: -90° đến +90°
  • Ví dụ: Gật đầu "có"

Roll (nghiêng đầu):

  • Quay quanh trục Z (độ sâu)
  • Nghiêng đầu sang trái/phải
  • Phạm vi: -180° đến +180°
  • Ví dụ: Nghiêng đầu nghe điện thoại

Ví dụ trực quan:

Yaw = -30° (quay phải):
     ╱───────╲
    │  (・_・) │───▶
     ╲───────╱

Pitch = +30° (ngước lên):
      ╱───────╲
     │  (・_・) │
      ╲───────╱


Roll = -45° (nghiêng trái):
      ╱───────╲
     ╱  (・_・) │
    ╱   ────╱

Nguồn: Euler Angles - Wikipedia


4. Code Thực Tế: Bước từng bước

Bước 1: Lấy 3 landmarks chính

typescript
// ✅ CODE THỰC TẾ từ face-liveness.ts

// Tại sao chọn 3 điểm này?
const p1 = landmarks[127]; // Má trái
const p2 = landmarks[356]; // Má phải
const p3 = landmarks[6]; // Sống mũi

// Lý do:
// - p1, p2: Tạo thành trục X (trục ngang)
// - p3: Tạo thành điểm tham chiếu cho trục Z (độ sâu)
// - 3 điểm không cùng nằm trên 1 đường thẳng
//   → Có thể tạo mặt phẳng của khuôn mặt

Trực quan:

    Góc nhìn phía trước:

    p3 (sống mũi)

      ╱│╲
     ╱ │ ╲
    ╱  │  ╲
   ●───┼───●
  p1   │   p2
  (má trái)(má phải)

Nguồn: Face Pose Estimation - OpenCV Docs

Bước 2: Chuyển đổi sang tọa độ pixel

typescript
// ✅ CODE THỰC TẾ

function convertToVector(
  landmark: { x: number; y: number; z: number },
  width: number,
  height: number
): number[] {
  return [
    landmark.x * width, // x: 0-1 → 0-width pixel
    landmark.y * height, // y: 0-1 → 0-height pixel
    landmark.z * width, // z: tỷ lệ theo width (để nhất quán)
  ];
}

// Ví dụ:
// Đầu vào: { x: 0.5, y: 0.4, z: -0.02 }
// Ảnh: 640x480
// Đầu ra: [320, 192, -12.8]

Tại sao nhân z với width?

  • MediaPipe z không có đơn vị rõ ràng
  • Nhân với width để tỷ lệ về cùng phạm vi với x, y
  • Giúp tính toán vector đồng nhất

Bước 3: Tạo vectors từ 3 điểm

typescript
// ✅ CODE THỰC TẾ

// Vector helper: từ p1 đến p3 (hướng mũi)
const vhelp = [
  p3[0] - p1[0], // dx
  p3[1] - p1[1], // dy
  p3[2] - p1[2], // dz
];

// Vector X: từ p1 đến p2 (trục ngang)
const vx_d = [p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2]];

Toán học:

Vector AB từ điểm A đến B:
  AB = B - A
  AB = [Bx - Ax, By - Ay, Bz - Az]

Ví dụ:
  A = [100, 200, -5]
  B = [300, 250, -3]
  AB = [200, 50, 2]

Nguồn: Vector Mathematics - Khan Academy

Bước 4: Cross Product (Tích có hướng)

typescript
// ✅ CODE THỰC TẾ

function crossProduct(v1: number[], v2: number[]): number[] {
  return [
    v1[1] * v2[2] - v1[2] * v2[1], // thành phần i
    v1[2] * v2[0] - v1[0] * v2[2], // thành phần j
    v1[0] * v2[1] - v1[1] * v2[0], // thành phần k
  ];
}

// vy_d = vhelp × vx_d (vector vuông góc với cả 2)
const vy_d = crossProduct(vhelp, vx_d);

Toán học tích có hướng:

Cho 2 vectors:
  u = [u1, u2, u3]
  v = [v1, v2, v3]

Tích có hướng u × v:
  │ i   j   k  │
  │ u1  u2  u3 │ = i(u2*v3 - u3*v2) - j(u1*v3 - u3*v1) + k(u1*v2 - u2*v1)
  │ v1  v2  v3 │

Kết quả: vector vuông góc với cả u và v
Hướng: Quy tắc bàn tay phải

Trực quan:

       u × v


    u ←──┼──→ v

    (vuông góc)

Nguồn: Cross Product - Wikipedia

Tại sao cần cross product?

  • Để tạo trục Y (vertical) của face
  • Trục Y phải vuông góc với cả trục X (p1→p2) và hướng mũi (vhelp)

Bước 5: Normalization (Chuẩn hóa vector)

typescript
// ✅ CODE THỰC TẾ

function normalizeVector(v: number[]): number[] {
  // Tính độ dài (magnitude)
  const length = Math.sqrt(v[0] ** 2 + v[1] ** 2 + v[2] ** 2);

  // Chia mỗi thành phần cho độ dài
  return [v[0] / length, v[1] / length, v[2] / length];
}

// Chuẩn hóa các vectors
const vx = normalizeVector(vx_d); // Trục X (ngang)
const vy = normalizeVector(vy_d); // Trục Y (dọc)
const vz = normalizeVector(crossProduct(vy_d, vx_d)); // Trục Z (độ sâu)

Toán học chuẩn hóa:

Vector v = [x, y, z]

Độ dài (magnitude):
  |v| = √(x² + y² + z²)

Vector đơn vị (độ dài = 1):
  v̂ = v / |v| = [x/|v|, y/|v|, z/|v|]

Ví dụ:
  v = [3, 4, 0]
  |v| = √(9 + 16 + 0) = 5
  v̂ = [3/5, 4/5, 0] = [0.6, 0.8, 0]
  |v̂| = √(0.36 + 0.64) = 1 ✓

Tại sao cần chuẩn hóa?

  • Ma trận quay yêu cầu cơ sở trực chuẩn (orthonormal basis)
  • Mỗi vector phải có độ dài = 1
  • Giúp tính toán góc chính xác

Nguồn: Unit Vector - Wikipedia

Bước 6: Tạo Rotation Matrix

typescript
// ✅ CODE THỰC TẾ (sử dụng Three.js)

const rotationMatrix3 = new THREE.Matrix3().fromArray([
  ...vx, // [vx[0], vx[1], vx[2]]  → Hàng 1
  ...vy, // [vy[0], vy[1], vy[2]]  → Hàng 2
  ...vz, // [vz[0], vz[1], vz[2]]  → Hàng 3
]);

Ma trận quay (3x3):

    Trục X      Trục Y      Trục Z
  ┌                              ┐
  │ vx[0]  vx[1]  vx[2] │ ← Hàng 1
  │ vy[0]  vy[1]  vy[2] │ ← Hàng 2
  │ vz[0]  vz[1]  vz[2] │ ← Hàng 3
  └                              ┘

Hoặc:
  ┌                ┐
  │ r11  r12  r13 │
  │ r21  r22  r23 │
  │ r31  r32  r33 │
  └                ┘

Tính chất:
- Trực giao: Mỗi hàng/cột vuông góc với các hàng/cột khác
- Chuẩn hóa: Mỗi hàng/cột có độ dài = 1
- Định thức = 1 (chỉ quay, không co giãn)

Ý nghĩa vật lý:

  • Ma trận này mô tả hướng (orientation) của khuôn mặt trong không gian 3D
  • Mỗi hàng là một trục của hệ tọa độ khuôn mặt

Nguồn: Rotation Matrix - Wikipedia

Bước 7: Chuyển đổi sang góc Euler

typescript
// ✅ CODE THỰC TẾ

// Chuyển đổi ma trận 3x3 sang 4x4 (yêu cầu bởi Three.js)
const rotationMatrix4 = new THREE.Matrix4().setFromMatrix3(rotationMatrix3);

// Trích xuất góc Euler sử dụng thứ tự ZYX
const euler = new THREE.Euler().setFromRotationMatrix(rotationMatrix4, "ZYX");

// Chuyển đổi radian sang độ
const yaw = THREE.MathUtils.radToDeg(euler.y); // Quay quanh trục Y
const pitch = THREE.MathUtils.radToDeg(euler.x); // Quay quanh trục X
const roll = THREE.MathUtils.radToDeg(euler.z); // Quay quanh trục Z

Công thức trích xuất góc Euler (thứ tự ZYX):

Cho ma trận quay:
  ┌                ┐
  │ r11  r12  r13 │
  │ r21  r22  r23 │
  │ r31  r32  r33 │
  └                ┘

Pitch (quay quanh X):
  pitch = -asin(r31)

Yaw (quay quanh Y):
  yaw = atan2(r21, r11)

Roll (quay quanh Z):
  roll = atan2(r32, r33)

Trong đó:
  asin(x)     = arcsin(x) = sin nghịch đảo
  atan2(y, x) = arctan(y/x) = tan nghịch đảo (4 góc phần tư)

Nguồn: Three.js Euler Class

Tại sao dùng thứ tự "ZYX"?

  • Góc Euler có 12 thứ tự có thể (XYZ, XZY, YXZ, YZX, ZXY, ZYX, ...)
  • ZYX = Áp dụng Roll trước, sau đó Pitch, cuối cùng Yaw
  • Thường dùng trong robot và hàng không
  • Nhất quán với hệ tọa độ MediaPipe

Vấn đề Gimbal Lock:

Cảnh báo: Góc Euler có gimbal lock khi pitch = ±90°
→ Yaw và Roll không phân biệt được
→ Portal giới hạn pitch trong phạm vi nhỏ hơn để tránh vấn đề này

Nguồn: Gimbal Lock - Wikipedia


5. Ví dụ hoàn chỉnh với số cụ thể

Giả sử ta có 3 điểm mốc (tọa độ đã chuẩn hóa):

typescript
// Điểm mốc đầu vào (từ MediaPipe)
const landmarks = {
  127: { x: 0.3, y: 0.5, z: -0.01 }, // Má trái
  356: { x: 0.7, y: 0.5, z: -0.01 }, // Má phải
  6: { x: 0.5, y: 0.4, z: -0.05 }, // Sống mũi
};

// Kích thước ảnh
const width = 640;
const height = 480;

Tính toán từng bước:

Bước 1: Chuyển đổi sang pixel

typescript
p1 = [0.3 * 640, 0.5 * 480, -0.01 * 640] = [192, 240, -6.4];
p2 = [0.7 * 640, 0.5 * 480, -0.01 * 640] = [448, 240, -6.4];
p3 = [0.5 * 640, 0.4 * 480, -0.05 * 640] = [320, 192, -32];

Bước 2: Tạo vectors

typescript
vhelp = p3 - p1 = [320-192, 192-240, -32-(-6.4)] = [128, -48, -25.6]
vx_d  = p2 - p1 = [448-192, 240-240, -6.4-(-6.4)] = [256, 0, 0]

Bước 3: Tích có hướng

typescript
vy_d = vhelp × vx_d
     = [(-48)*0 - (-25.6)*0, (-25.6)*256 - 128*0, 128*0 - (-48)*256]
     = [0, -6553.6, 12288]

Bước 4: Chuẩn hóa

typescript
|vx_d| = √(256² ++ 0²) = 256
vx = [256/256, 0/256, 0/256] = [1, 0, 0]

|vy_d| = √(0² + 6553.6² + 12288²) = 13942.4
vy = [0, -0.47, 0.88]

vz = vy_d × vx_d (normalized)
   ≈ [0, 0.88, 0.47]

Bước 5: Ma trận quay

R = ┌                      ┐
    │  1.00   0.00   0.00  │
    │  0.00  -0.47   0.88  │
    │  0.00   0.88   0.47  │
    └                      ┘

Bước 6: Trích xuất góc Euler

typescript
pitch = -asin(r31) = -asin(0) = 0°
yaw   = atan2(r21, r11) = atan2(0, 1) = 0°
roll  = atan2(r32, r33) = atan2(0.88, 0.47) ≈ 61.9°

Kết quả:
  yaw   = 0°    (nhìn thẳng)
  pitch = 0°    (không ngước/cúi)
  roll  = 61.9° (nghiêng đầu sang phải)

6. Xác thực trong Portal

✅ CODE THỰC TẾ: Ngưỡng

typescript
// face-liveness.ts

export function faceLiveNessCheck(results: Results, action: string): boolean {
  const angles = calculateFaceAngles(results);

  switch (action) {
    case "forward":
      // Yaw: -10° đến +10°
      // Pitch: -7° đến +7°
      // → Cho phép sai số nhỏ (không phải hoàn toàn thẳng)
      return (
        angles.yaw >= -10 &&
        angles.yaw <= 10 &&
        angles.pitch >= -7 &&
        angles.pitch <= 7
      );

    case "left":
      // Yaw >= +25°
      // → Phải quay rõ ràng (không phải hơi xoay)
      return angles.yaw >= 25;

    case "right":
      // Yaw <= -25°
      return angles.yaw <= -25;

    case "up":
      // Pitch >= +11°
      // Yaw: -20° đến +20° (không quay ngang quá nhiều)
      return pitchFromLandmarks >= 11 && angles.yaw >= -20 && angles.yaw <= 20;

    case "down":
      // Pitch <= -9°
      return pitchFromLandmarks <= -9 && angles.yaw >= -20 && angles.yaw <= 20;

    default:
      return false;
  }
}

Lý do cho các ngưỡng:

Forward (±10°, ±7°):
  → Cho phép người dùng không cần hoàn toàn thẳng
  → Tăng UX (dễ vượt qua hơn)
  → Vẫn đủ chặt để phát hiện giả mạo

Left/Right (25°):
  → 25° = Góc rõ ràng, dễ phát hiện
  → < 25° = Có thể là nhiễu/dao động
  → > 45° = Quá nghiêng, khó thực hiện

Up/Down (11°, 9°):
  → Phát hiện Pitch khó hơn Yaw
  → Ngưỡng thấp hơn để dễ vượt qua
  → Có 2 phương pháp (quay + điểm mốc) để tăng độ chính xác

7. Gỡ lỗi & Trực quan hóa

Log console trong code thực tế

typescript
// face-liveness.ts
console.log("[Face Liveness]", action, "angles:", {
  yaw: angles.yaw.toFixed(2),
  pitch: angles.pitch.toFixed(2),
  roll: angles.roll.toFixed(2),
});

Ví dụ đầu ra:

[Face Liveness] forward angles: { yaw: '2.34', pitch: '-1.56', roll: '0.89' }
[Face Liveness] forward check: true

[Face Liveness] left angles: { yaw: '28.45', pitch: '3.21', roll: '-2.10' }
[Face Liveness] left check: true

[Face Liveness] right angles: { yaw: '-31.67', pitch: '-0.45', roll: '1.23' }
[Face Liveness] right check: true

Thêm trực quan hóa (Tùy chọn)

typescript
// Vẽ vectors lên canvas để gỡ lỗi
function drawVectors(canvas: HTMLCanvasElement, p1, p2, p3) {
  const ctx = canvas.getContext("2d");

  // Vẽ điểm mốc
  ctx.fillStyle = "red";
  ctx.fillRect(p1[0] - 5, p1[1] - 5, 10, 10); // Má trái
  ctx.fillRect(p2[0] - 5, p2[1] - 5, 10, 10); // Má phải
  ctx.fillRect(p3[0] - 5, p3[1] - 5, 10, 10); // Mũi

  // Vẽ trục X (màu đỏ)
  ctx.strokeStyle = "red";
  ctx.beginPath();
  ctx.moveTo(p1[0], p1[1]);
  ctx.lineTo(p2[0], p2[1]);
  ctx.stroke();

  // Vẽ vector helper (màu xanh lá)
  ctx.strokeStyle = "green";
  ctx.beginPath();
  ctx.moveTo(p1[0], p1[1]);
  ctx.lineTo(p3[0], p3[1]);
  ctx.stroke();
}

8. Tài liệu tham khảo

Bài báo & Tài liệu

  1. MediaPipe FaceMesh

  2. Ma trận quay & Góc Euler

  3. Three.js

  4. Đại số tuyến tính

  5. Phát hiện & Nhận dạng khuôn mặt

Code References

  1. Portal Project (Internal)

    • portal-client/src/utils/ekyc/face-liveness.ts
    • portal-client/src/pages/information/directive/FaceDetectionCamera.vue
    • portal-client/src/utils/ekyc/MediaPipeFaceMeshWrapper.ts
  2. Ví dụ MediaPipe

  3. Ví dụ Three.js


9. Câu hỏi thường gặp & Vấn đề phổ biến

Q1: Tại sao không dùng 4 hoặc 5 điểm mốc thay vì 3?

A: 3 điểm là tối thiểu để xác định một mặt phẳng (plane) trong không gian 3D. Thêm điểm sẽ:

  • Tăng độ phức tạp
  • Có thể gây nhiễu (nếu các điểm không đồng phẳng)
  • Không cải thiện độ chính xác đáng kể

Q2: Gimbal lock có ảnh hưởng đến Portal không?

A: Ít ảnh hưởng vì:

  • Portal không yêu cầu người dùng nghiêng đầu quá 45°
  • Gimbal lock xảy ra khi pitch = ±90° (cúi/ngước cực độ)
  • Các hành động trong portal: forward (0°), up (+11°), down (-9°) → Phạm vi an toàn

Q3: Tại sao dùng Three.js thay vì tự triển khai?

A:

  • Three.js đã được tối ưu hóa và kiểm thử kỹ
  • Hỗ trợ nhiều thứ tự góc Euler
  • Xử lý các trường hợp biên (gimbal lock, lỗi chuẩn hóa)
  • Giảm lỗi, tăng khả năng bảo trì

Q4: Độ chính xác của góc tính được là bao nhiêu?

A:

  • Lý thuyết: ±0.1° (nếu điểm mốc hoàn hảo)
  • Thực tế: ±2-5° (do nhiễu, ánh sáng, chất lượng camera)
  • Ngưỡng Portal: ±7-10° → Đủ dung sai cho điều kiện thực tế

10. Tổng kết

Điểm chính

  1. Phát hiện khuôn mặt = Mô hình ML (CNN) phát hiện khuôn mặt + 468 điểm mốc
  2. MediaPipe FaceMesh = Nhẹ, thời gian thực, sẵn sàng cho mobile
  3. 3 điểm mốc (127, 356, 6) → Xác định hướng khuôn mặt
  4. Ma trận quay = Cơ sở trực chuẩn từ 3 vectors
  5. Góc Euler = Yaw (trái/phải), Pitch (lên/xuống), Roll (nghiêng)
  6. Ngưỡng Portal = Được điều chỉnh cho UX + Bảo mật

Tóm tắt quy trình

MediaPipe FaceMesh

468 điểm mốc (x, y, z)

Chọn 3 điểm chính (127, 356, 6)

Chuyển đổi sang tọa độ pixel

Tạo 3 vectors (vhelp, vx_d, vy_d)

Tích có hướng → Vectors trực giao

Chuẩn hóa → Vectors đơn vị

Xây dựng ma trận quay (3x3)

Trích xuất góc Euler (thứ tự ZYX)

Chuyển đổi radian → độ

Xác thực với ngưỡng

faceLiveNessCheck() → true/false

Đọc thêm

Nếu bạn muốn hiểu sâu hơn:


Chúc bạn học tốt! 🚀

Internal documentation for iNET Portal