Facial Landmark - xây dựng hệ thống cảnh báo ngủ gật

 

Chào mọi người, tiếp nối bài viết dự báo giá cổ phiếu với mạng LSTM mình muốn chia sẻ một phương pháp nhận dạng khuôn mặt đơn giản. Sau khi tìm hiểu xong, mình sẽ thử áp dụng để xây dựng hệ thống cảnh báo chống ngủ gật dành cho tài xế. Nếu có bất kỳ khó khăn hay thắc mắc nào trong quá trình tìm hiểu thì mọi người trao đổi với mình bằng cách bình luận phía dưới bài viết nha.

Thư viện dlib

Dlib - thư viện khiến việc nhận diện khuôn mặt trở nên dễ dàng trong OpenCV. Dlib xác định các yếu tố cấu thành nên khuôn mặt người (mắt, mũi miệng,...) và khoảng cách giữa các thành phần đó. Bộ quy tắc chung này được xây dựng để có thể nhận dạng đối tượng trên khuôn mặt người. Dlib cung cấp 68 điểm neo trên khuôn mắt, từ các điểm neo này mà chúng ta có thể dễ dàng bắt được các chuyển động trên khuôn mặt hoặc xác định vị trí các thành phần mắt, mũi, miệng, lông mày và đường viền khuôn mặt.


Trước khi  dlib Facial Landmark 68 xác định các thành phần trên khuôn mặt, chúng ta phải nhận diện đâu là một khuôn mặt trong khung hình. Do đó, cần sự hỗ trợ của thuật toán HOG (Histogram of Oriented Gradients) và SVM (Support Vector Machine) nhằm phát hiện khuôn mặt và tách khuôn mặt ra khỏi khung hình để áp dụng phương pháp xác định cử chỉ thông qua Facial Landmark. Thật may là thư viện dlib tích hợp các thuật toán này. Không những thế, hiện tại dlib còn tích hợp nhận dạng khuôn mặt thông qua mạng CNN. Trong hướng dẫn này, mình sẽ sử dụng HOG và SVM.

Làm quen dlib với ảnh

Các bạn có thể tải xuống mã nguồn chương trình tại đây. Chúng ta sẽ thử xử lý ảnh đơn bằng cách chạy chương trình facial_landmark_image.py

# facial_landmark_image.py
image = 'path/to/your/image.jpg'
model = 'model/shape_predictor_68_face_landmarks.dat'

detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor(model)

image = cv2.imread(image)
image = imutils.resize(image, width=1600)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

rects = detector(gray, 1)

for (i, rect) in enumerate(rects):
shape = predictor(gray, rect)
shape = face_utils.shape_to_np(shape)

(x, y, w, h) = face_utils.rect_to_bb(rect)

