赞
踩
“来源:投稿 作者:LSC
编辑:学姐
本篇文章的内容是从表情分类,到完成人脸表情识别,理论穿插实战代码。分为两部分,第一部分为表情分类实战;第二部分为人脸表情识别案例实战。
数据集: 表情数据集,可分为无表情、撅嘴、微笑、张嘴四类。
样本个数: 训练样本13598,验证样本1509
使用网络: resnet,multiscale-resnet
无表情0none:4763 训练集4287 测试集476
嘟嘴1pouting:3154 训练集2839 测试集315
微笑2smile:4841 训练集4357 测试集484
大笑3openmouth:2348 训练集2114 测试集234
训练数据:13597
测试数据:1509
格式:统一为128*128,jpg图像
训练模型要求:
(1) 输入大小统一为:96*96
(2) 最后一层特征输出大小为3*3,分类类别为4,使用迁移学习
使用torch.utils.data里的data函数实现编写过程;
分为_init_、_len_、_getitem_三个模块;
_init_完成某些参数的初始定义;
len 获取数据集的总数;
getitem 读取每幅图像和标签。
可以自行编写,也可以使用torchvision.transform
可以自行编写,也可以使用torchvision.models中的模型
评价指标记录函数的编写,主要为分类准确率.
从零开始整理数据,使用pytorch从零开始搭建表情识别模型
从数据整理到模型验证
1.3.1数据获取的常见方法
数据集(ImageNet等 数据质量高 成本低) 外包(阿里众包等 大规模 成本高) 自采集(自己采集或者爬虫 成本低 速度快)
1.3.2开源数据集
表情数据集: KDEF, RaFD, RAF, EMotioNet等
人脸数据集: Celaba等
1.3.3爬虫获取
用于准备小型的数据集(10000以内),图片来源主要是搜索引擎,快速获取第一批数据用于验证方案。
1.4.1数据预处理——归一化
去除损坏图片,防止读取失败
类型归一化(方便遍历,*.jpg | .*png,统一压缩方式)
去除尺寸异常图片(如长宽比大于10)
命名归一化(方便归类)
- # 数据读取
- def listfiles(rootDir):
- list_dirs = os.walk(rootDir)
- for root, dirs, files in list_dirs:
- for d i dirs:
- print(os.path.join(root, d))
- for f in files:
- fileid = f.split('.')[0]
- filepath = os.path.join(root, f)
- try:
- src = cv2.imread(filepath, 1)
- print("src = ", filepath, src.shape)
- os.remove(filepath)
- cv2.imwrite(os.path.join(root, fileid + '.jpg'), src)
- except:
- os.remove(filepath)
- continue
1.4.2数据预处理——人脸检测
OpenCv人脸检测,Dlib关键点检测,将嘴唇区域裁剪并适当扩大区域。
- # 人脸检测
- cascade_path = 'haarcascade_frontalface_default.xml'
- cascade = cv2.CascadeClassifier(cascade_path)
-
-
- #关键点检测
- PREDICTOR_PATH = "shape_predictor_68_face_landmarks.dat"
- predictor = dlib.shape_predictor(PREDICTOR_PATH)
-
-
- 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()])
-
-
- #提取出嘴唇区域,并适当扩大区域
- for i in range(48, 67):
- x = landmarks[i, 0]
- y = landmarks[i, 1]
15000多张图,包含微笑,嘟嘴,大笑,无表情4类
按9:1均匀划分为训练集和测试集,格式统一为128*128,jpg图像。
- import torchvision.dataset as dataset
- data_dir = './data' # 数据目录
- data = dataset.ImageFolder(data_dir, data_transform) # ImageFolder接口
- dataloader = data.DataLoader(data) # 数据指针
- Train和val文件夹都包含4个类的子文件夹,自动被torchvision转成标签。
添加随机缩放裁剪,随机翻转,去均值方差归一化操作。
transforms.RandomSizedCrop(48),将输入图进行随机裁剪,尺度由面积比参数scale控制,长宽比由ratio控制,然后缩放为48*48。
transforms.RandomHorizontalFlip()以0.5的概率进行随机翻转。
使用data.DataLoader获得数据指针
- data_dir = './data/'
- # 分别读取'../data/train'.和'./data/val'文件夹
- image_datasets = {x:dataset.ImageFolder(os.path.join(data_dir, x), data_transforms[x]) for x in ['train', 'val']}
- # 设置batchsize大小为4,数据读取线程为1,使用随机打乱操作
- dataloaders = {x: torch.util.data.DataLoader(image_datasets[x], batch_size = 16, shuffle = True, num_workers = 4) for x in ['train', 'val']}
- # 网络搭建
- calss Net(nn.Module, nclass):
- def __init__(self):
- super(Net, 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.bn2 = nn.BatchNorm2d(48)
- self.fc1 = nn.Linear(48 * 5 * 5, 1200) # conv3的输出为48 * 5 * 5
- self.fc2 = nn.Linear(1200, 128)
- self.fc3 = nn.Linear(128, nclass) # 输出Tensor尺寸[batch, 1, 1, nclass]
-
- def forward(self, x):
- x = F.relu(self.bn1(self.conv1(x)))
- 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
优化目标: 交叉熵损失
优化方法: 带动量的SGD算法(Momentum算法)
- 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) # 学习率迭代策略
模型定义与训练
- model = Net(4)
- model = train_model(model = model, criterion = criterion, optimizer = optimizer_ft,
- scheduler = exp_lr_scheduler, num_epochs = 500)
每一次epoch,包含数据batch数据迭代
- def train_model(model, criterion, optimizer, scheduler, num_epochs = 25):
- for epoch in range(num_epochs):
- for phase in ['train', 'val']:
- if phase == 'train':
- scheduler.step()
- model.train(True)
- else:
- model.train(False)
-
- running_loss = 0.0
- running_corrects = 0.0
-
- for data in dataLoader[phase]:
- inputs, labels = data
- if use_gpu = True:
- inputs = inputs.cuda()
- labels = labels.cuda()
- optimizer.zero_grad() # 梯度清零
- outputs = model(inputs)
- _, preds = torch.max(outputs.data, 1)
- loss = criterion(outputs, labels)
- if phase = "train":
- loss.backward() # 误差反向传播
- optimizer.step() # 参数更像
使用tensorboardX库进行可视化
- # 记录需要可视化的变量
- for tersorboardX import SummaryWriter
- writer = SummaryWriter()
-
-
- # 每一个batch
- if phase == 'train':
- writer.add_scaler('data/trainloss', epoch_loss, epoch)
- writer.add_scaler('data/trainacc', epoch_acc, epoch)
- else:
- writer.add_scaler('data/trainloss', epoch_loss, epoch)
- writer.add_scaler('data/trainacc', epoch_acc, epoch)
-
-
- # 每一个epoch
- writer.export_scalars_to_json('./all_scalars.json')
- writer.close()
第一步,用tensorboardX打开日志文件(默认为runs) tensorboard --logdir=runs/
第二步,在浏览器根据提示打开地址: http://longpengdeMBP:6007/
- # 测试
- # 载入预训练模型,前向推理获得预测结果
- form net import Net
- Net.load_state_dict(torch.load(modelpath, map_location = lambda storage, loc:storage))
- Net.eval() # 设置为推理模式,不会更新模型的k, b参数
- torch.no_grad() # 停止autograd模块的工作,加速和节省显存
-
-
- # 定义数据预处理
- testsize = 48 # 测试图大小
- data_transforms = transforms.Compose([
- transforms.ToTensor(),
- transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
- ])
-
-
- # 前向推理获得结果,读取数据,填充数据维度
- image = Image.open(imagepath)
- imgblob = data_transfroms(img).unsqueeze(0)
- predict = F.softmax(Net(imgblob))
- imdex = np.argmax(predict.detach().numpy())
-
-
- # 使用OpenCv的rectangle函数和putText函数将结果绘制到图像
- # 绘制框与文本
- pos_x = int(offsetx + roisize)
- pos_y = int(offsety + roisize)
- font = cv2.FONT_HERSHEY_SIMPLEX
- cv2.rectangle(im, (offsetx), offsety), (pos_x, pos_y), (0, 255, 0), 2)
-
-
- if index == 0:
- cv2.putText(im, 'none', (pos_x, pos_y), font, 1,2, (0, 255, 255), 1)
- elif index == 1:
- cv2.putText(im, 'pouting', (pos_x, pos_y), font, 1,2, (0, 255, 255), 1)
- elif index == 2:
- cv2.putText(im, 'smile', (pos_x, pos_y), font, 1,2, (0, 255, 255), 1)
- else:
- cv2.putText(im, 'open', (pos_x, pos_y), font, 1,2, (0, 255, 255), 1)
关注
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。