Skip to content

Commit 666f597

Browse files
authored
Face det (#9179)
* add "WIDERFaceEvalDataset" and "WiderFaceOnlineMetric" * add "support widerface eval online"
1 parent 7ba9f16 commit 666f597

File tree

9 files changed

+294
-32
lines changed

9 files changed

+294
-32
lines changed

configs/datasets/wider_face.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ TrainDataset:
99
data_fields: ['image', 'gt_bbox', 'gt_class']
1010

1111
EvalDataset:
12-
!WIDERFaceDataSet
12+
!WIDERFaceValDataset
1313
dataset_dir: dataset/wider_face
14-
anno_path: wider_face_split/wider_face_val_bbx_gt.txt
1514
image_dir: WIDER_val/images
16-
data_fields: ['image']
15+
anno_path: wider_face_split/wider_face_val_bbx_gt.txt
16+
gt_mat_path: WIDER_val/ground_truth
17+
data_fields: ['image', 'gt_bbox', 'gt_class', 'ori_gt_bbox']
1718

1819
TestDataset:
1920
!ImageFolder

configs/face_detection/_base_/face_reader.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
worker_num: 2
1+
worker_num: 8
22
TrainReader:
33
inputs_def:
44
num_max_boxes: 90
@@ -23,7 +23,7 @@ TrainReader:
2323
batch_transforms:
2424
- NormalizeImage: {mean: [123, 117, 104], std: [127.502231, 127.502231, 127.502231], is_scale: false}
2525
- Permute: {}
26-
batch_size: 8
26+
batch_size: 16
2727
shuffle: true
2828
drop_last: true
2929

@@ -34,6 +34,9 @@ EvalReader:
3434
- NormalizeImage: {mean: [123, 117, 104], std: [127.502231, 127.502231, 127.502231], is_scale: false}
3535
- Permute: {}
3636
batch_size: 1
37+
collate_samples: false
38+
shuffle: false
39+
drop_last: false
3740

3841

3942
TestReader:

configs/face_detection/blazeface_1000e.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ _BASE_: [
66
'_base_/face_reader.yml',
77
]
88
weights: output/blazeface_1000e/model_final
9-
multi_scale_eval: True
9+
snapshot_epoch: 10

configs/face_detection/blazeface_fpn_ssh_1000e.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ _BASE_: [
66
'_base_/face_reader.yml',
77
]
88
weights: output/blazeface_fpn_ssh_1000e/model_final
9-
multi_scale_eval: True
9+
snapshot_epoch: 10

ppdet/data/source/widerface.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
from collections import defaultdict
1516
import os
1617
import numpy as np
18+
from scipy.io import loadmat
1719

1820
from ppdet.core.workspace import register, serializable
1921
from .dataset import DetDataset
@@ -178,3 +180,82 @@ def _load_file_list(self, input_txt):
178180
def widerface_label():
179181
labels_map = {'face': 0}
180182
return labels_map
183+
184+
185+
@register
186+
@serializable
187+
class WIDERFaceValDataset(WIDERFaceDataSet):
188+
def __init__(self,
189+
dataset_dir=None,
190+
image_dir=None,
191+
anno_path=None,
192+
gt_mat_path=None,
193+
data_fields=['image'],
194+
sample_num=-1,
195+
with_lmk=False):
196+
super().__init__(
197+
dataset_dir=dataset_dir,
198+
image_dir=image_dir,
199+
anno_path=anno_path,
200+
data_fields=data_fields,
201+
sample_num=sample_num,
202+
with_lmk=with_lmk)
203+
self.gt_mat_path = gt_mat_path
204+
self.val_mat = os.path.join(self.dataset_dir, self.gt_mat_path, 'wider_face_val.mat')
205+
self.hard_mat_path = os.path.join(self.dataset_dir, self.gt_mat_path, 'wider_hard_val.mat')
206+
self.medium_mat_path = os.path.join(self.dataset_dir, self.gt_mat_path, 'wider_medium_val.mat')
207+
self.easy_mat_path = os.path.join(self.dataset_dir, self.gt_mat_path, 'wider_easy_val.mat')
208+
209+
assert os.path.exists(self.val_mat), f'{self.val_mat} not exist'
210+
assert os.path.exists(self.hard_mat_path), f'{self.hard_mat_path} not exist'
211+
assert os.path.exists(self.medium_mat_path), f'{self.medium_mat_path} not exist'
212+
assert os.path.exists(self.easy_mat_path), f'{self.easy_mat_path} not exist'
213+
214+
def parse_dataset(self):
215+
super().parse_dataset()
216+
217+
box_list, flie_list, event_list, hard_info_list, medium_info_list, \
218+
easy_info_list = self.get_gt_infos()
219+
setting_infos = [easy_info_list, medium_info_list, hard_info_list]
220+
settings = ['easy', 'medium', 'hard']
221+
info_by_name = defaultdict(dict)
222+
for setting_id in range(3):
223+
info_list = setting_infos[setting_id]
224+
setting = settings[setting_id]
225+
for i in range(len(event_list)):
226+
img_list = flie_list[i][0]
227+
gt_box_list = box_list[i][0]
228+
sub_info_list = info_list[i][0]
229+
for j in range(len(img_list)):
230+
img_name = str(img_list[j][0][0])
231+
gt_boxes = gt_box_list[j][0].astype(np.float32)
232+
info_by_name[img_name]['gt_ori_bbox'] = gt_boxes
233+
234+
keep_index = sub_info_list[j][0]
235+
ignore = np.zeros(gt_boxes.shape[0])
236+
if len(keep_index) != 0:
237+
ignore[keep_index-1] = 1
238+
info_by_name[img_name][f'gt_{setting}_ignore'] = ignore
239+
240+
for roidb in self.roidbs:
241+
img_file = roidb['im_file'].split('/')[-1]
242+
img_name = ".".join(img_file.split(".")[:-1])
243+
roidb.update(info_by_name[img_name])
244+
245+
def get_gt_infos(self):
246+
""" gt dir: (wider_face_val.mat, wider_easy_val.mat, wider_medium_val.mat, wider_hard_val.mat)"""
247+
248+
val_mat = loadmat(self.val_mat)
249+
hard_mat = loadmat(self.hard_mat_path)
250+
medium_mat = loadmat(self.medium_mat_path)
251+
easy_mat = loadmat(self.easy_mat_path)
252+
253+
box_list = val_mat['face_bbx_list']
254+
file_list = val_mat['file_list']
255+
event_list = val_mat['event_list']
256+
257+
hard_info_list = hard_mat['gt_list']
258+
medium_info_list = medium_mat['gt_list']
259+
easy_info_list = easy_mat['gt_list']
260+
261+
return box_list, file_list, event_list, hard_info_list, medium_info_list, easy_info_list

ppdet/engine/trainer.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -276,8 +276,8 @@ def _init_callbacks(self):
276276
self._compose_callback = ComposeCallback(self._callbacks)
277277
elif self.mode == 'eval':
278278
self._callbacks = [LogPrinter(self)]
279-
if self.cfg.metric == 'WiderFace':
280-
self._callbacks.append(WiferFaceEval(self))
279+
# if self.cfg.metric == 'WiderFace':
280+
# self._callbacks.append(WiferFaceEval(self))
281281
self._compose_callback = ComposeCallback(self._callbacks)
282282
elif self.mode == 'test' and self.cfg.get('use_vdl', False):
283283
self._callbacks = [VisualDLWriter(self)]
@@ -381,13 +381,8 @@ def _init_metrics(self, validate=False):
381381
save_prediction_only=save_prediction_only)
382382
]
383383
elif self.cfg.metric == 'WiderFace':
384-
multi_scale = self.cfg.multi_scale_eval if 'multi_scale_eval' in self.cfg else True
385384
self._metrics = [
386-
WiderFaceMetric(
387-
image_dir=os.path.join(self.dataset.dataset_dir,
388-
self.dataset.image_dir),
389-
anno_file=self.dataset.get_anno(),
390-
multi_scale=multi_scale)
385+
WiderFaceMetric()
391386
]
392387
elif self.cfg.metric == 'KeyPointTopDownCOCOEval':
393388
eval_dataset = self.cfg['EvalDataset']

