المزيد من مشاريع التعلم العميق......مشاريع التعلم العميق

مشروع 13: كشف معالم الوجه باستخدام التعلم العميق

اقرأ في هذا المقال
  • تنزيل مجموعة بيانات DLIB
  • التمثيل البياني لمجموعة البيانات
  • تكوين فئات مجموعة البيانات
  • رسم تحويلات التدريب
  • تقسيم مجموعة البيانات للتدريب والتنبؤ بمعالم الوجه
  • اختبار شكل بيانات الإدخال
  • تعريف نموذج اكتشاف معالم الوجه
  • دوال المساعدة
  • تدريب الشبكة العصبية لاكتشاف معالم الوجه
  • توقع معالم الوجه

هل فكرت يومًا في كيفية تمكن Snapchat من تطبيق فلاتر مذهلة وفقًا لوجهك؟ تمت برمجته لاكتشاف بعض العلامات على وجهك لإبراز فلتر وفقًا لتلك العلامات. في التعلم الآلي، تُعرف هذه العلامات باسم معالم الوجه Face Landmarks. في هذه المقالة، سأوجهك إلى كيفية اكتشاف معالم الوجوه باستخدام التعلم الآلي.

الآن، سأبدأ ببساطة باستيراد جميع المكتبات التي نحتاجها لهذه المهمة. سأستخدم PyTorch في هذه المقالة لمواجهة اكتشاف المعالم باستخدام التعلم العميق. لنستورد جميع المكتبات:

 

import time
import cv2
import os
import random
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
import imutils
import matplotlib.image as mpimg
from collections import OrderedDict
from skimage import io, transform
from math import *
import xml.etree.ElementTree as ET 

import torch
import torchvision
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import torchvision.transforms.functional as TF
from torchvision import datasets, models, transforms
from torch.utils.data import Dataset

تنزيل مجموعة بيانات DLIB

مجموعة البيانات التي سأختارها هنا لاكتشاف معالم الوجه في مجموعة بيانات DLIB الرسمية التي تتكون من أكثر من 6666 صورة بأبعاد مختلفة. سيقوم الكود أدناه بتنزيل مجموعة البيانات وفك الضغط لمزيد من الاستكشاف:

%%capture
if not os.path.exists('/content/ibug_300W_large_face_landmark_dataset'):
    !wget http://dlib.net/files/data/ibug_300W_large_face_landmark_dataset.tar.gz
    !tar -xvzf 'ibug_300W_large_face_landmark_dataset.tar.gz'    
    !rm -r 'ibug_300W_large_face_landmark_dataset.tar.gz'

التمثيل البياني لمجموعة البيانات

الآن، دعنا نلقي نظرة على ما نعمل معه، لنرى جميع تنظيف البيانات وفرص المعالجة المسبقة التي نحتاج إلى خوضها. فيما يلي مثال على صورة من مجموعة البيانات التي أخذناها لهذه المهمة.

file = open('ibug_300W_large_face_landmark_dataset/helen/trainset/100032540_1.pts')
points = file.readlines()[3:-1]

landmarks = []

for point in points:
    x,y = point.split(' ')
    landmarks.append([floor(float(x)), floor(float(y[:-1]))])

landmarks = np.array(landmarks)

plt.figure(figsize=(10,10))
plt.imshow(mpimg.imread('ibug_300W_large_face_landmark_dataset/helen/trainset/100032540_1.jpg'))
plt.scatter(landmarks[:,0], landmarks[:,1], s = 5, c = 'g')
plt.show()

يمكنك أن ترى أن الوجه يغطي مساحة أقل بكثير في الصورة. إذا كنا سنستخدم هذه الصورة في الشبكة العصبية، فستأخذ الخلفية أيضًا. لذا، مثلما نقوم بإعداد بيانات نصية، سنقوم بإعداد مجموعة بيانات الصور هذه لمزيد من الاستكشاف.

تكوين فئات مجموعة البيانات

دعنا الآن نتعمق أكثر في الفئات والتسميات في مجموعة البيانات. يتكون labels_ibug_300W_train.xml من الصور المدخلة والمعالم والمربع المحيط لاقتصاص الوجه. سوف أقوم بتخزين كل هذه القيم في القائمة حتى نتمكن من الوصول إليها بسهولة أثناء عملية التدريب.