cv2.putText(image, "nguyentrieuphong.com no.{}".format(i + 1), (x - 10, y - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1)

for (x, y) in shape:
cv2.circle(image, (x, y), 2, (0, 255, 0), -1)

cv2.imwrite('D:/output.jpg', image)
cv2.imshow("Output", image)
cv2.waitKey(0)


Thay đổi biến image bằng đường dẫn đến hình ảnh cần xác định các điểm neo sau đó khai báo đường dẫn đến model facial landmark, ví dụ như sau.

image = 'D:/test.jpg'
model = 'model/shape_predictor_68_face_landmarks.dat'

Đoạn python phía dưới, mình khai báo phương pháp nhận diện thông qua hàm dlib.get_frontal_face_detector(). Khái báo mô hình thông qua hàm dlib.shape_predictor().

detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor(model)

Thông thường khi xử lý ảnh mà không quan tâm đến màu sắc, nên chuyển ảnh đó sang ảnh xám. Mỗi pixel ảnh màu chứa cả 3 thông số RGB quy định màu sắc, hay nói cách khác ảnh màu là ảnh màu tập hợp các ma trận R, G, B riêng biệt có cùng kích thước. Trong khi đó, nếu chuyển ảnh màu về ảnh xám chúng ta sẽ được 3 ma trận xám R_gray, G_gray, B_gray tương ứng. Dễ dàng tổng hợp 3 ma trận xám này thành một ma trận duy nhất với công thức sau: Y = 0.2126R_gray + 0.7152G_gray + 0.0722B_gray (một trong những công thức thường được sử dụng)


Ảnh xám sau đó sẽ được đẩy vào hàm dlib.get_frontal_face_detector() thông qua biến detector, với các biến truyền vào gồm hình ảnh cần phát hiện khuôn mặt (ảnh xám) và độ phóng đại kích thước hình ảnh. Hàm trả về một list là danh sách các khuôn mặt đã phát hiện trong khung hình dưới dạng rectangle tuple với các thông số (left, top), (right, bottom) là các toạ độ tạo nên khung viền bao quanh khuôn mặt, lưu các kết quả này vào biến rects.

Một vòng lặp sẽ thực hiện vẽ các thông tin cần thiết và các điểm neo lên hình ảnh ban đầu (ảnh màu). Sau đó, lưu kết quả và hiển thị.


Phương pháp bắt chuyển động mắt

Trong bài báo được Soukupová và Čech's xuất bản năm 2017, các tác giả sử dụng phương pháp tỷ lệ khung hình mắt để phát hiện nháy mắt theo thời gian thực thông qua tính toán khoảng cách giữa các điểm neo trên khuôn mặt.


Theo đó, tỉ lệ giữa khoảng cách mí trên - mí dưới và khoảng cách giữa 2 đuôi mắt sẽ được tính toán để xác định trạng thái của mắt - tạm gọi là tỉ lệ khép mắt. Theo đồ thị kết quả phía trên, chúng ta có thể thấy tỉ lệ này giảm đột ngột về 0 khi phát hiện chớp mắt. Chúng ta có thể áp dụng phương pháp này để phát hiện ngủ gật bằng việc cài đặt một timer cho hành động nhắm mắt. Bắt đầu bằng việc xác định vùng mắt với dlib.

(lStart, lEnd) = face_utils.FACIAL_LANDMARKS_IDXS["left_eye"]
(rStart, rEnd) = face_utils.FACIAL_LANDMARKS_IDXS["right_eye"]

Function tính tỉ lệ khép mắt, yêu cầu khai báo hàm distance với tên dist trong thư viện scipy

def eye_aspect_ratio(eye):
A = dist.euclidean(eye[1], eye[5])
B = dist.euclidean(eye[2], eye[4])
C = dist.euclidean(eye[0], eye[3])
ear = (A + B) / (2.0 * C)
return ear

Xác định vùng mắt trên ảnh đơn, kết quả được như sau.


Áp dụng vào bắt chuyển động mắt trên video (video này được mình tạo từ ảnh, các bạn có thể tạo một video tương tự tại https://www.myheritage.com) được kết quả như sau:

Xây dựng hệ thống trên thời gian thực

Để hệ thống có thể xử lý trong thời gian thực, mình sử dụng một camera để theo dõi đối tượng lái xe sau đó xử lý từng frame trong video đó. Các bạn có thể dùng webcam của laptop hoặc chạy chương trình trên Raspberry Pi với Pi camera đều được.

    webcam = 0
    vs = VideoStream(webcam).start()
    time.sleep(1.0)

Sau khi khai báo webcam cần tạo một vòng lặp để xử lý các frame.

    while True:
  
    frame = vs.read()
    frame = imutils.resize(frame, width=450)
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    rects = detector(gray, 0)

    for rect in rects:             # # loop over the face detections

Tạo một theard riêng để xử lý báo động, đồng thời tạo hàm báo động khi phát hiện ngủ gật.

import playsound
from threading import Thread
alarm = ('path/to/alarm.wav')

def sound_alarm():
# play an alarm sound
playsound.playsound(alarm)

if alarm != "":
t = Thread(target=sound_alarm)
t.deamon = True
t.start()

Kết quả

Nhận xét

Thư viện dlib rất hiệu quả khi sử dụng với các dự án nhỏ thế này. Độ chính xác và tốc độ nhận dạng đều khiến mình ngạc nhiên. Mình đã thử nhúng mô hình này vào Raspberry Pi 3 và kết quả là hoạt động ổn định. Đối với phương pháp nhận dạng bằng RCNN thay vì HOG, độ chính xác sẽ tăng nhưng thời gian xử lý sẽ đánh đổi. Các bạn có thể tải về bộ code tham khảo cho ứng dụng này tại đây. Trong quá trình thực hành nếu có bất kì thắc mắc gì các bạn bình luận phía dưới bài viết này nhé, mình sẽ trả lời ngay khi có thể. Hy vọng các bạn chia sẻ để ủng hộ mình ra các bài viết mới.