天天看點

『深度學習項目四』基于ResNet101人臉特征點檢測總結

相關文章:

【深度學習項目一】全連接配接神經網絡實作mnist數字識别

【深度學習項目二】卷積神經網絡LeNet實作minst數字識别

【深度學習項目三】ResNet50多分類任務【十二生肖分類】

『深度學習項目四』基于ResNet101人臉特征點檢測

項目連結:https://aistudio.baidu.com/aistudio/projectdetail/1932295

一、人臉檢測原理簡介

人臉關鍵點檢測,是輸入一張人臉圖檔,模型會傳回人臉關鍵點的一系列坐标,進而定位到人臉的關鍵資訊。

『深度學習項目四』基于ResNet101人臉特征點檢測總結
『深度學習項目四』基于ResNet101人臉特征點檢測總結

1.1 圖像分類和回歸的差別

『深度學習項目四』基于ResNet101人臉特征點檢測總結

1.2 損失函數

圖像分類CrossEntropyLoss :資訊熵的計算

loss ⁡ j = −  input  [  class  ] + log ⁡ ( ∑ i = 0 K exp ⁡ (  input  i ) ) , j = 1 , … , K \operatorname{loss}_{j}=-\text { input }[\text { class }]+\log \left(\sum_{i=0}^{K} \exp \left(\text { input }_{i}\right)\right), j=1, \ldots, K lossj​=− input [ class ]+log(∑i=0K​exp( input i​)),j=1,…,K

人臉關鍵點檢測: L1Loss、L2Loss、SmoothL1Loss :距離的計算

Loss_1:

loss ⁡ ( x , y ) = 1 n ∑ i = 1 n ∣ y i − f ( x i ) ∣ \operatorname{loss}(x, y)=\frac{1}{n} \sum_{i=1}^{n}\left|y_{i}-f\left(x_{i}\right)\right| loss(x,y)=n1​∑i=1n​∣yi​−f(xi​)∣

Loss_2:

loss ⁡ ( x , y ) = 1 n ∑ i = 1 n ( y i − f ( x i ) ) 2 \operatorname{loss}(x, y)=\frac{1}{n} \sum_{i=1}^{n}\left(y_{i}-f\left(x_{i}\right)\right)^{2} loss(x,y)=n1​∑i=1n​(yi​−f(xi​))2

Loss_3:分段loss