ppdet/metrics/metrics.py

Lines changed: 88 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727

2828
from .map_utils import prune_zero_padding, DetectionMAP
2929
from .coco_utils import get_infer_results, cocoapi_eval
30-
from .widerface_utils import face_eval_run
30+
from .widerface_utils import (face_eval_run, image_eval, img_pr_info,
31+
dataset_pr_info, voc_ap)
3132
from ppdet.data.source.category import get_categories
3233
from ppdet.modeling.rbox_utils import poly2rbox_np
3334

@@ -337,22 +338,93 @@ def get_results(self):
337338

338339

339340
class WiderFaceMetric(Metric):
340-
def __init__(self, image_dir, anno_file, multi_scale=True):
341-
self.image_dir = image_dir
342-
self.anno_file = anno_file
343-
self.multi_scale = multi_scale
344-
self.clsid2catid, self.catid2name = get_categories('widerface')
345-
346-
def update(self, model):
347-
348-
face_eval_run(
349-
model,
350-
self.image_dir,
351-
self.anno_file,
352-
pred_dir='output/pred',
353-
eval_mode='widerface',
354-
multi_scale=self.multi_scale)
341+
def __init__(self, iou_thresh=0.5):
342+
self.iou_thresh = iou_thresh
343+
self.reset()
355344

