| from __future__ import division |
| import datetime |
| import os |
| import os.path as osp |
| import glob |
| import numpy as np |
| import cv2 |
| import sys |
| import onnxruntime |
| import onnx |
| import argparse |
| from onnx import numpy_helper |
| from insightface.data import get_image |
|
|
| class ArcFaceORT: |
| def __init__(self, model_path, cpu=False): |
| self.model_path = model_path |
| |
| self.providers = ['CPUExecutionProvider'] if cpu else None |
|
|
| |
| def check(self, track='cfat', test_img = None): |
| |
| max_model_size_mb=1024 |
| max_feat_dim=512 |
| max_time_cost=15 |
| if track.startswith('ms1m'): |
| max_model_size_mb=1024 |
| max_feat_dim=512 |
| max_time_cost=10 |
| elif track.startswith('glint'): |
| max_model_size_mb=1024 |
| max_feat_dim=1024 |
| max_time_cost=20 |
| elif track.startswith('cfat'): |
| max_model_size_mb = 1024 |
| max_feat_dim = 512 |
| max_time_cost = 15 |
| elif track.startswith('unconstrained'): |
| max_model_size_mb=1024 |
| max_feat_dim=1024 |
| max_time_cost=30 |
| else: |
| return "track not found" |
|
|
| if not os.path.exists(self.model_path): |
| return "model_path not exists" |
| if not os.path.isdir(self.model_path): |
| return "model_path should be directory" |
| onnx_files = [] |
| for _file in os.listdir(self.model_path): |
| if _file.endswith('.onnx'): |
| onnx_files.append(osp.join(self.model_path, _file)) |
| if len(onnx_files)==0: |
| return "do not have onnx files" |
| self.model_file = sorted(onnx_files)[-1] |
| print('use onnx-model:', self.model_file) |
| try: |
| session = onnxruntime.InferenceSession(self.model_file, providers=self.providers) |
| except: |
| return "load onnx failed" |
| input_cfg = session.get_inputs()[0] |
| input_shape = input_cfg.shape |
| print('input-shape:', input_shape) |
| if len(input_shape)!=4: |
| return "length of input_shape should be 4" |
| if not isinstance(input_shape[0], str): |
| |
| print('reset input-shape[0] to None') |
| model = onnx.load(self.model_file) |
| model.graph.input[0].type.tensor_type.shape.dim[0].dim_param = 'None' |
| new_model_file = osp.join(self.model_path, 'zzzzrefined.onnx') |
| onnx.save(model, new_model_file) |
| self.model_file = new_model_file |
| print('use new onnx-model:', self.model_file) |
| try: |
| session = onnxruntime.InferenceSession(self.model_file, providers=self.providers) |
| except: |
| return "load onnx failed" |
| input_cfg = session.get_inputs()[0] |
| input_shape = input_cfg.shape |
| print('new-input-shape:', input_shape) |
|
|
| self.image_size = tuple(input_shape[2:4][::-1]) |
| |
| input_name = input_cfg.name |
| outputs = session.get_outputs() |
| output_names = [] |
| for o in outputs: |
| output_names.append(o.name) |
| |
| if len(output_names)!=1: |
| return "number of output nodes should be 1" |
| self.session = session |
| self.input_name = input_name |
| self.output_names = output_names |
| |
| model = onnx.load(self.model_file) |
| graph = model.graph |
| if len(graph.node)<8: |
| return "too small onnx graph" |
|
|
| input_size = (112,112) |
| self.crop = None |
| if track=='cfat': |
| crop_file = osp.join(self.model_path, 'crop.txt') |
| if osp.exists(crop_file): |
| lines = open(crop_file,'r').readlines() |
| if len(lines)!=6: |
| return "crop.txt should contain 6 lines" |
| lines = [int(x) for x in lines] |
| self.crop = lines[:4] |
| input_size = tuple(lines[4:6]) |
| if input_size!=self.image_size: |
| return "input-size is inconsistant with onnx model input, %s vs %s"%(input_size, self.image_size) |
|
|
| self.model_size_mb = os.path.getsize(self.model_file) / float(1024*1024) |
| if self.model_size_mb > max_model_size_mb: |
| return "max model size exceed, given %.3f-MB"%self.model_size_mb |
|
|
| input_mean = None |
| input_std = None |
| if track=='cfat': |
| pn_file = osp.join(self.model_path, 'pixel_norm.txt') |
| if osp.exists(pn_file): |
| lines = open(pn_file,'r').readlines() |
| if len(lines)!=2: |
| return "pixel_norm.txt should contain 2 lines" |
| input_mean = float(lines[0]) |
| input_std = float(lines[1]) |
| if input_mean is not None or input_std is not None: |
| if input_mean is None or input_std is None: |
| return "please set input_mean and input_std simultaneously" |
| else: |
| find_sub = False |
| find_mul = False |
| for nid, node in enumerate(graph.node[:8]): |
| print(nid, node.name) |
| if node.name.startswith('Sub') or node.name.startswith('_minus'): |
| find_sub = True |
| if node.name.startswith('Mul') or node.name.startswith('_mul') or node.name.startswith('Div'): |
| find_mul = True |
| if find_sub and find_mul: |
| print("find sub and mul") |
| |
| input_mean = 0.0 |
| input_std = 1.0 |
| else: |
| input_mean = 127.5 |
| input_std = 127.5 |
| self.input_mean = input_mean |
| self.input_std = input_std |
| for initn in graph.initializer: |
| weight_array = numpy_helper.to_array(initn) |
| dt = weight_array.dtype |
| if dt.itemsize<4: |
| return 'invalid weight type - (%s:%s)' % (initn.name, dt.name) |
| if test_img is None: |
| test_img = get_image('Tom_Hanks_54745') |
| test_img = cv2.resize(test_img, self.image_size) |
| else: |
| test_img = cv2.resize(test_img, self.image_size) |
| feat, cost = self.benchmark(test_img) |
| batch_result = self.check_batch(test_img) |
| batch_result_sum = float(np.sum(batch_result)) |
| if batch_result_sum in [float('inf'), -float('inf')] or batch_result_sum != batch_result_sum: |
| print(batch_result) |
| print(batch_result_sum) |
| return "batch result output contains NaN!" |
|
|
| if len(feat.shape) < 2: |
| return "the shape of the feature must be two, but get {}".format(str(feat.shape)) |
|
|
| if feat.shape[1] > max_feat_dim: |
| return "max feat dim exceed, given %d"%feat.shape[1] |
| self.feat_dim = feat.shape[1] |
| cost_ms = cost*1000 |
| if cost_ms>max_time_cost: |
| return "max time cost exceed, given %.4f"%cost_ms |
| self.cost_ms = cost_ms |
| print('check stat:, model-size-mb: %.4f, feat-dim: %d, time-cost-ms: %.4f, input-mean: %.3f, input-std: %.3f'%(self.model_size_mb, self.feat_dim, self.cost_ms, self.input_mean, self.input_std)) |
| return None |
|
|
| def check_batch(self, img): |
| if not isinstance(img, list): |
| imgs = [img, ] * 32 |
| if self.crop is not None: |
| nimgs = [] |
| for img in imgs: |
| nimg = img[self.crop[1]:self.crop[3], self.crop[0]:self.crop[2], :] |
| if nimg.shape[0] != self.image_size[1] or nimg.shape[1] != self.image_size[0]: |
| nimg = cv2.resize(nimg, self.image_size) |
| nimgs.append(nimg) |
| imgs = nimgs |
| blob = cv2.dnn.blobFromImages( |
| images=imgs, scalefactor=1.0 / self.input_std, size=self.image_size, |
| mean=(self.input_mean, self.input_mean, self.input_mean), swapRB=True) |
| net_out = self.session.run(self.output_names, {self.input_name: blob})[0] |
| return net_out |
|
|
|
|
| def meta_info(self): |
| return {'model-size-mb':self.model_size_mb, 'feature-dim':self.feat_dim, 'infer': self.cost_ms} |
|
|
|
|
| def forward(self, imgs): |
| if not isinstance(imgs, list): |
| imgs = [imgs] |
| input_size = self.image_size |
| if self.crop is not None: |
| nimgs = [] |
| for img in imgs: |
| nimg = img[self.crop[1]:self.crop[3],self.crop[0]:self.crop[2],:] |
| if nimg.shape[0]!=input_size[1] or nimg.shape[1]!=input_size[0]: |
| nimg = cv2.resize(nimg, input_size) |
| nimgs.append(nimg) |
| imgs = nimgs |
| blob = cv2.dnn.blobFromImages(imgs, 1.0/self.input_std, input_size, (self.input_mean, self.input_mean, self.input_mean), swapRB=True) |
| net_out = self.session.run(self.output_names, {self.input_name : blob})[0] |
| return net_out |
|
|
| def benchmark(self, img): |
| input_size = self.image_size |
| if self.crop is not None: |
| nimg = img[self.crop[1]:self.crop[3],self.crop[0]:self.crop[2],:] |
| if nimg.shape[0]!=input_size[1] or nimg.shape[1]!=input_size[0]: |
| nimg = cv2.resize(nimg, input_size) |
| img = nimg |
| blob = cv2.dnn.blobFromImage(img, 1.0/self.input_std, input_size, (self.input_mean, self.input_mean, self.input_mean), swapRB=True) |
| costs = [] |
| for _ in range(50): |
| ta = datetime.datetime.now() |
| net_out = self.session.run(self.output_names, {self.input_name : blob})[0] |
| tb = datetime.datetime.now() |
| cost = (tb-ta).total_seconds() |
| costs.append(cost) |
| costs = sorted(costs) |
| cost = costs[5] |
| return net_out, cost |
|
|
|
|
| if __name__ == '__main__': |
| parser = argparse.ArgumentParser(description='') |
| |
| parser.add_argument('workdir', help='submitted work dir', type=str) |
| parser.add_argument('--track', help='track name, for different challenge', type=str, default='cfat') |
| args = parser.parse_args() |
| handler = ArcFaceORT(args.workdir) |
| err = handler.check(args.track) |
| print('err:', err) |
|
|