loss ⁡ ( x , y ) = 1 n ∑ i = 1 n { . 5 ∗ ( y i − f ( x i ) ) 2 ,  if  ∣ y i − f ( x ) ∣ ∣ y i − f ( x i ) ∣ − 0.5 ,  otherwise  \operatorname{loss}(x, y)=\frac{1}{n} \sum_{i=1}^{n}\left\{\begin{array}{ll} .5 *\left(y_{i}-f\left(x_{i}\right)\right)^{2}, & \text { if } \mid y_{i}-f(x)| \\ \left|y_{i}-f\left(x_{i}\right)\right|-0.5, & \text { otherwise } \end{array}\right. loss(x,y)=n1​∑i=1n​{.5∗(yi​−f(xi​))2,∣yi​−f(xi​)∣−0.5,​ if ∣yi​−f(x)∣ otherwise ​

有利于快速收斂!

1.3 評估名額 NME

『深度學習項目四』基于ResNet101人臉特征點檢測總結
# 環境導入
import os
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.image as mpimg

import cv2
import paddle

#paddle.set_device('gpu') # 手動設定設定為GPU

import warnings 
warnings.filterwarnings('ignore') # 忽略 warning
           

二、資料準備

2.1 下載下傳資料集

本次實驗所采用的資料集來源為github的開源項目

加載後可以直接使用下面的指令解壓。

unzip是一個常見的解壓縮指令:

-l:顯示壓縮檔案内所包含的檔案;

-t:檢查壓縮檔案是否正确;

-o:不必先詢問使用者,unzip執行後覆寫原有的檔案;

-n:解壓縮時不要覆寫原有的檔案;

-q:執行時不顯示任何資訊;

-d<目錄>:指定檔案解壓縮後所要存儲的目錄;

解壓後的資料集結構為

data/
|—— test
|   |—— Abdel_Aziz_Al-Hakim_00.jpg
    ... ...
|—— test_frames_keypoints.csv
|—— training
|   |—— Abdullah_Gul_10.jpg
    ... ...
|—— training_frames_keypoints.csv
           

其中,

training

test

檔案夾分别存放訓練集和測試集。

training_frames_keypoints.csv

test_frames_keypoints.csv

存放着訓練集和測試集的标簽。首先看一下訓練集的标簽

training_frames_keypoints.csv

檔案,是如何定義的

key_pts_frame = pd.read_csv('data/training_frames_keypoints.csv') # 讀取資料集
print('Number of images: ', key_pts_frame.shape[0]) # 輸出資料集大小
key_pts_frame.head(5) # 看前五條資料
           
『深度學習項目四』基于ResNet101人臉特征點檢測總結

上表中每一行都代表一條資料,其中,第一列是圖檔的檔案名,之後從第0列到第135列,就是該圖的關鍵點資訊。因為每個關鍵點可以用兩個坐标【橫縱坐标】表示,是以 136/2 = 68,就可以看出這個資料集為68點人臉關鍵點資料集。

Tips1: 目前常用的人臉關鍵點标注,有如下點數的标注

  • 5點
  • 21點
  • 68點
  • 98點

Tips2:本次所采用的68标注,标注順序如下:

『深度學習項目四』基于ResNet101人臉特征點檢測總結
# 計算标簽的均值和标準差,用于标簽的歸一化
key_pts_values = key_pts_frame.values[:,1:] # 取出标簽資訊
data_mean = key_pts_values.mean() # 計算均值
data_std = key_pts_values.std()   # 計算标準差
print('标簽的均值為:', data_mean)
print('标簽的标準差為:', data_std)
           

标簽的均值為: 104.4724870017331

标簽的标準差為: 43.17302271754281

2.2 檢視圖像

對以下函數的幾點解釋:

len(key_pts)//2 :因為key_pts裡面是一個128的一維數組,本次人臉檢測是68個關鍵點,128個資料裡面應該是兩個兩個一組,分别組成一個關鍵點的(x,y)坐标。

擴充,圖像的坐标分布:

圖像的坐标是從左上角開始,一般以水準向右為x軸正方向,豎直向下為y軸正方向。

def show_keypoints(image, key_pts):  
    """
    Args:
       需要列印 image: 圖像資訊
               key_pts: 關鍵點資訊,
    展示圖檔和關鍵點資訊
    """
    plt.imshow(image.astype('uint8'))  # 展示圖檔資訊
    for i in range(len(key_pts)//2,):
        plt.scatter(key_pts[i*2], key_pts[i*2+1], s=20, marker='.', c='b') # 展示關鍵點資訊
           
# 展示多條資料

index = [5,10,15,20] # n為資料在表格中的索引 
for n in index:
    image_name = key_pts_frame.iloc[n, 0] # 擷取圖像名稱 
    #key_pts = key_pts_frame.iloc[n, 1:].as_matrix() # 将圖像label格式轉為numpy.array的格式   會報錯'Series' object has no attribute 'as_matrix'改成下面
    key_pts = key_pts_frame.iloc[n, 1:].values   #主要原因是庫版本更新,'as_matrix()‘改為了’values’。
    key_pts = key_pts.astype('float').reshape(-1) # 擷取圖像關鍵點資訊
    print("the image's name is : {}, key_pts is : {}".format(image_name, key_pts.shape)) # 列印圖像資訊
    plt.figure(figsize=(5, 5)) # 展示的圖像大小
    images = show_keypoints(mpimg.imread(os.path.join('data/training/', image_name)), key_pts) # 展示圖像與關鍵點資訊
    plt.show(images) # 展示圖像

           
『深度學習項目四』基于ResNet101人臉特征點檢測總結

the image’s name is : Albert_Brooks_12.jpg, key_pts is : (136,)

『深度學習項目四』基于ResNet101人臉特征點檢測總結

the image’s name is : Paul_Otellini_01.jpg, key_pts is : (136,)

2.3 資料集定義

使用飛槳架構高層API的 ``paddle.io.Dataset`` 自定義資料集類,具體可以參考官網文檔 [自定義資料集](https://www.paddlepaddle.org.cn/documentation/docs/zh/guides/02_paddle2.0_develop/02_data_load_cn.html#id3)。
import paddle
from paddle.io import Dataset

BATCH_SIZE = 64
BATCH_NUM = 20

IMAGE_SIZE = (28, 28)
CLASS_NUM = 10


class MyDataset(Dataset):
    """
    步驟一:繼承paddle.io.Dataset類
    """
    def __init__(self, num_samples):
        """
        步驟二:實作構造函數,定義資料集大小
        """
        super(MyDataset, self).__init__()
        self.num_samples = num_samples

    def __getitem__(self, index):
        """
        步驟三:實作__getitem__方法,定義指定index時如何擷取資料,并傳回單條資料(訓練資料,對應的标簽)
        """
        data = paddle.uniform(IMAGE_SIZE, dtype='float32')
        label = paddle.randint(0, CLASS_NUM-1, dtype='int64')

        return data, label

    def __len__(self):
        """
        步驟四:實作__len__方法,傳回資料集總數目
        """
        return self.num_samples

# 測試定義的資料集
custom_dataset = MyDataset(BATCH_SIZE * BATCH_NUM)

print('=============custom dataset=============')
for data, label in custom_dataset:
    print(data.shape, label.shape)
    break

           
# 按照Dataset的使用規範,建構人臉關鍵點資料集

from paddle.io import Dataset

class FacialKeypointsDataset(Dataset):
    # 人臉關鍵點資料集
    """
    步驟一:繼承paddle.io.Dataset類
    """
    def __init__(self, csv_file, root_dir, transform=None):
        """
        步驟二:實作構造函數,定義資料集大小
        Args:
            csv_file (string): 帶标注的csv檔案路徑
            root_dir (string): 圖檔存儲的檔案夾路徑
            transform (callable, optional): 應用于圖像上的資料處理方法
        """
        self.key_pts_frame = pd.read_csv(csv_file) # 讀取csv檔案
        self.root_dir = root_dir # 擷取圖檔檔案夾路徑
        self.transform = transform # 擷取 transform 方法

    def __getitem__(self, idx):
        """
        步驟三:實作__getitem__方法,定義指定index時如何擷取資料,并傳回單條資料(訓練資料,對應的标簽)
        """

        image_name = os.path.join(self.root_dir,
                                self.key_pts_frame.iloc[idx, 0]) #檔案名
        # 擷取圖像
        image = mpimg.imread(image_name)
        
        # 圖像格式處理,如果包含 alpha 通道,那麼忽略它
        if(image.shape[2] == 4):
            image = image[:,:,0:3]
        
        # 擷取關鍵點資訊
        #key_pts = self.key_pts_frame.iloc[idx, 1:].as_matrix()  #第一列到最後一列,轉為numpy
        key_pts = self.key_pts_frame.iloc[idx, 1:].values
        key_pts = key_pts.astype('float').reshape(-1) # [136, 1] 136個關鍵點

        # 如果定義了 transform 方法,使用 transform方法
        if self.transform:
            image, key_pts = self.transform([image, key_pts])
        
        # 轉為 numpy 的資料格式
        image = np.array(image, dtype='float32')
        key_pts = np.array(key_pts, dtype='float32')

        return image, key_pts

    def __len__(self):
        """
        步驟四:實作__len__方法,傳回資料集總數目
        """
        return len(self.key_pts_frame) # 傳回資料集大小,即圖檔的數量

           

2.4 訓練集可視化

執行個體化資料集并顯示一些圖像。

# 建構一個資料集類
face_dataset = FacialKeypointsDataset(csv_file='data/training_frames_keypoints.csv',
                                      root_dir='data/training/')

# 輸出資料集大小
print('資料集大小為: ', len(face_dataset))
# 根據 face_dataset 可視化資料集
num_to_display = 3

for i in range(num_to_display):
    
    # 定義圖檔大小
    fig = plt.figure(figsize=(20,10))
    
    # 随機選擇圖檔
    rand_i = np.random.randint(0, len(face_dataset))
    sample = face_dataset[rand_i]

    # 輸出圖檔大小和關鍵點的數量
    print(i, sample[0].shape, sample[1].shape)  #圖檔和label

    # 設定圖檔列印資訊
    ax = plt.subplot(1, num_to_display, i + 1)
    ax.set_title('Sample #{}'.format(i))
    
    # 輸出圖檔
    show_keypoints(sample[0], sample[1])
           
資料集大小為:  3462
0 (99, 89, 3) (136,)
1 (259, 243, 3) (136,)
2 (275, 254, 3) (136,)
           
『深度學習項目四』基于ResNet101人臉特征點檢測總結
『深度學習項目四』基于ResNet101人臉特征點檢測總結

上述代碼雖然完成了資料集的定義,但是還有一些問題,如:

  • 每張圖像的大小不一樣,圖像大小需要統一以适配網絡輸入要求 /不然網絡會爆炸
  • 圖像格式需要适配模型的格式輸入要求 / 轉化成CHW
  • 資料量比較小,沒有進行資料增強 /3000條資料

這些問題都會影響模型最終的性能,是以需要對資料進行預處理。

2.5 Transforms

對圖像進行預處理,包括灰階化、歸一化、重新設定尺寸、随機裁剪,修改通道格式等等,以滿足資料要求;每一類的功能如下:

  • 灰階化:丢棄顔色資訊,保留圖像邊緣資訊;識别算法對于顔色的依賴性不強,加上顔色後魯棒性會下降,而且灰階化圖像次元下降(3->1),保留梯度的同時會加快計算。 #人臉特征對顔色依賴不強主要看特征不是色彩
  • 歸一化:加快收斂
  • 重新設定尺寸:資料增強 對圖像進行改變大小resize後,label會對應不上,也需要一一映射
  • 随機裁剪:資料增強
  • 修改通道格式:改為模型需要的結構
# 标準化自定義 transform 方法

class TransformAPI(object):
    """
    步驟一:繼承 object 類
    """
    def __call__(self, data):

        """
        步驟二:在 __call__ 中定義資料處理方法
        """
        
        processed_data = data
        return  processed_data
           
import paddle.vision.transforms.functional as F

class GrayNormalize(object):
    # 将圖檔變為灰階圖,并将其值放縮到[0, 1]
    # 将 label 放縮到 [-1, 1] 之間

    def __call__(self, data):
        image = data[0]   # 擷取圖檔
        key_pts = data[1] # 擷取标簽
        
        image_copy = np.copy(image)
        key_pts_copy = np.copy(key_pts)

        # 灰階化圖檔
        gray_scale = paddle.vision.transforms.Grayscale(num_output_channels=3) #灰階正常情況通道數設定成為1  但是resnet50輸入要求3是以設定
        image_copy = gray_scale(image_copy)
        
        # 将圖檔值放縮到 [0, 1],歸一化直接除就行
        image_copy = image_copy / 255.0
        
        # 将坐标點放縮到 [-1, 1]
        mean = data_mean # 擷取标簽均值
        std = data_std   # 擷取标簽标準差
        key_pts_copy = (key_pts_copy - mean)/std

        return image_copy, key_pts_copy

class Resize(object):
    # 将輸入圖像調整為指定大小

    def __init__(self, output_size):
        assert isinstance(output_size, (int, tuple))
        self.output_size = output_size

    def __call__(self, data):

        image = data[0]    # 擷取圖檔
        key_pts = data[1]  # 擷取标簽

        image_copy = np.copy(image)      
        key_pts_copy = np.copy(key_pts)

        h, w = image_copy.shape[:2]
        if isinstance(self.output_size, int):
            if h > w:
                new_h, new_w = self.output_size * h / w, self.output_size
            else:
                new_h, new_w = self.output_size, self.output_size * w / h
        else:
            new_h, new_w = self.output_size

        new_h, new_w = int(new_h), int(new_w)

        img = paddle.vision.transforms.resize(image_copy, (new_h, new_w))
        
        # scale the pts, too 同比例尺放縮
        key_pts_copy[::2] = key_pts_copy[::2] * new_w / w
        key_pts_copy[1::2] = key_pts_copy[1::2] * new_h / h

        return img, key_pts_copy


class RandomCrop(object):
    # 随機位置裁剪輸入的圖像
    
    def __init__(self, output_size):
        assert isinstance(output_size, (int, tuple))
        if isinstance(output_size, int):
            self.output_size = (output_size, output_size)
        else:
            assert len(output_size) == 2
            self.output_size = output_size

    def __call__(self, data):
        image = data[0]
        key_pts = data[1]

        image_copy = np.copy(image)
        key_pts_copy = np.copy(key_pts)

        h, w = image_copy.shape[:2]
        new_h, new_w = self.output_size
        #h=256 new_h=224  h - new_h=32
        top = np.random.randint(0, h - new_h)  #(0,32)随機生成一個點
        left = np.random.randint(0, w - new_w)
        #裁剪方式
        image_copy = image_copy[top: top + new_h,
                      left: left + new_w]
       #關鍵點的值,減去左上角和上面的值得到新的值
        key_pts_copy[::2] = key_pts_copy[::2] - left
        key_pts_copy[1::2] = key_pts_copy[1::2] - top

        return image_copy, key_pts_copy

class ToCHW(object):
    # 将圖像的格式由HWC改為CHW
    def __call__(self, data):

        image = data[0]
        key_pts = data[1]

        transpose = paddle.vision.transforms.Transpose((2, 0, 1)) # 改為CHW
        image = transpose(image)
        
        return image, key_pts
           

看一下每種圖像預處理方法的的效果。

import paddle.vision.transforms as T

# 測試 Resize
resize = Resize(256)

# 測試 RandomCrop
random_crop = RandomCrop(128)

# 測試 GrayNormalize 灰階+歸一化
norm = GrayNormalize()

# 測試 Resize + RandomCrop,圖像大小變到256*256, 然後截取出224*224的圖像塊
composed = paddle.vision.transforms.Compose([Resize(256), RandomCrop(224)])  #list一次做變化

test_num = 500 # 測試的資料下标
data = face_dataset[test_num]

transforms = {'None': None, 
              'norm': norm,
              'random_crop': random_crop,
              'resize': resize ,
              'composed': composed}
for i, func_name in enumerate(['None', 'norm', 'random_crop', 'resize', 'composed']):  #enumerate進行枚舉
    
    # 定義圖檔大小
    fig = plt.figure(figsize=(20,10))
    
    # 處理圖檔
    if transforms[func_name] != None:
        transformed_sample = transforms[func_name](data)
    else:
        transformed_sample = data

    # 設定圖檔列印資訊
    ax = plt.subplot(1, 5, i + 1)
    ax.set_title(' Transform is #{}'.format(func_name))
    
    # 輸出圖檔
    show_keypoints(transformed_sample[0], transformed_sample[1])
           
『深度學習項目四』基于ResNet101人臉特征點檢測總結
『深度學習項目四』基于ResNet101人臉特征點檢測總結
『深度學習項目四』基于ResNet101人臉特征點檢測總結
『深度學習項目四』基于ResNet101人臉特征點檢測總結
『深度學習項目四』基于ResNet101人臉特征點檢測總結

2.6 使用資料預處理的方式完成資料定義

讓我們将

Resize、RandomCrop、GrayNormalize、ToCHW

應用于新的資料集

from paddle.vision.transforms import Compose

data_transform = Compose([Resize(256), RandomCrop(224), GrayNormalize(), ToCHW()])

# create the transformed dataset
train_dataset = FacialKeypointsDataset(csv_file='data/training_frames_keypoints.csv',
                                       root_dir='data/training/',
                                       transform=data_transform)
print('Number of train dataset images: ', len(train_dataset))

for i in range(5):
    sample = train_dataset[i]
    print(i, sample[0].shape, sample[1].shape)

test_dataset = FacialKeypointsDataset(csv_file='data/test_frames_keypoints.csv',
                                             root_dir='data/test/',
                                             transform=data_transform)

print('Number of test dataset images: ', len(test_dataset))
           

三、模型組建

3.1 組網

根據前文的分析可知,人臉關鍵點檢測和分類,可以使用同樣的網絡結構,如LeNet、Resnet50等完成特征的提取,隻是在原來的基礎上,需要修改模型的最後部分,将輸出調整為 人臉關鍵點的數量*2,即每個人臉關鍵點的橫坐标與縱坐标,就可以完成人臉關鍵點檢測任務了,具體可以見下面的代碼,也可以參考官網案例:人臉關鍵點檢測

網絡結構如下:

『深度學習項目四』基于ResNet101人臉特征點檢測總結

為了加強網絡的訓練效果,可以考慮換用Resnet101

利用paddle高層API,一行代碼就可以實作對網絡的調用:paddle.vision.models.resnet101(pretrained=True) ,pretrained,表示model使用預訓練好的參數

import paddle.nn as nn
from paddle.vision.models import resnet50
class SimpleNet(nn.Layer):
    
    def __init__(self, key_pts):

        super(SimpleNet, self).__init__()

        # 使用resnet50作為backbone
        self.backbone = paddle.vision.models.resnet50(pretrained=True) #輸出為1000  ResNet-1  [[1, 3, 224, 224]]    [1, 1000]      0   
                          #paddle.vision.models.resnet101(pretrained=True) 
        # 添加第一個線性變換層
        self.linear1 = nn.Linear(in_features=1000, out_features=512)

        # 使用 ReLU 激活函數
        self.act1 = nn.ReLU()

        # 添加第二個線性變換層作為輸出,輸出元素的個數為 key_pts*2,代表每個關鍵點的坐标 68點
        self.linear2 = nn.Linear(in_features=512, out_features=key_pts*2)

    def forward(self, x):

        x = self.backbone(x)
        x = self.linear1(x)
        x = self.act1(x)
        x = self.linear2(x)

        return x
           

3.2 網絡結構可視化

使用

model.summary

可視化網絡結構。

model = paddle.Model(SimpleNet(key_pts=68))
#random_crop[224,224,3]
#toCHW[3,224,224]
model.summary((-1, 3, 224, 224)) #--1 batchsize可以後續自己定義大小
           

四、模型訓練

4.1 模型配置

訓練模型前,需要設定訓練模型所需的優化器,損失函數和評估名額。

  • 優化器:Adam優化器,快速收斂。
  • 損失函數:SmoothL1Loss
  • 評估名額:NME

4.2 自定義評估名額

特定任務的 Metric 計算方式在架構既有的 Metric接口中不存在,或算法不符合自己的需求,那麼需要我們自己來進行Metric的自定義。這裡介紹如何進行Metric的自定義操作,更多資訊可以參考官網文檔自定義Metric;首先來看下面的代碼。

from paddle.metric import Metric

class NME(Metric):
    """
    1. 繼承paddle.metric.Metric
    """
    def __init__(self, name='nme', *args, **kwargs):
        """
        2. 構造函數實作,自定義參數即可
        """
        super(NME, self).__init__(*args, **kwargs)
        self._name = name
        self.rmse = 0
        self.sample_num = 0
    
    def name(self):
        """
        3. 實作name方法,傳回定義的評估名額名字
        """
        return self._name
    
    def update(self, preds, labels):
        """
        4. 實作update方法,用于單個batch訓練時進行評估名額計算。
        - 當`compute`類函數未實作時,會将模型的計算輸出和标簽資料的展平作為`update`的參數傳入。
        """
        N = preds.shape[0]

        preds = preds.reshape((N, -1, 2))
        labels = labels.reshape((N, -1, 2))

        self.rmse = 0
        
        for i in range(N):
            pts_pred, pts_gt = preds[i, ], labels[i, ]
            interocular = np.linalg.norm(pts_gt[36, ] - pts_gt[45, ])

            self.rmse += np.sum(np.linalg.norm(pts_pred - pts_gt, axis=1)) / (interocular * preds.shape[1])
            self.sample_num += 1

        return self.rmse / N
    
    def accumulate(self):
        """
        5. 實作accumulate方法,傳回曆史batch訓練積累後計算得到的評價名額值。
        每次`update`調用時進行資料積累,`accumulate`計算時對積累的所有資料進行計算并傳回。
        結算結果會在`fit`接口的訓練日志中呈現。
        """
        return self.rmse / self.sample_num
    
    def reset(self):
        """
        6. 實作reset方法,每個Epoch結束後進行評估名額的重置,這樣下個Epoch可以重新進行計算。
        """
        self.rmse = 0
        self.sample_num = 0
           
# 使用 paddle.Model 封裝模型
model = paddle.Model(SimpleNet(key_pts=68))

# 定義Adam優化器
optimizer = paddle.optimizer.Adam(learning_rate=0.001,
                                weight_decay=5e-4,
                                parameters=model.parameters())
# 定義SmoothL1Loss
loss = nn.SmoothL1Loss()

# 使用自定義metrics
metric = NME()

model.prepare(optimizer=optimizer, loss=loss, metrics=metric)
           

損失函數的選擇:L1Loss、L2Loss、SmoothL1Loss的對比

  • L1Loss: 在訓練後期,預測值與ground-truth差異較小時, 損失對預測值的導數的絕對值仍然為1,此時如果學習率不變,損失函數将在穩定值附近波動,難以繼續收斂達到更高精度。
  • L2Loss: 在訓練初期,預測值與ground-truth差異較大時,損失函數對預測值的梯度十分大,導緻訓練不穩定。
  • SmoothL1Loss: 在x較小時,對x梯度也會變小,而在x很大時,對x的梯度的絕對值達到上限 1,也不會太大以至于破壞網絡參數。

4.2 模型訓練

callback= paddle.callbacks.VisualDL(log_dir='visualdl_log')
model.fit(train_dataset, epochs=50, batch_size=64, verbose=1,callbacks=callback)

           
Epoch 40/50
step 55/55 [==============================] - loss: 0.0231 - nme: 3.8331e-04 - 528ms/step     
Epoch 41/50
step 55/55 [==============================] - loss: 0.0449 - nme: 4.5357e-04 - 506ms/step     
Epoch 42/50
step 55/55 [==============================] - loss: 0.0281 - nme: 3.3320e-04 - 506ms/step     
Epoch 43/50
step 55/55 [==============================] - loss: 0.0198 - nme: 3.0033e-04 - 513ms/step     
Epoch 44/50
step 55/55 [==============================] - loss: 0.0263 - nme: 3.9367e-04 - 509ms/step     
Epoch 45/50
step 55/55 [==============================] - loss: 0.0448 - nme: 4.6078e-04 - 518ms/step     
Epoch 46/50
step 55/55 [==============================] - loss: 0.0321 - nme: 3.2545e-04 - 522ms/step     
Epoch 47/50
step 55/55 [==============================] - loss: 0.0249 - nme: 3.2067e-04 - 520ms/step     
Epoch 48/50
step 55/55 [==============================] - loss: 0.0270 - nme: 3.2314e-04 - 520ms/step     
Epoch 49/50
step 55/55 [==============================] - loss: 0.0183 - nme: 2.7263e-04 - 531ms/step     
Epoch 50/50
step 55/55 [==============================] - loss: 0.0166 - nme: 2.7703e-04 - 542ms/step 
           
『深度學習項目四』基于ResNet101人臉特征點檢測總結

4.3 模型儲存

checkpoints_path = './checkpoints/models'
model.save(checkpoints_path)
           

五、模型預測

# 定義功能函數

def show_all_keypoints(image, predicted_key_pts):
    """
    展示圖像,預測關鍵點
    Args:
        image:裁剪後的圖像 [224, 224, 3]
        predicted_key_pts: 預測關鍵點的坐标
    """
    # 展示圖像
    plt.imshow(image.astype('uint8'))

    # 展示關鍵點
    for i in range(0, len(predicted_key_pts), 2):
        plt.scatter(predicted_key_pts[i], predicted_key_pts[i+1], s=20, marker='.', c='m')

def visualize_output(test_images, test_outputs, batch_size=1, h=20, w=10):
    """
    展示圖像,預測關鍵點
    Args:
        test_images:裁剪後的圖像 [224, 224, 3]
        test_outputs: 模型的輸出
        batch_size: 批大小
        h: 展示的圖像高
        w: 展示的圖像寬
    """

    if len(test_images.shape) == 3:
        test_images = np.array([test_images])

    for i in range(batch_size):

        plt.figure(figsize=(h, w))
        ax = plt.subplot(1, batch_size, i+1)

        # 随機裁剪後的圖像
        image = test_images[i]

        # 模型的輸出,未還原的預測關鍵點坐标值
        predicted_key_pts = test_outputs[i]

        # 還原後的真實的關鍵點坐标值
        predicted_key_pts = predicted_key_pts * data_std + data_mean
        
        # 展示圖像和關鍵點
        show_all_keypoints(np.squeeze(image), predicted_key_pts)
            
        plt.axis('off')

    plt.show()
           
# 讀取圖像
img = mpimg.imread('4.jpg')

# 關鍵點占位符
kpt = np.ones((136, 1))

transform = Compose([Resize(256), RandomCrop(224)])
#【224,224,3】
# 對圖像先重新定義大小,并裁剪到 224*224的大小
rgb_img, kpt = transform([img, kpt])

norm = GrayNormalize()
to_chw = ToCHW()

# 對圖像進行歸一化和格式變換
img, kpt = norm([rgb_img, kpt])
img, kpt = to_chw([img, kpt])

img = np.array([img], dtype='float32')


# 加載儲存好的模型進行預測
model = paddle.Model(SimpleNet(key_pts=68))
model.load(checkpoints_path)
model.prepare()

# 預測結果
out = model.predict_batch([img])
out = out[0].reshape((out[0].shape[0], 136, -1))


# 可視化
visualize_output(rgb_img, out, batch_size=1)

           
『深度學習項目四』基于ResNet101人臉特征點檢測總結
# 讀取圖像
img = mpimg.imread('3.jpg')

# 關鍵點占位符
kpt = np.ones((136, 1))

transform = Compose([Resize(256), RandomCrop(224)])
#【224,224,3】
# 對圖像先重新定義大小,并裁剪到 224*224的大小
rgb_img, kpt = transform([img, kpt])

norm = GrayNormalize()
to_chw = ToCHW()

# 對圖像進行歸一化和格式變換
img, kpt = norm([rgb_img, kpt])
img, kpt = to_chw([img, kpt])

img = np.array([img], dtype='float32')


# 加載儲存好的模型進行預測
model = paddle.Model(SimpleNet(key_pts=68))
model.load(checkpoints_path)
model.prepare()

# 預測結果
out = model.predict_batch([img])
out = out[0].reshape((out[0].shape[0], 136, -1))


# 可視化
visualize_output(rgb_img, out, batch_size=1)

           
『深度學習項目四』基于ResNet101人臉特征點檢測總結
# 讀取圖像
img = mpimg.imread('1.jpg')

# 關鍵點占位符
kpt = np.ones((136, 1))

transform = Compose([Resize(256), RandomCrop(224)])
#【224,224,3】
# 對圖像先重新定義大小,并裁剪到 224*224的大小
rgb_img, kpt = transform([img, kpt])

norm = GrayNormalize()
to_chw = ToCHW()

# 對圖像進行歸一化和格式變換
img, kpt = norm([rgb_img, kpt])
img, kpt = to_chw([img, kpt])

img = np.array([img], dtype='float32')


# 加載儲存好的模型進行預測
model = paddle.Model(SimpleNet(key_pts=68))
model.load(checkpoints_path)
model.prepare()

# 預測結果
out = model.predict_batch([img])
out = out[0].reshape((out[0].shape[0], 136, -1))


# 可視化
visualize_output(rgb_img, out, batch_size=1)
           
『深度學習項目四』基于ResNet101人臉特征點檢測總結

六、擴充應用

當我們得到關鍵點的資訊後,就可以進行一些擴充的應用。

# 定義功能函數

def show_fu(image, predicted_key_pts):
    """
    展示加了貼紙的圖像
    Args:
        image:裁剪後的圖像 [224, 224, 3]
        predicted_key_pts: 預測關鍵點的坐标
    """
    # 計算坐标,15 和 34點的中間值
    x = (int(predicted_key_pts[28]) + int(predicted_key_pts[66]))//2
    y = (int(predicted_key_pts[29]) + int(predicted_key_pts[67]))//2

    # 打開 春節小圖
    star_image = mpimg.imread('light.jpg')

    # 處理通道
    if(star_image.shape[2] == 4):
        star_image = star_image[:,:,1:4]
    
    # 将小圖放到原圖上
    image[y:y+len(star_image[0]), x:x+len(star_image[1]),:] = star_image

    # 展示處理後的圖檔
    plt.imshow(image.astype('uint8'))

    # 展示關鍵點資訊
    for i in range(len(predicted_key_pts)//2,):
        plt.scatter(predicted_key_pts[i*2], predicted_key_pts[i*2+1], s=20, marker='.', c='m') # 展示關鍵點資訊


def custom_output(test_images, test_outputs, batch_size=1, h=20, w=10):
    """
    展示圖像,預測關鍵點
    Args:
        test_images:裁剪後的圖像 [224, 224, 3]
        test_outputs: 模型的輸出
        batch_size: 批大小
        h: 展示的圖像高
        w: 展示的圖像寬
    """

    if len(test_images.shape) == 3:
        test_images = np.array([test_images])

    for i in range(batch_size):

        plt.figure(figsize=(h, w))
        ax = plt.subplot(1, batch_size, i+1)

        # 随機裁剪後的圖像
        image = test_images[i]

        # 模型的輸出,未還原的預測關鍵點坐标值
        predicted_key_pts = test_outputs[i]

        # 還原後的真實的關鍵點坐标值
        predicted_key_pts = predicted_key_pts * data_std + data_mean
        
        # 展示圖像和關鍵點
        show_fu(np.squeeze(image), predicted_key_pts)
            
        plt.axis('off')

    plt.show()

# 讀取圖像
img = mpimg.imread('4.jpg')

# 關鍵點占位符
kpt = np.ones((136, 1))

transform = Compose([Resize(256), RandomCrop(224)])

# 對圖像先重新定義大小,并裁剪到 224*224的大小
rgb_img, kpt = transform([img, kpt])

norm = GrayNormalize()
to_chw = ToCHW()

# 對圖像進行歸一化和格式變換
img, kpt = norm([rgb_img, kpt])
img, kpt = to_chw([img, kpt])

img = np.array([img], dtype='float32')

# 加載儲存好的模型進行預測
# model = paddle.Model(SimpleNet())
# model.load(checkpoints_path)
# model.prepare()

# 預測結果
out = model.predict_batch([img])
out = out[0].reshape((out[0].shape[0], 136, -1))

# 可視化
custom_output(rgb_img, out, batch_size=1)
           
『深度學習項目四』基于ResNet101人臉特征點檢測總結

總結

上面換用了resnet101,為了縮短訓練時間将Epoch設定為25.但是經過實驗發現模型效果不理想,還是改為50個EPOCH。并且開啟VisualDL,便于觀察模型訓練情況。訓練情況可視化,可以發現loss下降的很快。使用resnet101并且Epoch為50的時候,模型是過拟合的,因為另一方面資料集的量很少。是以建議還是使用Resnet50就行,調優可以從優化方法,batch,等下手。

效果不是很好可能訓練樣本有很大關系!

『深度學習項目四』基于ResNet101人臉特征點檢測總結
『深度學習項目四』基于ResNet101人臉特征點檢測總結
『深度學習項目四』基于ResNet101人臉特征點檢測總結
『深度學習項目四』基于ResNet101人臉特征點檢測總結
『深度學習項目四』基于ResNet101人臉特征點檢測總結

可以看到識别精度不是很好,尤其是遇到人臉有角度的情況下準确率十分低,造成這個情況可能是資料樣本太少以及網絡結構簡單。需要更大的資料集以及更好的網絡進行調試

繼續閱讀