Tích hợp Thư viện - Flow Dữ liệu Chi tiết
Mục tiêu: Giải thích chi tiết các thư viện được sử dụng trong eKYC, cách dữ liệu chuyển từ thư viện này sang thư viện khác, và cách tạo ra kết quả cuối cùng. Tất cả dựa trên implementation thực tế trong dự án Portal.
1. Tổng quan các Thư viện
1.1. Danh sách Thư viện Chính
// package.json dependencies
{
"@mediapipe/face_mesh": "^0.4.1633559619", // Phát hiện khuôn mặt + 468 landmarks
"three": "^0.181.0", // Tính toán góc Euler từ rotation matrix
"axios": "^1.7.9", // HTTP client cho API calls
"vue": "^3.5.13", // Framework chính
"howler": "^2.2.4" // Audio feedback
}1.2. Vai trò từng Thư viện
┌─────────────────────────────────────────────────────────────┐
│ │
│ 📹 Camera (HTMLVideoElement) │
│ ↓ │
│ 🔍 MediaPipe FaceMesh │
│ → Phát hiện khuôn mặt │
│ → 468 landmarks (x, y, z) │
│ ↓ │
│ 📐 face-liveness.ts (Custom Utility) │
│ → Chọn 3 landmarks chính (127, 356, 6) │
│ → Tạo vectors, cross product, normalize │
│ ↓ │
│ 🎯 Three.js (Matrix3, Matrix4, Euler) │
│ → Tạo rotation matrix │
│ → Trích xuất góc Euler (yaw, pitch, roll) │
│ ↓ │
│ ✅ faceLiveNessCheck() │
│ → So sánh góc với thresholds │
│ → Trả về true/false │
│ ↓ │
│ 📸 Canvas/Video Capture │
│ → Chuyển đổi sang base64 │
│ ↓ │
│ 🌐 Axios (HTTP Client) │
│ → Gửi ảnh + OCR data lên backend │
│ → Nhận kết quả verification │
│ │
└─────────────────────────────────────────────────────────────┘2. Flow Dữ liệu Chi tiết (Giải thích Đơn giản)
Lưu ý: Phần này giải thích bằng ngôn ngữ đơn giản, không đi sâu vào toán học phức tạp. Chỉ cần hiểu "làm gì" và "tại sao", không cần hiểu "như thế nào" chi tiết.
2.1. Bước 1: Camera → MediaPipe FaceMesh
Đầu vào: Video từ camera (giống như bạn đang quay phim)
Làm gì:
- MediaPipe nhận video từ camera
- Mỗi 10ms (rất nhanh!), MediaPipe "nhìn" vào 1 khung hình
- MediaPipe tìm khuôn mặt trong khung hình đó
- Nếu tìm thấy, MediaPipe đánh dấu 468 điểm trên khuôn mặt (mắt, mũi, miệng, cằm, v.v.)
Đầu ra: Danh sách 468 điểm (gọi là "landmarks")
Ví dụ dễ hiểu:
Giống như bạn vẽ chấm lên ảnh:
- Chấm số 127 = ở má trái
- Chấm số 356 = ở má phải
- Chấm số 6 = ở sống mũi
- ... và 465 chấm khác nữaDữ liệu thực tế (bạn không cần hiểu chi tiết, chỉ cần biết có 468 điểm):
results = {
multiFaceLandmarks: [
[
{ x: 0.3, y: 0.5, z: -0.01 }, // Điểm 127 (má trái)
{ x: 0.7, y: 0.5, z: -0.01 }, // Điểm 356 (má phải)
{ x: 0.5, y: 0.4, z: -0.05 }, // Điểm 6 (sống mũi)
// ... 465 điểm khác
],
],
image: { width: 640, height: 480 },
};2.2. Bước 2: Chọn 3 Điểm Quan trọng
Đầu vào: 468 điểm từ MediaPipe
Làm gì:
- Trong 468 điểm, chúng ta chỉ cần 3 điểm quan trọng:
- Điểm 127 = Má trái
- Điểm 356 = Má phải
- Điểm 6 = Sống mũi
Tại sao chỉ 3 điểm?
- Giống như bạn cần 3 điểm để xác định một mặt phẳng (ví dụ: 3 chân bàn)
- 3 điểm này đủ để biết khuôn mặt đang quay về hướng nào
Ví dụ dễ hiểu:
Nếu bạn nhìn vào gương:
- Má trái và má phải = cho biết bạn quay trái hay phải
- Sống mũi = cho biết bạn ngước hay cúi đầuChuyển đổi tọa độ (đơn giản hóa):
- MediaPipe trả về tọa độ dạng 0-1 (ví dụ: 0.3 = 30% từ trái sang)
- Chúng ta nhân với kích thước ảnh để ra số pixel thực tế
- Ví dụ: 0.3 × 640 = 192 pixel từ trái sang
2.3. Bước 3: Tính Toán Hướng Khuôn mặt (Đơn giản hóa)
Đầu vào: 3 điểm (má trái, má phải, sống mũi)
Làm gì:
- Tính toán xem khuôn mặt đang quay về hướng nào
- Giống như bạn nhìn vào la bàn để biết hướng Bắc/Nam/Đông/Tây
Ví dụ dễ hiểu:
Nếu bạn quay đầu sang trái:
- Má trái sẽ "xa" camera hơn
- Má phải sẽ "gần" camera hơn
- Code sẽ tính toán và biết: "Ồ, bạn đang quay trái 30 độ!"
Nếu bạn ngước đầu lên:
- Sống mũi sẽ "cao" hơn so với má
- Code sẽ tính toán và biết: "Ồ, bạn đang ngước lên 15 độ!"Bạn không cần hiểu chi tiết toán học:
- Code sẽ tự động tính toán các "vector" (mũi tên chỉ hướng)
- Code sẽ tự động tính "cross product" (phép toán để tìm hướng vuông góc)
- Code sẽ tự động "normalize" (chuẩn hóa về độ dài = 1)
Chỉ cần biết: Code đang tính toán hướng khuôn mặt dựa trên 3 điểm!
2.4. Bước 4: Chuyển Đổi Thành 3 Góc (Yaw, Pitch, Roll)
Đầu vào: Hướng khuôn mặt (từ bước 3)
Làm gì:
- Three.js (thư viện toán học) sẽ chuyển đổi hướng thành 3 góc dễ hiểu:
- Yaw = Quay trái/phải (giống như bạn lắc đầu "không")
- Pitch = Ngước/cúi (giống như bạn gật đầu "có")
- Roll = Nghiêng đầu (giống như bạn nghiêng đầu nghe điện thoại)
Ví dụ dễ hiểu:
Nếu bạn nhìn thẳng vào camera:
- Yaw = 0° (không quay trái/phải)
- Pitch = 0° (không ngước/cúi)
- Roll = 0° (không nghiêng)
Nếu bạn quay đầu sang trái 30 độ:
- Yaw = 30° (quay trái)
- Pitch = 0° (không ngước/cúi)
- Roll = 0° (không nghiêng)
Nếu bạn ngước đầu lên 15 độ:
- Yaw = 0° (không quay trái/phải)
- Pitch = 15° (ngước lên)
- Roll = 0° (không nghiêng)Bạn không cần hiểu "rotation matrix":
- Đây là công thức toán học phức tạp
- Three.js đã làm sẵn cho bạn
- Chỉ cần biết: Input = hướng khuôn mặt, Output = 3 góc (yaw, pitch, roll)
Kết quả:
{
yaw: 0, // 0° = nhìn thẳng
pitch: 0, // 0° = không ngước/cúi
roll: 0 // 0° = không nghiêng
}2.5. Bước 5: Kiểm Tra Xem Bạn Có Làm Đúng Không?
Đầu vào: 3 góc (yaw, pitch, roll) + Hành động cần làm (forward/left/right/up/down)
Làm gì:
- So sánh góc thực tế với góc yêu cầu
- Nếu khớp → Trả về
true(đúng!) - Nếu không khớp → Trả về
false(sai, làm lại!)
Ví dụ dễ hiểu:
Trường hợp 1: "Nhìn thẳng" (forward)
Yêu cầu:
- Yaw: -10° đến +10° (cho phép sai số nhỏ)
- Pitch: -7° đến +7° (cho phép sai số nhỏ)
Nếu bạn nhìn thẳng (yaw = 0°, pitch = 0°):
→ ✅ Đúng! Trả về true
Nếu bạn quay trái quá nhiều (yaw = 20°):
→ ❌ Sai! Trả về falseTrường hợp 2: "Quay trái" (left)
Yêu cầu:
- Yaw >= 25° (phải quay trái rõ ràng, không phải hơi xoay)
Nếu bạn quay trái 30°:
→ ✅ Đúng! Trả về true
Nếu bạn chỉ quay trái 10°:
→ ❌ Sai! Trả về false (quay chưa đủ)Trường hợp 3: "Ngước lên" (up)
Yêu cầu:
- Pitch >= 11° (phải ngước lên rõ ràng)
- Yaw: -20° đến +20° (không được quay ngang quá nhiều)
Nếu bạn ngước lên 15° và không quay ngang:
→ ✅ Đúng! Trả về true
Nếu bạn ngước lên nhưng quay ngang 30°:
→ ❌ Sai! Trả về false (quay ngang quá nhiều)Kết quả: true hoặc false (đúng hoặc sai)
2.6. Bước 6: Chụp Ảnh Khi Đã Làm Đúng
Đầu vào: Video từ camera (khi liveness check = true)
Làm gì:
- Đợi 500ms (nửa giây) để đảm bảo ảnh không bị mờ
- "Chụp" 1 khung hình từ video (giống như bạn chụp ảnh màn hình)
- Chuyển đổi ảnh thành chuỗi text (gọi là "base64")
Ví dụ dễ hiểu:
Giống như bạn:
1. Quay phim bằng điện thoại
2. Dừng lại ở 1 khung hình đẹp
3. Chụp ảnh màn hình
4. Gửi ảnh đó cho bạn bè
Code cũng làm vậy:
1. Video đang chạy
2. Dừng lại ở 1 khung hình (khi bạn làm đúng)
3. "Chụp" khung hình đó
4. Chuyển thành text để gửi lên serverTại sao đợi 500ms?
- Camera cần thời gian để ổn định
- Giảm blur (mờ) trong ảnh
- Ảnh sẽ rõ nét hơn
Kết quả: Một chuỗi text dài (base64) đại diện cho ảnh
2.7. Bước 7: Gửi Ảnh Lên Server
Đầu vào:
- Ảnh khuôn mặt (base64)
- Ảnh CCCD mặt trước (base64)
- Ảnh CCCD mặt sau (base64)
- Thông tin OCR (đã đọc từ CCCD)
Làm gì:
- Gửi tất cả dữ liệu lên server qua HTTP request
- Server sẽ so sánh khuôn mặt với ảnh trên CCCD
- Server trả về kết quả: thành công hay thất bại
Ví dụ dễ hiểu:
Giống như bạn:
1. Chụp ảnh khuôn mặt
2. Chụp ảnh CCCD
3. Gửi tất cả lên server
4. Server kiểm tra: "Khuôn mặt này có khớp với ảnh trên CCCD không?"
5. Server trả về: "Khớp!" hoặc "Không khớp!"Kết quả:
{
message: {
error_code: 'SUCCESS',
error_message: 'Verification successful',
}
}Hoặc nếu lỗi:
{
message: {
error_code: 'ERR01',
error_message: 'Khuôn mặt không khớp với CCCD',
}
}3. Sơ đồ Flow Đơn giản (Dễ hiểu)
┌─────────────────────────────────────────────────────────────┐
│ │
│ 📹 BƯỚC 1: Camera │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Video từ camera (giống như quay phim) │ │
│ └───────────────────────────────────────────────────────┘ │
│ ↓ │
│ 🔍 BƯỚC 2: MediaPipe Tìm Khuôn mặt │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ MediaPipe "nhìn" vào video │ │
│ │ Tìm thấy khuôn mặt → Đánh dấu 468 điểm │ │
│ │ (mắt, mũi, miệng, cằm, v.v.) │ │
│ └───────────────────────────────────────────────────────┘ │
│ ↓ │
│ 📐 BƯỚC 3: Chọn 3 Điểm Quan trọng │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Trong 468 điểm, chọn 3 điểm: │ │
│ │ - Má trái (điểm 127) │ │
│ │ - Má phải (điểm 356) │ │
│ │ - Sống mũi (điểm 6) │ │
│ └───────────────────────────────────────────────────────┘ │
│ ↓ │
│ 🎯 BƯỚC 4: Tính Toán Hướng Khuôn mặt │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Dựa vào 3 điểm, tính toán: │ │
│ │ - Bạn đang quay trái/phải bao nhiêu độ? (Yaw) │ │
│ │ - Bạn đang ngước/cúi bao nhiêu độ? (Pitch) │ │
│ │ - Bạn đang nghiêng bao nhiêu độ? (Roll) │ │
│ │ │ │
│ │ Ví dụ: Yaw = 0°, Pitch = 0°, Roll = 0° │ │
│ │ → Bạn đang nhìn thẳng! │ │
│ └───────────────────────────────────────────────────────┘ │
│ ↓ │
│ ✅ BƯỚC 5: Kiểm Tra Xem Bạn Có Làm Đúng Không? │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ So sánh góc thực tế với góc yêu cầu: │ │
│ │ │ │
│ │ Nếu yêu cầu "nhìn thẳng": │ │
│ │ → Yaw: -10° đến +10° ✓ │ │
│ │ → Pitch: -7° đến +7° ✓ │ │
│ │ → Kết quả: TRUE (đúng!) │ │
│ │ │ │
│ │ Nếu yêu cầu "quay trái": │ │
│ │ → Yaw >= 25° ✓ │ │
│ │ → Kết quả: TRUE (đúng!) │ │
│ └───────────────────────────────────────────────────────┘ │
│ ↓ │
│ 📸 BƯỚC 6: Chụp Ảnh (Khi Đã Làm Đúng) │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Đợi 500ms → Chụp 1 khung hình từ video │ │
│ │ Chuyển đổi thành text (base64) │ │
│ └───────────────────────────────────────────────────────┘ │
│ ↓ │
│ 🌐 BƯỚC 7: Gửi Lên Server │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Gửi ảnh khuôn mặt + ảnh CCCD lên server │ │
│ │ Server so sánh: "Khuôn mặt có khớp với CCCD không?" │ │
│ │ │ │
│ │ Response: { │ │
│ │ error_code: 'SUCCESS', │ │
│ │ error_message: 'Verification successful' │ │
│ │ } │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘Tóm tắt bằng lời:
- Camera quay video
- MediaPipe tìm khuôn mặt và đánh dấu 468 điểm
- Chọn 3 điểm quan trọng (má trái, má phải, sống mũi)
- Tính toán xem bạn đang quay về hướng nào (yaw, pitch, roll)
- Kiểm tra xem bạn có làm đúng yêu cầu không
- Nếu đúng → Chụp ảnh
- Gửi ảnh lên server để xác thực
4. Code Examples Từ Dự án Thực tế
4.1. MediaPipeFaceMeshWrapper.ts
// ✅ CODE THỰC TẾ từ MediaPipeFaceMeshWrapper.ts
export class MediaPipeFaceMeshWrapper {
private faceMesh: any = null;
private videoElement: HTMLVideoElement | null = null;
private intervalId: number | null = null;
async initialize(): Promise<void> {
// Dynamic import để tránh build issues
const faceMeshModule = await import("@mediapipe/face_mesh");
const FaceMeshClass = faceMeshModule.FaceMesh;
// Tạo instance với local files
this.faceMesh = new FaceMeshClass({
locateFile: (file: string) => `/face_mesh/${file}`,
});
// Cấu hình options
this.faceMesh.setOptions({
selfieMode: true,
maxNumFaces: 1,
refineLandmarks: true,
minDetectionConfidence: 0.5,
minTrackingConfidence: 0.5,
});
}
startDetection(): void {
// Gửi video frame mỗi 10ms (≈100 FPS)
this.intervalId = window.setInterval(async () => {
if (this.videoElement && this.videoElement.readyState === 4) {
await this.faceMesh.send({ image: this.videoElement });
}
}, 10);
}
onResults(callback: (results: Results) => void): void {
this.faceMesh.onResults(callback);
}
}4.2. face-liveness.ts
// ✅ CODE THỰC TẾ từ face-liveness.ts
import * as THREE from "three";
export function calculateFaceAngles(results: Results): IFaceAngles {
// 1. Chọn 3 landmarks chính
const landmarks = results.multiFaceLandmarks[0];
const p1 = landmarks[127]; // Má trái
const p2 = landmarks[356]; // Má phải
const p3 = landmarks[6]; // Sống mũi
// 2. Chuyển đổi sang pixel coordinates
const width = results.image.width;
const height = results.image.height;
const p1_vec = [p1.x * width, p1.y * height, p1.z * width];
const p2_vec = [p2.x * width, p2.y * height, p2.z * width];
const p3_vec = [p3.x * width, p3.y * height, p3.z * width];
// 3. Tạo vectors
const vhelp = [
p3_vec[0] - p1_vec[0],
p3_vec[1] - p1_vec[1],
p3_vec[2] - p1_vec[2],
];
const vx_d = [
p2_vec[0] - p1_vec[0],
p2_vec[1] - p1_vec[1],
p2_vec[2] - p1_vec[2],
];
const vy_d = crossProduct(vhelp, vx_d);
// 4. Chuẩn hóa
const vx = normalizeVector(vx_d);
const vy = normalizeVector(vy_d);
const vz = normalizeVector(crossProduct(vy_d, vx_d));
// 5. Tạo rotation matrix và trích xuất góc Euler
const rotationMatrix3 = new THREE.Matrix3().fromArray([...vx, ...vy, ...vz]);
const rotationMatrix4 = new THREE.Matrix4().setFromMatrix3(rotationMatrix3);
const euler = new THREE.Euler().setFromRotationMatrix(rotationMatrix4, "ZYX");
return {
yaw: THREE.MathUtils.radToDeg(euler.y),
pitch: THREE.MathUtils.radToDeg(euler.x),
roll: THREE.MathUtils.radToDeg(euler.z),
};
}4.3. FaceDetectionCamera.vue
// ✅ CODE THỰC TẾ từ FaceDetectionCamera.vue
import { MediaPipeFaceMeshWrapper } from "@/utils/ekyc/MediaPipeFaceMeshWrapper";
import { faceLiveNessCheck, type Results } from "@/utils/ekyc/face-liveness";
let faceMeshWrapper: MediaPipeFaceMeshWrapper | null = null;
let videoElement: HTMLVideoElement | null = null;
onMounted(async () => {
// 1. Khởi tạo wrapper
faceMeshWrapper = new MediaPipeFaceMeshWrapper();
await faceMeshWrapper.initialize();
// 2. Set video element
faceMeshWrapper.setVideoElement(videoElement);
// 3. Đăng ký callback
faceMeshWrapper.onResults((results: Results) => {
// 4. Kiểm tra liveness
const currentAction = randomActionSequence.value[step.value]?.action;
if (faceLiveNessCheck(results, currentAction)) {
validFrameCount.value += 1;
// 5. Khi đủ frames, capture image
if (validFrameCount.value >= VALID_FRAME) {
captureFinalImage();
}
}
});
// 6. Bắt đầu detection loop
faceMeshWrapper.startDetection();
});
const captureFinalImage = async () => {
// Đợi 500ms để đảm bảo ảnh không bị mờ
await new Promise((resolve) => setTimeout(resolve, 500));
// Tạo canvas và capture
const canvas = document.createElement("canvas");
canvas.width = videoElement.videoWidth;
canvas.height = videoElement.videoHeight;
const ctx = canvas.getContext("2d");
ctx?.drawImage(videoElement, 0, 0);
// Chuyển đổi sang base64
const imageBase64 = canvas.toDataURL("image/jpeg", 0.95);
// Emit event
emit("takingImage", imageBase64);
};4.4. face-verification.ts
// ✅ CODE THỰC TẾ từ face-verification.ts
import apiClient from "@/config/http-common";
export async function getFaceVerificationData(
frontCardImage: string,
faceImage: string,
backCardImage: string,
frontCardData: IOcrData,
backCardData: IOcrData
): Promise<IFaceVerificationData> {
// 1. Chuẩn bị request body
const request: IFaceVerificationRequest = {
frontEndBase64: frontCardImage,
faceidBase64: faceImage,
backEndBase64: backCardImage,
imageCardData: frontCardData,
imageCardBackendData: backCardData,
// ... other fields
};
// 2. Gửi request qua Axios
const response = await apiClient().post("api/v1/ekyc/verify-face", request, {
headers: qrToken ? { qrToken } : {},
});
// 3. Parse response
const portalResponse = response.data;
if (portalResponse?.status === "error") {
return {
message: {
error_code: portalResponse.values.error_code,
error_message: portalResponse.values.error_message,
},
};
}
return portalResponse;
}5. Data Types & Interfaces
5.1. MediaPipe Results
interface Results {
multiFaceLandmarks?: Array<
Array<{
x: number; // 0-1 (normalized)
y: number; // 0-1 (normalized)
z: number; // depth (relative, có thể âm)
}>
>;
image?: {
width: number;
height: number;
toDataURL?: (format: string) => string;
};
}5.2. Face Angles
interface IFaceAngles {
yaw: number; // Độ (quay trái/phải)
pitch: number; // Độ (ngước/cúi)
roll: number; // Độ (nghiêng)
}5.3. Face Verification Request
interface IFaceVerificationRequest {
frontEndBase64: string; // Base64 image từ OCR front
faceidBase64: string; // Base64 image từ face capture
backEndBase64: string; // Base64 image từ OCR back
imageCardData: IOcrData; // OCR results từ front
imageCardBackendData: IOcrData; // OCR results từ back
cookieEmail?: string;
cookiePhone?: string;
request_id?: string;
hostname?: string;
fileFrontEnd?: { name: string; type: string; size: number };
fileBackEnd?: { name: string; type: string; size: number };
fileFaceid?: { name: string; type: string; size: number };
}5.4. Face Verification Response
interface IFaceVerificationData {
message: {
error_code: string;
error_message: string;
};
// ... other fields
}6. Performance & Optimization
6.1. Detection Loop Frequency
// MediaPipeFaceMeshWrapper.ts
// Gửi frame mỗi 10ms (≈100 FPS)
setInterval(async () => {
await faceMesh.send({ image: videoElement });
}, 10);Lý do:
- MediaPipe có thể xử lý ~100 FPS trên mobile
- 10ms interval = đủ nhanh để phát hiện chuyển động mượt mà
- Không quá nhanh để tránh lãng phí CPU
6.2. Image Capture Delay
// FaceDetectionCamera.vue
// Đợi 500ms trước khi capture để đảm bảo ảnh không bị mờ
await new Promise((resolve) => setTimeout(resolve, 500));Lý do:
- Camera cần thời gian để ổn định sau khi user thực hiện action
- Giảm blur trong ảnh cuối cùng
- Cải thiện chất lượng verification
6.3. Base64 Image Quality
// JPEG quality: 0.95 (95%)
canvas.toDataURL("image/jpeg", 0.95);Lý do:
- Balance giữa chất lượng và kích thước file
- 0.95 = đủ chất lượng cho face verification
- Giảm kích thước so với PNG (100%)
7. Error Handling
7.1. MediaPipe Initialization Errors
try {
await faceMeshWrapper.initialize();
} catch (error) {
console.error("MediaPipe init error:", error);
// Fallback: Show error message to user
message.error("Không thể khởi tạo camera. Vui lòng thử lại.");
}7.2. Face Detection Errors
faceMeshWrapper.onResults((results: Results) => {
try {
if (
!results.multiFaceLandmarks ||
results.multiFaceLandmarks.length === 0
) {
// Không phát hiện khuôn mặt
return;
}
// ... process results
} catch (error) {
console.error("Face detection error:", error);
}
});7.3. API Call Errors
try {
const response = await apiClient().post("api/v1/ekyc/verify-face", request);
// ... handle success
} catch (error: any) {
if (error.response?.data?.values?.error_message) {
// Hiển thị lỗi từ backend
message.error(error.response.data.values.error_message);
} else {
// Lỗi network hoặc unknown
message.error("Có lỗi xảy ra. Vui lòng thử lại.");
}
}8. Tổng kết (Đơn giản)
8.1. Flow Tóm tắt Bằng Lời
1. Camera quay video
2. MediaPipe tìm khuôn mặt → Đánh dấu 468 điểm
3. Chọn 3 điểm quan trọng (má trái, má phải, sống mũi)
4. Tính toán hướng khuôn mặt → Ra 3 góc (yaw, pitch, roll)
5. Kiểm tra xem có làm đúng yêu cầu không
6. Nếu đúng → Chụp ảnh
7. Gửi ảnh lên server để xác thực8.2. Điểm Chính (Dễ hiểu)
MediaPipe FaceMesh:
- Làm gì: Tìm khuôn mặt và đánh dấu 468 điểm
- Giống như: Bạn vẽ chấm lên ảnh để đánh dấu các bộ phận
face-liveness.ts:
- Làm gì: Chọn 3 điểm quan trọng, tính toán hướng khuôn mặt
- Giống như: Bạn nhìn vào la bàn để biết hướng
Three.js:
- Làm gì: Chuyển đổi hướng thành 3 góc dễ hiểu (yaw, pitch, roll)
- Giống như: Bạn chuyển đổi "hướng Bắc" thành "0 độ"
faceLiveNessCheck:
- Làm gì: So sánh góc thực tế với góc yêu cầu
- Giống như: Bạn kiểm tra xem có làm đúng bài tập không
Canvas API:
- Làm gì: Chụp ảnh từ video
- Giống như: Bạn chụp ảnh màn hình
Axios:
- Làm gì: Gửi ảnh lên server
- Giống như: Bạn gửi email kèm file đính kèm
8.3. Thư viện Được Dùng (Đơn giản)
- @mediapipe/face_mesh: Tìm khuôn mặt
- three: Tính toán góc (bạn không cần hiểu chi tiết)
- axios: Gửi dữ liệu lên server
- vue: Framework chính (giao diện)
Lưu ý: Bạn không cần hiểu chi tiết toán học của Three.js. Chỉ cần biết nó tính toán góc cho bạn!
9. Câu hỏi Thường gặp (FAQ)
Q1: Tôi không hiểu "vector", "cross product", "normalize" là gì?
A: Không sao cả! Bạn không cần hiểu chi tiết. Chỉ cần biết:
- Code đang tính toán hướng khuôn mặt dựa trên 3 điểm
- Three.js sẽ tự động làm tất cả phép toán phức tạp cho bạn
- Kết quả cuối cùng là 3 góc dễ hiểu: yaw, pitch, roll
Ví dụ: Giống như bạn không cần biết động cơ xe hoạt động như thế nào, chỉ cần biết nhấn ga là xe chạy!
Q2: Tại sao cần 3 điểm? Tại sao không dùng 1 hoặc 2 điểm?
A:
- 1 điểm: Chỉ biết vị trí, không biết hướng
- 2 điểm: Chỉ biết 1 đường thẳng, không biết mặt phẳng
- 3 điểm: Đủ để xác định mặt phẳng và hướng
Ví dụ: Giống như bạn cần 3 chân để đặt bàn vững chắc!
Q3: "Rotation matrix" là gì? Tôi có cần hiểu không?
A: Không cần! Đây là công thức toán học phức tạp. Chỉ cần biết:
- Input: Hướng khuôn mặt (từ 3 điểm)
- Output: 3 góc (yaw, pitch, roll)
- Three.js tự động làm tất cả cho bạn
Ví dụ: Giống như bạn không cần biết công thức nấu ăn, chỉ cần biết món ăn ngon!
Q4: Tại sao phải đợi 500ms trước khi chụp ảnh?
A:
- Camera cần thời gian để ổn định
- Giảm blur (mờ) trong ảnh
- Ảnh sẽ rõ nét hơn
Ví dụ: Giống như bạn chụp ảnh, cần đợi camera focus xong mới chụp!
Q5: "Base64" là gì? Tại sao phải chuyển đổi?
A:
- Base64 là cách chuyển đổi ảnh thành text
- Dễ dàng gửi qua HTTP request
- Server có thể nhận và chuyển lại thành ảnh
Ví dụ: Giống như bạn zip file để gửi email, base64 là cách "zip" ảnh thành text!
Q6: Tôi có cần học toán để hiểu phần này không?
A: Không cần!
- Phần này chỉ cần hiểu "làm gì" và "tại sao"
- Không cần hiểu "như thế nào" chi tiết
- Code đã làm tất cả phép toán phức tạp cho bạn
Ví dụ: Giống như bạn không cần học cách làm bánh để ăn bánh!
10. Tài liệu Tham khảo
Code Files
portal-client/src/utils/ekyc/MediaPipeFaceMeshWrapper.tsportal-client/src/utils/ekyc/face-liveness.tsportal-client/src/pages/information/directive/FaceDetectionCamera.vueportal-client/src/utils/ekyc/face-verification.tsportal-client/src/utils/ekyc/ocr.ts
Related Articles
- Bài 03. MediaPipe Intro - Cấu hình MediaPipe
- Bài 04. Wrapper Class - MediaPipe wrapper
- Bài 06. Liveness Calculation - Tính toán liveness
- Bài 06a. Face Detection Fundamentals - Toán học cơ bản
- Bài 07. Backend Integration - API integration
Happy Coding! 🚀