LÝ DO CHỌN ĐỀ TÀI Chúng em thực hiện bài tập nhóm lần này theo yêu cầu thầy đưa ra, qua đó có thể tích lũy cho bản thân một số điều sau: - Sử dụng thuần thục các thư viện, thuật toán - G
Trang 1BỘ GIÁO DỤC VÀ ĐÀO TẠO TRƯỜNG ĐẠI HỌC SƯ PHẠM KỸ THUẬT TP HCM
KHOA CƠ KHÍ CHẾ TẠO MÁY
BÁO CÁO BÀI TẬP NHÓM NHẬN DIỆN KHUÔN MẶT BẰNG AI
DỰ ĐOÁN TUỔI VÀ GIỚI TÍNH
MÔN HỌC: TRÍ TUỆ NHÂN TẠO GVHD: TS BÙI HÀ ĐỨC
SVTH: Phan Thu Huyền 21104014 Chu Tú Trinh 21104088 Nguyễn Thị Thảo Uyên 21104053 Trương Điệp Y 21104093
Tp Hồ Chí Minh, tháng 11 năm 2023
Trang 2MỤC LỤC
1 LÝ DO CHỌN ĐỀ TÀI 1
2 TỔNG QUAN SƠ BỘ 1
3 PHÂN TÍCH CODE 1
4 KHÓ KHĂN VÀ GIẢI QUYẾT 10
Trang 31
1 LÝ DO CHỌN ĐỀ TÀI
Chúng em thực hiện bài tập nhóm lần này theo yêu cầu thầy đưa ra, qua đó có thể tích lũy cho bản thân một số điều sau:
- Sử dụng thuần thục các thư viện, thuật toán
- Giúp nhận biết được gương mặt, dự đoán được tuổi và giới tính của người ngồi trước camera, sau đó xem người đó có tập trung vào trong quảng cáo đang chiếu ra trên màn hình hay không Từ đó xác định được các nhóm đối tượng phù hợp để xuất hiện quảng cáo, tăng hiệu quả của công việc
- Cải thiện và phát triển các kỹ năng làm việc nhóm, khả năng research,
2 TỔNG QUAN SƠ BỘ
Thừa hưởng những thành tựu của nền khoa học kỹ thuật phát triển Nhận diện khuôn mặt
là một công nghệ được ứng dụng rộng rãi trong đời sống hằng ngày của con người như các hệ thống giám sát tại các tòa nhà, sân bay, trạm ATM, hệ thống chấm công, camera chống trộm, xác thực danh tính, …có rất nhiều các phương pháp nhận dạng khuôn mặt để nâng cao hiệu suất, bảo mật hiệu quả, cải thiện độ chính xác, tích hợp dễ dàng hơn Hệ thống nhận dạng khuôn mặt được sử dụng trong các trường hợp như đăng nhập ID, phát hiện gian lận, an ninh mạng, kiểm soát sân bay và biên giới, ngân hàng, chăm sóc sức khỏe Tuy nhiên dù ít hay nhiều thì những phương pháp này đang gặp phải những khó khăn, thử thách như về độ sáng, hướng nghiêng, kích thước hình ảnh, hay ảnh hưởng của tham số môi trường
3 PHÂN TÍCH CODE
Đầu tiên, chúng em import các thư viện và mô đun cần thiết để chạy code
import tensorflow as tf
from tensorflow.keras.utils import load_img
from keras.models import Sequential, Model
from keras.layers import Dense, Conv2D, Dropout, Flatten, MaxPooling2D, Input
import numpy as np
import random
import matplotlib pyplot as plt
import os
import seaborn as sns
import warnings
from tqdm notebook import tqdm
warnings filterwarnings ( 'ignore' )
Sau đó, kết nối với GG Drive và giải nén dữ liệu tệp zip UTKFace.zip
from google.colab import drive
drive.mount( '/content/drive' )
! unzip '/content/drive/MyDrive/UTKFace.zip'
Tiếp theo, cần tạo ra ba danh sách để lưu trữ độ tuổi, giới tính và đường dẫn hình ảnh Trích
Trang 4xuất tên tệp hình ảnh bằng cách sử dụng thư mục os.list cung cấp tất cả tên tệp bên trong UTKFace và xáo trộn các tên tệp đó vì dữ liệu khi nhận diện là ngẫu nhiên, không tuần tự BASE_DIR = '/content/UTKFace'
age_labels = []
gender_labels = []
image_paths = []
image_filenames = os listdir ( BASE_DIR )
random shuffle ( image_filenames )
Đoạn code dưới đây đang duyệt qua danh sách các hình ảnh để lấy thông tin và giới tính từ tên tệp:
for image in tqdm ( image_filenames ):
image_path = os path join ( BASE_DIR , image )
img_components = image split ( '_' )
age_label = int ( img_components [ 0 ])
gender_label = int ( img_components [ 1 ])
# Append the image_path, age_label, and gender_label
age_labels append ( age_label )
gender_labels append ( gender_label )
image_paths append ( image_path )
Sau đó in ra để xác định xem số lượng nhãn độ tuổi, nhãn giới tính và đường dẫn hình ảnh có giống nhau hay không
print ( 'Number of age_labels: { len ( age_labels ) } , Number of gender_labels: { len ( gender_labels ) } , Number of image_paths: { len ( image_paths ) } ' )
Tiếp theo tạo một từ điển đơn giản, 0 là Male, 1 là Female
gender_mapping = {
1 : 'Female' ,
0 : 'Male'
}
Chuyển đổi từng danh sách đã tạo thành từng cột riêng để khung dữ liệu ngắn gọn và đẹp mắt hơn
import pandas as pd
df = pd DataFrame ()
df [ 'image_path' ], df [ 'age' ], df [ 'gender' ] = image_paths , age_labels , gender_labels
df head ( 5 )
Trang 53
Và chúng em có đoạn code cho hiển thị một hình ảnh ngẫu nhiên, phân tích dữ liệu và cho ra tuổi cùng giới tính của tấm hình ấy
from PIL import Image
rand_index = random randint ( 0 , len ( image_paths ))
age = df [ 'age' ][ rand_index ]
gender = df [ 'gender' ][ rand_index ]
IMG = Image open ( df [ 'image_path' ][ rand_index ])
plt title ( 'Age: { age } Gender: { gender_mapping [ gender ] } ' )
plt axis ( 'off' )
plt imshow ( IMG )
Dưới đây là code tạo biểu đồ phân phối tuổi và giới tính trong tệp dữ liệu
sns distplot( df [ 'age' ])
sns countplot( df [ 'gender' ])
Sau đó, sẽ cho xuất hiện một loạt các hình ảnh trong tệp với các thông tin như độ tuổi và giới tính tương ứng
for index , sample , age , gender in samples itertuples ():
plt subplot ( 4 , 4 , index + 1 )
img = load_img( sample )
img = np array ( img )
plt axis ( 'off' )
plt title ( 'Age: { age } Gender: { gender_mapping [ gender ] } ' )
plt imshow ( img )
def extract_image_features ( images ):
features = list () Đây là khai báo hàm Python có tên là ‘extract_image_features’, nhận một tham số là
images Tham số này là một danh sách chứa đường dẫn đến các hình ảnh hoặc nội dung hình ảnh
features = list(): Tạo một biến features là một danh sách rỗng được sử dụng để lưu trữ các đặc trưng của hình ảnh sau khi được xử lý
for image in tqdm ( images ):
img = load_img( image , grayscale = True ) img = img resize(( 128 , 128 ), Image ANTIALIAS) img = np array ( img )
features append ( img ) features = np array ( features ) features = features reshape ( len ( features ), 128 , 128 , 1 ) return features
Trang 6Thực hiện việc xử lý từng hình ảnh trong danh sách ‘images’, duyệt và thay đổi hình ảnh sau
đó thay đổi kích thước ảnh thành 128x128 pixel, chuyển đổi thành Numpy thêm vào danh sách ‘features’
X = extract_image_features ( df [ 'image_path' ])
X shape
X = X 255.0 Gán kết quả trả về chứa các đặc trưng của tất cả hình ảnh
y_gender = np array ( df [ 'gender' ])
y_age = np array ( df [ 'age' ] ) Đoạn code này giúp chuyển đổi dữ liệu từ cột của DataFrame thành các mảng NumPy để thuận tiện cho việc xử lý và tính toán
input_shape = ( 128 , 128 , ) Dòng code này định nghĩa biến input_shape là một tuple có giá trị là (128, 128, 1)
inputs = Input( (input_shape) )
conv_1 = Conv2D( 32 , kernel_size =( 3 , 3 ), activation = 'relu' )( inputs )
max_1 = MaxPooling2D( pool_size =( 2 , 2 ))( conv_1 )
conv_2 = Conv2D( 64 , kernel_size =( 3 , 3 ), activation = 'relu' )( max_1 )
max_2 = MaxPooling2D( pool_size =( 2 , 2 ))( conv_2 )
conv_3 = Conv2D( 128 , kernel_size =( 3 , 3 ), activation = 'relu' )( max_2 )
max_3 = MaxPooling2D( pool_size =( 2 , 2 ))( conv_3 )
conv_4 = Conv2D( 256 , kernel_size =( 3 , 3 ), activation = 'relu' )( max_3 )
max_4 = MaxPooling2D( pool_size =( 2 , 2 ))( conv_4 )
flatten = Flatten()( max_4 )
Định nghĩa đầu vào của mô hình với kích thước là input_shape Sau đó, Tích chập đầu vào với 32 bộ lọc kích thước (3, 3) và hàm kích hoạt ReLU, Lấy giá trị lớn nhất từ các vùng 2x2, giúp giảm kích thước của đầu ra Lớp Flatten được sử dụng để biến đổi đầu ra của lớp pooling cuối cùng (max_4) thành một vector 1 chiều Nó "làm phẳng" dữ liệu, giữ nguyên thông tin từ các bức ảnh đã được trích xuất các đặc trưng
dense_1 = Dense( 256 , activation = 'relu' )( flatten )
dense_2 = Dense( 256 , activation = 'relu' )( flatten )
dropout_1 = Dropout( 0.3 )( dense_1 )
dropout_2 = Dropout( 0.3 )( dense_2 )
output_1 = Dense( 1 activation = 'sigmoid' , name = 'gender_out' )( dropout_1 )
output_2 = Dense( 1 activation = 'relu' , name = 'age_out' )( dropout_2 )
model = Model( inputs =[ inputs ], outputs =[ output_1 , output_2 ])
model compile( loss =[ 'binary_crossentropy' , 'mae' ],
optimizer = 'adam' , metrics =[ 'accuracy' ])
Đoạn code đang xây dựng một mô hình mạng nơ-ron sâu (deep learning) để thực hiện hai nhiệm vụ: dự đoán giới tính và tuổi từ ảnh đầu vào
from tensorflow.keras.utils import plot_model
plot_model( model )
Sau khi chạy đoạn mã trên sẽ vẽ biểu đồ cấu trúc của mô hình Biểu đồ này sẽ mô tả mối quan hệ giữa các lớp trong mô hình, giúp hiểu cấu trúc tổng quan của mô hình nhanh chóng
history = model fit( x = X , y =[ y_gender , y_age ],
batch_size = 32 , epochs = 50 , validation_split = 0.2 )
Tiến hành training
Trang 75
# plot results for gender
acc = history history[ 'gender_out_accuracy' ]
val_acc = history history[ 'val_gender_out_accuracy' ]
epochs = range ( len ( acc ))
plt plot ( epochs , acc , 'b' , label = 'Training Accuracy' )
plt plot ( epochs , val_acc , 'r' , label = 'Validation Accuracy' )
plt title ( 'Accuracy Graph' )
plt legend ()
plt figure ()
Dòng code này giúp theo dõi sự phát triển của độ chính xác trên cả tập training và tập kiểm thử qua các epoch, giúp đánh giá hiệu suất của mô hình và phát hiện hiện tượng overfitting hoặc underfitting
loss = history history[ 'gender_out_loss' ]
val_loss = history history[ 'val_gender_out_loss' ]
Lấy thông tin về giá trị mất mát trên tập training từ lịch sử training (history) Trong đoạn mã này, "gender_out_loss" là mất mát của lớp đầu ra dự đoán giới tính và lấy thông tin về giá trị mất mát trên tập kiểm thử từ lịch sử training
plt plot ( epochs , loss , 'b' , label = 'Training Loss' )
plt plot ( epochs , val_loss , 'r' , label = 'Validation Loss' )
plt title ( 'Loss Graph' )
plt legend ()
plt show ()
- 'b' và 'r' trong ‘plt.plot’ chỉ định màu của các đường ('b' cho màu xanh lam và 'r' cho màu đỏ)
- Tham số ‘label’ trong ‘plt.plot’ được sử dụng để gắn nhãn cho các dòng cho chú giải
- ‘plt.title’ đặt tiêu đề cho plot
- ‘plt.legend()’ hiển thị chú giải, rất hữu ích khi vẽ nhiều dòng để phân biệt giữa chúng
Mã này thường được sử dụng trong đào tạo machine learning model trong đó 'epochs' biểu thị số lần thuật toán học sẽ hoạt động thông qua toàn bộ tập dữ liệu đào tạo 'Training Loss'
và 'Validation Loss' là các số liệu được sử dụng để đánh giá mức độ hoạt động của mô hình trong quá trình đào tạo và trên dữ liệu không nhìn thấy tương ứng Plot giúp hình dung các giá trị mất mát này thay đổi như thế nào qua các giai đoạn huấn luyện
# plot results for age
loss = history history[ 'age_out_loss' ]
val_loss = history history[ 'val_age_out_loss' ]
epochs = range ( len ( loss ))
- Vẽ đồ thị các giá trị tổn thất huấn luyện và xác thực cho đầu ra 'age' của mô hình
'age_out_loss' và 'val_age_out_loss' là các phần giữ chỗ cho các khóa thực tế trong đối tượng 'history' của bạn để lưu trữ các giá trị tổn thất trong quá trình đào tạo và xác thực cho đầu ra 'age'
- Đảm bảo điều chỉnh mã theo các khóa thực tế được sử dụng trong đối tượng 'history' của bạn Ngoài ra, việc cung cấp nhãn trục với ‘plt.xlabel’ và ‘plt.ylabel’ có thể nâng cao khả năng diễn giải biểu đồ của bạn
plt plot ( epochs , loss , 'b' , label = 'Training Loss' )
plt plot ( epochs , val_loss , 'r' , label = 'Validation Loss' )
plt title ( 'Loss Graph' )
Trang 8plt legend ()
plt show ()
Sử dụng lại đoạn mã để vẽ sơ đồ mất dữ liệu đào tạo và xác thực qua các epochs Mã này được sử dụng để trực quan hóa xu hướng mất đi quá trình đào tạo và xác thực trong quá trình đào tạo
def get_image_features ( image ):
img = load_img( image , grayscale = True )
img = img resize(( 128 , 128 ), Image ANTIALIAS)
img = np array ( img )
img = img reshape ( 1 , 128 , 128 , 1 )
img = img 255.0
return img
- Load Image:Hàm sử dụng hàm Load_img từ Keras để tải hình ảnh ở thang độ xám
- Resize Image: Nó thay đổi kích thước hình ảnh thành kích thước cố định 128x128 pixel bằng cách sử dụng tính năng khử răng cưa để thay đổi kích thước mượt mà
- Convert to NumPy Array: Hình ảnh được chuyển đổi thành mảng NumPy bằng np.array
- Reshape: Mảng được định hình lại để có kích thước (1, 128, 128, 1) Các kích thước bổ sung thường được sử dụng khi làm việc với mạng nơ ron tích chập (CNN)
- Normalization: Các giá trị pixel được chuẩn hóa thành phạm vi [0, 1] bằng cách chia mỗi giá trị pixel cho 255,0
Kết quả có thể được sử dụng làm đầu vào cho mô hình mong đợi hình ảnh có kích thước 128x128 pixel
img_to_test = '/content/drive/MyDrive/2.png'
features = get_image_features ( img_to_test )
pred = model predict( features )
gender = gender_mapping [ round ( pred [ 0 ][ 0 ][ 0 ])]
age = round ( pred [ 1 ][ 0 ][ 0 ])
plt title ( 'Predicted Age: { age } Predicted Gender: { gender } ' )
plt axis ( 'off' )
plt imshow ( np array (load_img( img_to_test )))
- Sử dụng hàm ‘get_image_features’ để xử lý trước hình ảnh rồi đưa ra dự đoán bằng mô hình Sau khi nhận được dự đoán, bạn sẽ hiển thị hình ảnh gốc cùng với độ tuổi và giới tính được dự đoán
- Giới tính dự đoán được lấy từ ‘gender_mapping’, có lẽ là mapping từ các giá trị số sang nhãn giới tính
- Độ tuổi dự đoán được làm tròn, giả sử đó là dự đoán hồi quy
- ‘plt.title’, ‘plt.axis('off')’ và ‘plt.imshow’ được sử dụng để hiển thị hình ảnh gốc với độ tuổi
và giới tính được dự đoán
from IPython display import display , Javascript, Image
from google.colab.output import eval_js
from base64 import b64decode , b64encode
import cv2
import numpy as np
import PIL
import io
import html
import time
Trang 97
Import các thư viện cần thiết
def js_to_image ( js_reply ):
"""
Params:
js_reply: JavaScript object containing image from webcam
Returns:
img: OpenCV BGR image
"""
# decode base64 image
image_bytes = b64decode ( js_reply split( ',' )[ 1 ])
# convert bytes to numpy array
jpg_as_np = np frombuffer ( image_bytes , dtype = np uint8 )
# decode numpy array into OpenCV BGR image
img = cv2 imdecode( jpg_as_np , flags = 1 )
return img
- Decode Base64 Image: Hàm lấy hình ảnh được mã hóa base64 từ đối tượng JavaScript (‘js_reply’), phân tách nó để lấy nội dung được mã hóa base64 thực tế, sau đó giải mã nó bằng ‘b64decode’ Bước này chuyển đổi hình ảnh được mã hóa base64 thành byte
- Convert Bytes to NumPy Array: Các byte được giải mã sau đó được chuyển đổi thành mảng NumPy bằng cách sử dụng ‘np.frombuffer’
- Decode NumPy Array into OpenCV BGR Image: Mảng NumPy được giải mã thành hình ảnh OpenCV BGR bằng ‘cv2.imdecode’ Tham số ‘flags=1’ chỉ ra rằng hình ảnh phải được đọc ở dạng màu
Kết quả là hình ảnh OpenCV BGR có thể được xử lý hoặc hiển thị thêm bằng các hàm OpenCV
# function to convert OpenCV Rectangle bounding box image into base64 byte string to be overlayed on video stream
def bbox_to_bytes ( bbox_array ):
"""
Params:
bbox_array: Numpy array (pixels) containing rectangle to overlay on video stream
Returns:
bytes: Base64 image byte string
"""
# convert array into PIL image
bbox_PIL = PIL Image.fromarray( bbox_array , 'RGBA' )
iobuf = io BytesIO ()
# format bbox into png for return
bbox_PIL save( iobuf , format = 'png' )
# format return string
bbox_bytes = 'data:image/png;base64, {} ' format (( str ( b64encode ( iobuf getvalue ()), 'utf-8' )))
return bbox_bytes
- Convert Array to PIL Image: Hàm chuyển đổi mảng NumPy đầu vào (được giả sử ở định dạng RGBA) thành Hình ảnh PIL với chế độ 'RGBA'
- Create Binary Stream: Một luồng nhị phân trong bộ nhớ (‘io.BytesIO()’) được tạo
- Save Image to Binary Stream:: Hình ảnh PIL được lưu vào luồng nhị phân ở định dạng PNG
Trang 10- Convert to Base64 Byte String: Luồng nhị phân sau đó được chuyển đổi thành chuỗi byte được mã hóa base64
Kết quả ‘bbox_byte’ có thể được sử dụng để phủ hình chữ nhật trên luồng video bằng cách nhúng nó vào HTML hoặc sử dụng nó trong ứng dụng web
face_cascade = cv2 CascadeClassifier( cv2 samples.findFile( cv2 data.haarcascades +
'haarcascade_frontalface_default.xml' ))
def take_photo ( filename = 'photo.png' , quality = 0.8 ):
js = Javascript( '''
async function takePhoto(quality) {
const div = document.createElement('div');
const capture = document.createElement('button');
capture.textContent = 'Capture';
div.appendChild(capture);
const video = document.createElement('video');
video.style.display = 'block';
const stream = await navigator.mediaDevices.getUserMedia( {video: true} );
document.body.appendChild(div);
div.appendChild(video);
video.srcObject = stream;
await video.play();
// Resize the output to fit the video element
google.colab.output.setIframeHeight(document.documentElement.scrollHeight, true);
// Wait for Capture to be clicked
await new Promise((resolve) => capture.onclick = resolve);
const canvas = document.createElement('canvas');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
canvas.getContext('2d').drawImage(video, 0, 0);
stream.getVideoTracks()[0].stop();
div.remove();
return canvas.toDataURL('image/jpeg', quality);
}
''' )
display ( js )
Ở bước này, chúng ta cung cấp một chức năng để chụp ảnh từ webcam của người dùng bằng môi trường Google Colab Hàm này được đặt tên là takePhoto và nhận hai tham số tùy chọn như sau: tên tệp mặc định là 'photo.png' và chất lượng mặc định là 0,8 Chức năng này thiết lập một nút để chụp ảnh, khởi tạo thành phần video để hiển thị nguồn cấp dữ liệu webcam
và trả về hình ảnh đã chụp dưới dạng URL dữ liệu ở định dạng JPEG Để sử dụng hàm này trong Jupyter, có thể gọi nó từ ô mã sau khi chạy mã được cung cấp
Thao tác này sẽ hiển thị nút "Chụp" và khi nhấp vào nút này, nó sẽ chụp ảnh từ webcam và trả về URL dữ liệu của hình ảnh Chúng ta có thể tùy chỉnh tên tệp và thông số chất lượng nếu cần Mã này được thiết kế cho môi trường Google Colab và dựa trên các tính năng dành riêng cho môi trường đó, chẳng hạn như google.colab.output.setIframeHeight Nếu sử dụng một môi trường khác, ta cần phải điều chỉnh code