class Transforms():
    def __init__(self):
        pass
    
    def rotate(self, image, landmarks, angle):
        angle = random.uniform(-angle, +angle)

        transformation_matrix = torch.tensor([
            [+cos(radians(angle)), -sin(radians(angle))], 
            [+sin(radians(angle)), +cos(radians(angle))]
        ])

        image = imutils.rotate(np.array(image), angle)

        landmarks = landmarks - 0.5
        new_landmarks = np.matmul(landmarks, transformation_matrix)
        new_landmarks = new_landmarks + 0.5
        return Image.fromarray(image), new_landmarks

    def resize(self, image, landmarks, img_size):
        image = TF.resize(image, img_size)
        return image, landmarks

    def color_jitter(self, image, landmarks):
        color_jitter = transforms.ColorJitter(brightness=0.3, 
                                              contrast=0.3,
                                              saturation=0.3, 
                                              hue=0.1)
        image = color_jitter(image)
        return image, landmarks

    def crop_face(self, image, landmarks, crops):
        left = int(crops['left'])
        top = int(crops['top'])
        width = int(crops['width'])
        height = int(crops['height'])

        image = TF.crop(image, top, left, height, width)

        img_shape = np.array(image).shape
        landmarks = torch.tensor(landmarks) - torch.tensor([[left, top]])
        landmarks = landmarks / torch.tensor([img_shape[1], img_shape[0]])
        return image, landmarks

    def __call__(self, image, landmarks, crops):
        image = Image.fromarray(image)
        image, landmarks = self.crop_face(image, landmarks, crops)
        image, landmarks = self.resize(image, landmarks, (224, 224))
        image, landmarks = self.color_jitter(image, landmarks)
        image, landmarks = self.rotate(image, landmarks, angle=10)
        
        image = TF.to_tensor(image)
        image = TF.normalize(image, [0.5], [0.5])
        return image, landmarks
class FaceLandmarksDataset(Dataset):

    def __init__(self, transform=None):

        tree = ET.parse('ibug_300W_large_face_landmark_dataset/labels_ibug_300W_train.xml')
        root = tree.getroot()

        self.image_filenames = []
        self.landmarks = []
        self.crops = []
        self.transform = transform
        self.root_dir = 'ibug_300W_large_face_landmark_dataset'
        
        for filename in root[2]:
            self.image_filenames.append(os.path.join(self.root_dir, filename.attrib['file']))

            self.crops.append(filename[0].attrib)

            landmark = []
            for num in range(68):
                x_coordinate = int(filename[0][num].attrib['x'])
                y_coordinate = int(filename[0][num].attrib['y'])
                landmark.append([x_coordinate, y_coordinate])
            self.landmarks.append(landmark)

        self.landmarks = np.array(self.landmarks).astype('float32')     

        assert len(self.image_filenames) == len(self.landmarks)

    def __len__(self):
        return len(self.image_filenames)

    def __getitem__(self, index):
        image = cv2.imread(self.image_filenames[index], 0)
        landmarks = self.landmarks[index]
        
        if self.transform:
            image, landmarks = self.transform(image, landmarks, self.crops[index])

        landmarks = landmarks - 0.5

        return image, landmarks

dataset = FaceLandmarksDataset(Transforms())

رسم تحويلات التدريب:

الآن دعونا نلقي نظرة سريعة على ما قمنا به حتى الآن. سوف نرسم مجموعة البيانات فقط من خلال إجراء التحويل الذي ستوفره الفئات المذكورة أعلاه لمجموعة البيانات:

تقسيم مجموعة البيانات للتدريب والتنبؤ بمعالم الوجه

الآن، للمضي قدمًا، سأقسم مجموعة البيانات إلى مجموعة تدريب ومجموعة بيانات التحقق من الصحة:

# split the dataset into validation and test sets
len_valid_set = int(0.1*len(dataset))
len_train_set = len(dataset) - len_valid_set

print("The length of Train set is {}".format(len_train_set))
print("The length of Valid set is {}".format(len_valid_set))

train_dataset , valid_dataset,  = torch.utils.data.random_split(dataset , [len_train_set, len_valid_set])

# shuffle and batch the datasets
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=64, shuffle=True, num_workers=4)
valid_loader = torch.utils.data.DataLoader(valid_dataset, batch_size=8, shuffle=True, num_workers=4)

 

The length of Train set is 6000
The length of Valid set is 666 

اختبار شكل بيانات الإدخال:

الآن سأستخدم ResNet18 كإطار عمل أساسي لدينا. سوف أقوم بتعديل الطبقتين الأولى والأخيرة بحيث تتناسب الطبقات بسهولة مع غرضنا:

class Network(nn.Module):
    def __init__(self,num_classes=136):
        super().__init__()
        self.model_name='resnet18'
        self.model=models.resnet18()
        self.model.conv1=nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3, bias=False)
        self.model.fc=nn.Linear(self.model.fc.in_features, num_classes)
        
    def forward(self, x):
        x=self.model(x)
        return x

