Download deploy/eval_onnx.py from gatilin/WeDetect-Ref-4B-Base: direct link, hf CLI and curl.
- Browser
- Download file 9.55 kB
-
https://huggingface.co/gatilin/WeDetect-Ref-4B-Base/resolve/main/deploy/eval_onnx.py
- Command line
-
hf download hf://gatilin/WeDetect-Ref-4B-Base/deploy/eval_onnx.py
-
curl -L -o eval_onnx.py https://huggingface.co/gatilin/WeDetect-Ref-4B-Base/resolve/main/deploy/eval_onnx.py
9.55 kB
| # Copyright (c) Tencent Inc. All rights reserved. | |
| """Validate the exported WeDetect ONNX model(s) on COCO ``instances_val2017``. | |
| This script is the ONNX counterpart of ``test_coco_pytorch.py``. It runs the | |
| network through ONNXRuntime instead of PyTorch but reuses *exactly the same* | |
| pre-/post-processing (letterbox + RGB/255, DFL is already baked into the ONNX | |
| graph, then score filtering + class-aware NMS + letterbox rescale) and the same | |
| Chinese COCO vocabulary / id mapping, so the reported mAP is directly | |
| comparable to ``test_coco_pytorch.py``. | |
| Two layouts are supported (``--mode``), matching ``export_onnx.py``: | |
| * ``dual`` : two ONNX files. The language tower runs once (the vocabulary is | |
| fixed) and the vision tower runs per image. | |
| --vision-onnx ... --language-onnx ... | |
| * ``whole`` : a single ONNX file; the text tower is recomputed per image. | |
| --onnx model_whole.onnx | |
| Usage (dual, recommended) | |
| ------------------------- | |
| python deploy/eval_onnx.py --mode dual \ | |
| --language-model xlm-roberta-base \ | |
| --vision-onnx deploy/onnx_models/wedetect_base_vision.onnx \ | |
| --language-onnx deploy/onnx_models/wedetect_base_language.onnx \ | |
| --coco-ann /path/to/instances_val2017.json \ | |
| --coco-img /path/to/val2017 | |
| Usage (whole) | |
| ------------- | |
| python deploy/eval_onnx.py --mode whole \ | |
| --language-model xlm-roberta-base \ | |
| --onnx deploy/onnx_models/wedetect_base_whole.onnx \ | |
| --coco-ann /path/to/instances_val2017.json \ | |
| --coco-img /path/to/val2017 | |
| """ | |
| import argparse | |
| import os.path as osp | |
| import sys | |
| import numpy as np | |
| import torch | |
| import torchvision | |
| import tqdm | |
| from PIL import Image | |
| from pycocotools.coco import COCO | |
| from pycocotools.cocoeval import COCOeval | |
| import onnxruntime as ort | |
| from transformers import AutoTokenizer | |
| # Reuse the standalone pre-/post-processing and the COCO vocabulary so the | |
| # ONNX evaluation matches ``test_coco_pytorch.py`` exactly. | |
| sys.path.insert(0, osp.dirname(osp.abspath(__file__))) | |
| from test_coco_pytorch import ( # noqa: E402 | |
| COCO_CLASSES_ZH, | |
| COCO_ID_MAP, | |
| filter_scores_and_topk, | |
| letterbox, | |
| ) | |
| # --------------------------------------------------------------------------- # | |
| # Pre- / post-processing (mirrors test_coco_pytorch) # | |
| # --------------------------------------------------------------------------- # | |
| def preprocess_image(img_path, img_size): | |
| """Load an image, letterbox it and return the ONNX input + recovery info. | |
| Returns: | |
| image : float32 [1, 3, H, W], RGB and divided by 255. | |
| ratio : letterbox scale ratio. | |
| offset : (dw/2, dh/2) padding to undo before rescaling. | |
| ori_shape : (height, width) of the original image. | |
| """ | |
| img = Image.open(img_path).convert('RGB') | |
| width, height = img.size | |
| img_lb, ratio, offset = letterbox(img, (img_size, img_size)) | |
| arr = np.array(img_lb).astype(np.float32).transpose(2, 0, 1) / 255.0 | |
| return arr[None], ratio, offset, (height, width) | |
| def postprocess(bboxes, scores, ratio, offset, ori_shape, | |
| score_thr, nms_iou, pre_nms_topk, post_nms_topk): | |
| """ONNX outputs (letterbox space) -> boxes/scores/labels (original image). | |
| bboxes : [N, 4] decoded xyxy in the letter-boxed coordinate space. | |
| scores : [N, K] per-class confidence (already sigmoid). | |
| """ | |
| bboxes = torch.from_numpy(bboxes).float() | |
| scores = torch.from_numpy(scores).float() | |
| scores, labels, keep_idxs, _ = filter_scores_and_topk( | |
| scores, score_thr, pre_nms_topk) | |
| bboxes = bboxes[keep_idxs] | |
| keep = torchvision.ops.batched_nms( | |
| bboxes.float(), scores.float(), labels, nms_iou)[:post_nms_topk] | |
| bboxes = bboxes[keep] | |
| scores = scores[keep] | |
| labels = labels[keep] | |
| # undo letterbox padding + resize, then clamp to the original image. | |
| off_x, off_y = offset | |
| bboxes -= bboxes.new_tensor([off_x, off_y, off_x, off_y]) | |
| bboxes /= ratio | |
| ori_h, ori_w = ori_shape | |
| bboxes[:, 0::2].clamp_(0, ori_w) | |
| bboxes[:, 1::2].clamp_(0, ori_h) | |
| return bboxes, scores, labels | |
| # --------------------------------------------------------------------------- # | |
| # ONNXRuntime helpers # | |
| # --------------------------------------------------------------------------- # | |
| def make_session(path, device): | |
| providers = ['CUDAExecutionProvider', 'CPUExecutionProvider'] \ | |
| if device == 'cuda' else ['CPUExecutionProvider'] | |
| return ort.InferenceSession(path, providers=providers) | |
| def tokenise(language_model, class_names): | |
| tokenizer = AutoTokenizer.from_pretrained(language_model) | |
| tokens = tokenizer(text=class_names, return_tensors='np', padding=True) | |
| input_ids = tokens['input_ids'].astype(np.int64) | |
| attention_mask = tokens['attention_mask'].astype(np.int64) | |
| return input_ids, attention_mask | |
| # --------------------------------------------------------------------------- # | |
| # Evaluation # | |
| # --------------------------------------------------------------------------- # | |
| def evaluate(args): | |
| class_names = COCO_CLASSES_ZH | |
| input_ids, attention_mask = tokenise(args.language_model, class_names) | |
| print(f'[text] {len(class_names)} classes tokenised, ' | |
| f'seq_len={input_ids.shape[1]}') | |
| # ---- ONNX sessions --------------------------------------------------- # | |
| txt_feats = None | |
| if args.mode == 'whole': | |
| assert args.onnx, '--onnx is required for mode=whole' | |
| whole_sess = make_session(args.onnx, args.device) | |
| else: | |
| assert args.vision_onnx and args.language_onnx, \ | |
| '--vision-onnx and --language-onnx are required for mode=dual' | |
| vision_sess = make_session(args.vision_onnx, args.device) | |
| language_sess = make_session(args.language_onnx, args.device) | |
| txt_feats = language_sess.run( | |
| ['txt_feats'], | |
| {'input_ids': input_ids, 'attention_mask': attention_mask})[0] | |
| txt_feats = txt_feats.astype(np.float32) | |
| print(f'[text] txt_feats shape = {txt_feats.shape}') | |
| # ---- iterate over COCO val ------------------------------------------- # | |
| coco_gt = COCO(args.coco_ann) | |
| img_ids = coco_gt.getImgIds() | |
| if args.num_images > 0: | |
| img_ids = img_ids[:args.num_images] | |
| results = [] | |
| for img_id in tqdm.tqdm(img_ids): | |
| img_info = coco_gt.loadImgs(img_id)[0] | |
| img_path = osp.join(args.coco_img, img_info['file_name']) | |
| image, ratio, offset, ori_shape = preprocess_image( | |
| img_path, args.img_size) | |
| if args.mode == 'whole': | |
| bboxes, scores = whole_sess.run( | |
| ['bboxes', 'scores'], | |
| {'image': image, | |
| 'input_ids': input_ids, | |
| 'attention_mask': attention_mask}) | |
| else: | |
| bboxes, scores = vision_sess.run( | |
| ['bboxes', 'scores'], | |
| {'image': image, 'txt_feats': txt_feats}) | |
| boxes, sc, labels = postprocess( | |
| bboxes[0], scores[0], ratio, offset, ori_shape, | |
| args.score_thr, args.nms_iou, args.pre_nms_topk, | |
| args.post_nms_topk) | |
| for box, score, label in zip(boxes, sc, labels): | |
| xmin, ymin, xmax, ymax = box.cpu().numpy() | |
| results.append({ | |
| 'image_id': img_id, | |
| 'category_id': COCO_ID_MAP[label.item()], | |
| 'bbox': [float(xmin), float(ymin), | |
| float(xmax - xmin), float(ymax - ymin)], | |
| 'score': float(score.item()), | |
| }) | |
| print('\nComputing metrics ...') | |
| coco_dt = coco_gt.loadRes(results) | |
| coco_eval = COCOeval(coco_gt, coco_dt, 'bbox') | |
| if args.num_images > 0: | |
| coco_eval.params.imgIds = img_ids | |
| coco_eval.evaluate() | |
| coco_eval.accumulate() | |
| coco_eval.summarize() | |
| return coco_eval | |
| def parse_args(): | |
| parser = argparse.ArgumentParser(description='Evaluate WeDetect ONNX') | |
| parser.add_argument('--mode', choices=['whole', 'dual'], default='dual') | |
| parser.add_argument('--onnx', help='whole-model ONNX (mode=whole)') | |
| parser.add_argument('--vision-onnx', help='vision ONNX (mode=dual)') | |
| parser.add_argument('--language-onnx', help='language ONNX (mode=dual)') | |
| parser.add_argument('--language-model', required=True, | |
| help='path/name of the XLM-RoBERTa model directory ' | |
| '(only the tokenizer is used here)') | |
| parser.add_argument('--coco-ann', required=True, | |
| help='path to instances_val2017.json') | |
| parser.add_argument('--coco-img', required=True, | |
| help='path to the val2017 image directory') | |
| parser.add_argument('--img-size', type=int, default=640, | |
| help='must match the size used at export time') | |
| parser.add_argument('--num-images', type=int, default=-1, | |
| help='limit number of evaluated images (-1 = all)') | |
| parser.add_argument('--device', choices=['cpu', 'cuda'], default='cpu') | |
| parser.add_argument('--score-thr', type=float, default=0.001) | |
| parser.add_argument('--nms-iou', type=float, default=0.7) | |
| parser.add_argument('--pre-nms-topk', type=int, default=30000) | |
| parser.add_argument('--post-nms-topk', type=int, default=300) | |
| return parser.parse_args() | |
| def main(): | |
| args = parse_args() | |
| evaluate(args) | |
| if __name__ == '__main__': | |
| main() | |