Chào anh em, đợt trước mình đã có giới thiệu qua về model Facenet tại đây. Tuy nhiên đó là bài làm việc trực tiếp trên source của Facenet để nếu anh em có train lại hay cấu hình, tinh chỉnh gì đó thì mới cần. Hôm nay tranh thủ rảnh rang chút mình triển khai bài toán nhận diện mặt trong video dùng Facenet pretrain trên Keras để anh em biết thêm 1 cách nữa nhé. Và cũng là để cho bài toán có tính chất thú vị, đỡ buồn ngủ thì mình có mời mấy em Japan Anti Virus gọi tắt là JAV về tham dự với vai trò làm model cho vui kaka.
Tóm tắt vấn đề:
- Bài toán: Nhận diện diễn viên JAV trong video.
- Các kiến thức sẽ học được:
- Kỹ năng tách ảnh nhỏ từ ảnh to sử dụng slice numpy array
- Kỹ năng cài đặt và triển khai model Facenet trên Keras
OK! Let’s go!
Đầu tiên là anh em tạo cho mình cái thư mục MìAI_JAV đã nào, chứa các thứ cho gọn. Sau đó là clone cái git về đã nhé
git clone https://github.com/thangnch/MiAI_JAV
Code language: PHP (php)
Xong thì cài đặt các thư viện bằng lệnh:
pip install -r setup.txt
Code language: CSS (css)
Cài xong thì anh em tải cái pretrain facenet.h5 về nhé. Link tải tại đây, tải về anh em copy vào thư mục có mấy file .py ấy nhé.
Phần 1 – Nói qua về Facenet trên Keras
Facenet thì quá nổi tiếng rồi và bữa trước mình đã có làm việc với project FaceNet by David Sandberg. Project này viết bằng TF và giờ có một đại ca khác convert sang Keras cho chúng ta tại đây FaceNet by Hiroki Taniai. Với anh em mới học thì cú pháp của Keras vẫn dễ đọc, dễ hiểu hơn thằng TF. Do đó hôm nay chúng ta sẽ sử dụng pretrain Facenet của bác này để làm việc nhé.
Phần 2 – Các bước triển khai model Facenet trên Keras
Nhiều bạn vẫn chưa hiểu khi sử dụng pretrain thế thì sẽ làm như nào, các bước ra sao nên mình cùng bắt đầu từng bước luôn nha.
Bước 1 – Tiền xử lý ảnh
Bài này nếu đúng ra phải collect ảnh của đầy đủ các diên viên, càng nhiều càng tốt, càng nhiều “tư thế” càng tốt thì kết quả sẽ cao hơn. Tuy nhiên đây là bản demo và đợt này mình cũng bận dự án nên chơi các lazy, mà nhờ cách lazy mà ta lại học thêm được một tý về xử lý ảnh.
Cách lazy của mình là kiếm 1 cái ảnh tổng hợp các “thánh nữ” như sau:
Bây giờ cúng ta cần tách các khuôn mặt này ra thành các folder riêng, mỗi folder chứa ảnh 1 khuôn mặt để chút nữa train Facenet SVM cho dễ.
Đại khái chúng ta nhìn qua sẽ có 50 ảnh chia thành 10 cột và 5 dòng, chúng ta sẽ lặp và dùng slice để lấy riêng từng ảnh ra (và chú ý trừ độ cao đi 1 chút dể bỏ đi phần tên bên dưới hạn chế nhiễu khi train). Đoạn code như sau:
import cv2
import os
image = cv2.imread("jav1.jpg")
# Tính toán phần cao và rộng của ảnh từng khuôn mặt
W = image.shape[1]//10
H = image.shape[0]//5
cv2.imshow("A", image)
# Đọc tên các em từ trái sang phải, trên xuống dưới
namelist = []
infile = open('name.txt','r')
for line in infile:
namelist.append(line.strip())
infile.close()
# Bắt đầu loop và cắt
c = 0
for i in range(1,6):
for j in range(1,11):
c +=1
# Trừ đi 35 pixel tên bên dưới
imageij = image[(i-1)*H:i*H-35,(j-1)*W:j*W]
# Ghi vào thư mục
os.mkdir("data/raw/" + str(namelist[c-1]))
cv2.imwrite("data/raw/" + str(namelist[c-1]) + "/" + str(namelist[c-1]) + ".png",imageij)
Code language: PHP (php)
Sau khi xử lý xong chúng ta sẽ có được thư mục data/raw chứa đầy ảnh các em, mỗi em một folder. Các bạn chú ý tạo trước trong thư mục MiAI_JAV một thư mục data và một thư mục raw trong thư mục data đó nhé.
Công việc tiền xử lý mình để trong file preprocess.py.
Bước 2 – Trích xuất khuôn mặt từ ảnh raw
Với bài toán thông thường thì ảnh raw đầu vào sẽ phong phú và đa dạng lắm, đủ các tư thế đứng, ngồi, nằm với các chi tiết thừa xung quanh nên để có ảnh chất lượng để train chúng ta sẽ chỉ trích xuất đúng khuôn mặt thôi.
Ví dụ như sau:
Với bài toán này chúng ta không cần thực hiện việc đó vì mặt các em đã được CROP sẵn rồi, tuy nhiên với các bài toán khác các bạn cần chạy một đoạn script như sau để crop mặt và lưu sang thư mục data/processed với cấu trúc tương tự (mỗi em 1 folder và trong các folder chỉ còn mặt, các khúc khác bỏ đi).
Hàm trích khuôn mặt như sau:
def get_faces(raw_folder=raw_folder, processed_folder=processed_folder, copy=True):
dest_size = (160, 160)
print("Bắt đầu xử lý crop mặt...")
# Lặp qua các folder con trong thư mục raw
for folder in listdir(raw_folder):
# Tạo thư mục chứa ảnh processed
os.mkdir(processed_folder + folder);
# Lặp qua các file trong từng thư mục chứa các em
for file in listdir(raw_folder + folder):
image = Image.open(raw_folder + folder + "/" + file)
image = image.convert('RGB')
# Nếu không cần xử lý thì chỉ resize và save
if copy:
image = image.resize(dest_size)
# Save file
image.save(processed_folder + folder + "/" + file)
else:
# Nếu cần xử lý thì đọc ảnh và lấy mặt
pixels = asarray(image)
results = detector.detect_faces(pixels)
# Chỉ lấy khuôn mặt đầu tiên, ta coi các ảnh train chỉ có 1 mặt
x1, y1, width, height = results[0]['box']
x1, y1 = abs(x1), abs(y1)
x2, y2 = x1 + width, y1 + height
face = pixels[y1:y2, x1:x2]
image = Image.fromarray(face)
image = image.resize(dest_size)
# Save file
image.save(processed_folder + folder + "/" + file )
return
Code language: PHP (php)
Bước 3 – Tạo vector đặc trưng / embeding vector từ các khuôn mặt
Sau bước số 2 chúng ta đã có các khuôn mặt lưu trong data/processed. Bây giờ chúng ta sẽ tạo vector đặc trưng bằng cách sử dụng pretrain model keras-facenet. Nói nôm na là ta dưa ảnh qua cái pretrain model này và nhận đầu ra 1 vector đặc trưng cho cái ảnh đó.
Đầu tiền là load model facenet:
facenet_model = load_model('facenet_keras.h5')
Code language: JavaScript (javascript)
Sau đó chúng ta lặp qua các ảnh trong thư mục data/processed và tạo vector embeding lưu vào X_train. Ta cũng lưu label vào y_train và thực hiện LabelEncode cái y_train đó luôn (chuyển từ “Maria”, “Yuna”…. thành 0,1,2,3…).
def load_faces(train_folder = processed_folder):
if os.path.exists("faces_data.npz"):
data = np.load('faces_data.npz')
X_train,y_train = data["arr_0"],data["arr_1"]
return X_train, y_train
else:
X_train = []
y_train = []
# enumerate folders, on per class
for folder in listdir(train_folder):
# Lặp qua các file trong từng thư mục chứa các em
for file in listdir(train_folder + folder):
# Read file
image = Image.open(train_folder + folder + "/" + file)
# convert to RGB, if needed
image = image.convert('RGB')
# convert to array
pixels = np.asarray(image)
# Thêm vào X
X_train.append(pixels)
y_train.append(folder)
X_train = np.array(X_train)
y_train = np.array(y_train)
# Check dữ liệu
print(X_train.shape)
print(y_train.shape)
print(y_train[0:5])
# Convert du lieu y_train
output_enc = LabelEncoder()
output_enc.fit(y_train)
y_train = output_enc.transform(y_train)
pkl_filename = "output_enc.pkl"
with open(pkl_filename, 'wb') as file:
pickle.dump(output_enc, file)
print(y_train[0:5])
# Convert du lieu X_train sang embeding
X_train_emb = []
for x in X_train:
X_train_emb.append( get_embedding(facenet_model, x))
X_train_emb = np.array(X_train_emb)
print("Load faces done!")
# Save
np.savez_compressed('faces_data.npz', X_train_emb, y_train);
return X_train_emb, y_train
Code language: PHP (php)
Sau khi làm xong ta lưu các vector đó vào file faces_data.npz để dùng lần sau.
Bước 4 – Train model SVM để predict các em
Bây giờ ta tạo một model SVM, sử dụng kernel linear để classify mấy em này dựa vào chỗ vector đặc trưng đã tạo ở trên.
# Main program
X_train, y_train = load_faces()
# Train SVM với kernel tuyến tính
model = SVC(kernel='linear',probability=True)
model.fit(X_train, y_train)
# Save model
pkl_filename = "faces_svm.pkl"
with open(pkl_filename, 'wb') as file:
pickle.dump(model, file)
print("Saved model")
Code language: PHP (php)
Sau khi train xong thì ta lưu vào file faces_svm.pkl để load ra dùng ở bước test nhé.
Tất cả các bước này các bạn xem trong file facenet_svm_train.py nhé.
Phần 3- Kiểm thử bằng cách nhận diện video Ji A Vê
Rồi bây giờ các bạn chuyển sang phase test. Về phase này thì chúng ta sẽ làm như sau:
Bước 1 – Load các model chúng ta đã train
Có model SVM và cái LabelEncoder đã lưu, chúng ta thực hiện load lên chờ sẵn:
# Load SVM model từ file
pkl_filename = 'faces_svm.pkl'
with open(pkl_filename, 'rb') as file:
pickle_model = pickle.load(file)
# Load ouput_enc từ file để hiển thị nhãn
pkl_filename = 'output_enc.pkl'
with open(pkl_filename, 'rb') as file:
output_enc = pickle.load(file)
Code language: PHP (php)
Bước 2 – Đọc từng frame ảnh từ video và xử lý
Cách làm tương tự như phase train. Với mỗi ảnh có được chúng ta detect xem có mặt trong đó không, nếu có chúng ta sẽ đưa qua model facenet pretrain để lấy vector embedding.
Sau khi có vector đó rồi đưa vào model SVM đã train để nó classify ra là em nào và viết tên em ấy lên màn hình thôi 😀
Các bạn đọc chi tiết trong file facenet_svm_test.py nha!
while(True):
# Capture ảnh từ video
ret, frame = cap.read()
if not ret:
break
pixels = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# Detect khuôn mặt
results = detector.detect_faces(pixels)
if len(results) > 0:
# Chỉ lấy khuôn mặt đầu tiên, ta coi các ảnh train chỉ có 1 mặt
x1, y1, width, height = results[0]['box']
x1, y1 = abs(x1), abs(y1)
x2, y2 = x1 + width, y1 + height
face = pixels[y1:y2, x1:x2]
image = Image.fromarray(face)
image = image.resize(dest_size)
# Lây face embeding
face_emb = get_embedding(facenet_model, np.array(image))
# Chuyển thành tensor
face_emb = np.expand_dims(face_emb, axis=0)
# Predict qua SVM
y_hat = pickle_model.predict(face_emb)
# Lấy nhãn và viết lên ảnh
predict_names = output_enc.inverse_transform(y_hat)
if predict_names!=None:
cv2.putText(frame,predict_names[0],(50,50),cv2.FONT_HERSHEY_COMPLEX,1,(0,255,0),2)
Code language: PHP (php)
Và thành quả của các anh em đây rồi, ảnh GIF luôn cho nó nóng 😀
Phần 4 – Tổng kết
Vậy là bài này mình đã chỉ anh em cách dùng facenet trên keras đơn giản hơn lần trước. Anh em có thể dùng cách này nếu như không có nhu cầu train lại Facenet bằng dataset của mình.
Tuy nhiên, bài này mình đang làm ở mức cơ bản nhất, anh em có thể tăng độ chính xác bằng cách search các keyword về : augment face, align face… để model chính xác hơn nhé.
Chúc anh em thành công! Hãy join cùng Mì AI nhé!
#MiAI
Fanpage: http://facebook.com/miaiblog
Group trao đổi, chia sẻ: https://www.facebook.com/groups/miaigroup
Website: https://miai.vn
Youtube: http://bit.ly/miaiyoutube