Blog #15 - Ứng dụng AI tự động chuyển màn hình code khi phát hiện sếp đến gần

13 phút đọc

Xin chào các bạn. Có lẽ sợ sếp là một bệnh thâm niên ở mỗi người làm văn phòng nói chung và đặc biệt là anh em IT nói riêng. Đã bao giờ bạn gặp phải tình huống rất oái oăm khi mà ngồi code cả buổi thì sếp chả ghé thăm, đến lúc vừa rảnh tay lên Youtube nghe nhạc một tý xíu thì sếp đến nhẹ nhàng vỗ vai và thủ thỉ vào tai bạn một câu nói 2 giây nhưng dài như thế kỉ ARE YOU CODING?. Thấu hiểu nỗi đau đó của anh em, hôm nay mình mạn phép hướng dẫn các bạn làm một ứng dụng rất hay ho đó là xử dụng AI và các mô hình Deep Learning để qua camera sẽ tự động phát hiện sếp sắp đến lại gần và rồi tự động chuyển ngay màn hình sang chế độ coding rất hữu dụng đặc biệt là cho anh em IT. Xin lưu ý mục đích của bài viết chỉ mang tính chất vui vẻ, giúp anh em cảm thấy đỡ áp lực sau những giờ làm việc căng thẳng. Hoàn toàn không cổ vũ cho việc mọi người làm việc riêng trong giờ làm.. OK chúng ta sẽ bắt đầu ngay thôi. Tuy nhiên trước tiên mời bạn xem thử demo dưới đây.

Bước 1: Cách tiếp cận bài toán

Đối với một bài toán mới chúng ta cần phải phân tích bài toán để tìm ra cách tiếp cận sao cho phù hợp. Tức là phân tích ra inputoutput của vấn đề. Đối với bài toán đã nêu ra, mục đích của chúng ta là phát hiện xem khi nào sếp đến (tất nhiên là dựa trên hình ảnh) vậy chắc chắn đầu vào của chúng ta sẽ từ một ảnh khuôn mặt, đầu ra cần phải xác định đó có phải là sếp hay không và nếu chúng ta xác định được đó là sếp thì thực hiện một xử lý nào đó (ví dụ như bật màn hình code lên thay vì màn hình đọc báo chẳng hạn). Vậy tựu chung lại chúng ta có thể tổng quát hóa các bước lại như sau:

  • Khoanh vùng được các khuôn mặt
  • Nhận ra xem khuôn mặt đó có giống sếp hay không
  • Nếu giống sếp thì bật màn hình code lên (như đang làm việc bình thường)
  • Nếu không phải sếp thì cứ đọc báo tiếp.

Hai bước sau thì đơn giản rồi phải không, bây giờ chúng ta sẽ bàn luận đến hai bước đầu tiên nhé. Tạm gác lại việc làm sao để khoanh vùng được mặt trong một bức ảnh bởi vì chúng ta không có quà nhiều thời gian. Hơn nữa bài toán này cũng khá quen thuộc nếu như các bạn chịu khó follow các topic về Deep Learning trên Viblo. Chuyển sang bước thứ hai, làm sao để biết được khuôn mặt được khoanh vùng có phải là sếp của mình hay không? Chúng ta cùng tìm hiểu hai cách tiếp cận cho vấn đề này nhé

Cách tiếp cận theo Face Verificiation

Hiểu đơn giản thì cách tiếp cận này sẽ đi trả lời câu hỏi:

Với một khuôn mặt này có giống với sếp mình hay không và nếu giống thì giống bao nhiêu phần trăm.

Ở đây chúng ta đơn thuần đi so sánh (compare) khuôn mặt mới với một khuôn mặt cũ đã có trong cơ sở dữ liệu (ví dụ các ảnh của sếp) xem hai bức ảnh này giống nhau bao nhiêu phần trăm. Thông thường bài toán này dẫn chúng ta đến một bài toán khác là làm thế nào để xác định được hai khuôn mặt này có giống nhau hay không. Như chúng ta đã biết máy tính chỉ hoạt động trên số, và tất cả cá giải thuật về Deep Learning cũng đều quy về việc xử lý trên không gian ma trận thực. Chính vì thế chúng ta cần một cách nào đó để mã hóa một ảnh của chúng ta thành vector gọi là embeding. Chúng ta có thể xem sơ đồ xử lý như sau:

Trên hình chúng ta thấy nhiệm vụ của Face Embedding Model chính là chuyển đổi từ hình ảnh sang một vector số thực. Để làm được điều này thường người ta sử dụng mạng nơ ron với hàm loss là một Triplet loss. Mọi người có thể tìm hiểu thêm về kĩ thuật này trong các bài viết khác trên Viblo

