Spaces:
Build error
Build error
| from __future__ import division | |
| import argparse | |
| import datetime | |
| import glob | |
| import os | |
| import os.path as osp | |
| import sys | |
| import cv2 | |
| import numpy as np | |
| import onnx | |
| import onnxruntime | |
| from insightface.data import get_image | |
| from onnx import numpy_helper | |
| class ArcFaceORT: | |
| def __init__(self, model_path, cpu=False): | |
| self.model_path = model_path | |
| # providers = None will use available provider, for onnxruntime-gpu it will be "CUDAExecutionProvider" | |
| self.providers = ["CPUExecutionProvider"] if cpu else None | |
| # input_size is (w,h), return error message, return None if success | |
| def check(self, track="cfat", test_img=None): | |
| # default is cfat | |
| 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): | |
| # return "input_shape[0] should be str to support batch-inference" | |
| 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]) | |
| # print('image_size:', self.image_size) | |
| input_name = input_cfg.name | |
| outputs = session.get_outputs() | |
| output_names = [] | |
| for o in outputs: | |
| output_names.append(o.name) | |
| # print(o.name, o.shape) | |
| 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 | |
| # print(self.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") | |
| # mxnet arcface model | |
| 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="") | |
| # general | |
| 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) | |