345+
def reset(self):
346+
self.pred_boxes_list = []
347+
self.gt_boxes_list = []
348+
self.aps = []
349+
350+
self.hard_ignore_list = []
351+
self.medium_ignore_list = []
352+
self.easy_ignore_list = []
353+
354+
def update(self, data, outs):
355+
batch_pred_bboxes = outs['bbox']
356+
batch_pred_bboxes_num = outs['bbox_num']
357+
assert len(batch_pred_bboxes_num) == len(data['gt_bbox'])
358+
batch_size = len(data['gt_bbox'])
359+
box_cnt = 0
360+
for batch_id in range(batch_size):
361+
pred_bboxes_num = batch_pred_bboxes_num[batch_id]
362+
pred_bboxes = batch_pred_bboxes[box_cnt: box_cnt +
363+
pred_bboxes_num].numpy()
364+
box_cnt += pred_bboxes_num
365+
366+
det_conf = pred_bboxes[:, 1]
367+
det_xmin = pred_bboxes[:, 2]
368+
det_ymin = pred_bboxes[:, 3]
369+
det_xmax = pred_bboxes[:, 4]
370+
det_ymax = pred_bboxes[:, 5]
371+
det = np.column_stack((det_xmin, det_ymin, det_xmax,
372+
det_ymax, det_conf))
373+
self.pred_boxes_list.append(det) # xyxy conf
374+
self.gt_boxes_list.append(data['gt_ori_bbox'][batch_id].numpy()) # xywh
375+
self.hard_ignore_list.append(
376+
data['gt_hard_ignore'][batch_id].numpy())
377+
self.medium_ignore_list.append(
378+
data['gt_medium_ignore'][batch_id].numpy())
379+
self.easy_ignore_list.append(
380+
data['gt_easy_ignore'][batch_id].numpy())
381+
382+
def accumulate(self):
383+
total_num = len(self.gt_boxes_list)
384+
settings = ['easy', 'medium', 'hard']
385+
setting_ingores = [self.easy_ignore_list,
386+
self.medium_ignore_list,
387+
self.hard_ignore_list]
388+
thresh_num = 1000
389+
aps = []
390+
for setting_id in range(3):
391+
count_face = 0
392+
pr_curve = np.zeros((thresh_num, 2)).astype(np.float32)
393+
gt_ignore_list = setting_ingores[setting_id]
394+
for i in range(total_num):
395+
pred_boxes = self.pred_boxes_list[i] # xyxy conf
396+
gt_boxes = self.gt_boxes_list[i] # xywh
397+
ignore = gt_ignore_list[i]
398+
count_face += np.sum(ignore)
399+
400+
if len(gt_boxes) == 0 or len(pred_boxes) == 0:
401+
continue
402+
pred_recall, proposal_list = image_eval(pred_boxes, gt_boxes,
403+
ignore, self.iou_thresh)
404+
_img_pr_info = img_pr_info(thresh_num, pred_boxes,
405+
proposal_list, pred_recall)
406+
pr_curve += _img_pr_info
407+
pr_curve = dataset_pr_info(thresh_num, pr_curve, count_face)
408+
409+
propose = pr_curve[:, 0]
410+
recall = pr_curve[:, 1]
411+
412+
ap = voc_ap(recall, propose)
413+
aps.append(ap)
414+
self.aps = aps
415+
416+
def log(self):
417+
logger.info("==================== Results ====================")
418+
logger.info("Easy Val AP: {}".format(self.aps[0]))
419+
logger.info("Medium Val AP: {}".format(self.aps[1]))
420+
logger.info("Hard Val AP: {}".format(self.aps[2]))
421+
logger.info("=================================================")
422+
423+
def get_results(self):
424+
return {
425+
'easy_ap': self.aps[0],
426+
'medium_ap': self.aps[1],
427+
'hard_ap': self.aps[2]}
356428

357429
class RBoxMetric(Metric):
358430
def __init__(self, anno_file, **kwargs):

0 commit comments

Comments
 (0)