Hiểu đơn giản đó là việc chúng ta đưa những ảnh có nội dung giống nhau lại gần nhau và ảnh có nội dung khác nhau ra xa nhau. Mọi người có thể tìm hiểu thêm về phương pháp này trên các bài viết về Deep Learning trên Viblo. Giờ chúng ta sẽ cùng tìm hiểu phương pháp tiếp theo.

Cách tiếp cận theo Face Recognition

Thay vì việc phải tính toán một độ tương tự và so sánh như cách tiếp cận đầu tiên, chúng ta sẽ định nghĩa một mô hình và câu trả lời output của nó chính là xác suất của khuôn mặt có phải là sếp hay không. Việc này được giải quyết khá đơn giản và hiệu quả bởi mạng nơ ron tích chập (CNN) chúng ta có thể thấy trong mô hình sau:

Lúc này mạng của chúng ta có thể trả lời luôn câu hỏi rằng

Đây là ai? - Output là sếp hoặc không phải là sếp OK sau khi đã hiểu một vấn đề chúng ta sẽ tiến hành lựa chọn một trong hai phương pháp để tính toán. Trong bài viết này mình sẽ sử dụng phương pháp thứ hai và các bạn có thể thử cài đặt phương pháp thứ nhất xem sao. Mình nghĩ là không có nhiều khó khăn lắm đâu. OK chúng ta bắt đầu nhé

Bước 2: Chuẩn bị dữ liệu.

Đối với mỗi một bài toán Machine Learning nào đều cần phải chuẩn bị về mặt dữ liệu. Nếu không có dữ liệu thì sẽ không có gì cả. Các bạn hãy thu thập các dữ liệu mặt của sếp mình, càng nhiều càng đa dạng càng tốt, Kiểu đa sắc thái như thế này là một nguồn dữ liệu rất quý giá.

Sau đó thu thập dữ liệu của các loại mặt khác không phải là mặt của sếp mình cho tất cả vào một folder chẳng hạn. Lúc này chúng ta sẽ có hai folder có tổ chức như sau:

Càng bạn lưu ý rằng nên thu thập nhiều ảnh một chút model sẽ chính xác hơn

Bước 3: Tiền xử lý dữ liệu

Đây là một bước khá quan trọng trong khi thực hiện một bài toán Deep Learning nào. Chúng ta đã biết rằng Garbage in - Garbage out chính vì thế nên việc đưa dữ liệu vào trong mô hình cũng cần phải được xử lý một cách kĩ lưỡng. Chúng ta có một vài thao tác xử lý dữ liệu như sau:

  • Resize image
  • Chuyển về gray scale (nếu cần)
  • Normalization image - chuẩn hóa ảnh giúp cho việc training được cải thiện về tốc độ và độ chính xác
  • Augumentation image - giúp sinh thêm các dữ liệu mới, làm giàu hơn tập dữ liệu đã có
  • Loại bỏ các dữ liệu nhiễu giúp mô hình không bị lệch
  • Cân bằng lại dữ liệu giúp cho mô hình học được tổng quát hơn

Trong bài viết này chúng ta sử dụng Keras và model Mobilenet để training nên các bước tiền xử lý dữ liệu này chúng ta sử dụng trực tiếp của Keras luôn. Tiến hành import các thư viện cần thiết:

# Import dependency 
import os 
import cv2
import keras
import numpy as np
from keras import backend as K
from keras.layers.core import Dense, Activation
from keras.optimizers import Adam
from keras.metrics import categorical_crossentropy
from keras.preprocessing.image import ImageDataGenerator
from keras.preprocessing import image
from keras.models import Model
from keras.applications import imagenet_utils
from keras.layers import Dense, GlobalAveragePooling2D
from keras.applications import MobileNet
from keras.applications.mobilenet import preprocess_input
from IPython.display import Image
from keras.optimizers import Adam

Và định nghĩa các tham số

# Define parameters
IMAGE_SIZE = 224
IMAGE_DATA = './data'

images = []
labels = []

Sau đó chúng ta định nghĩa hàm preprocess như sau:

def prepare_image(file):
    img_path = ''
    img = image.load_img(img_path + file, target_size=(IMAGE_SIZE, IMAGE_SIZE))
    img_array = image.img_to_array(img)
    img_array_expanded_dims = np.expand_dims(img_array, axis=0)
    return keras.applications.mobilenet.preprocess_input(img_array_expanded_dims)

Và đoạn code để sinh dữ liệu mẫu dùng cho việc training như sau:

# Generate dataset

for path, subdirs, files in os.walk(IMAGE_DATA):
    for name in files:
        img_path = os.path.join(path, name)
        images.append(prepare_image(img_path))
        labels.append(path.split('/')[-1])

Bước 4: Định nghĩa mô hình