دوال المساعدة:

import sys

def print_overwrite(step, total_step, loss, operation):
    sys.stdout.write('\r')
    if operation == 'train':
        sys.stdout.write("Train Steps: %d/%d  Loss: %.4f " % (step, total_step, loss))   
    else:
        sys.stdout.write("Valid Steps: %d/%d  Loss: %.4f " % (step, total_step, loss))
        
    sys.stdout.flush()

تدريب الشبكة العصبية لاكتشاف معالم الوجه

سأستخدم الآن الخطأ التربيعي المتوسط بين معالم الوجه الحقيقية والمتوقعة:

torch.autograd.set_detect_anomaly(True)
network = Network()
network.cuda()    

criterion = nn.MSELoss()
optimizer = optim.Adam(network.parameters(), lr=0.0001)

loss_min = np.inf
num_epochs = 10

start_time = time.time()
for epoch in range(1,num_epochs+1):
    
    loss_train = 0
    loss_valid = 0
    running_loss = 0
    
    network.train()
    for step in range(1,len(train_loader)+1):
    
        images, landmarks = next(iter(train_loader))
        
        images = images.cuda()
        landmarks = landmarks.view(landmarks.size(0),-1).cuda() 
        
        predictions = network(images)
        
        # clear all the gradients before calculating them
        optimizer.zero_grad()
        
        # find the loss for the current step
        loss_train_step = criterion(predictions, landmarks)
        
        # calculate the gradients
        loss_train_step.backward()
        
        # update the parameters
        optimizer.step()
        
        loss_train += loss_train_step.item()
        running_loss = loss_train/step
        
        print_overwrite(step, len(train_loader), running_loss, 'train')
        
    network.eval() 
with torch.no_grad():
        
        for step in range(1,len(valid_loader)+1):
            
            images, landmarks = next(iter(valid_loader))
        
            images = images.cuda()
            landmarks = landmarks.view(landmarks.size(0),-1).cuda()
        
            predictions = network(images)

            # find the loss for the current step
            loss_valid_step = criterion(predictions, landmarks)

            loss_valid += loss_valid_step.item()
            running_loss = loss_valid/step

            print_overwrite(step, len(valid_loader), running_loss, 'valid')
    
    loss_train /= len(train_loader)
    loss_valid /= len(valid_loader)
    
    print('\n--------------------------------------------------')
    print('Epoch: {}  Train Loss: {:.4f}  Valid Loss: {:.4f}'.format(epoch, loss_train, loss_valid))
    print('--------------------------------------------------')
    
    if loss_valid < loss_min:
        loss_min = loss_valid
        torch.save(network.state_dict(), '/content/face_landmarks.pth') 
        print("\nMinimum Validation Loss of {:.4f} at epoch {}/{}".format(loss_min, epoch, num_epochs))
        print('Model Saved\n')
print('Training Complete')
print("Total Elapsed Time : {} s".format(time.time()-start_time))
توقع معالم الوجه
دعنا الآن نستخدم النموذج الذي دربناه أعلاه على الصور التي لم يتم مشاهدتها مسبقاً في مجموعة البيانات:
start_time = time.time()

with torch.no_grad():

    best_network = Network()
    best_network.cuda()
    best_network.load_state_dict(torch.load('/content/face_landmarks.pth')) 
    best_network.eval()
    
    images, landmarks = next(iter(valid_loader))
    
    images = images.cuda()
    landmarks = (landmarks + 0.5) * 224

    predictions = (best_network(images).cpu() + 0.5) * 224
    predictions = predictions.view(-1,68,2)
    
    plt.figure(figsize=(10,40))
    
    for img_num in range(8):
        plt.subplot(8,1,img_num+1)
        plt.imshow(images[img_num].cpu().numpy().transpose(1,2,0).squeeze(), cmap='gray')
        plt.scatter(predictions[img_num,:,0], predictions[img_num,:,1], c = 'r', s = 5)
        plt.scatter(landmarks[img_num,:,0], landmarks[img_num,:,1], c = 'g', s = 5)

print('Total number of test images: {}'.format(len(valid_dataset)))

end_time = time.time()
print("Elapsed Time : {}".format(end_time - start_time))

 

بواسطة
thecleverprogrammer
المصدر
Face Landmarks Detection

د. علاء طعيمة

كلية علوم الحاسوب وتكنولوجيا المعلومات / جامعة القادسية / العراق

مقالات ذات صلة

تعليق واحد

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *

زر الذهاب إلى الأعلى