This commit is contained in:
nttstar
2018-02-08 19:35:55 +08:00
11 changed files with 370 additions and 23 deletions

View File

@@ -5,6 +5,8 @@
### Recent Update
 **`2018.02.07`**: We evaluate LFW,CFP,AgeDB-30 again after removing training set overlaps, the results almost stay the same. See [Results](#results) for detail.
**`2018.01.30`**: We provide a *LResNet50E-IR* model which can achieve **`99.80@LFW`** and **`97.64%`** at MegaFace 1M Acc. See [Pretrained-Models](#pretrained-models) for detail.
**`2018.01.29`**: Caffe *LResNet34E-IR* model is available now. We get it by converting original MXNet model to Caffe format but there's some performance drop. See [Pretrained-Models](#pretrained-models) for detail.
@@ -276,7 +278,13 @@ export MXNET_ENGINE_TYPE=ThreadedEnginePerDevice
| ------- | ------ | --------- | --------- | ----------- | ------------- |
| Ours | 99.7+ | 99.6+ | 97.1+ | 95.7+ | - |
We report the verification accuracy/performance after removing training set overlaps, to make our results more stable and reliable. `(C) means after cleaning`
| Dataset | Identities | Images | Identites(C) | Images(C) | Acc | Acc(C) |
| -------- | ---------- | ------- | ------------ | --------- | ----- | ------ |
| LFW | 85742 | 3850179 | 80995 | 3586128 | 99.83 | 99.81 |
| CFP-FP | 85742 | 3850179 | 83706 | 3736338 | 94.04 | 94.03 |
| AgeDB-30 | 85742 | 3850179 | 83775 | 3761329 | 98.08 | 97.87 |
### Contribution
- Any type of PR or third-party contribution are welcome.

View File

@@ -0,0 +1,47 @@
import tensorflow as tf
import cv2
import sys
import mxnet as mx
import os
import numpy as np
input_dir = sys.argv[1]
output_dir = './data'
writer = mx.recordio.MXIndexedRecordIO(os.path.join(output_dir, 'train.idx'), os.path.join(output_dir, 'train.rec'), 'w')
idx = 1
for _file in os.listdir(input_dir):
if not _file.endswith('tfrecords'):
continue
data_file = os.path.join(input_dir, _file)
for serialized_example in tf.python_io.tf_record_iterator(data_file):
example = tf.train.Example()
example.ParseFromString(serialized_example)
features = example.features.feature
image = features['image'].bytes_list.value[0]
width = features['width'].int64_list.value[0]
height = features['height'].int64_list.value[0]
image = np.fromstring(image, dtype=np.uint8)
image = cv2.imdecode(image, cv2.CV_LOAD_IMAGE_COLOR)
#print(image.shape)
n_landmarks = features['n_landmarks'].int64_list.value[0]
mask_index = features['mask_index'].bytes_list.value[0]
status = features['status'].int64_list.value[0]
gt_mask = features['gt_mask'].bytes_list.value[0]
gt_mask = np.fromstring(gt_mask, dtype=np.uint8)
#print(gt_mask.shape)
gt_pts = features['gt_pts'].bytes_list.value[0]
gt_pts = np.fromstring(gt_pts, dtype=np.float32)
#print(gt_pts.shape, n_landmarks)
#print(gt_pts)
#for k in features:
# print(k)
#print(len(image),width, height, n_landmarks, status, gt_mask, gt_pts)
nlabel = list(gt_pts)
nheader = mx.recordio.IRHeader(0, nlabel, idx, 0)
s = mx.recordio.pack_img(nheader, image, quality=95, img_fmt='.jpg')
writer.write_idx(idx, s)
idx+=1

View File

@@ -101,7 +101,8 @@ def main(args):
pnet, rnet, onet = detect_face.create_mtcnn(sess, None)
minsize = 100 # minimum size of face
threshold = [ 0.6, 0.7, 0.7 ] # three steps's threshold
#threshold = [ 0.6, 0.7, 0.7 ] # three steps's threshold
threshold = [ 0.6, 0.6, 0.3 ] # three steps's threshold
factor = 0.709 # scale factor
print(minsize)
@@ -126,8 +127,9 @@ def main(args):
v = datamap.get(person, None)
if v is None:
continue
if not img_id in v[1]:
continue
#TODO
#if not img_id in v[1]:
# continue
labelid = v[0]
img_str = base64.b64decode(vec[-1])
nparr = np.fromstring(img_str, np.uint8)
@@ -148,7 +150,8 @@ def main(args):
if fimage.bbox is not None:
_bb = fimage.bbox
_minsize = min( [_bb[2]-_bb[0], _bb[3]-_bb[1], img.shape[0]//2, img.shape[1]//2] )
else:
_minsize = min(img.shape[0]//5, img.shape[1]//5)
bounding_boxes, points = detect_face.detect_face(img, _minsize, pnet, rnet, onet, threshold, factor)
bindex = -1
nrof_faces = bounding_boxes.shape[0]

View File

@@ -6,9 +6,7 @@ from skimage import transform as trans
def parse_lst_line(line):
vec = line.strip().split("\t")
assert len(vec)>=3
aligned = False
if int(vec[0])==1:
aligned = True
aligned = int(vec[0])
image_path = vec[1]
label = int(vec[2])
bbox = None

View File

@@ -59,7 +59,7 @@ class FaceImageIter(io.DataIter):
path_imgrec = None,
shuffle=False, aug_list=None, mean = None,
rand_mirror = False,
c2c_threshold = 0.0, output_c2c = 0,
c2c_threshold = 0.0, output_c2c = 0, c2c_mode = -10,
ctx_num = 0, images_per_identity = 0, data_extra = None, hard_mining = False,
triplet_params = None, coco_mode = False,
mx_model = None,
@@ -74,6 +74,7 @@ class FaceImageIter(io.DataIter):
s = self.imgrec.read_idx(0)
header, _ = recordio.unpack(s)
self.idx2cos = {}
self.idx2flag = {}
self.idx2meancos = {}
self.c2c_auto = False
if output_c2c or c2c_threshold>0.0:
@@ -82,7 +83,11 @@ class FaceImageIter(io.DataIter):
if os.path.exists(path_c2c):
for line in open(path_c2c, 'r'):
vec = line.strip().split(',')
self.idx2cos[int(vec[0])] = float(vec[1])
idx = int(vec[0])
self.idx2cos[idx] = float(vec[1])
self.idx2flag[idx] = 1
if len(vec)>2:
self.idx2flag[idx] = int(vec[2])
else:
self.c2c_auto = True
self.c2c_step = 10000
@@ -91,10 +96,65 @@ class FaceImageIter(io.DataIter):
self.header0 = (int(header.label[0]), int(header.label[1]))
#assert(header.flag==1)
self.imgidx = range(1, int(header.label[0]))
if c2c_threshold>0.0:
if c2c_mode==0:
imgidx2 = []
for idx in self.imgidx:
c = self.idx2cos[idx]
f = self.idx2flag[idx]
if f!=1:
continue
imgidx2.append(idx)
print('idx count', len(self.imgidx), len(imgidx2))
self.imgidx = imgidx2
elif c2c_mode==1:
imgidx2 = []
for idx in self.imgidx:
c = self.idx2cos[idx]
f = self.idx2flag[idx]
if f==2 and c>=0.05:
continue
imgidx2.append(idx)
print('idx count', len(self.imgidx), len(imgidx2))
self.imgidx = imgidx2
elif c2c_mode==2:
imgidx2 = []
for idx in self.imgidx:
c = self.idx2cos[idx]
f = self.idx2flag[idx]
if f==2 and c>=0.1:
continue
imgidx2.append(idx)
print('idx count', len(self.imgidx), len(imgidx2))
self.imgidx = imgidx2
elif c2c_mode==-1:
imgidx2 = []
for idx in self.imgidx:
c = self.idx2cos[idx]
f = self.idx2flag[idx]
if f==2:
continue
if c<0.1:
continue
imgidx2.append(idx)
print('idx count', len(self.imgidx), len(imgidx2))
self.imgidx = imgidx2
elif c2c_mode==-2:
imgidx2 = []
for idx in self.imgidx:
c = self.idx2cos[idx]
f = self.idx2flag[idx]
if f==2:
continue
if c<0.2:
continue
imgidx2.append(idx)
print('idx count', len(self.imgidx), len(imgidx2))
self.imgidx = imgidx2
elif c2c_threshold>0.0:
imgidx2 = []
for idx in self.imgidx:
c = self.idx2cos[idx]
f = self.idx2flag[idx]
if c<c2c_threshold:
continue
imgidx2.append(idx)
@@ -684,6 +744,9 @@ class FaceImageIter(io.DataIter):
if self.output_c2c:
meancos = self.idx2meancos[idx]
label = [label, meancos]
else:
if isinstance(label, list):
label = label[0]
return label, img, None, None
else:
label, fname, bbox, landmark = self.imglist[idx]

136
src/data/dataset_c2c.py Normal file
View File

@@ -0,0 +1,136 @@
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import os
import sys
import mxnet as mx
from mxnet import ndarray as nd
import random
import argparse
import cv2
import time
import sklearn
from sklearn.decomposition import PCA
from easydict import EasyDict as edict
from sklearn.cluster import DBSCAN
import numpy as np
sys.path.append(os.path.join(os.path.dirname(__file__),'..', 'common'))
import face_image
def main(args):
ctx = []
cvd = os.environ['CUDA_VISIBLE_DEVICES'].strip()
if len(cvd)>0:
for i in xrange(len(cvd.split(','))):
ctx.append(mx.gpu(i))
if len(ctx)==0:
ctx = [mx.cpu()]
print('use cpu')
else:
print('gpu num:', len(ctx))
ctx_num = len(ctx)
path_imgrec = os.path.join(args.input, 'train.rec')
path_imgidx = os.path.join(args.input, 'train.idx')
imgrec = mx.recordio.MXIndexedRecordIO(path_imgidx, path_imgrec, 'r') # pylint: disable=redefined-variable-type
outf = open(os.path.join(args.input, 'c2c'), 'w')
s = imgrec.read_idx(0)
header, _ = mx.recordio.unpack(s)
assert header.flag>0
print('header0 label', header.label)
header0 = (int(header.label[0]), int(header.label[1]))
#assert(header.flag==1)
imgidx = range(1, int(header.label[0]))
id2range = {}
seq_identity = range(int(header.label[0]), int(header.label[1]))
for identity in seq_identity:
s = imgrec.read_idx(identity)
header, _ = mx.recordio.unpack(s)
id2range[identity] = (int(header.label[0]), int(header.label[1]))
print('id2range', len(id2range))
prop = face_image.load_property(args.input)
image_size = prop.image_size
print('image_size', image_size)
vec = args.model.split(',')
prefix = vec[0]
epoch = int(vec[1])
print('loading',prefix, epoch)
model = mx.mod.Module.load(prefix, epoch, context = ctx)
model.bind(data_shapes=[('data', (args.batch_size, 3, image_size[0], image_size[1]))], label_shapes=[('softmax_label', (args.batch_size,))])
nrof_images = 0
nrof_removed = 0
idx = 1
id2label = {}
pp = 0
for _id, v in id2range.iteritems():
pp+=1
if pp%100==0:
print('processing id', pp)
_list = range(*v)
ocontents = []
for i in xrange(len(_list)):
_idx = _list[i]
#print('_idx', _id, _idx)
s = imgrec.read_idx(_idx)
ocontents.append(s)
#continue
embeddings = None
headers = [None]*len(ocontents)
#print(len(ocontents))
ba = 0
while True:
bb = min(ba+args.batch_size, len(ocontents))
if ba>=bb:
break
_batch_size = bb-ba
_batch_size2 = max(_batch_size, ctx_num)
data = nd.zeros( (_batch_size2,3, image_size[0], image_size[1]) )
label = nd.zeros( (_batch_size2,) )
count = bb-ba
ii=0
for i in xrange(ba, bb):
header, img = mx.recordio.unpack(ocontents[i])
headers[i] = header
img = mx.image.imdecode(img)
img = nd.transpose(img, axes=(2, 0, 1))
data[ii][:] = img
label[ii][:] = header.label[0]
ii+=1
while ii<_batch_size2:
data[ii][:] = data[0][:]
label[ii][:] = label[0][:]
ii+=1
db = mx.io.DataBatch(data=(data,), label=(label,))
model.forward(db, is_train=False)
net_out = model.get_outputs()
net_out = net_out[0].asnumpy()
if embeddings is None:
embeddings = np.zeros( (len(ocontents), net_out.shape[1]))
embeddings[ba:bb,:] = net_out[0:_batch_size,:]
ba = bb
embeddings = sklearn.preprocessing.normalize(embeddings)
emb_mean = np.mean(embeddings, axis=0, keepdims=True)
emb_mean = sklearn.preprocessing.normalize(emb_mean)
sim = np.dot(embeddings, emb_mean.T)
#print(sim.shape)
sims = sim.flatten()
assert len(_list)==len(sims)
assert len(_list)==len(ocontents)
for i in xrange(len(ocontents)):
_sim = sims[i]
_idx = _list[i]
_header = headers[i]
#TODO
outf.write("%d,%f,%d\n"%(_idx, _sim, int(_header.label[1])))
outf.close()
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='')
# general
parser.add_argument('--input', default='', type=str, help='')
parser.add_argument('--model', default='../model/softmax,50', help='path to load model.')
parser.add_argument('--batch-size', default=32, type=int, help='')
args = parser.parse_args()
main(args)

View File

@@ -12,10 +12,11 @@ import time
import sklearn
from sklearn.decomposition import PCA
from easydict import EasyDict as edict
import face_image
from sklearn.cluster import DBSCAN
import numpy as np
sys.path.append(os.path.join(os.path.dirname(__file__),'..', 'common'))
import face_image
def do_clean(args):

40
src/data/dataset_info.py Normal file
View File

@@ -0,0 +1,40 @@
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import os
import sys
import mxnet as mx
from mxnet import ndarray as nd
import random
import argparse
import cv2
import time
import sklearn
from sklearn.decomposition import PCA
from easydict import EasyDict as edict
from sklearn.cluster import DBSCAN
import numpy as np
sys.path.append(os.path.join(os.path.dirname(__file__),'..', 'common'))
import face_image
def main(args):
path_imgrec = os.path.join(args.input, 'train.rec')
path_imgidx = os.path.join(args.input, 'train.idx')
imgrec = mx.recordio.MXIndexedRecordIO(path_imgidx, path_imgrec, 'r') # pylint: disable=redefined-variable-type
s = imgrec.read_idx(0)
header, _ = mx.recordio.unpack(s)
assert header.flag>0
print('header0 label', header.label)
header0 = (int(header.label[0]), int(header.label[1]))
print('identities', header0[1]-header0[0])
print('images', header0[0])
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='')
# general
parser.add_argument('--input', default='', type=str, help='')
args = parser.parse_args()
main(args)

View File

@@ -18,6 +18,9 @@ import numpy as np
sys.path.append(os.path.join(os.path.dirname(__file__),'..', 'common'))
import face_image
sys.path.append(os.path.join(os.path.dirname(__file__),'..', 'eval'))
import verification
def ch_dev(arg_params, aux_params, ctx):
new_args = dict()
new_auxs = dict()
@@ -177,6 +180,20 @@ def main(args):
_id_list.append( (_ds_id, identity, embedding) )
if test_limit>0 and pp>=test_limit:
break
else:
_id_list = []
data_set = verification.load_bin(args.exclude, image_size)[0][0]
print(data_set.shape)
data = nd.zeros( (1,3,image_size[0], image_size[1]))
for i in xrange(data_set.shape[0]):
data[0] = data_set[i]
db = mx.io.DataBatch(data=(data,))
model.forward(db, is_train=False)
net_out = model.get_outputs()
embedding = net_out[0].asnumpy().flatten()
_norm=np.linalg.norm(embedding)
embedding /= _norm
_id_list.append( (i, i, embedding) )
#X = []
#for id_item in all_id_list:
@@ -259,7 +276,7 @@ if __name__ == '__main__':
parser.add_argument('--model', default='../model/softmax,50', help='path to load model.')
parser.add_argument('--batch-size', default=32, type=int, help='')
parser.add_argument('--param1', default=0.3, type=float, help='')
parser.add_argument('--param2', default=0.45, type=float, help='')
parser.add_argument('--param2', default=0.4, type=float, help='')
parser.add_argument('--mode', default=1, type=int, help='')
parser.add_argument('--test', default=0, type=int, help='')
args = parser.parse_args()

View File

@@ -52,21 +52,22 @@ def read_list(path_in):
break
item = edict()
item.flag = 0
item.image_path, item.label, item.bbox, item.landmark, item.aligned = face_preprocess.parse_lst_line(line)
item.image_path, label, item.bbox, item.landmark, item.aligned = face_preprocess.parse_lst_line(line)
if not item.aligned and item.landmark is None:
#print('ignore line', line)
continue
item.id = _id
item.label = [label, item.aligned]
yield item
if item.label!=last[0]:
if label!=last[0]:
if last[1]>=0:
identities.append( (last[1], _id) )
last[0] = item.label
last[0] = label
last[1] = _id
_id+=1
identities.append( (last[1], _id) )
item = edict()
item.flag = 1
item.flag = 2
item.id = 0
item.label = [float(_id), float(_id+len(identities))]
yield item
@@ -82,6 +83,7 @@ def read_list(path_in):
def image_encode(args, i, item, q_out):
oitem = [item.id]
#print('flag', item.flag)
if item.flag==0:
fullpath = item.image_path
header = mx.recordio.IRHeader(item.flag, item.label, item.id, 0)
@@ -97,7 +99,7 @@ def image_encode(args, i, item, q_out):
img = face_preprocess.preprocess(img, bbox = item.bbox, landmark=item.landmark, image_size='%d,%d'%(args.image_h, args.image_w))
s = mx.recordio.pack_img(header, img, quality=args.quality, img_fmt=args.encoding)
q_out.put((i, s, oitem))
else: #flag==1 or 2
else:
header = mx.recordio.IRHeader(item.flag, item.label, item.id, 0)
#print('write', item.flag, item.id, item.label)
s = mx.recordio.pack(header, '')
@@ -133,6 +135,7 @@ def write_worker(q_out, fname, working_dir):
s, item = buf[count]
del buf[count]
if s is not None:
#print('write idx', item[0])
record.write_idx(item[0], s)
if count % 1000 == 0:
@@ -250,7 +253,9 @@ if __name__ == '__main__':
image_encode(args, i, item, q_out)
if q_out.empty():
continue
_, s, _ = q_out.get()
_, s, item = q_out.get()
#header, _ = mx.recordio.unpack(s)
#print('write header label', header.label)
record.write_idx(item[0], s)
if cnt % 1000 == 0:
cur_time = time.time()
@@ -259,3 +264,4 @@ if __name__ == '__main__':
cnt += 1
if not count:
print('Did not find and list file with prefix %s'%args.prefix)

View File

@@ -143,6 +143,8 @@ def parse_args():
help='')
parser.add_argument('--c2c-threshold', type=float, default=0.0,
help='')
parser.add_argument('--c2c-mode', type=int, default=-10,
help='')
parser.add_argument('--output-c2c', type=int, default=0,
help='')
parser.add_argument('--margin', type=int, default=4,
@@ -443,6 +445,30 @@ def get_symbol(args, arg_params, aux_params):
triplet_loss = mx.symbol.mean(triplet_loss)
#triplet_loss = mx.symbol.sum(triplet_loss)/(args.per_batch_size//3)
extra_loss = mx.symbol.MakeLoss(triplet_loss)
elif args.loss_type==13: #triplet loss with insightface margin
m = args.margin_m
sin_m = math.sin(m)
cos_m = math.cos(m)
nembedding = mx.symbol.L2Normalization(embedding, mode='instance', name='fc1n')
anchor = mx.symbol.slice_axis(nembedding, axis=0, begin=0, end=args.per_batch_size//3)
positive = mx.symbol.slice_axis(nembedding, axis=0, begin=args.per_batch_size//3, end=2*args.per_batch_size//3)
negative = mx.symbol.slice_axis(nembedding, axis=0, begin=2*args.per_batch_size//3, end=args.per_batch_size)
ap = anchor * positive
an = anchor * negative
ap = mx.symbol.sum(ap, axis=1, keepdims=1) #(T,1)
an = mx.symbol.sum(an, axis=1, keepdims=1) #(T,1)
#ap = mx.symbol.arccos(ap)
#an = mx.symbol.arccos(an)
#triplet_loss = mx.symbol.Activation(data = (ap-an+args.margin_m), act_type='relu')
body = ap*ap
body = 1.0-body
body = mx.symbol.sqrt(body)
body = body*sin_m
ap = ap*cos_m
ap = ap-body
triplet_loss = mx.symbol.Activation(data = (an-ap), act_type='relu')
triplet_loss = mx.symbol.mean(triplet_loss)
extra_loss = mx.symbol.MakeLoss(triplet_loss)
elif args.loss_type==9: #coco loss
centroids = []
for i in xrange(args.per_identities):
@@ -530,7 +556,7 @@ def train_net(args):
os.environ['BETA'] = str(args.beta)
data_dir_list = args.data_dir.split(',')
if args.loss_type!=12:
if args.loss_type!=12 and args.loss_type!=13:
assert len(data_dir_list)==1
data_dir = data_dir_list[0]
args.use_val = False
@@ -569,7 +595,7 @@ def train_net(args):
args.images_per_identity = 2
elif args.loss_type==10 or args.loss_type==9:
args.images_per_identity = 16
elif args.loss_type==12:
elif args.loss_type==12 or args.loss_type==13:
args.images_per_identity = 5
assert args.per_batch_size%3==0
assert args.images_per_identity>=2
@@ -624,7 +650,7 @@ def train_net(args):
for i in xrange(args.per_identities):
data_extra[c+i][i] = 1.0
c+=args.per_batch_size
elif args.loss_type==12:
elif args.loss_type==12 or args.loss_type==13:
triplet_params = [args.triplet_bag_size, args.triplet_alpha, args.triplet_max_ap]
elif args.loss_type==9:
coco_mode = True
@@ -663,7 +689,7 @@ def train_net(args):
else:
val_dataiter = None
if len(data_dir_list)==1 and args.loss_type!=12:
if len(data_dir_list)==1 and args.loss_type!=12 and args.loss_type!=13:
train_dataiter = FaceImageIter(
batch_size = args.batch_size,
data_shape = data_shape,
@@ -673,6 +699,7 @@ def train_net(args):
mean = mean,
c2c_threshold = args.c2c_threshold,
output_c2c = args.output_c2c,
c2c_mode = args.c2c_mode,
ctx_num = args.ctx_num,
images_per_identity = args.images_per_identity,
data_extra = data_extra,
@@ -695,6 +722,7 @@ def train_net(args):
mean = mean,
c2c_threshold = args.c2c_threshold,
output_c2c = args.output_c2c,
c2c_mode = args.c2c_mode,
ctx_num = args.ctx_num,
images_per_identity = args.images_per_identity,
data_extra = data_extra,
@@ -723,7 +751,7 @@ def train_net(args):
_rescale = 1.0/args.ctx_num
opt = optimizer.SGD(learning_rate=base_lr, momentum=base_mom, wd=base_wd, rescale_grad=_rescale)
som = 20
if args.loss_type==12:
if args.loss_type==12 or args.loss_type==13:
som = 2
_cb = mx.callback.Speedometer(args.batch_size, som)