Giống như đã mô tả ở trên chúng ta sử dụng mô hình Mobilenet để dự đoán mặt vào có phải là sếp hay không. Mình sẽ không nhắc lại lý thuyết về CNN nữa vì nó đã xuất hiện quá nhiều trên Viblo cũng như các diễn đàn khác. Các bạn có thể tìm hiểu nhé.

Khai báo mô hình đơn giản với Keras như sau:

model = keras.applications.mobilenet.MobileNet(classes=2, weights=None)

Mọi người có thể sử dụng model.summary() để hiển thị số lượng các tham số của mô hình

Mapping label

Vì mô hình của chúng ta là mô hình phân loại nên chúng ta sẽ cần sử dụng hàm loss là cross_entropy để tính toán. Chính vì thế nên việc đầu tiên là chuyển các class từ boss thành 1 và other thành 0. Chúng ta thực hiện như sau

# Mapping label

mapped_labels = list(map(lambda x: 1 if x == 'boss' else 0, labels))

from keras.utils import np_utils

y_data = np_utils.to_categorical(mapped_labels)

Bước 5: Phân chia dữ liệu

Chúng ta sử dụng thư viện train_test_split để tiến hành phân chia dữ liệu thành hai tập training và testing. Giống như bao bài toán khác

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(images, y_data, test_size=0.2)

Bước 6: Training thuật toán

Giờ là lúc chúng ta dạy cho mô hình biết được đâu là boss và đâu không phải là boss rồi đấy. Có một lưu ý là do mạng Mobilenet là một hàm khá lớn dù là 2 classs nhưng số lượng tham số cũng lên đến 3,230,914 nên chúng ta cần sử dụng nhiều dữ liệu hơn để tránh bị Overfiting khiến cho mô hình kém hiệu quả. Dù sao thì dữ liệu vẫn là yếu tố quan trọng nhất phải không các bạn. Tiếp theo chúng ta cần complie mô hình

Complie mô hình

model.compile(loss='categorical_crossentropy',
                           optimizer='adam',
                           metrics=['accuracy'])

Đặt checkpoint

Việc đặt checkpoint rất quan trọng trong quá trình training mô hình. Giúp chúng ta lưu lại được mô hình tốt nhất theo một tiêu chí nào đó ví dụ như val_loss hay val_accuracy cũng là cách để chúng ta phòng tránh trường hợp đang training thì mất điện hay muốn training tiếp trong lần sau mà không cần training lại từ đâu. Tóm lại là phải viết checkpoint vào không mất mô hình đừng kêu

from keras.callbacks import ModelCheckpoint

model_file = "boss_model/model_mobilenet.hdf5"

checkpoint = ModelCheckpoint(model_file, monitor='val_loss', verbose=1, save_best_only=True, mode='min')
callbacks_list = [checkpoint]

Training

Sau cùng là bước training

model.fit(x=X_train, y=y_train, batch_size=16, epochs=100, verbose=1, validation_data=(X_test, y_test), callbacks=callbacks_list)

Các bạn cứ đợi và theo dõi thôi nhé. Do tập dữ liệu của mình nhỏ khoảng 750 ảnh nên việc training cũng sẽ diễn ra rất nhanh thôi. Đây là kết quả training trên máy của mình sau vài epochs

Epoch 1/100
750/750 [==============================] - 5s 37ms/step - loss: 0.3298 - acc: 0.8667 - val_loss: 0.0221 - val_acc: 1.0000

Epoch 00001: val_loss improved from inf to 0.02205, saving model to boss_model/model_mobilenet.hdf5
Epoch 2/100
750/750 [==============================] - 3s 7ms/step - loss: 0.0143 - acc: 0.9600 - val_loss: 0.0159 - val_acc: 0.9465

Epoch 00002: val_loss improved from 0.02205 to 0.01592, saving model to boss_model/model_mobilenet.hdf5
Epoch 3/100
750/750 [==============================] - 3s 7ms/step - loss: 0.0017 - acc: 0.9952 - val_loss: 0.0027 - val_acc: 0.9815

Bước 7: Chuẩn bị mô hình cho deploy

Lưu mô hình

Sau khi tiến hành traininig lại mô hình nhiều lần chúng ta sẽ thu được một mô hình tốt nhất và sử dụng nó để deploy lên hệ thống thực. Việc này được thực hiện như sau:

model.save('boss_model/final_model.h5')

Load lại mô hình

Và sau đó các bạn thử load lại mô hình này và predict một vài mẫu dữ liệu xem sao nhé:

from keras.models import load_model
import keras
from keras.utils.generic_utils import CustomObjectScope

# Load pretrained model
with CustomObjectScope({'relu6': keras.applications.mobilenet.relu6,'DepthwiseConv2D': keras.applications.mobilenet.DepthwiseConv2D}):
    model = load_model('./boss_model/final_model.h5')

