大家好,欢迎来到专栏《CV项目实战》,在这个专栏中我们会讲述计算机视觉相关的项目实战,有大型的完整项目,也有精炼的核心算法实战。
本次主要讲述一个完整的工业级别图像分类项目的标准流程,涉及环境配置,数据准备,模型定义,模型训练,模型测试。
作者&编辑 | 言有三
本文资源与项目结果展示


本文篇幅:7900字
背景要求:会使用Python和任一深度学习开源框架
附带资料:代码一份,版本包括Pytorch+Tensorflow+Caffe
同步平台:有三AI知识星球(一周内)
1 项目背景
对于很多初入深度学习计算机视觉领域的朋友来说,当前开源资料非常多,但有时候难以适从,其中很多资料都没有包含完整的项目流程,而只是对某个流程的部分截取,对能力的锻炼不够。
图像分类是整个计算机视觉领域中最基础的任务,也是最重要的任务之一,最适合拿来进行学习实践。为了让新手们能够一次性体验一个工业级别的图像分类任务的完整流程,本次我们选择带领大家完成一个对视频中人脸进行表情识别的任务。人脸表情识别(facial expression recognition, FER)作为人脸识别技术中的一个重要组成部分,近年来在人机交互、安全、机器人制造、自动化、医疗、通信和驾驶领域得到了广泛的关注,成为学术界和工业界的研究热点,是人脸属性分析的重点。
2 准备工作
本次项目开发需要以下环境:
(1) Linux系统,推荐ubuntu16.04或者ubuntu18.04。使用windows系统也可以完成,但是使用Linux效率更高。
(2) 最好拥有一块显存不低于6G的GPU显卡,如果没有使用CPU进行训练速度较慢。
(3) 安装好的Tensorflow或者其他开源框架(caffe,pytorch等)
为什么要选择Linux就不多说了,公司开发都是使用Linux服务器,个人开发者机器常常会安装windows与Linux双系统。双系统的安装每个人的个人电脑环境不同,一定是会有差异的,只有安装过程中才会知道,推荐的双系统安装博客教程:
https://www.zhihu.com/question/34611974/answer/373037398,如果遇到了任何问题,可以自行百度解决,总的流程是:
(1) 去ubuntu官网下载ubuntu系统镜像16.04或者18.04。
(2) 制作U盘启动盘。
(3) 使用启动盘进行安装,安装过程中选择好分区大小。
(4) 安装完之后即可进入系统。
(5) Linux系统初学者需要掌握一些基本命令。
3 数据获取
很多实际项目我们不会有现成的数据集,虽然可以通过开源数据集获取,但是我们还是要学会自己从零开始获取和整理。下面讲述如何准备好本次项目所需要的数据集,包括以下部分。
(1) 学会使用爬虫爬取图像。
(2) 对获得的图片数据进行整理,包括重命名,格式统一。
(3) 利用人脸检测算法删选出有用的样本,利用关键点检测算法裁剪出用于算法训练的嘴唇区域。
3.1 数据爬取
由于没有直接对应的开源数据集,或者开源数据集中的数据比较少,尤其是对于嘟嘴,大笑等类的数据,而搜索引擎上有海量数据,所以我们可以从中爬取。下面开始讲述具体的步骤,我们的任务是一个表情分类任务,因此需要爬取相关图片,包括嘟嘴,微笑,大笑等表情。
参考有三AI公众号的爬虫文章,文章链接如下:
爬虫的GUI界面如下:

我们对百度搜索引擎,自行定义搜索词(比如嘟嘴),爬取图片结果如下:

自此就可以获得数千张图像,然后进行第二步,数据整理。
3.2 数据整理
爬取得到的数据是比较脏的,需要进行整理,主要包括统一图片后缀和重命名。
统一后缀格式可以减少以后写数据API时的压力,也可以测试图片是不是可以正常的读取,及时防止未知问题的出现,这很重要。
首先我们看下爬取完毕的数据有多少种数据格式。

