• 基于人脸的常见表情识别——模型搭建、训练与测试


    模型搭建与训练

    数据接口准备

    data_transforms = {
        'train': transforms.Compose([
            transforms.Scale(64),
            transforms.RandomSizedCrop(48),
            transforms.RandomHorizontalFlip(),
            transforms.ToTensor(),
            transforms.Normalize([0.5,0.5,0.5], [0.5,0.5,0.5])
        ]),
        'val': transforms.Compose([
            transforms.Scale(64),
            transforms.CenterCrop(48),
            transforms.ToTensor(),
            transforms.Normalize([0.5,0.5,0.5], [0.5,0.5,0.5])
        ]),
    }
    
    data_dir = './train_val_data/'
    image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x),
                                              data_transforms[x]) for x in ['train', 'val']}
    dataloders = {x: torch.utils.data.DataLoader(image_datasets[x],
                                                 batch_size=16,
                                                 shuffle=True,
                                                 num_workers=4) for x in ['train', 'val']}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    模型定义

    import torch.nn as nn
    import torch.nn.functional as F
    
    class simpleconv3(nn.Module):
        def __init__(self):
            super(simpleconv3,self).__init__()
            self.conv1 = nn.Conv2d(3, 12, 3, 2)
            self.bn1 = nn.BatchNorm2d(12)
            self.conv2 = nn.Conv2d(12, 24, 3, 2)
            self.bn2 = nn.BatchNorm2d(24)
            self.conv3 = nn.Conv2d(24, 48, 3, 2)
            self.bn3 = nn.BatchNorm2d(48)
            self.fc1 = nn.Linear(48 * 5 * 5 , 1200)
            self.fc2 = nn.Linear(1200 , 128)
            self.fc3 = nn.Linear(128 , 4)
    
        def forward(self , x):
            x = F.relu(self.bn1(self.conv1(x)))
            #print "bn1 shape",x.shape
            x = F.relu(self.bn2(self.conv2(x)))
            x = F.relu(self.bn3(self.conv3(x)))
            x = x.view(-1 , 48 * 5 * 5) 
            x = F.relu(self.fc1(x))
            x = F.relu(self.fc2(x))
            x = self.fc3(x)
            return x
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    模型训练

    #coding:utf8
    from __future__ import print_function, division
    
    import torch
    import torch.nn as nn
    import torch.optim as optim
    from torch.optim import lr_scheduler
    from torch.autograd import Variable
    import torchvision
    from torchvision import datasets, models, transforms
    import time
    import os
    from tensorboardX import SummaryWriter
    import torch.nn.functional as F
    import numpy as np
    
    import warnings
    
    warnings.filterwarnings('ignore')
    
    writer = SummaryWriter()
    
    def train_model(model, criterion, optimizer, scheduler, num_epochs=25):
        for epoch in range(num_epochs):
            print('Epoch {}/{}'.format(epoch, num_epochs - 1))
            for phase in ['train', 'val']:
                if phase == 'train':
                    scheduler.step()
                    model.train(True)  # Set model to training mode
                else:
                    model.train(False)  # Set model to evaluate mode
    
                running_loss = 0.0
                running_corrects = 0.0
    
                for data in dataloders[phase]:
                    inputs, labels = data
                    if use_gpu:
                        inputs = Variable(inputs.cuda())
                        labels = Variable(labels.cuda())
                    else:
                        inputs, labels = Variable(inputs), Variable(labels)
    
                    optimizer.zero_grad()
                    outputs = model(inputs)
                    _, preds = torch.max(outputs.data, 1)
                    loss = criterion(outputs, labels)
                    if phase == 'train':
                        loss.backward()
                        optimizer.step()
    
                    running_loss += loss.data.item()
                    running_corrects += torch.sum(preds == labels).item()
    
                epoch_loss = running_loss / dataset_sizes[phase]
                epoch_acc = running_corrects / dataset_sizes[phase]
               
                if phase == 'train':
                    writer.add_scalar('data/trainloss', epoch_loss, epoch)
                    writer.add_scalar('data/trainacc', epoch_acc, epoch)
                else:
                    writer.add_scalar('data/valloss', epoch_loss, epoch)
                    writer.add_scalar('data/valacc', epoch_acc, epoch)
    
                print('{} Loss: {:.4f} Acc: {:.4f}'.format(
                    phase, epoch_loss, epoch_acc))
    
        writer.export_scalars_to_json("./all_scalars.json")
        writer.close()
        return model
    
    if __name__ == '__main__':
    
        data_transforms = {
            'train': transforms.Compose([
                transforms.Scale(64),
                transforms.RandomSizedCrop(48),
                transforms.RandomHorizontalFlip(),
                transforms.ToTensor(),
                transforms.Normalize([0.5,0.5,0.5], [0.5,0.5,0.5])
            ]),
            'val': transforms.Compose([
                transforms.Scale(64),
                transforms.CenterCrop(48),
                transforms.ToTensor(),
                transforms.Normalize([0.5,0.5,0.5], [0.5,0.5,0.5])
            ]),
        }
    
        data_dir = './Emotion_Recognition_File/train_val_data/' # 数据集所在的位置
        image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x),
                                                  data_transforms[x]) for x in ['train', 'val']}
        dataloders = {x: torch.utils.data.DataLoader(image_datasets[x],
                                                     batch_size=64,
                                                     shuffle=True if x=="train" else False,
                                                     num_workers=8) for x in ['train', 'val']}
    
        dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'val']}
    
        use_gpu = torch.cuda.is_available()
        print("是否使用 GPU", use_gpu)
        modelclc = simpleconv3()
        print(modelclc)
        if use_gpu:
            modelclc = modelclc.cuda()
    
        criterion = nn.CrossEntropyLoss()
        optimizer_ft = optim.SGD(modelclc.parameters(), lr=0.1, momentum=0.9)
        exp_lr_scheduler = lr_scheduler.StepLR(optimizer_ft, step_size=100, gamma=0.1)
    
        modelclc = train_model(model=modelclc,
                               criterion=criterion,
                               optimizer=optimizer_ft,
                               scheduler=exp_lr_scheduler,
                               num_epochs=10)  # 这里可以调节训练的轮次
        if not os.path.exists("models"):
            os.mkdir('models')
        torch.save(modelclc.state_dict(),'models/model.ckpt')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118

    模型测试

    # coding:utf8
    
    import sys
    import numpy as np
    import cv2
    import os
    import dlib
    
    import torch
    import torch.nn as nn
    import torch.optim as optim
    from torch.optim import lr_scheduler
    from torch.autograd import Variable
    import torchvision
    from torchvision import datasets, models, transforms
    import time
    from PIL import Image
    import torch.nn.functional as F
    
    import matplotlib.pyplot as plt
    import warnings
    
    warnings.filterwarnings('ignore')
    
    
    PREDICTOR_PATH = "./Emotion_Recognition_File/face_detect_model/shape_predictor_68_face_landmarks.dat"
    predictor = dlib.shape_predictor(PREDICTOR_PATH)
    cascade_path = './Emotion_Recognition_File/face_detect_model/haarcascade_frontalface_default.xml'
    cascade = cv2.CascadeClassifier(cascade_path)
    
    if not os.path.exists("results"):
        os.mkdir("results")
        
    
    def standardization(data):
        mu = np.mean(data, axis=0)
        sigma = np.std(data, axis=0)
        return (data - mu) / sigma
    
    
    def get_landmarks(im):
        rects = cascade.detectMultiScale(im, 1.3, 5)
        x, y, w, h = rects[0]
        rect = dlib.rectangle(int(x), int(y), int(x + w), int(y + h))
        return np.matrix([[p.x, p.y] for p in predictor(im, rect).parts()])
    
    
    def annotate_landmarks(im, landmarks):
        im = im.copy()
        for idx, point in enumerate(landmarks):
            pos = (point[0, 0], point[0, 1])
            cv2.putText(im,
                        str(idx),
                        pos,
                        fontFace=cv2.FONT_HERSHEY_SCRIPT_SIMPLEX,
                        fontScale=0.4,
                        color=(0, 0, 255))
            cv2.circle(im, pos, 3, color=(0, 255, 255))
        return im
    
    
    testsize = 48  # 测试图大小
    
    data_transforms = transforms.Compose([
        transforms.ToTensor(),
        transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
    ])
    net = simpleconv3()
    net.eval()
    modelpath = "./models/model.ckpt"  # 模型路径
    net.load_state_dict(
        torch.load(modelpath, map_location=lambda storage, loc: storage))
    
    # 一次测试一个文件
    img_path = "./Emotion_Recognition_File/find_face_img/"
    imagepaths = os.listdir(img_path)  # 图像文件夹
    for imagepath in imagepaths:
        im = cv2.imread(os.path.join(img_path, imagepath), 1)
        try:
            rects = cascade.detectMultiScale(im, 1.3, 5)
            x, y, w, h = rects[0]
            rect = dlib.rectangle(int(x), int(y), int(x + w), int(y + h))
            landmarks = np.matrix([[p.x, p.y]
                                   for p in predictor(im, rect).parts()])
        except:
    #         print("没有检测到人脸")
            continue  # 没有检测到人脸
    
        xmin = 10000
        xmax = 0
        ymin = 10000
        ymax = 0
    
        for i in range(48, 67):
            x = landmarks[i, 0]
            y = landmarks[i, 1]
            if x < xmin:
                xmin = x
            if x > xmax:
                xmax = x
            if y < ymin:
                ymin = y
            if y > ymax:
                ymax = y
    
        roiwidth = xmax - xmin
        roiheight = ymax - ymin
    
        roi = im[ymin:ymax, xmin:xmax, 0:3]
    
        if roiwidth > roiheight:
            dstlen = 1.5 * roiwidth
        else:
            dstlen = 1.5 * roiheight
    
        diff_xlen = dstlen - roiwidth
        diff_ylen = dstlen - roiheight
    
        newx = xmin
        newy = ymin
    
        imagerows, imagecols, channel = im.shape
        if newx >= diff_xlen / 2 and newx + roiwidth + diff_xlen / 2 < imagecols:
            newx = newx - diff_xlen / 2
        elif newx < diff_xlen / 2:
            newx = 0
        else:
            newx = imagecols - dstlen
    
        if newy >= diff_ylen / 2 and newy + roiheight + diff_ylen / 2 < imagerows:
            newy = newy - diff_ylen / 2
        elif newy < diff_ylen / 2:
            newy = 0
        else:
            newy = imagerows - dstlen
    
        roi = im[int(newy):int(newy + dstlen), int(newx):int(newx + dstlen), 0:3]
        roi = cv2.cvtColor(roi, cv2.COLOR_BGR2RGB)
        roiresized = cv2.resize(roi,
                                (testsize, testsize)).astype(np.float32) / 255.0
        imgblob = data_transforms(roiresized).unsqueeze(0)
        imgblob.requires_grad = False
        imgblob = Variable(imgblob)
        torch.no_grad()
        predict = F.softmax(net(imgblob))
        print(predict)
        index = np.argmax(predict.detach().numpy())
    
        im_show = cv2.imread(os.path.join(img_path, imagepath), 1)
        im_h, im_w, im_c = im_show.shape
        pos_x = int(newx + dstlen)
        pos_y = int(newy + dstlen)
        font = cv2.FONT_HERSHEY_SIMPLEX
        cv2.rectangle(im_show, (int(newx), int(newy)),
                      (int(newx + dstlen), int(newy + dstlen)), (0, 255, 255), 2)
        if index == 0:
            cv2.putText(im_show, 'none', (pos_x, pos_y), font, 1.5, (0, 0, 255), 2)
        if index == 1:
            cv2.putText(im_show, 'pout', (pos_x, pos_y), font, 1.5, (0, 0, 255), 2)
        if index == 2:
            cv2.putText(im_show, 'smile', (pos_x, pos_y), font, 1.5, (0, 0, 255), 2)
        if index == 3:
            cv2.putText(im_show, 'open', (pos_x, pos_y), font, 1.5, (0, 0, 255), 2)
    #     cv2.namedWindow('result', 0)
    #     cv2.imshow('result', im_show)
        cv2.imwrite(os.path.join('results', imagepath), im_show)
    #     print(os.path.join('results', imagepath))
        plt.imshow(im_show[:, :, ::-1])  # 这里需要交换通道,因为 matplotlib 保存图片的通道顺序是 RGB,而在 OpenCV 中是 BGR
        plt.show()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
  • 相关阅读:
    《吐血整理》进阶系列教程-拿捏Fiddler抓包教程(17)-Fiddler如何充当第三者,再识AutoResponder标签-下篇
    Jenkins环境部署与任务构建
    K8s版本升级---v1.21.0至v1.21.12
    java基于ssm+jsp仓库管理系统
    JAVA毕业设计146—基于Java+Springboot+vue+uniapp的景区旅游购票小程序(源代码+数据库+9000字论文)
    2022.9.1 SAP RFC
    CCES软件开发ADSP-21489的详解
    springboot整合https使用自签名证书实现浏览器和服务器之间的双向认证
    qt使用mysql数据库
    A. Grass Field
  • 原文地址:https://blog.csdn.net/GodGump/article/details/126062235