diff --git a/research/cv/PSPNet/README.md b/research/cv/PSPNet/README.md index 83bfac1c692ab149160554eff6f74e7dd473522e..1df328e46cc8b847b372b2dddce61dffd0ebab97 100644 --- a/research/cv/PSPNet/README.md +++ b/research/cv/PSPNet/README.md @@ -1,5 +1,6 @@ # Contents +- [Contents](#contents) - [PSPNet Description](#PSPNet-description) - [Model Architecture](#PSPNet-Architeture) - [Dataset](#PSPNet-Dataset) @@ -16,10 +17,11 @@ - [Evaluation Result](#evaluation-resul) - [Export MindIR](#export-mindir) - [310 infer](#310-inference) + - [ONNX CPU infer](#onnx-cpu-infer) - [Model Description](#model-description) - [Performance](#performance) - [Evaluation Performance](#evaluation-performance) - - [Inference Performance](#inference-performance) + - [Distributed Training Performance](#distributed-training-performance) - [Description of Random Situation](#description-of-random-situation) - [ModelZoo Homepage](#modelzoo-homepage) @@ -36,6 +38,8 @@ The pyramid pooling module fuses features under four different pyramid scales.Fo # [Dataset](#Content) - [Semantic Boundaries Dataset](http://home.bharathh.info/pubs/codes/SBD/download.html) + +- [PASCAL VOC 2012 Website](http://host.robots.ox.ac.uk/pascal/VOC/voc2012) - It contains 11,357 finely annotated images split into training and testing sets with 8,498 and 2,857 images respectively. - The path formats in train.txt and val.txt are partial. And the mat file in the cls needs to be converted to image. You can run preprocess_dataset.py to convert the mat file and generate train_list.txt and val_list.txt. As follow锛� @@ -112,9 +116,10 @@ Datasets: attributes (names and colors) are needed, and please download as follo 鈹� 鈹斺攢鈹€ voc2012_pspnet50.yaml 鈹溾攢鈹€ src # PSPNet 鈹� 鈹溾攢鈹€ dataset # data processing -鈹� 鈹� 鈹溾攢鈹€ dataset.py +鈹� 鈹� 鈹溾攢鈹€ pt_dataset.py 鈹� 鈹� 鈹溾攢鈹€ create_data_txt.py # generate train_list.txt and val_list.txt -鈹� 鈹� 鈹斺攢鈹€ transform.py +鈹� 鈹� 鈹溾攢鈹€ create_voc_list.py # generate train_list.txt and val_list.txt +鈹� 鈹� 鈹斺攢鈹€ pt_transform.py 鈹� 鈹溾攢鈹€ model # models for training and test 鈹� 鈹� 鈹溾攢鈹€ PSPNet.py 鈹� 鈹� 鈹溾攢鈹€ resnet.py @@ -130,6 +135,7 @@ Datasets: attributes (names and colors) are needed, and please download as follo 鈹� 鈹溾攢鈹€ run_distribute_train_ascend.sh # multi cards distributed training in ascend 鈹� 鈹溾攢鈹€ run_train1p_ascend.sh # 1P training in ascend 鈹� 鈹溾攢鈹€ run_infer_310.sh # 310 infer +鈹� 鈹溾攢鈹€ run_eval_onnx_cpu.sh # ONNX infer 鈹� 鈹斺攢鈹€ run_eval.sh # validation script 鈹斺攢鈹€ train.py # The training python file for ADE20K/VOC2012 ``` @@ -208,10 +214,18 @@ epoch time: 428776.845 ms, per step time: 403.365 ms Check the checkpoint path in config/ade20k_pspnet50.yaml and config/voc2012_pspnet50.yaml used for evaluation before running the following command. +#### Evalueation on gpu + ```shell bash run_eval.sh [YAML_PATH] [DEVICE_ID] ``` +#### Evalueation on cpu + +```shell + bash run_eval.sh [YAML_PATH] cpu +``` + ### Evaluation Result The results at eval.log were as follows: @@ -221,13 +235,21 @@ ADE20K:mIoU/mAcc/allAcc 0.4164/0.5319/0.7996. VOC2012:mIoU/mAcc/allAcc 0.7380/0.8229/0.9293. ```` -## [Export MindIR](#contents) +## [Export](#contents) + +### Export MINDIR ```shell python export.py --yaml_path [YAML_PTAH] --ckpt_file [CKPT_PATH] ``` -The ckpt_file parameter is required, +### Export ONNX + +```shell +python export.py --yaml_path [YAML_PTAH] --ckpt_file [CKPT_PATH] --file_format ONNX +``` + +The ckpt_file parameter and yaml_path are required. ## 310 infer @@ -237,6 +259,14 @@ The ckpt_file parameter is required, bash run_infer_310.sh [MINDIR PTAH [YAML PTAH] [DATA PATH] [DEVICE ID] ``` +## ONNX CPU infer + +- Note: Before executing ONNX CPU infer, please export onnx model first. + +```shell + bash PSPNet/scripts/run_eval_onnx_cpu.sh PSPNet/config/voc2012_pspnet50.yaml +``` + # [Model Description](#Content) ## Performance diff --git a/research/cv/PSPNet/config/ade20k_pspnet50.yaml b/research/cv/PSPNet/config/ade20k_pspnet50.yaml index 05a4a6c31b1cd0584b8702d63544ebe2bb7bff09..814b7c74fea72f81132e8f30c41311a5e9e3cbaa 100644 --- a/research/cv/PSPNet/config/ade20k_pspnet50.yaml +++ b/research/cv/PSPNet/config/ade20k_pspnet50.yaml @@ -51,3 +51,7 @@ TEST: result_path: ./result/ade/ color_txt: ./ade20k/ade20k_colors.txt name_txt: ./ade20k/ade20k_names.txt + +ONNX_INFER: + onnx_path: /home/mindspore/pspnet/PSPNet/PSPNet.onnx + device_target: cpu \ No newline at end of file diff --git a/research/cv/PSPNet/config/voc2012_pspnet50.yaml b/research/cv/PSPNet/config/voc2012_pspnet50.yaml index 422a5078d701088b20eb8428d7ca14b9e5f6ff91..5d1ff1b63f84a48d5b8a444d1634b558e7710de1 100644 --- a/research/cv/PSPNet/config/voc2012_pspnet50.yaml +++ b/research/cv/PSPNet/config/voc2012_pspnet50.yaml @@ -51,3 +51,8 @@ TEST: result_path: ./result/voc/ color_txt: ./voc2012/voc2012_colors.txt name_txt: ./voc2012/voc2012_names.txt + + +ONNX_INFER: + onnx_path: /home/mindspore/pspnet/PSPNet/PSPNet.onnx + device_target: cpu diff --git a/research/cv/PSPNet/eval_cpu.py b/research/cv/PSPNet/eval_cpu.py new file mode 100644 index 0000000000000000000000000000000000000000..ed15d8d46486e3d1d28bd77adc686b6e625b9743 --- /dev/null +++ b/research/cv/PSPNet/eval_cpu.py @@ -0,0 +1,283 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +""" Evaluate on CPU Platform. """ +import os +import time +import logging +import argparse +import cv2 +import numpy +from src.dataset import pt_dataset, pt_transform +import src.utils.functions_args as fa +from src.utils.p_util import AverageMeter as AM +from src.utils.p_util import intersectionAndUnion, check_makedirs, colorize +import mindspore.numpy as np +import mindspore +from mindspore import Tensor +import mindspore.dataset as ds +from mindspore import context +import mindspore.nn as nn +import mindspore.ops as ops +from mindspore.train.serialization import load_param_into_net, load_checkpoint + + +def get_log(): + """ Eval Logger """ + logger_name = "eval-logger" + log = logging.getLogger(logger_name) + log.setLevel(logging.INFO) + handler = logging.StreamHandler() + fmt = "[%(asctime)s %(levelname)s %(filename)s line %(lineno)d %(process)d] %(message)s" + handler.setFormatter(logging.Formatter(fmt)) + log.addHandler(handler) + return log + + +def main(): + """ The main function of the evaluate process """ + logger.info("=> creating PSPNet model ...") + logger.info("Class number: %s", args.classes) + + value_scale = 255 + mean = [0.485, 0.456, 0.406] + mean = [item * value_scale for item in mean] + std = [0.229, 0.224, 0.225] + std = [item * value_scale for item in std] + gray_folder = os.path.join(args.result_path, 'gray') + color_folder = os.path.join(args.result_path, 'color') + test_transform = pt_transform.Compose([pt_transform.Normalize(mean=mean, std=std, is_train=False)]) + test_data = pt_dataset.SemData( + split='val', data_root=args.data_root, + data_list=args.val_list, + transform=test_transform) + test_loader = ds.GeneratorDataset(test_data, column_names=["data", "label"], shuffle=False) + test_loader.batch(1) + colors = numpy.loadtxt(args.color_txt).astype('uint8') + names = [line.rstrip('\n') for line in open(args.name_txt)] + + from src.model import pspnet + PSPNet = pspnet.PSPNet( + feature_size=args.feature_size, + num_classes=args.classes, + backbone=args.backbone, + pretrained=False, + pretrained_path="", + aux_branch=False, + deep_base=True + ) + ms_checkpoint = load_checkpoint(args.ckpt) + load_param_into_net(PSPNet, ms_checkpoint, strict_load=True) + PSPNet.set_train(False) + test_model(test_loader, test_data.data_list, PSPNet, args.classes, mean, std, args.base_size, args.test_h, + args.test_w, args.scales, gray_folder, color_folder, colors) + if args.split != 'test': + calculate_acc(test_data.data_list, gray_folder, args.classes, names, colors) + + +def net_process(model, image, mean, std=None, flip=True): + """ Give the input to the model""" + transpose = ops.Transpose() + input_ = transpose(image, (2, 0, 1)) # (473, 473, 3) -> (3, 473, 473) + mean = np.array(mean) + std = np.array(std) + if std is None: + input_ = input_ - mean[:, None, None] + else: + input_ = (input_ - mean[:, None, None]) / std[:, None, None] + + expand_dim = ops.ExpandDims() + input_ = expand_dim(input_, 0) + if flip: + flip_input = np.flip(input_, axis=3) + concat = ops.Concat(axis=0) + input_ = concat((input_, flip_input)) + model.set_train(False) + output = model(input_) + _, _, h_i, w_i = input_.shape + _, _, h_o, w_o = output.shape + if (h_o != h_i) or (w_o != w_i): + bi_linear = nn.ResizeBilinear() + output = bi_linear(output, size=(h_i, w_i), align_corners=True) + softmax = nn.Softmax(axis=1) + output = softmax(output) + if flip: + output = (output[0] + np.flip(output[1], axis=2)) / 2 + else: + output = output[0] + output = transpose(output, (1, 2, 0)) # Tensor + output = output.asnumpy() + return output + + +def calculate_acc(data_list, pred_folder, classes, names, colors): + """ Calculation evaluating indicator """ + colors = colors.tolist() + overlap_meter = AM() + union_meter = AM() + target_meter = AM() + length = len(data_list) + for index, (image_path, target_path) in enumerate(data_list): + image_name = image_path.split('/')[-1].split('.')[0] + pred = cv2.imread(os.path.join(pred_folder, image_name + '.png'), cv2.IMREAD_GRAYSCALE) + if args.prefix == 'voc': + target = cv2.imread(target_path) + target = cv2.cvtColor(target, cv2.COLOR_BGR2RGB) + anno_label = convert(target, colors) + + if args.prefix == 'ADE': + anno_label = cv2.imread(target_path, cv2.IMREAD_GRAYSCALE) + anno_label -= 1 + overlap, union, target = intersectionAndUnion(pred, anno_label, classes) + overlap_meter.update(overlap) + union_meter.update(union) + target_meter.update(target) + acc = sum(overlap_meter.val) / (sum(target_meter.val) + 1e-10) + logger.info( + 'Evaluating {0}/{1} on image {2}, accuracy {3:.4f}.'.format(index + 1, length, image_name + '.png', acc)) + iou_class = overlap_meter.sum / (union_meter.sum + 1e-10) + accuracy_class = overlap_meter.sum / (target_meter.sum + 1e-10) + mIoU = numpy.mean(iou_class) + mAcc = numpy.mean(accuracy_class) + allAcc = sum(overlap_meter.sum) / (sum(target_meter.sum) + 1e-10) + + logger.info('Eval result: mIoU/mAcc/allAcc {:.4f}/{:.4f}/{:.4f}.'.format(mIoU, mAcc, allAcc)) + for i in range(classes): + logger.info('Class_{} result: iou/accuracy {:.4f}/{:.4f}, name: {}.'.format(i, iou_class[i], accuracy_class[i], + names[i])) + + +def scale_proc(model, image, classes, crop_h, crop_w, h, w, mean, std=None, stride_rate=2 / 3): + """ Process input size """ + ori_h, ori_w, _ = image.shape + pad_h = max(crop_h - ori_h, 0) + pad_w = max(crop_w - ori_w, 0) + pad_h_half = int(pad_h / 2) + pad_w_half = int(pad_w / 2) + if pad_h > 0 or pad_w > 0: + image = cv2.copyMakeBorder(image, pad_h_half, pad_h - pad_h_half, pad_w_half, pad_w - pad_w_half, + cv2.BORDER_CONSTANT, value=mean) + + new_h, new_w, _ = image.shape + image = Tensor.from_numpy(image) + stride_h = int(numpy.ceil(crop_h * stride_rate)) + stride_w = int(numpy.ceil(crop_w * stride_rate)) + g_h = int(numpy.ceil(float(new_h - crop_h) / stride_h) + 1) + g_w = int(numpy.ceil(float(new_w - crop_w) / stride_w) + 1) + pred_crop = numpy.zeros((new_h, new_w, classes), dtype=float) + count_crop = numpy.zeros((new_h, new_w), dtype=float) + for idh in range(0, g_h): + for idw in range(0, g_w): + s_h = idh * stride_h + e_h = min(s_h + crop_h, new_h) + s_h = e_h - crop_h + s_w = idw * stride_w + e_w = min(s_w + crop_w, new_w) + s_w = e_w - crop_w + image_crop = image[s_h:e_h, s_w:e_w].copy() + count_crop[s_h:e_h, s_w:e_w] += 1 + pred_crop[s_h:e_h, s_w:e_w, :] += net_process(model, image_crop, mean, std) + pred_crop /= numpy.expand_dims(count_crop, 2) + pred_crop = pred_crop[pad_h_half:pad_h_half + ori_h, pad_w_half:pad_w_half + ori_w] + pred = cv2.resize(pred_crop, (w, h), interpolation=cv2.INTER_LINEAR) + return pred + + +def get_parser(): + """ + Read parameter file + -> for ADE20k: ./config/ade20k_pspnet50.yaml + -> for voc2012: ./config/voc2012_pspnet50.yaml + """ + parser = argparse.ArgumentParser(description='MindSpore Semantic Segmentation') + parser.add_argument('--config', type=str, required=True, default='./config/ade20k_pspnet50.yaml', + help='config file') + parser.add_argument('opts', help='see ./config/voc2012_pspnet50.yaml for all options', default=None, + nargs=argparse.REMAINDER) + args_ = parser.parse_args() + assert args_.config is not None + cfg = fa.load_cfg_from_cfg_file(args_.config) + if args_.opts is not None: + cfg = fa.merge_cfg_from_list(cfg, args_.opts) + return cfg + + +def test_model(data_iter, path_iter, model, classes, mean, std, origin_size, c_h, c_w, scales, gray_folder, + color_folder, colors): + """ Generate evaluate image """ + logger.info('>>>>>>>>>>>>>>>>Evaluation Start>>>>>>>>>>>>>>>>') + model.set_train(False) + batch_time = AM() + data_time = AM() + begin_time = time.time() + scales_num = len(scales) + img_num = len(path_iter) + for i, (input_, _) in enumerate(data_iter): + data_time.update(time.time() - begin_time) + input_ = input_.asnumpy() + image = numpy.transpose(input_, (1, 2, 0)) + h, w, _ = image.shape + pred = numpy.zeros((h, w, classes), dtype=float) + for ratio in scales: + long_size = round(ratio * origin_size) + new_h = long_size + new_w = new_h + if h < w: + new_h = round(long_size / float(w) * h) + else: + new_w = round(long_size / float(h) * w) + + new_image = cv2.resize(image, (new_w, new_h), interpolation=cv2.INTER_LINEAR) + pred = pred + scale_proc(model, new_image, classes, c_h, c_w, h, w, mean, std) + pred = pred / scales_num + pred = numpy.argmax(pred, axis=2) + batch_time.update(time.time() - begin_time) + begin_time = time.time() + if ((i + 1) % 10 == 0) or (i + 1 == img_num): + logger.info('Test: [infer {} images, {} images in total] ' + 'Data {data_time.val:.3f} ({data_time.avg:.3f}) ' + 'Batch {batch_time.val:.3f} ({batch_time.avg:.3f}).'.format(i + 1, img_num, + data_time=data_time, + batch_time=batch_time)) + check_makedirs(color_folder) + check_makedirs(gray_folder) + gray = numpy.uint8(pred) + image_path, _ = path_iter[i] + color = colorize(gray, colors) + image_name = image_path.split('/')[-1].split('.')[0] + cv2.imwrite(os.path.join(gray_folder, image_name + '.png'), gray) + color.save(os.path.join(color_folder, image_name + '.png')) + logger.info('<<<<<<<<<<<<<<<<< End Evaluation <<<<<<<<<<<<<<<<<') + + +def convert(label, colors): + """Convert classification ids in labels.""" + annotation = numpy.zeros((label.shape[0], label.shape[1])) + for i in range(len(label)): + for j in range(len(label[i])): + if colors.count(label[i][j].tolist()): + annotation[i][j] = colors.index(label[i][j].tolist()) + else: + annotation[i][j] = 0 + a = Tensor(annotation, dtype=mindspore.uint8) + annotation = a.asnumpy() + return annotation + + +if __name__ == '__main__': + cv2.ocl.setUseOpenCL(False) + context.set_context(mode=context.GRAPH_MODE, device_target="CPU", save_graphs=False) + args = get_parser() + logger = get_log() + main() diff --git a/research/cv/PSPNet/eval_onnx_cpu.py b/research/cv/PSPNet/eval_onnx_cpu.py new file mode 100644 index 0000000000000000000000000000000000000000..102d485247b000e39fc783608ef0c70ecb8ad912 --- /dev/null +++ b/research/cv/PSPNet/eval_onnx_cpu.py @@ -0,0 +1,270 @@ +# Copyright 2022 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +""" ONNX EVALUATE CPU""" +import os +import time +import logging +import argparse +import cv2 +import numpy +from src.dataset import pt_dataset +from src.dataset import pt_transform as trans +import src.utils.functions_args as fa +from src.utils.p_util import AverageMeter, intersectionAndUnion, check_makedirs, colorize +import mindspore.numpy as np +from mindspore import Tensor +import mindspore.dataset as ds +from mindspore import context +import mindspore.nn as nn +import mindspore.ops as ops +import onnxruntime + +cv2.ocl.setUseOpenCL(False) +context.set_context(mode=context.GRAPH_MODE, device_target="CPU", + save_graphs=False) + +def get_logger(): + """ logger """ + logger_name = "main-logger" + log = logging.getLogger(logger_name) + log.setLevel(logging.INFO) + handler = logging.StreamHandler() + fmt = "[%(asctime)s %(levelname)s %(filename)s line %(lineno)d %(process)d] %(message)s" + handler.setFormatter(logging.Formatter(fmt)) + log.addHandler(handler) + return log + +# Due to Onnx-gpu can not support PSPNet Inference on GPU platform, this function only provide CPUExecutionProvider. +def getonnxmodel(): + model = onnxruntime.InferenceSession(args.onnx_path, providers=['CPUExecutionProvider']) + return model + +def get_Config(): + config_parser = argparse.ArgumentParser(description='MindSpore Semantic Segmentation') + config_parser.add_argument('--config', type=str, required=True, help='config file') + config_parser.add_argument('opts', help='see ./src/config/voc2012_pspnet50.yaml for all options', default=None, + nargs=argparse.REMAINDER) + args_ = config_parser.parse_args() + assert args_.config is not None + cfg = fa.load_cfg_from_cfg_file(args_.config) + if args_.opts is not None: + cfg = fa.merge_cfg_from_list(cfg, args_.opts) + return cfg + +def main(): + """ The main function of the evaluate process """ + logger.info("=> Load PSPNet ...") + logger.info("%s: class num:%s", args.prefix, args.classes) + value_scale = 255 + m = [0.485, 0.456, 0.406] + m = [item * value_scale for item in m] + s = [0.229, 0.224, 0.225] + s = [item * value_scale for item in s] + gray_folder = os.path.join(args.result_path, 'gray') + color_folder = os.path.join(args.result_path, 'color') + + test_transform = trans.Compose([trans.Normalize(mean=m, std=s, is_train=False)]) + test_data = pt_dataset.SemData( + split='val', data_root=args.data_root, + data_list=args.val_list, + transform=test_transform) + + test_loader = ds.GeneratorDataset(test_data, column_names=["data", "label"], + shuffle=False) + test_loader.batch(1) + colors = numpy.loadtxt(args.color_txt).astype('uint8') + names = [line.rstrip('\n') for line in open(args.name_txt)] + model = getonnxmodel() + + test(test_loader, test_data.data_list, model, args.classes, m, s, args.base_size, args.test_h, + args.test_w, args.scales, gray_folder, color_folder, colors) + if args.split != 'test': + calculate_acc(test_data.data_list, gray_folder, args.classes, names, colors) + + +def net_process(model, image, mean, std=None, flip=False): + """ Give the input to the model""" + transpose = ops.Transpose() + input_ = transpose(image, (2, 0, 1)) # (473, 473, 3) -> (3, 473, 473) + mean = np.array(mean) + std = np.array(std) + if std is None: + input_ = input_ - mean[:, None, None] + else: + input_ = (input_ - mean[:, None, None]) / std[:, None, None] + + expand_dim = ops.ExpandDims() + input_ = expand_dim(input_, 0) + if flip: + flip_input = np.flip(input_, axis=3) + concat = ops.Concat(axis=0) + input_ = concat((input_, flip_input)) + input_ = input_.asnumpy() + inputs = {model.get_inputs()[0].name: input_} + output = model.run(None, inputs) + output = Tensor(output) + _, _, h_i, w_i = input_.shape + _, _, _, h_o, w_o = output.shape + if (h_o != h_i) or (w_o != w_i): + bi_linear = nn.ResizeBilinear() + output = bi_linear(output, size=(h_i, w_i), align_corners=True) + + softmax = nn.Softmax(axis=2) + output = softmax(output) + if flip: + output = (output[0] + np.flip(output[1], axis=2)) / 2 + else: + output = output[0][0] + output = transpose(output, (1, 2, 0)) # Tensor + output = output.asnumpy() + return output + + +def scale_proc(model, image, classes, crop_h, crop_w, h, w, mean, std=None, stride_rate=2 / 3): + """ Process input size """ + ori_h, ori_w, _ = image.shape + pad_h = max(crop_h - ori_h, 0) + pad_w = max(crop_w - ori_w, 0) + pad_h_half = int(pad_h / 2) + pad_w_half = int(pad_w / 2) + if pad_h > 0 or pad_w > 0: + image = cv2.copyMakeBorder(image, pad_h_half, pad_h - pad_h_half, pad_w_half, pad_w - pad_w_half, + cv2.BORDER_CONSTANT, value=mean) + + new_h, new_w, _ = image.shape + image = Tensor.from_numpy(image) + stride_h = int(numpy.ceil(crop_h * stride_rate)) + stride_w = int(numpy.ceil(crop_w * stride_rate)) + g_h = int(numpy.ceil(float(new_h - crop_h) / stride_h) + 1) + g_w = int(numpy.ceil(float(new_w - crop_w) / stride_w) + 1) + pred_crop = numpy.zeros((new_h, new_w, classes), dtype=float) + count_crop = numpy.zeros((new_h, new_w), dtype=float) + for idh in range(0, g_h): + for idw in range(0, g_w): + s_h = idh * stride_h + e_h = min(s_h + crop_h, new_h) + s_h = e_h - crop_h + s_w = idw * stride_w + e_w = min(s_w + crop_w, new_w) + s_w = e_w - crop_w + image_crop = image[s_h:e_h, s_w:e_w].copy() + count_crop[s_h:e_h, s_w:e_w] += 1 + pred_crop[s_h:e_h, s_w:e_w, :] += net_process(model, image_crop, mean, std) + pred_crop /= numpy.expand_dims(count_crop, 2) + pred_crop = pred_crop[pad_h_half:pad_h_half + ori_h, pad_w_half:pad_w_half + ori_w] + pred = cv2.resize(pred_crop, (w, h), interpolation=cv2.INTER_LINEAR) + return pred + + +def test(test_loader, data_list, model, classes, m, s, base_size, crop_h, crop_w, scales, gray_folder, + color_folder, colors): + """ Generate evaluate image """ + logger.info('>>>>>>>>>>>>>>>> Start Evaluation >>>>>>>>>>>>>>>>') + data_time = AverageMeter() + batch_time = AverageMeter() + end = time.time() + data_num = len(data_list) + scales_num = len(scales) + for index, (input_, _) in enumerate(test_loader): + data_time.update(time.time() - end) + input_ = input_.asnumpy() + image = numpy.transpose(input_, (1, 2, 0)) + height, weight, _ = image.shape + pred = numpy.zeros((height, weight, classes), dtype=float) + for ratio in scales: + long_size = round(ratio * base_size) + new_h = long_size + new_w = long_size + if height > weight: + new_w = round(long_size / float(height) * weight) + else: + new_h = round(long_size / float(weight) * height) + image_scale = cv2.resize(image, (new_w, new_h), interpolation=cv2.INTER_LINEAR) + pred += scale_proc(model, image_scale, classes, crop_h, crop_w, height, weight, m, s) + pred = pred / scales_num + pred = numpy.argmax(pred, axis=2) + batch_time.update(time.time() - end) + end = time.time() + if ((index + 1) % 10 == 0) or (index + 1 == data_num): + logger.info('Test: [{}/{}] ' + 'Data {data_time.val:.3f} ({data_time.avg:.3f}) ' + 'Batch {batch_time.val:.3f} ({batch_time.avg:.3f}).'.format(index + 1, data_num, + data_time=data_time, + batch_time=batch_time)) + check_makedirs(gray_folder) + check_makedirs(color_folder) + gray = numpy.uint8(pred) + color = colorize(gray, colors) + image_path, _ = data_list[index] + image_name = image_path.split('/')[-1].split('.')[0] + gray_img = os.path.join(gray_folder, image_name + '.png') + color_img = os.path.join(color_folder, image_name + '.png') + cv2.imwrite(gray_img, gray) + color.save(color_img) + logger.info('<<<<<<<<<<<<<<<<< End Evaluation <<<<<<<<<<<<<<<<<') + +def convert_label(label, colors): + """Convert classification ids in labels.""" + mask_map = numpy.zeros((label.shape[0], label.shape[1])) + for i in range(len(label)): + for j in range(len(label[i])): + if colors.count(label[i][j].tolist()): + mask_map[i][j] = colors.index(label[i][j].tolist()) + import mindspore + a = Tensor(mask_map, dtype=mindspore.uint8) + mask_map = a.asnumpy() + return mask_map + +def calculate_acc(data_list, pred_folder, classes, names, colors): + """ Calculation evaluating indicator """ + colors = colors.tolist() + overlap_meter = AverageMeter() + union_meter = AverageMeter() + target_meter = AverageMeter() + for i, (image_path, label_path) in enumerate(data_list): + image_name = image_path.split('/')[-1].split('.')[0] + pred = cv2.imread(os.path.join(pred_folder, image_name + '.png'), cv2.IMREAD_GRAYSCALE) + if args.prefix != "ADE": + target = cv2.imread(label_path) + target = cv2.cvtColor(target, cv2.COLOR_BGR2RGB) + anno_label = convert_label(target, colors) + + if args.prefix == 'ADE': + anno_label = cv2.imread(label_path, cv2.IMREAD_GRAYSCALE) + anno_label -= 1 + overlap, union, label = intersectionAndUnion(pred, anno_label, args.classes) + overlap_meter.update(overlap) + union_meter.update(union) + target_meter.update(label) + accuracy = sum(overlap_meter.val) / (sum(target_meter.val) + 1e-10) + logger.info( + 'Evaluating {0}/{1} on image {2}, accuracy {3:.4f}.'.format(i + 1, len(data_list), image_name + '.png', + accuracy)) + iou_class = overlap_meter.sum / (union_meter.sum + 1e-10) + accuracy_class = overlap_meter.sum / (target_meter.sum + 1e-10) + mIoU = numpy.mean(iou_class) + mAcc = numpy.mean(accuracy_class) + allAcc = sum(overlap_meter.sum) / (sum(target_meter.sum) + 1e-10) + + logger.info('Eval result: mIoU/mAcc/allAcc {:.4f}/{:.4f}/{:.4f}.'.format(mIoU, mAcc, allAcc)) + for i in range(classes): + logger.info('Class_{} result: iou/accuracy {:.4f}/{:.4f}, name: {}.'.format(i, iou_class[i], accuracy_class[i], + names[i])) + + +if __name__ == '__main__': + args = get_Config() + logger = get_logger() + main() diff --git a/research/cv/PSPNet/requirements.txt b/research/cv/PSPNet/requirements.txt index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..8a4af7891afd28f47a3a7b9d4df896425dbde7d0 100644 --- a/research/cv/PSPNet/requirements.txt +++ b/research/cv/PSPNet/requirements.txt @@ -0,0 +1,5 @@ +onnxruntime-gpu +numpy +opencv-python +argparse +mindspore-gpu \ No newline at end of file diff --git a/research/cv/PSPNet/scripts/run_eval.sh b/research/cv/PSPNet/scripts/run_eval.sh index 5574e853c89b367cdb40dcb5e65da5bbc6dbed9f..3aa613b28bdb828ad94e76f849478546f95e5582 100644 --- a/research/cv/PSPNet/scripts/run_eval.sh +++ b/research/cv/PSPNet/scripts/run_eval.sh @@ -18,7 +18,9 @@ if [ $# != 2 ] then echo "==============================================================================================================" echo "Usage: bash /PSPNet/scripts/run_eval.sh [YAML_PATH] [DEVICE_ID]" - echo "for example: bash PSPNet/scripts/run_eval.sh PSPNet/config/voc2012_pspnet50.yaml 0" + echo "Warning: before cpu infer, you need check device_target in config file." + echo "for gpu example: bash PSPNet/scripts/run_eval.sh PSPNet/config/voc2012_pspnet50.yaml 0" + echo "for cpu example: bash PSPNet/scripts/run_eval.sh PSPNet/config/voc2012_pspnet50.yaml cpu" echo "==============================================================================================================" exit 1 fi @@ -31,5 +33,11 @@ export RANK_ID=0 export DEVICE_ID=$2 echo "start evaluating for device $DEVICE_ID" env > env.log - -python3 eval.py --config="$YAML_PATH" > ./LOG/eval_log.txt 2>&1 & \ No newline at end of file +if [ "$2" == "cpu" ] +then + python3 eval_cpu.py --config="$YAML_PATH" > ./LOG/eval_log.txt 2>&1 & +fi +if [ "$2" != "cpu" ] +then + python3 eval.py --config="$YAML_PATH" > ./LOG/eval_log.txt 2>&1 & +fi diff --git a/research/cv/PSPNet/scripts/run_eval_onnx_cpu.sh b/research/cv/PSPNet/scripts/run_eval_onnx_cpu.sh new file mode 100644 index 0000000000000000000000000000000000000000..3e6442c923d017b9048f06383b1dc9b84850699f --- /dev/null +++ b/research/cv/PSPNet/scripts/run_eval_onnx_cpu.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# Copyright 2022 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ + +if [ $# != 1 ] +then + echo "==============================================================================================================" + echo "Usage: bash /PSPNet/scripts/run_eval_onnx_cpu.sh [YAML_PATH]" + echo "for example: bash PSPNet/scripts/run_eval_onnx_cpu.sh PSPNet/config/voc2012_pspnet50.yaml" + echo "==============================================================================================================" + exit 1 +fi + +rm -rf LOG +mkdir ./LOG +export YAML_PATH=$1 +export RANK_SIZE=1 +export RANK_ID=0 +echo "start evaluating on CPU" +env > env.log + +python3 eval_onnx_cpu.py --config="$YAML_PATH" > ./LOG/eval_onnx.txt 2>&1 & diff --git a/research/cv/PSPNet/src/dataset/create_voc_list.py b/research/cv/PSPNet/src/dataset/create_voc_list.py new file mode 100644 index 0000000000000000000000000000000000000000..a8a00171de8c154f2161ef4f565f6caef1346b2c --- /dev/null +++ b/research/cv/PSPNet/src/dataset/create_voc_list.py @@ -0,0 +1,47 @@ +# Copyright 2022 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ + + +from argparse import ArgumentParser + + +def parse_args(): + """ + parse args + """ + parser = ArgumentParser(description="generate train_list.txt or val_list.txt") + parser.add_argument("--dataset_list_txt", type=str, default="", help="path of val.txt in VOC2012") + parser.add_argument("--image_prefix", type=str, default="JPEGImages", + help="the relative path in the data_root, until to the level of images.jpg") + parser.add_argument("--mask_prefix", type=str, default="SegmentationClass", + help="the relative path in the data_root, until to the level of mask.jpg") + parser.add_argument("--output_txt", type=str, default="voc2012_val.txt", help="name of output txt") + + args = parser.parse_args() + return args + +def main(): + args = parse_args() + with open(args.dataset_list_txt, 'r') as f: + with open(args.output_txt, 'w') as fw: + for i in f: + image_path = args.image_prefix + "/" + i[:-1] + ".jpg " + label_path = args.mask_prefix + "/" + i[:-1] + ".png\n" + line = image_path + label_path + fw.writelines(line) + fw.close() + f.close() + +main()