总共1364张图,可以看到总有jpeg,bmp,png格式,我们首先将其全部转换为jpg格式,这也是所有框架支持的格式,格式转换代码如下:
import os
import sys
import cv2
import numpy as np
def listfiles(rootDir):
list_dirs = os.walk(rootDir)
for root, dirs, files in list_dirs:
for d in 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
listfiles(sys.argv[1]) ##输入文件夹即可
统一格式为jpg之后预览如下:

另外,在刚才的脚本中我们可以加上一个计数器完成重命名,不过因为这里爬虫已经完成了这个重命名操作,我们就略去。
3.3 数据清洗
利用搜索引擎爬取得到的图片肯定有不符合要求的,数据清洗主要是删除不合适的图片,即非人脸的照片。

可以采用肉眼观察的方式,也可以利用程序进行筛选,我们调用opencv的人脸检测算法进行筛选,代码如下:
#coding:utf8
import cv2
import dlib
import numpy as np
import sys
import os
cascade_path='haarcascade_frontalface_default.xml'
cascade = cv2.CascadeClassifier(cascade_path)
images = os.listdir(sys.argv[1])
for image in images:
im=cv2.imread(os.path.join(sys.argv[1],image),1)
rects = cascade.detectMultiScale(im, 1.3,5)
print "detected face",len(rects)
if len(rects) == 0:
cv2.namedWindow('Result',0)
cv2.imshow('Result',im)
os.remove(os.path.join(sys.argv[1],image))
k =cv2.waitKey(0)
if k == ord('q'):
break
这个人脸检测算法是传统算法,召回率不高,因此会有一些好样本被删除。
最后剩下732张样本,可以看到都是比较好的样本了,后面提取人脸关键点也会简单很多。

如果利用人脸检测算法仍然无法清除干净样本,则需要手动筛选。
当然如果你使用多个关键词或者使用不同的搜索引擎同样的关键词,或者从视频中提取图片,那么爬取回来的图片很可能有重复或者非常的相似,这样的数据集需要去重,我们以后再讲。
3.4 提取嘴唇区域
接下来我们要将样本处理成我们真正训练所需要的图像,本任务只对嘴唇部分的表情进行识别,我们利用Opencv+Dlib算法提取嘴唇区域,Dlib算法会得到面部的68个关键点,我们从中得到嘴唇区域,并适当扩大。
import cv2
import dlib
import numpy as np
import sys
import os
PREDICTOR_PATH = "shape_predictor_68_face_landmarks.dat"
predictor = dlib.shape_predictor(PREDICTOR_PATH)
cascade_path='haarcascade_frontalface_default.xml'
cascade = cv2.CascadeClassifier(cascade_path)
def get_landmarks(im):
rects = cascade.detectMultiScale(im, 1.3,5)
x,y,w,h =rects[0]
rect=dlib.rectangle(x,y,x+w,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, 5, color=(0, 255, 255))
return im
def getlipfromimage(im,landmarks):
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
print "xmin=",xmin
print "xmax=",xmax
print "ymin=",ymin
print "ymax=",ymax
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]
return roi
def listfiles(rootDir):
list_dirs = os.walk(rootDir)
for root, dirs, files in list_dirs:
for d in dirs:
print os.path.join(root,d)
for f in files:
fileid = f.split('.')[0]
filepath = os.path.join(root,f)
try:
im = cv2.imread(filepath,1)
landmarks = get_landmarks(im)
roi = getlipfromimage(im,landmarks)
roipath = filepath.replace('.jpg','_mouth.png')
cv2.imwrite(roipath,roi)
except:
print "error"
continue
listfiles(sys.argv[1])
结果如下,自此就完成了自建数据集操作。

