#
#作者:韦访
#博客:https://blog.csdn.net/rookie_wei
#微信:1007895847
#添加微信的备注一下是CSDN的
#欢迎大家一起学习
#
1、概述
来分析一下FaceNet的validate_on_lfw.py的源码,作为前面几篇人脸识别的补充。参考前面的几篇博客一起看:
https://blog.csdn.net/rookie_wei/article/details/81676177
https://blog.csdn.net/rookie_wei/article/details/82078373
https://blog.csdn.net/rookie_wei/article/details/82085152
https://blog.csdn.net/rookie_wei/article/details/86651369
2、命令
重点参考第27讲的人脸识别(中)那篇博客,我们先来看看在验证LFW数据集的命令,
python src/validate_on_lfw.py ../lfw_align_160/ ../20180402-114759
入口是src/validate_on_lfw.py文件,再带两个参数,第一个参数是我们已经预处理(人脸检测和人脸对齐)后的数据集的路径,第二个参数则是下载的模型的路径。好,直接来看src/validate_on_lfw.py文件。
3、通过pairs.txt文件导入待对比的图片路径
打开src/validate_on_lfw.py文件,找到入口函数,如下,
def parse_arguments(argv):
parser = argparse.ArgumentParser()
parser.add_argument('lfw_dir', type=str,
help='Path to the data directory containing aligned LFW face patches.')
parser.add_argument('--lfw_batch_size', type=int,
help='Number of images to process in a batch in the LFW test set.', default=100)
parser.add_argument('model', type=str,
help='Could be either a directory containing the meta_file and ckpt_file or a model protobuf (.pb) file')
parser.add_argument('--image_size', type=int,
help='Image size (height, width) in pixels.', default=160)
parser.add_argument('--lfw_pairs', type=str,
help='The file containing the pairs to use for validation.', default='data/pairs.txt')
parser.add_argument('--lfw_nrof_folds', type=int,
help='Number of folds to use for cross validation. Mainly used for testing.', default=10)
parser.add_argument('--distance_metric', type=int,
help='Distance metric 0:euclidian, 1:cosine similarity.', default=0)
parser.add_argument('--use_flipped_images',
help='Concatenates embeddings for the image and its horizontally flipped counterpart.', action='store_true')
parser.add_argument('--subtract_mean',
help='Subtract feature mean before calculating distance.', action='store_true')
parser.add_argument('--use_fixed_image_standardization',
help='Performs fixed standardization of images.', action='store_true')
return parser.parse_args(argv)
if __name__ == '__main__':
main(parse_arguments(sys.argv[1:]))
参数就没什么可说的了,我们传入的是lfw_dir和model参数,其他参数用到的时候再来看。接着往下看,main函数,
def main(args):
with tf.Graph().as_default():
with tf.Session() as sess:
# Read the file containing the pairs used for testing
#args.lfw_pairs = 'data/pairs.txt'
pairs = lfw.read_pairs(os.path.expanduser(args.lfw_pairs))
在这里就直接创建会话了,我们先来看一下lfw.read_pairs函数干了什么。
#读取pairs.txt文件
def read_pairs(pairs_filename):
pairs = []
#打开文件
with open(pairs_filename, 'r') as f:
#一行一行的读,忽略第一行
for line in f.readlines()[1:]:
#以空格为分隔符,将每行的字符串分开
pair = line.strip().split()
#append到pairs列表里
pairs.append(pair)
#转成numpy数组
return np.array(pairs)
呃,先来看一下args.lfw_pairs传入的”data/paris.txt”是个什么东西吧。
有很多行字符串组成,看每行的第一个字符串,应该是人名,对应lfw数据集下的文件夹名,第二个和第三个字符串是两个数字,有什么含义?暂时不知道。经过args.read_pairs函数后,我们得到的结果如下,
接着往下看,
# Get the paths for the corresponding images
paths, actual_issame = lfw.get_paths(os.path.expanduser(args.lfw_dir), pairs)
来看看这个函数做了什么,
#获取图片路径
# lfw_dir: 我们传入的lfw数据集路径
# pairs:lfw.read_pairs返回的数组
def get_paths(lfw_dir, pairs):
nrof_skipped_pairs = 0
path_list = []
issame_list = []
# 解析每一行 pairs
for pair in pairs:
if len(pair) == 3:
# 如果pairs每行只有3个字符串
# 由lfw_dir 和 pairs 的 3个字符串组成图片路径,pairs的第二和第三个字符串分别组成两张不同的图片,pairs的格式如下,
# Abel_Pacheco 1 4
# 图片的文件名如下,
# Abel_Pacheco_0001.png
path0 = add_extension(os.path.join(lfw_dir, pair[0], pair[0] + '_' + '%04d' % int(pair[1])))
path1 = add_extension(os.path.join(lfw_dir, pair[0], pair[0] + '_' + '%04d' % int(pair[2])))
# 这两张人脸图片是同一个人的
issame = True
elif len(pair) == 4:
# 如果pairs每行有4个字符串,
# 则前两个字符串和后两个字符串分别组成两张图片,,pairs的格式如下,
# Robert_Downey_Jr 1 Tommy_Shane_Steiner 1
path0 = add_extension(os.path.join(lfw_dir, pair[0], pair[0] + '_' + '%04d' % int(pair[1])))
path1 = add_extension(os.path.join(lfw_dir, pair[2], pair[2] + '_' + '%04d' % int(pair[3])))
# 这两张人脸图片不是同一个人的
issame = False
if os.path.exists(path0) and os.path.exists(path1): # Only add the pair if both paths exist
path_list += (path0,path1)
issame_list.append(issame)
else:
nrof_skipped_pairs += 1
if nrof_skipped_pairs>0:
print('Skipped %d image pairs' % nrof_skipped_pairs)
return path_list, issame_list
现在明白pairs.txt里每行的两个数字代表什么意思了吧。有两种情况,第一种是每行只有3个字符串(Abel_Pacheco 1 4),则第一个字符串就是文件夹名,也就是人名,第二个和第三个数字和第一个字符串分别组成该文件夹下的两张图片名。这两张图片是同一个人脸,所以,用issame=True标记它。而第二种情况是每行4个字符串(Robert_Downey_Jr 1 Tommy_Shane_Steiner 1),第一个和第三个字符串是两个不同的人名,第二个和第四个数字则分别对应这两个人名对应的文件夹下的图片。
所以,path_list返回了要对比的两张图片的路径的list,issame_list则对于于这两张图片是否同一个人的list。
4、创建占位符
接着,创建一些占位符,
# 创建占位符
# 图片路径
image_paths_placeholder = tf.placeholder(tf.string, shape=(None,1), name='image_paths')
# 标签
labels_placeholder = tf.placeholder(tf.int32, shape=(None,1), name='labels')
# batch长度
batch_size_placeholder = tf.placeholder(tf.int32, name='batch_size')
# control,用于判断是否对图片进行一些预处理,比如翻转啊
control_placeholder = tf.placeholder(tf.int32, shape=(None,1), name='control')
# phase_train
phase_train_placeholder = tf.placeholder(tf.bool, name='phase_train')
5、创建图片队列
还记得第四讲中,我们在CIFAR-10图像识别中,对图片的读取机制是以队列的形式吗?如果忘记了,可以再去看看,链接如下,
https://blog.csdn.net/rookie_wei/article/details/80187950
代码如下,
nrof_preprocess_threads = 4
# 图片大小
image_size = (args.image_size, args.image_size)
# 创建队列
eval_input_queue = data_flow_ops.FIFOQueue(capacity=2000000,
dtypes=[tf.string, tf.int32, tf.int32],
shapes=[(1,), (1,), (1,)],
shared_name=None, name=None)
# 将image_paths_placeholder、labels_placeholder、control_placeholder 元素排入队列
eval_enqueue_op = eval_input_queue.enqueue_many([image_paths_placeholder, labels_placeholder, control_placeholder], name='eval_enqueue_op')
# 将图片放入队列中
image_batch, label_batch = facenet.create_input_pipeline(eval_input_queue, image_size, nrof_preprocess_threads, batch_size_placeholder)
# 加载模型
input_map = {'image_batch': image_batch, 'label_batch': label_batch, 'phase_train': phase_train_placeholder}
facenet.load_model(args.model, input_map=input_map)
# 从加载进来的模型中获取 "embeddings:0" 张量, 很重要
embeddings = tf.get_default_graph().get_tensor_by_name("embeddings:0")
#
coord = tf.train.Coordinator()
# 启动队列
tf.train.start_queue_runners(coord=coord, sess=sess)
我们这里的读取机制跟以前是一样的,只是我们没有直接使用tf.train.string_input_producer方法,如果你去看看tf.train.string_input_producer的源码就明白了,tf.train.string_input_producer的实现方式跟我们上面的代码很像的。
来看看facenet.create_input_pipeline函数,
# 1: Random rotate 2: Random crop 4: Random flip 8: Fixed image standardization 16: Flip
RANDOM_ROTATE = 1
RANDOM_CROP = 2
RANDOM_FLIP = 4
FIXED_STANDARDIZATION = 8
FLIP = 16
# 如果设置了control,则先对图片进行相应的数据处理,再放入队列中
def create_input_pipeline(input_queue, image_size, nrof_preprocess_threads, batch_size_placeholder):
images_and_labels_list = []
for _ in range(nrof_preprocess_threads):
# 从队列中取出数据
filenames, label, control = input_queue.dequeue()
images = []
for filename in tf.unstack(filenames):
# 读取图片
file_contents = tf.read_file(filename)
image = tf.image.decode_image(file_contents, 3)
# 随机旋转
image = tf.cond(get_control_flag(control[0], RANDOM_ROTATE),
lambda:tf.py_func(random_rotate_image, [image], tf.uint8),
lambda:tf.identity(image))
# 随机剪裁
image = tf.cond(get_control_flag(control[0], RANDOM_CROP),
lambda:tf.random_crop(image, image_size + (3,)),
lambda:tf.image.resize_image_with_crop_or_pad(image, image_size[0], image_size[1]))
# 随机翻转
image = tf.cond(get_control_flag(control[0], RANDOM_FLIP),
lambda:tf.image.random_flip_left_right(image),
lambda:tf.identity(image))
# 固定图像标准化
image = tf.cond(get_control_flag(control[0], FIXED_STANDARDIZATION),
lambda:(tf.cast(image, tf.float32) - 127.5)/128.0,
lambda:tf.image.per_image_standardization(image))
# 翻转
image = tf.cond(get_control_flag(control[0], FLIP),
lambda:tf.image.flip_left_right(image),
lambda:tf.identity(image))
#pylint: disable=no-member
image.set_shape(image_size + (3,))
images.append(image)
images_and_labels_list.append([images, label])
image_batch, label_batch = tf.train.batch_join(
images_and_labels_list, batch_size=batch_size_placeholder,
shapes=[image_size + (3,), ()], enqueue_many=True,
capacity=4 * nrof_preprocess_threads * 100,
allow_smaller_final_batch=True)
return image_batch, label_batch
如果我们传给占位符control_placeholder相应的值(1: Random rotate 2: Random crop 4: Random flip 8: Fixed image standardization 16: Flip),则这里就先对图片做出相应的预处理,再存到队列里,那么,这个control怎么设置呢?很简单,命令行加上--use_flipped_images参数就可以了,比如执行下面的命令,
python src/validate_on_lfw.py --use_flipped_images dataset/lfw_align_160/ model/20180402-114759
接着,我想将这个过程可视化一下吧,将预处理以后的图片保存到本地,好增加理解,稍微修改一下代码,修改下面的evaluate函数,将,
for i in range(nrof_batches):
feed_dict = {phase_train_placeholder:False, batch_size_placeholder:batch_size}
emb, lab = sess.run([embeddings, labels], feed_dict=feed_dict)
lab_array[lab] = lab
emb_array[lab, :] = emb
if i % 10 == 9:
print('.', end='')
sys.stdout.flush()
改成,
for i in range(nrof_batches):
feed_dict = {phase_train_placeholder:False, batch_size_placeholder:batch_size}
emb, imgs, lab = sess.run([embeddings, image_batch, labels], feed_dict=feed_dict)
for img,l in zip(imgs, lab):
scipy.misc.toimage(img).save('images/FLIP/images_%d.jpg' % l)
lab_array[lab] = lab
emb_array[lab, :] = emb
if i % 10 == 9:
print('.', end='')
sys.stdout.flush()
将函数调用,
evaluate(sess, eval_enqueue_op, image_paths_placeholder, labels_placeholder, phase_train_placeholder, batch_size_placeholder, control_placeholder,
embeddings, label_batch, paths, actual_issame, args.lfw_batch_size, args.lfw_nrof_folds, args.distance_metric, args.subtract_mean,
args.use_flipped_images, args.use_fixed_image_standardization)
改成,
evaluate(sess, eval_enqueue_op, image_paths_placeholder, labels_placeholder, phase_train_placeholder, batch_size_placeholder, control_placeholder,
embeddings, label_batch, paths, actual_issame, args.lfw_batch_size, args.lfw_nrof_folds, args.distance_metric, args.subtract_mean,
args.use_flipped_images, args.use_fixed_image_standardization, image_batch)
将函数定义,
def evaluate(sess, enqueue_op, image_paths_placeholder, labels_placeholder, phase_train_placeholder, batch_size_placeholder, control_placeholder,
embeddings, labels, image_paths, actual_issame, batch_size, nrof_folds, distance_metric, subtract_mean, use_flipped_images, use_fixed_image_standardization):
改成,
def evaluate(sess, enqueue_op, image_paths_placeholder, labels_placeholder, phase_train_placeholder, batch_size_placeholder, control_placeholder,
embeddings, labels, image_paths, actual_issame, batch_size, nrof_folds, distance_metric, subtract_mean, use_flipped_images, use_fixed_image_standardization, image_batch):
然后,在images文件夹下新建一个文件夹FLIP,再执行,
python src/validate_on_lfw.py --use_flipped_images dataset/lfw_align_160/ model/20180402-114759
运行结果,
可以看到,人脸有翻转的处理了,你也可以试试其他几种预处理,修改下图代码中的黄色的常量为下面几个常量中的任意一个常量即可,
RANDOM_ROTATE = 1
RANDOM_CROP = 2
RANDOM_FLIP = 4
FIXED_STANDARDIZATION = 8
FLIP = 16
好了,这里我们只是为了查看翻转效果才加了--use_flipped_images参数,后面的分析中我们还是执行下面的命令,
python src/validate_on_lfw.py dataset/lfw_align_160/ model/20180402-114759
如果您感觉本篇博客对您有帮助,请打开支付宝,领个红包支持一下,祝您扫到99元,谢谢~~