由唐宇迪大佬的视频和课题组的项目有感,为课题室做一个人脸打卡系统,各个部分基本都是抄袭加借鉴,希望大家海涵呀~
项目分阶段进行,实时更新,由于本人实在太菜,趟坑无数,所以更新也没个准头。。
项目1阶段:简单的人脸检测
项目环境及配置:window10+GTX 1060+python3.6+anaconda5.2.0+tensorflow1.9
1、数据获取
人脸数据网上的资源非常非常的多,如下附上了几个获取数据的网站。在下载和查找数据的时候需要同时百度一下这个数据的使用方法,如:原始图片在哪,含有标注的.txt文件或.mat文件都在哪等,否则自己瞎搞很容易浪费时间。
这两个链接包含了大多数开源的人脸数据
http://www.cvmart.net/community/article/detail/148
https://blog.csdn.net//chenriwei2/article/details/50631212
项目1阶段训练使用的人脸数据集为AFLW,如下可下载,其中aflw-images-0.tar.gz,aflw-images-2.tar.gz,aflw-images-3.tar.gz这三个文件是图片数据,解压的时候把后面那个.gz删掉就可以解压了。AFLW的标注就是那个aflw的txt文件,我也忘了从哪个文件里找的了。。你们就直接用就行,当时找这个找了好久。。。至于标注的格式是矩形框的左上角和右下角。
AFLW:https://pan.baidu.com/s/14McWGRZCnOcP2SBhK2ryrQ
验证集选用FDDB,链接一搜就有
非人脸数据部分验证采用了PASCAL VOC 2007、2012。这俩网上教程有的是,实在懒得帖多了,就贴一个吧。。
PASCAL VOC 2007教程:https://blog.csdn.net/julialove102123/article/details/78330752
训练集选用COCO非人数据,参照如下:
COCO数据集下载参照 https://blog.csdn.net/daniaokuye/article/details/78699138
具体标注信息:https://blog.csdn.net/wc781708249/article/details/79603522
API安装参照:https://www.jianshu.com/p/de455d653301
API下载参照:https://github.com/cocodataset/cocoapi
2、数据集制作
首先要从数据集种截取出人脸图片和非人脸图片。
人脸截取相对容易,对着标注截就行(由于是简单项目,这个不要求IoU大于多少多少就算正样本),总共有24384张人脸,程序如下:
import cv2
import time
begin=time.time()
addr='...\\AFLW\\' #图片解压会出现0,2,3这三个文件夹,集体放入AFLW文件中
f=open(r'...\alfw.txt')
j=0
for line in f.readlines():
line=line.strip().split()
path=addr+line[0]
img=cv2.imread(path)
if img is None:
continue
x=int(line[1])
if x<0:x=0
y=int(line[2])
if y<0:y=0
w=int(line[3])
h=int(line[4])
img=img[y:y+h,x:x+w]
cv2.imwrite('...\\alfw_face\\alfw_face_%d.jpg'%(j),img)
j+=1
print(time.time()-begin)
对于非人脸,那么问题就来了,虽然没有人脸的图片都可以,且IoU<0.3就可以认为是非人脸,但咱们也不能瞎搞,咱们的数据没人家多,装备没人家好,但是咱们的目的不是为了去lfw争第一的(至少我不是,我只想把工程搞好,满足基本需求。。),所以我的这个项目,截取的IoU都是<0.01的。。。数据集总数为4.8W就好(强迫症不想让数据里有脏的。。),但这只是问题的开始,问题总结如下:
1、AFLW的标注不全,导致即使IoU小于你设的阈值还是可能有人脸的,所以此时此刻,需要你自己去翻了。。我是翻了好几天的。。;
2、建议随机截取图片中部的地方,AFLW的图片很多边边角角都是纯色的,你截下来一堆这样的当负样本虽然训练时很容易训练(我的AlexNet模型曾经3000次就收敛),但是实测烂到爆;
3、参考https://www.cnblogs.com/hrlnw/p/5243853.html中负样本的选择,不要只选风景图片,原因同2,模型基本学不到太多东西;
4、参考《From Facial Parts Responses to Face Detection: A Deep Learning Approach》这篇论文,他在fine-tune模型的时候非人脸数据选用了PASCAL VOC 2007的非人数据,那么我们也可以对着抄一下,不对,参考一下方法哈~~
截取的程序根据上面的改改就行,我就不贴了,我的程序贼low。。
PASCAL VOC 2007上面这个链接讲的够详细了,2012也是一样的,在main文件夹下找到person_trainval.txt文件,按照-1的label把图片都拿出来就行。(别自作聪明的把那仨person的全提取出来,person_trainval是另外俩的合集,别问我咋知道的,看教程不看全的后果。。)程序如下:
import cv2
import time
import time
#之所以采用两个是因为2012的数据集内并没有2007的label
begin=time.time()
i=0
addr='...\\JPEGImages\\' #图片保存的路径,此程序为2012的,2007的同理
doc=['person_trainval']
for name in doc:
f=open(r'...\VOCdevkit\VOC2012\ImageSets\Main\%s.txt'%(name))
for line in f.readlines():
line=line.strip().split()
if line[1]!='1':
path=addr+line[0]+'.jpg'
img=cv2.imread(path)
cv2.imwrite('...\\VOC2012_others\\VOC_others_%d.jpg'%(i),img)
i+=1
print(time.time()-begin)
非人脸数据多搞搞,搞到和人脸数据差不多就行了,随后我们将这两种图片转化为tensorflow的文件格式:TFRecord。
制作过程中发现了一个问题,兴许是anaconda的bug由jupyter notebook制作出的文件总会出现DATALOSS的错误,详情请看我的另一篇问题解答:
https://blog.csdn.net/rrui7739/article/details/81003577
我是用Spyder制作的,程序抄袭如下:
#注:由于不能很好shuffle,所以只能调用random模块对文件名手动shuffle后保存为TFRecord格式
import os
import tensorflow as tf
import cv2
import time
import random
begin=time.time()
classes=['ALFW_others','ALFW_face']
face=os.listdir('C:\\Users\\312\\Desktop\\Face_Detection\\ALFW_face\\')
others=os.listdir('C:\\Users\\312\\Desktop\\Face_Detection\\ALFW_others\\')
random.shuffle(face)
random.shuffle(others)
writer = tf.python_io.TFRecordWriter("face_detection.tfrecords")
for i in range(1,480):
if i%50==0:
print(i)
for index, name in enumerate(classes):
class_path='C:\\Users\\312\\Desktop\\Face_Detection\\'+name+'\\'
if name=='ALFW_face':
docu_name=face
else:
docu_name=others
for img_name in docu_name[50*(i-1):50*i]:
img_path = class_path + img_name
img = cv2.imread(img_path)
if img is None:
continue
img = cv2.resize(img,(227, 227))
img_raw = img.tobytes() #将图片转化为原生bytes
example = tf.train.Example(features=tf.train.Features(feature={
"label": tf.train.Feature(int64_list=tf.train.Int64List(value=[index])),
'img': tf.train.Feature(bytes_list=tf.train.BytesList(value=[img_raw]))
}))
writer.write(example.SerializeToString()) #序列化为字符串
writer.close()
print(time.time()-begin)
此程序运行完以后,想看看数据制作的对不对的,可以看上面那个问题解答的链接,里面有读取实验的程序,在这我就不贴了。这段程序是制作4.8W训练数据的,以及1W验证集。如果有人想制作测试数据的话也可以根据这个照葫芦画瓢做一下,但是一定要注意,train、validate、test这三种数据一定不要重复,否则数据泄露后点子不好那对你的系统就是核打击。。
至此我们的第二阶段数据集制作就告一段落了。。呼。。。
3、AlexNet模型训练
由于本人一贯秉持着抄袭原则,所以说模型咱们也要照抄不误~
同时也由于本人秉持着重基础原则,所以说模型着全是拿原生API手撸的(除了BN层)。。暂时就先告别Keras和Slim吧(其实我也是没用过不咋熟。。)。。
import tensorflow as tf
import numpy as np
import time
from tensorflow.python.framework import graph_util
import cv2
begin=time.time()
# def kaka(image_batch):
# a=np.mean(image_batch)
# image_batch=image_batch-a
# return image_batch
def wenjianliu():
filename_queue = tf.train.string_input_producer(['E:\\friedhelm\\face_train_227.tfrecords'],shuffle=True)
reader = tf.TFRecordReader()
_, serialized_example = reader.read(filename_queue) #返回文件名和文件
features = tf.parse_single_example(serialized_example,
features={
'label':tf.FixedLenFeature([],tf.int64),
'img':tf.FixedLenFeature([],tf.string),
})
img=tf.decode_raw(features['img'],tf.uint8)
label=tf.cast(features['label'],tf.int32)
img = tf.reshape(img, [227,227,3])
# img=kaka(img)
min_after_dequeue = 10000
batch_size = 64
capacity = min_after_dequeue + 10 * batch_size
image_batch, label_batch = tf.train.shuffle_batch([img, label],
batch_size=batch_size,
capacity=capacity,
min_after_dequeue=min_after_dequeue,
num_threads=7)
return image_batch,label_batch
def wenjianliu1():
filename_queue = tf.train.string_input_producer(['E:\\friedhelm\\face_val_227.tfrecords'],shuffle=True,seed=77)
reader = tf.TFRecordReader()
_, serialized_example = reader.read(filename_queue) #返回文件名和文件
features = tf.parse_single_example(serialized_example,
features={
'label':tf.FixedLenFeature([],tf.int64),
'img':tf.FixedLenFeature([],tf.string),
})
img=tf.decode_raw(features['img'],tf.uint8)
label=tf.cast(features['label'],tf.int32)
img = tf.reshape(img, [227,227,3])
# img=kaka(img)
min_after_dequeue = 1000
batch_size = 64
capacity = min_after_dequeue + 10 * batch_size
image_batch, label_batch = tf.train.shuffle_batch([img, label],
batch_size=batch_size,
capacity=capacity,
min_after_dequeue=min_after_dequeue,
num_threads=7)
return image_batch,label_batch
def model(x,prob,is_training):
with tf.variable_scope('conv1',reuse=tf.AUTO_REUSE):
weight1=tf.get_variable('weight',[11,11,3,96],initializer=tf.truncated_normal_initializer(stddev=0.1))
bias1=tf.get_variable('bias',[96],initializer=tf.constant_initializer(0.0))
conv1=tf.nn.conv2d(x,weight1,strides=[1,4,4,1],padding='VALID')
he1=tf.nn.bias_add(conv1,bias1)
bn1=tf.layers.batch_normalization(he1,fused=False,training=is_training)
relu1=tf.nn.relu(bn1)
with tf.variable_scope('pool1',reuse=tf.AUTO_REUSE):
pool1=tf.nn.max_pool(relu1,ksize=[1,3,3,1],strides=[1,2,2,1],padding='VALID')
with tf.variable_scope('conv2',reuse=tf.AUTO_REUSE):
weight2=tf.get_variable('weight',[3,3,96,256],initializer=tf.truncated_normal_initializer(stddev=0.1))
bias2=tf.get_variable('bias',[256],initializer=tf.constant_initializer(0.0))
conv2=tf.nn.conv2d(pool1,weight2,strides=[1,1,1,1],padding='SAME')
he2=tf.nn.bias_add(conv2,bias2)
bn2=tf.layers.batch_normalization(he2,fused=False,training=is_training)
relu2=tf.nn.relu(bn2)
with tf.variable_scope('pool2',reuse=tf.AUTO_REUSE):
pool2=tf.nn.max_pool(relu2,ksize=[1,3,3,1],strides=[1,2,2,1],padding='VALID')
with tf.variable_scope('conv3',reuse=tf.AUTO_REUSE):
weight3=tf.get_variable('weight',[3,3,256,384],initializer=tf.truncated_normal_initializer(stddev=0.1))
bias3=tf.get_variable('bias',[384],initializer=tf.constant_initializer(0.0))
conv3=tf.nn.conv2d(pool2,weight3,strides=[1,1,1,1],padding='SAME')
he3=tf.nn.bias_add(conv3,bias3)
mean3,vias3=tf.nn.moments(he3,0)
bn3=tf.layers.batch_normalization(he3,fused=False,training=is_training)
relu3=tf.nn.relu(bn3)
with tf.variable_scope('conv4',reuse=tf.AUTO_REUSE):
weight4=tf.get_variable('weight',[3,3,384,384],initializer=tf.truncated_normal_initializer(stddev=0.1))
bias4=tf.get_variable('bias',[384],initializer=tf.constant_initializer(0.0))
conv4=tf.nn.conv2d(relu3,weight4,strides=[1,1,1,1],padding='SAME')
he4=tf.nn.bias_add(conv4,bias4)
bn4=tf.layers.batch_normalization(he4,fused=False,training=is_training)
relu4=tf.nn.relu(bn4)
with tf.variable_scope('conv5',reuse=tf.AUTO_REUSE):
weight5=tf.get_variable('weight',[3,3,384,256],initializer=tf.truncated_normal_initializer(stddev=0.1))
bias5=tf.get_variable('bias',[256],initializer=tf.constant_initializer(0.0))
conv5=tf.nn.conv2d(relu4,weight5,strides=[1,1,1,1],padding='SAME')
he5=tf.nn.bias_add(conv5,bias5)
bn5=tf.layers.batch_normalization(he5,fused=False,training=is_training)
relu5=tf.nn.relu(bn5)
with tf.variable_scope('pool3',reuse=tf.AUTO_REUSE):
pool3=tf.nn.max_pool(relu5,ksize=[1,3,3,1],strides=[1,2,2,1],padding='VALID')
with tf.variable_scope('fc1',reuse=tf.AUTO_REUSE):
weight6=tf.get_variable('weight',[6,6,256,2000],initializer=tf.truncated_normal_initializer(stddev=0.1))
bias6=tf.get_variable('bias',[2000],initializer=tf.constant_initializer(0.0))
conv6=tf.nn.conv2d(pool3,weight6,strides=[1,1,1,1],padding='VALID')
he6=tf.nn.bias_add(conv6,bias6)
bn6=tf.layers.batch_normalization(he6,fused=False,training=is_training)
relu6=tf.nn.relu(bn6)
fc1=tf.nn.dropout(relu6,prob)
with tf.variable_scope('fc2',reuse=tf.AUTO_REUSE):
weight7=tf.get_variable('weight',[1,1,2000,200],initializer=tf.truncated_normal_initializer(stddev=0.1))
bias7=tf.get_variable('bias',[200],initializer=tf.constant_initializer(0.0))
conv7=tf.nn.conv2d(fc1,weight7,strides=[1,1,1,1],padding='VALID')
he7=tf.nn.bias_add(conv7,bias7)
bn7=tf.layers.batch_normalization(he7,fused=False,training=is_training)
relu7=tf.nn.relu(bn7)
fc2=tf.nn.dropout(relu7,prob)
with tf.variable_scope('fc3',reuse=tf.AUTO_REUSE):
weight8=tf.get_variable('weight',[1,1,200,1],initializer=tf.truncated_normal_initializer(stddev=0.1))
bias8=tf.get_variable('bias',[1],initializer=tf.constant_initializer(0.0))
conv8=tf.nn.conv2d(fc2,weight8,strides=[1,1,1,1],padding='VALID')
he8=tf.nn.bias_add(conv8,bias8,name='he')
logit=tf.nn.sigmoid(he8,name='logit')
return logit
def train(x,y_,prob,is_training,af_y):
y=model(x,prob,is_training)
y=tf.reshape(y,(1,64))[0]
with tf.name_scope('loss'):
loss_all=tf.add(-y_*tf.log(y+1e-9),-(1-y_)*tf.log(1-y+1e-9))
loss=tf.reduce_mean(loss_all,name='loss')
# tf.summary.scalar('loss',loss)
update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
with tf.control_dependencies(update_ops):
opt=tf.train.AdamOptimizer(0.001).minimize(loss)
with tf.name_scope('accuracy'):
accuracy=tf.reduce_mean(tf.cast(tf.equal(af_y,y_),tf.float32),name='accuracy')
tf.summary.scalar('accuracy',accuracy)
# var_list = tf.trainable_variables()
# g_list = tf.global_variables()
# bn_moving_vars = [g for g in g_list if 'moving_mean' in g.name]
# bn_moving_vars += [g for g in g_list if 'moving_variance' in g.name]
# var_list += bn_moving_vars
# saver = tf.train.Saver(var_list=var_list, max_to_keep=5)
saver=tf.train.Saver()
image_batch,label_batch=wenjianliu()
image_batch1,label_batch1=wenjianliu1()
with tf.Session() as sess:
sess.run((tf.global_variables_initializer(),
tf.local_variables_initializer()))
coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(sess=sess,coord=coord)
writer=tf.summary.FileWriter('C:\\Users\\312\\Desktop\\',sess.graph)
merged=tf.summary.merge_all()
for i in range(50001):
img,label=sess.run([image_batch,label_batch])
label=np.reshape(label,(64))
sess.run(opt,feed_dict={x:img,y_:label,prob:0.7,is_training:True})
if(i%100==0):
img1,label1=sess.run([image_batch1,label_batch1])
label1=np.reshape(label1,(64))
p_y=sess.run(y,feed_dict={x:img1,y_:label1,prob:1,is_training:False})
ppp=lambda ppp:1 if ppp>0.95 else 0
p_y=[ppp(i) for i in p_y]
# summary=sess.run(merged,feed_dict={x:img,y_:label,prob:0.7,is_training:True,af_y:p_y})
summary=sess.run(merged,feed_dict={y_:label1,af_y:p_y})
writer.add_summary(summary,i)
if(i%1000==0):
print('次数',i)
print('loss',sess.run(loss,feed_dict={x:img,y_:label,prob:0.7,is_training:True}))
print('accuracy',sess.run(accuracy,feed_dict={af_y:p_y,y_:label1}))
print('time',time.time()-begin)
# constant_graph = graph_util.convert_variables_to_constants(sess, sess.graph_def, ["fc3/logit"])
# with tf.gfile.FastGFile('C:\\Users\\312\\Desktop\\face_model.pb', mode='wb') as f:
# f.write(constant_graph.SerializeToString())
saver.save(sess,"C:\\Users\\312\\Desktop\\Face_Detection\\model.ckpt")
writer.close()
def main():
with tf.name_scope('input'):
x=tf.placeholder(tf.float32,name='x')
y_=tf.placeholder(tf.float32,name='y')
prob=tf.placeholder(tf.float32,name='prob')
is_training = tf.placeholder(tf.bool,name='is_train')
af_y=tf.placeholder(tf.float32,name='af_y')
train(x,y_,prob,is_training,af_y)
if __name__=='__main__':
main()
# tensorboard --logdir=C:\\Users\\312\\Desktop\\
模型方面我把第三层的5*5卷积核换成了3*3的,全连接层换成全卷积层,并且将深度也改变了。
这块有坑死我自己的几个地方,以及需要注意的地方,我提出来大家看看有没有用吧:
1、在建立模型并更换全卷积层的时候需要特别注意上一pooling层的输出大小,这就是我第三层的5*5卷积核换成了3*3的原因,方便我理解和计算,感受野与你的stride有关,和size无关;
2、label不是one-hot的,所以最后输出的0~1之间值即为label为1的概率值;
3、关于BN层,没有BN层的话收敛特别慢,BN层的解释可以看看别人的博客。tensorflow的BN如果要用low-level的API的话需要自己写滑动平均,而且还要写tf.cond判断,tf.layers.batch_normalization(he2,training=is_training,fused=False)是现成的(我承认我偷懒了,重基础都是假的~),通过查看源码可以深入理解一下,fused说官方文档是可以快速实现,但是源码越看越远我就放弃了,所以就不用fused了,正常不用的话是跟BN论文的实现是一样的,所以设置为fused=False,如果为None和True则会使用fused;
4、文件读取时需要先run出来再feed进去,必须同时run出来,否则出来的数据会对应不上,这是tensorflow的机制决定的(眼瞎的我踩了两天坑一直以为是读取的代码错了,直到我看到了两个run。。);
5、模型中以后用到的tensor的名称要提前指定好,否则将来用的时候会发现还需要重新训练。。。;
6、从文件中读取出的img一定要经过tf.reshape操作,否则会报shape的错误,我看很多网上的教程里都没有写这个;
7、如果在训练时长时间读取不了文件流的数据,兴许是TFRecord格式的文件损坏了,再生成一份就好了;
其他的坑要不别人都写了,要不就是我没有踩到,暂时总结到这吧。。
loss
accuracy
loss和accuracy的问题在于不是一个输入,loss我知道程序和数据必然收敛后就不想再费劲显示它了,所以只显示一个验证集的,瞅着准确率还不错。。(仅仅是瞅着)
注:这两张图是我两回训练截的图
4、正式开始识别~
具体的代码参照了唐宇迪大佬的视频,首先输入图片的纯手工造高斯金字塔(调用金字塔函数亲测反而更慢):
import tensorflow as tf
import numpy as np
import cv2
import matplotlib.pyplot as plt
total_box=[]
scales=[]
img=cv2.imread(r'...')
factor=0.7
large=2270/max(img.shape[0:2])
scale=large
small=large*min(img.shape[0:2])
i=j=0
while small>=227:
scales.append(scale)
scale*=factor
small*=factor
j+=1
print(j)
for scale in scales:
scale_img=cv2.resize(img,((int(img.shape[0]*scale)),(int(img.shape[1]*scale))))
print(np.shape(scale_img))
boxes=featuremap(scale_img,scale)
if(boxes):
for box in boxes:
total_box.append(box)
i+=1
print(i)
其中featuremap为特征图函数,具体代码如下,经过这个函数可得到人脸框:
def featuremap(img,scale):
boundingBox=[]
graph_path='...\\model.ckpt.meta'
model_path='...\\model.ckpt'
saver=tf.train.import_meta_graph(graph_path)
blue = (0, 255, 0)
stride=32
with tf.Session() as sess:
saver.restore(sess,model_path)
graph = tf.get_default_graph()
x= graph.get_tensor_by_name("input/x:0")
y=graph.get_tensor_by_name("input/prob:0")
p=graph.get_tensor_by_name("input/is_train:0")
sliding= graph.get_tensor_by_name("fc3/logit:0")
img1=np.reshape(img,(-1,img.shape[0],img.shape[1],img.shape[2]))
a=sliding.eval(feed_dict={x:img1,y:1,p:False})[0]
for (x,y,z),prob in np.ndenumerate(a):
if prob>0.99:
boundingBox.append([float(x*stride)/scale,float(y*stride)/scale, float(x*stride+227)/scale, float(y*stride+227)/scale,prob])
return boundingBox
随后经过如下NMS函数,这个函数在人脸识别上还是蛮重要的,真心建议大家一定要看懂,理解它每一步都干了啥,像我一样,特别明白,然后就忘了,哈哈~
def NMS(box):
if len(box) == 0:
return []
#xmin, ymin, xmax, ymax, score, cropped_img, scale
box.sort(key=lambda x :x[4])
box.reverse()
pick = []
x_min = np.array([box[i][0] for i in range(len(box))],np.float32)
y_min = np.array([box[i][1] for i in range(len(box))],np.float32)
x_max = np.array([box[i][2] for i in range(len(box))],np.float32)
y_max = np.array([box[i][3] for i in range(len(box))],np.float32)
area = (x_max-x_min)*(y_max-y_min)
idxs = np.array(range(len
(box)))
while len(idxs) > 0:
i = idxs[0]
pick.append(i)
xx1 = np.maximum(x_min[i],x_min[idxs[1:]])
yy1 = np.maximum(y_min[i],y_min[idxs[1:]])
xx2 = np.minimum(x_max[i],x_max[idxs[1:]])
yy2 = np.minimum(y_max[i],y_max[idxs[1:]])
w = np.maximum(xx2-xx1,0)
h = np.maximum(yy2-yy1,0)
overlap = (w*h)/(area[idxs[1:]] + area[i] - w*h)
idxs = np.delete(idxs, np.concatenate(([0],np.where(((overlap >= 0.7) & (overlap <= 1)))[0]+1)))
return [box[i] for i in pick]
经过NMS函数后就出来检测的结果了,然后结果跟视频里的图片以及想象的不一样,烂到爆。。我就截一个图给大家看看吧。。
由于我懒得找别的图片了,直接拿AFLW的原图去测试的,这是经过了NMS的图像,基本无法框出来脸,在数据泄露的如此彻底的情况下现实和理想的差距。。
毕竟是简单的实现,对结果要求不了太多。。
5、总结
本文只是个入门文章,自己走过了一遍流程,趟过了无数的小白坑,现在咋的也算一个小灰级别了。文内主要提到了一些我遇到的坑,对于CNN理论方面并没有涉及,现在CNN的理论烂大街,大家可以随意百度,我也不在此赘述了。其中尤其重要的就是特征图的问题,大家一定要搞透,对于以后还想弄目标检测的未来大佬们,fast和faster-rcnn都是你们要踩的坑,我估计是没时间喽。。
文中不懂的地方可以查找fast和faster-rcnn的论文,以及百度,当然唐宇迪大神的视频内还是有一定的讲解的。
在接下来的文章,我会争取复现《Joint Face Detection and Alignment Using Multitask Cascaded Convolutional Networks》这篇论文,使用MTCNN。该论文目前是我看到的人脸识别相当不错的论文(由于本人只是兴趣爱好以及项目需求,更好的论文兴许没有看到,如有推荐,不胜感激!)
2018.8.26