4 模型训练
得到了数据之后,接下来咱们使用TensorFlow 这个框架来进行模型的训练,当然了,你也可以选择使用Caffe,Pytorch等框架,文末我们会提供代码获取方法。
整个训练流程包括数据接口准备、模型定义、结果保存与分析。
4.1数据接口准备
TensorFlow中有多种数据集接口,我们首先把不同类的图像放在不同的文件夹下面,然后生成一个txt文件,它的每一行的格式是image、labelid,使用的genelist.py脚本如下:
import os
import sys
def listfiles(rootDir,txtfile,label=0):
ftxtfile = open(txtfile,'w')
list_dirs = os.walk(rootDir)
count = 0
dircount = 0
for root, dirs, files in list_dirs:
for d in dirs:
print os.path.join(root,d)
dircount = dircount + 1
for f in files:
print os.path.join(root,f)
ftxtfile.write(os.path.join(root,f)+' '+str(label)+'\n')
count = count + 1
print rootDir+"has"+str(count)+"files"
if __name__ == '__main__':
listfiles('../../../../../datas/mouth/0','mouth0.txt',0)
listfiles('../../../../../datas/mouth/1','mouth1.txt',1)
上面脚本中的函数,输入一个文件夹,输出txt文件以及标签,于是所有的图片和标签就按行被准备到了一个txt文件中,如下:
../../../../datas/mouth/1/182smile.jpg 1
../../../../datas/mouth/1/435smile.jpg 1
../../../../datas/mouth/0/40neutral.jpg 0
../../../../datas/mouth/1/206smile.jpg 1
../../../../datas/mouth/0/458neutral.jpg 0
../../../../datas/mouth/0/158neutral.jpg 0
../../../../datas/mouth/1/322smile.jpg 1
../../../../datas/mouth/1/83smile.jpg 1
../../../../datas/mouth/0/403neutral.jpg 0
当然,我们还需要将其分为训练集和测试集,使用split_train_val.py,代码如下:
import sys
def splittrain_val(fileall,valratio=0.1):
fileids = fileall.split('.')
fileid = fileids[len(fileids)-2]
f=open(fileall);
ftrain=open(fileid+"_train.txt",'w');
fval=open(fileid+"_val.txt",'w');
count = 0
if valratio == 0 or valratio >= 1:
valratio = 0.1
interval = (int)(1.0 / valratio)
while 1:
line = f.readline()
if line:
count = count + 1
if count % interval == 0:
fval.write(line)
else:
ftrain.write(line)
else:
break
splittrain_val(sys.argv[1],0.1)
当然代码有更简洁的写法,可以自己尝试。
接下来我们定义一个类,叫 imagedata,用于生成tensorflow中要使用的数据指针,代码如下dataset.py。
import tensorflow as tf
from tensorflow.contrib.data import Dataset
from tensorflow.python.framework import dtypes
from tensorflow.python.framework.ops import convert_to_tensor
import numpy as np
class ImageData:
def read_txt_file(self):
self.img_paths = []
self.labels = []
for line in open(self.txt_file, 'r'):
items = line.split(' ')
self.img_paths.append(items[0])
self.labels.append(int(items[1]))
def __init__(self, txt_file, batch_size, num_classes,
image_size,buffer_scale=100):
self.image_size = image_size
self.batch_size = batch_size
self.txt_file = txt_file ##txt list file,stored as: imagename id
self.num_classes = num_classes
buffer_size = batch_size * buffer_scale
# 读取图片
self.read_txt_file()
self.dataset_size = len(self.labels)
print "num of train datas=",self.dataset_size
# 转换成Tensor
self.img_paths = convert_to_tensor(self.img_paths, dtype=dtypes.string)
self.labels = convert_to_tensor(self.labels, dtype=dtypes.int32)
# 创建数据集
data = Dataset.from_tensor_slices((self.img_paths, self.labels))
print "data type=",type(data)
data = data.map(self.parse_function)
data = data.repeat(1000)
data = data.shuffle(buffer_size=buffer_size)
# 设置self data Batch
self.data = data.batch(batch_size)
print "self.data type=",type(self.data)
def augment_dataset(self,image,size):
distorted_image = tf.image.random_brightness(image,
max_delta=63)
distorted_image = tf.image.random_contrast(distorted_image,
lower=0.2, upper=1.8)
# Subtract off the mean and divide by the variance of the pixels.
float_image = tf.image.per_image_standardization(distorted_image)
return float_image
def parse_function(self, filename, label):
label_ = tf.one_hot(label, self.num_classes)
img = tf.read_file(filename)
img = tf.image.decode_jpeg(img, channels=3)
img = tf.image.convert_image_dtype(img, dtype = tf.float32)
img = tf.random_crop(img,[self.image_size[0],self.image_size[1],3])
img = tf.image.random_flip_left_right(img)
img = self.augment_dataset(img,self.image_size)
return img, label_
下面来分析上面的代码,类是 ImageData,它包含几个函数,__init__构造函数,read_txt_file数据读取函数,parse_function数据预处理函数,augment_dataset数据增强函数。
我们直接看构造函数吧,分为几个步骤:
(1) 读取变量,文本 list 文件txt_file,批处理大小batch_size,类别数num_classes,要处理成的图片大小image_size,一个内存变量buffer_scale=100。
(2) 在获取完这些值之后,就到了read_txt_file函数。代码很简单,就是利用self.img_paths和 self.labels存储输入 txt 中的文件列表和对应的 label,这一点和 Caffe 很像了。
(3) 然后,就是分别将img_paths和 labels 转换为 Tensor,函数是convert_to_tensor,这是 Tensor 内部的数据结构。
(4) 创建dataset,Dataset.from_tensor_slices,这一步是为了将img和label合并到一个数据格式,此后我们将利用它的接口,来循环读取数据做训练。当然创建好dataset之后,我们需要给它赋值才能真正的有数据。data.map 就是数据的预处理,包括读取图片、转换格式、随机旋转等操作,可以在这里做。
data = data.repeat(1000) 是将数据复制1000份,这可以满足我们训练1000个 epochs。data = data.shuffle(buffer_size=buffer_size)就是数据shuffle了,buffer_size就是在做shuffle操作时的控制变量,内存越大,就可以用越大的值。
(5) 给selft.data赋值,我们每次训练的时候,是取一个batchsize的数据,所以 self.data = data.batch(batch_size),就是从上面创建的dataset中,一次取一个 batch的数据。
到此,数据接口就定义完毕了,接下来在训练代码中看如何使用迭代器进行数据读取就可以了。
4.2 模型定义
创建数据接口后,我们开始定义一个网络net.py。
def simpleconv3net(x):
x_shape = tf.shape(x)
with tf.variable_scope("conv3_net"):
conv1 = tf.layers.conv2d(x, name="conv1", filters=12,kernel_size=[3,3], strides=(2,2), activation=tf.nn.relu,kernel_initializer=tf.contrib.layers.xavier_initializer(),bias_initializer=tf.contrib.layers.xavier_initializer())
bn1 = tf.layers.batch_normalization(conv1, training=True, name='bn1')
conv2 = tf.layers.conv2d(bn1, name="conv2", filters=24,kernel_size=[3,3], strides=(2,2), activation=tf.nn.relu,kernel_initializer=tf.contrib.layers.xavier_initializer(),bias_initializer=tf.contrib.layers.xavier_initializer())
bn2 = tf.layers.batch_normalization(conv2, training=True, name='bn2')
conv3 = tf.layers.conv2d(bn2, name="conv3", filters=48,kernel_size=[3,3], strides=(2,2), activation=tf.nn.relu,kernel_initializer=tf.contrib.layers.xavier_initializer(),bias_initializer=tf.contrib.layers.xavier_initializer())
bn3 = tf.layers.batch_normalization(conv3, training=True, name='bn3')
conv3_flat = tf.reshape(bn3, [-1, 5 * 5 * 48])
dense = tf.layers.dense(inputs=conv3_flat, units=128, activation=tf.nn.relu,name="dense",kernel_initializer=tf.contrib.layers.xavier_initializer())
logits= tf.layers.dense(inputs=dense, units=2, activation=tf.nn.relu,name="logits",kernel_initializer=tf.contrib.layers.xavier_initializer())
return logits
上面就是我们定义的网络,是一个简单的3层卷积。在 tf.layers 下,有各种网络层,这里就用到了 tf.layers.conv2d,tf.layers.batch_normalization和 tf.layers.dense,分别是卷积层,BN 层和全连接层。我们以一个卷积层为例:
conv1 = tf.layers.conv2d(x, name="conv1", filters=12,kernel_size=[3,3], strides=(2,2), activation=tf.nn.relu,kernel_initializer=tf.contrib.layers.xavier_initializer(),bias_initializer=tf.contrib.layers.xavier_initializer())
x 即输入,name 是网络名字,filters 是卷积核数量,kernel_size即卷积核大小,strides 是卷积 stride,activation 即激活函数,kernel_initializer和bias_initializer分别是初始化方法。可见已经将激活函数整合进了卷积层,更全面的参数,请自查 API。其实网络的定义,还有其他接口,tf.nn、tf.layers、tf.contrib,各自重复,有些混乱。这里之所以用tf.layers,就是因为参数丰富,适合从头训练一个模型。
4.3 模型训练
直接上整个训练代码train.py,如下。
from dataset import *
from net import simpleconv3net
import sys
import os
import cv2
////-------1 定义一些全局变量-------////
txtfile = sys.argv[1]
batch_size = 64
num_classes = 2
image_size = (48,48)
learning_rate = 0.0001
debug=False
if __name__=="__main__":
////-------2 载入网络结构,定义损失函数,创建计算图-------////
dataset = ImageData(txtfile,batch_size,num_classes,image_size)
iterator = dataset.data.make_one_shot_iterator()
dataset_size = dataset.dataset_size
batch_images,batch_labels = iterator.get_next()
Ylogits = simpleconv3net(batch_images)
print "Ylogits size=",Ylogits.shape
Y = tf.nn.softmax(Ylogits)
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits=Ylogits, labels=batch_labels)
cross_entropy = tf.reduce_mean(cross_entropy)
correct_prediction = tf.equal(tf.argmax(Y, 1), tf.argmax(batch_labels, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
with tf.control_dependencies(update_ops):
train_step = tf.train.AdamOptimizer(learning_rate).minimize(cross_entropy)
saver = tf.train.Saver()
in_steps = 100
checkpoint_dir = 'checkpoints/'
if not os.path.exists(checkpoint_dir):
os.mkdir(checkpoint_dir)
log_dir = 'logs/'
if not os.path.exists(log_dir):
os.mkdir(log_dir)
summary = tf.summary.FileWriter(logdir=log_dir)
loss_summary = tf.summary.scalar("loss", cross_entropy)
acc_summary = tf.summary.scalar("acc", accuracy)
image_summary = tf.summary.image("image", batch_images)
////-------3 执行会话,保存相关变量,还可以添加一些debug函数来查看中间结果-------////
with tf.Session() as sess:
init = tf.global_variables_initializer()
sess.run(init)
steps = 10000
for i in range(steps):
_,cross_entropy_,accuracy_,batch_images_,batch_labels_,loss_summary_,acc_summary_,image_summary_ = sess.run([train_step,cross_entropy,accuracy,batch_images,batch_labels,loss_summary,acc_summary,image_summary])
if i % in_steps == 0 :
print i,"iterations,loss=",cross_entropy_,"acc=",accuracy_
saver.save(sess, checkpoint_dir + 'model.ckpt', global_step=i)
summary.add_summary(loss_summary_, i)
summary.add_summary(acc_summary_, i)
summary.add_summary(image_summary_, i)
4.4 可视化
TensorFlow 很方便的一点,就是 Tensorboard 可视化。Tensorboard 的具体原理就不细说了,很简单,就是三步。
第一步,创建日志目录。
log_dir = 'logs/'
if not os.path.exists(log_dir): os.mkdir(log_dir)
第二步,创建 summary 操作并分配标签,如我们要记录 loss、acc 和迭代中的图片,则创建了下面的变量:
loss_summary = tf.summary.scalar("loss", cross_entropy)acc_summary = tf.summary.scalar("acc", accuracy)image_summary = tf.summary.image("image", batch_images)
第三步,session 中记录结果,如下面代码:
_,cross_entropy_,accuracy_,batch_images_,batch_labels_,loss_summary_,acc_summary_,image_summary_ = sess.run([train_step,cross_entropy,accuracy,batch_images,batch_labels,loss_summary,acc_summary,image_summary])
查看训练过程和最终结果时使用:
tensorboard --logdir=logs
Loss 和 acc 的曲线图如下,可以知道已经收敛了。

5 模型测试
上面已经训练好了模型,我们接下来的目标,就是要用它来做推理,真正把模型用起来,下面我们载入一个图片,用模型进行测试。
import tensorflow as tf
from net import simpleconv3
import sys
import numpy as np
import cv2
import os
import dlib
PREDICTOR_PATH = "shape_predictor_68_face_landmarks.dat"
outputor = dlib.shape_predictor(PREDICTOR_PATH)
cascade_path='haarcascade_frontalface_default.xml'
cascade = cv2.CascadeClassifier(cascade_path)
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(x,y,x+w,y+h)
return np.matrix([[p.x, p.y] for p in outputor(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 ##测试图大小
input = tf.placeholder(tf.float32, [1,testsize,testsize,3])
output = simpleconv3(input,False) ##定义网络
output = tf.nn.softmax(output) ##网络预测结果
## 全局变量
## sys.argv[1] 权重文件
## sys.argv[2] 图像文件夹
with tf.Session() as sess:
init = tf.global_variables_initializer() ##初始化
sess.run(init)
saver = tf.train.Saver()
saver.restore(sess,sys.argv[1]) ##载入权重
#一次测试一个文件
imagepaths = os.listdir(sys.argv[2])
for imagepath in imagepaths:
im = cv2.imread(os.path.join(sys.argv[2],imagepath),1)
try:
rects = cascade.detectMultiScale(im, 1.3,5)
x,y,w,h =rects[0]
rect=dlib.rectangle(x,y,x+w,y+h)
landmarks = np.matrix([[p.x, p.y] for p in outputor(im, rect).parts()])
except:
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 = imagecols - 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))
roiresized = standardization(roiresized)
imgs = np.zeros([1,testsize,testsize,3],dtype=np.float32)
imgs[0:1,] = roiresized
print(imgs.shape)
result = sess.run(output, feed_dict={input:imgs}) ##进行预测
## 获得结果,并统计指标
result = np.squeeze(result)
print(result)
index = np.argmax(result)
im_show = cv2.imread(os.path.join(sys.argv[2],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.2, (0,0,255), 1)
else:
cv2.putText(im_show, 'smile', (pos_x,pos_y), font, 1.2, (0,0,255), 1)
cv2.namedWindow('result',0)
cv2.imshow('result',im_show)
cv2.imwrite(os.path.join('results',imagepath),im_show)
cv2.waitKey(0)
从上面的代码可知,与 Train 时同样,需要定义模型。然后,用 restore 函数从 saver 中载入参数,读取图像并准备好网络的格式,sess.run 就可以得到最终的结果了。如下,黄色框是我们的嘴唇区域,'none'表示没有表情。

总结
本次我们完成了一个图像分类任务项目的全部流程,读者可以将其拓展成视频的格式,如本文最开始展示的gif效果。
想要获取tensorflow/pytorch/caffe等3个框架的完整代码,请移步有三AI知识星球社区。

294



被折叠的 条评论
为什么被折叠?