Một lưu ý là chúng ta cần phải chạy qua các bước tiền xử lý khi dự đoán, giống hệt lúc chúng ta training.

Bước 8: Deploy bằng OpenCV

Xử lý model để dự đoán

Việc đầu tiên cần làm là Tạo file boss_model.py. Chúng ta sử dụng file này để load model đã được lưu từ các bước trên bao gồm cả các hàm về xử lý dữ liệu và dự đoán kết quả


import cv2
import keras
import numpy as np
from keras.models import load_model
from keras.preprocessing import image
from keras.utils.generic_utils import CustomObjectScope
# Load pretrained model

with CustomObjectScope({'relu6': keras.applications.mobilenet.relu6,'DepthwiseConv2D': keras.applications.mobilenet.DepthwiseConv2D}):
    model = load_model('./model/model.h5')

IMAGE_SIZE=224

def prepare_image(img):
    img_array = image.img_to_array(img)
    img_array_expanded_dims = np.expand_dims(img_array, axis=0)
    return keras.applications.mobilenet.preprocess_input(img_array_expanded_dims)

def predict(img):
    """
    Predict face crop from frame
    :param img:
    :return: If boss is appear when open the code IDE
    """
    try:
        img = cv2.resize(img, (IMAGE_SIZE, IMAGE_SIZE), interpolation=cv2.INTER_AREA)
        probs = model.predict(prepare_image(img))
        is_boss = np.argmax(probs[0])
        return is_boss
    except:
        return False

Detect khuôn mặt bằng dlib

Giống như mô tả ở phái đầu tiên của bài viết, trong quá trình dự đoán chúng ta cần phải detect khuôn mặt và sử dụng một thư viện rất nổi tiếng đó là dlib. Quá trình xử lý của nó như sau:

import dlib
hog_face_detector = dlib.get_frontal_face_detector()
faces_hog = hog_face_detector(image)

Kết quả của nó như sau:

Sử dụng OpenCV để stream dữ liệu từ Webcam

Chúng ta cần sử dụng dữ liệu stream từ webcam để theo dõi sếp và phát hiện ra khi nào sếp đến gần một cách tức thời. Việc này có thể thực hiện bằng cách sử dụng webcam và OpenCV để stream dữ liệu. Về cơ bản chúng ta có thể thực hiện nó như sau :

import cv2

cap = cv2.VideoCapture(0)
while True:
    ret, frame = cap.read()
    if ret:
        # Excute frame here 
        cv2.imshow("frame", frame)
        key = cv2.waitKey(1)
        if key == 27:
            break

cap.release()
cv2.destroyAllWindows()

Xử lý crop mặt và dự đoán

Chúng ta sẽ crop từng khuôn mặt trong từng frame và dự đoán kết quả. Nếu là boss thì thực hiện mở ngay cửa sổ code

    if ret:
        # Resize window
        cv2.namedWindow('frame', cv2.WINDOW_NORMAL)
        cv2.resizeWindow('frame', 400, 400)
        # Resize frame for decrease predict time
        scale = 0.5
        resize_frame = cv2.resize(frame, (int(frame.shape[1]*scale), int(frame.shape[0]*scale)))
        faces_hog = hog_face_detector(resize_frame)
        frame_h, frame_w, _ = frame.shape
        reindex_x = lambda x: max(min(x, frame_w), 1)
        reindex_y = lambda x: max(min(x, frame_h), 1)

        # loop over detected faces
        for face in faces_hog:
            x = reindex_x(int(face.left() / scale))
            y = reindex_y(int(face.top() / scale))
            r = reindex_x(int(face.right() / scale))
            b = reindex_y(int(face.bottom() / scale))

            # draw box over face
            crop_face = frame[x: r, y: b]
            is_boss = predict(crop_face)
            if is_boss:
                cv2.putText(frame, "BOSS", (10, 100), cv2.FONT_HERSHEY_SIMPLEX, 2,
                            (0, 0, 255), 4)
                cv2.rectangle(frame, (x, y), (r, b), (0, 0, 255), 2)
                boss_count += 1
                # Open your IDE application for coding
                if boss_count > 3:
                    os.system('open -a "PyCharm CE"')
                    boss_count = 0
            else:
                cv2.putText(frame, "NORMAL", (10, 100), cv2.FONT_HERSHEY_SIMPLEX, 2,
                            (0, 255, 0), 4)
                cv2.rectangle(frame, (x, y), (r, b), (0, 255, 0), 2)

Mọi người có thể tham khảo kĩ hơn source code trong file này tại đây

Source code

Các bạn có thể tham khảo source code của bài viết tại đây

Cảm ơn các bạn đã theo dõi bài viết hẹn gặp lại trong những bài viết tiếp theo.

Để lại bình luận