Nhận diện ai-đồ với face-api.js
Trước đây, anh tôi đi code dạo đã từng làm 1 series nhận diện idol dùng Microsoft Cognitive Service nhưng sau đó không lâu phải đóng cửa sau khi được anh em ủng hộ quá nhiệt tình (lý do là đây ^^). Dạo gần đây đi lượn lượn loanh quanh tự dưng thấy bộ face-api.js cũng hay hay nên mình quyết định dựng lại trang web để nhận diện các em idol thử xem sao.
Chuẩn bị nào
Có lẽ anh em sẽ nghĩ cần phải tìm hiểu về machine learning, về thuật toán này nọ lọ chai trước khi bắt đầu.... nhưng KHÔNG. Bài toán face recognition là bài toán rất phổ biến hiện tại nên có rất nhiều thư viện hỗ trợ thuật toán sẵn giùm anh em. Việc còn lại chúng ta chỉ cần xem cách dùng những thư viện này như thế nào và làm thế nào để có thể nhận diện các idol. Và như tiêu đề thì mình sẽ dùng bộ face-api.js.
Face-api.js là gì?
JavaScript API for face detection and face recognition in the browser and nodejs with tensorflow.js
face-api.js là một JavaScript API dùng để xác định và nhận diện gương mặt trên browser và Nodejs server với tensorflow.js. Các tính năng chính bao gồm:
- Phát hiện khuôn mặt
- Xác định Face Landmark (không biết dịch sao luôn)
- Nhận diện gương mặt
- Nhận diện cảm xúc của gương mặt
- Dự đoán tuổi và nhận diện giới tính
Lý thuyết là vậy nhưng không có nghĩa anh em cứ bỏ bộ này vào là sẽ nhận diện được các idol. Chúng ta cần phải dạy cho nó nhận biết idol nào với idol nào (training step). Và muốn dạy thì chúng ta cần phải chuẩn bị "giáo án" (data). Mình cùng đi chuẩn bị "giáo án" với các "cô giáo Thảo" nào.
Thu thập dữ liệu
Đối với các dự án AI/Machine Learning, ngoài thuật toán ra thì việc thu thập và chuẩn hóa dữ liệu là vô cùng quan trọng. Thuật toán ngon đến mấy mà giáo án sơ sài (quá ít data mẫu, hoặc data không chuẩn, chất lượng không tốt) thì cũng không thể cho kết quả chính xác được.
Sau khi lục tìm kha khá trang 18+ để tìm "tài liệu", mình cũng tìm được vài nguồn "uy tín" và ko cần phải "tín dụng". Mình thường code server Nodejs nên mình cũng dùng Nodejs để viết bộ crawler. Mình xài axios để gửi request, cheerio để xử lý DOM, crawl data và xài firebase lưu data cũng như các lượt tìm kiếm để xếp hạng. Thêm vào đó là 1 ít logic với Promise và async/await để chạy mượt mà hơn (ban đầu code ngu bùm phát máy kêu o o o và out of memory luôn :(()
const fetchPage = async (url) => {
const res = await utils.fetchUrl(url);
const html = res.data;
const $ = cheerio.load(html);
const listIdolSectionsDom = $('.shop-section .portfolio .el');
const listIdolUrls = [];
listIdolSectionsDom.each(function () {
let url = $(this).find('a').attr('href');
listIdolUrls.push(domain + url);
});
for (const idolUrl of listIdolUrls) {
const data = await fetchDetail(idolUrl);
firebase.addData(data);
}
console.log('Done');
};
Đây là bước tốn nhiều thời gian và "khó tập trung" nhất. Sau 1 hồi "vật vã", mình cũng đã crawl được hơn 300 "cô giáo" và phân loại, group hình ảnh theo tên sẵn sàng để training.
Training
Mình sẽ follow theo document của face-api.js. Ở đây mình cũng sẽ train bằng server Nodejs. Đầu tiên mình sẽ down cái models mà bộ face-api.js cung cấp ở đây về, bỏ vào trong folder source code và load các model cần thiết cho việc face recognition:
- SsdMobilenetV1 Model: Pre-trained model dùng để phát hiện gương mặt.
- FaceLandmark68Net Model: Pre-trained model dùng để xác định được các điểm xung quanh mặt của mình.
- FaceRecognitionNet Model: Pre-trained model dùng để nhận dạng gương mặt.
// Load model
await faceapi.nets.ssdMobilenetv1.loadFromDisk('./ml/models');
await faceapi.nets.faceLandmark68Net.loadFromDisk('./ml/models');
await faceapi.nets.faceRecognitionNet.loadFromDisk('./ml/models');
Sau đó, mình thử viết 1 function để load hình lên và máy sẽ nhận diện các đặc điểm của 1 bức hình idol xem sao.
const trainingFunc = async (idolUrl) {
console.log('Training: ', idolUrl);
const img = await canvas.loadImage(idolUrl);
const detection = await faceapi
.detectSingleFace(img, faceDetectionOptions)
.withFaceLandmarks()
.withFaceDescriptor();
return detection.descriptor;
}
Chạy thử thì không thấy báo lỗi gì nên mình được đà lấn tới, thử train với số lượng lớn xem sao. Giờ thì bắt đầu gắn nhãn (label) cho các idol để máy biết được các đặc điểm nhận dạng đã lấy được ở trên thuộc về idol nào.
const trainIdol = async (idolLabel) => {
console.log('Training idol:', idolLabel);
const listImgPromise = [];
const listIdolsFolders = fs.readdirSync(`${folderPath}/${idolLabel}`);
const listIdolFiles = listIdolsFolders.filter((file) =>
/\.(gif|jpe?g|tiff|png|webp|bmp)$/i.test(file),
);
for (const fileName of listIdolFiles) {
listImgPromise.push(trainingFunc(`${folderPath}/${idolLabel}/${fileName}`));
}
const descriptions = await Promise.all(listImgPromise);
const LabeledFaceDescriptors = new faceapi.LabeledFaceDescriptors(
label,
descriptions,
);
console.log('Done idol:', label);
return LabeledFaceDescriptors;
};
Export trained data
Mọi thứ có vẻ thuận lợi nhưng lúc này lại xuất hiện 1 vấn đề khá nan giải khác.... Để đỡ bị tràn ram hay đơ máy trong lúc training, mình đã code để xử lý data từ từ nên sẽ tốn khá nhiều thời gian: chỉ với hơn 300 idol (tổng cộng hơn 650Mb hình ảnh), sẽ tốn gần 1 tiếng để train xong. Chẳng lẽ lúc nào muốn nhận diện 1 bức hình cũng phải chờ chừng đó thời gian????
Chắc chắn là không thể như vậy được. Trong các dự án AI/ML, sau khi training xong thì họ sẽ export kết quả của quá trình training ra để có thể xử dụng ở các dự án khác, giúp tiết kiệm thời gian training. Và với project này của mình chắc chắn cũng không ngoại lệ.
Lại chui vào document của face-api.js tìm cách để export data nhưng.... chẳng thấy gì cả :(( Tưởng đã bế tắc nhưng mình tin chắc chắn phải có nhưng thanh niên cùng suy nghĩ với mình. Vậy là bay qua mục Issues của project tìm kiếm. Quả là trời không phụ lòng người. Sau 1 hồi ngụp lặn trong mớ issues của face-api.js, mình cũng đã thấy nó.
(async () => {
const descriptors = await detectAllLabeledFaces();
console.log('+++++++++ Export model +++++++++');
saveFile(
'labeledFacesData.json',
JSON.stringify(descriptors.map((x) => x.toJSON())),
);
})();
Thử phát thì ai ngờ nó chạy được thật.... và kết quả là đây :D
Dựng UI web
Sau khi bộ core có vẻ ổn, mình bắt đầu dựng nhanh UI cho trang web. Do bộ face-api.js có thể chạy được trên browser nên mình quyết định sẽ không dùng server (để tiết kiệm chi phí) và bỏ trained data xuống web chạy luôn.
Mình code bằng React.js, UI thì dùng bộ Ant design cho nhanh. Cũng chỉ là nhét mấy cái component vào với nhau + chỉnh nhẹ css thôi nên cũng hem có gì để nói về bước này. Sau 1 hồi nghí ngoáy, cũng ra được cái UI tàm tạm đơn giản :D
Túm váy lại
Một project nhỏ nhỏ thử chơi, cũng được nghịch nhiều thứ như crawler data, Machine Learning.... cuối cùng cũng build ra được 1 trang web chạy được. Tới đây chắc nhiều anh em nghĩ build 1 trang web với face recognition cũng đơn giản (ez như 1 trò đùa) thôi nhưng.... đời không như là mơ :((
Sau khi build xong mình nhận ra còn rất nhiều vấn đề hóc búa cần phải giải quyết. Đơn cử như việc data mình export ra, sau khi train 650Mb hình idol, nặng hơn 20Mb @@ Và với quyết định không xài server của mình thì lần đầu tiên truy cập vào trang web, người dùng sẽ phải chờ down file data hơn 20Mb đó (chưa kể thời gian để face-api.js load đống data đó nữa :(( không ăn chửi mới là lạ :(((()
Hay 1 vấn đề đau đầu hơn nữa với các dự án AI/ML là về.... độ chính xác. Ban đầu, mình thử với chỉ vài idol thì nhận diện khá chính xác. nhưng sau khi mình train với hơn 300 idol thì.... kết quả là đây =))))
Máy nhận diện mình là em Yura Sakura mới ghê :)))) Cạn lời thật sự luôn anh em ah @@
Còn rất nhiều vấn đề cần phải xử lý nên chắc bài này sẽ chưa thể kết thúc ở đây được. Mình sẽ cố gắng tìm hiểu và sẽ gửi đến anh em trong những bài viết sau :D
P/s: trong lúc chờ phiên bản chính thức, anh em có thể nghịch thử version sida tại đây