Spaces:
Sleeping
Sleeping
# Copyright (c) OpenMMLab. All rights reserved. | |
"""This script is in the experimental verification stage and cannot be | |
guaranteed to be completely correct. Currently Grad-based CAM and Grad-free CAM | |
are supported. | |
The target detection task is different from the classification task. It not | |
only includes the AM map of the category, but also includes information such as | |
bbox and mask, so this script is named bboxam. | |
""" | |
import argparse | |
import os.path | |
import warnings | |
from functools import partial | |
import cv2 | |
import mmcv | |
from mmengine import Config, DictAction, MessageHub | |
from mmengine.utils import ProgressBar | |
try: | |
from pytorch_grad_cam import AblationCAM, EigenCAM | |
except ImportError: | |
raise ImportError('Please run `pip install "grad-cam"` to install ' | |
'pytorch_grad_cam package.') | |
from mmyolo.utils.boxam_utils import (BoxAMDetectorVisualizer, | |
BoxAMDetectorWrapper, DetAblationLayer, | |
DetBoxScoreTarget, GradCAM, | |
GradCAMPlusPlus, reshape_transform) | |
from mmyolo.utils.misc import get_file_list | |
GRAD_FREE_METHOD_MAP = { | |
'ablationcam': AblationCAM, | |
'eigencam': EigenCAM, | |
# 'scorecam': ScoreCAM, # consumes too much memory | |
} | |
GRAD_BASED_METHOD_MAP = {'gradcam': GradCAM, 'gradcam++': GradCAMPlusPlus} | |
ALL_SUPPORT_METHODS = list(GRAD_FREE_METHOD_MAP.keys() | |
| GRAD_BASED_METHOD_MAP.keys()) | |
IGNORE_LOSS_PARAMS = { | |
'yolov5': ['loss_obj'], | |
'yolov6': ['loss_cls'], | |
'yolox': ['loss_obj'], | |
'rtmdet': ['loss_cls'], | |
'yolov7': ['loss_obj'], | |
'yolov8': ['loss_cls'], | |
'ppyoloe': ['loss_cls'], | |
} | |
# This parameter is required in some algorithms | |
# for calculating Loss | |
message_hub = MessageHub.get_current_instance() | |
message_hub.runtime_info['epoch'] = 0 | |
def parse_args(): | |
parser = argparse.ArgumentParser(description='Visualize Box AM') | |
parser.add_argument( | |
'img', help='Image path, include image file, dir and URL.') | |
parser.add_argument('config', help='Config file') | |
parser.add_argument('checkpoint', help='Checkpoint file') | |
parser.add_argument( | |
'--method', | |
default='gradcam', | |
choices=ALL_SUPPORT_METHODS, | |
help='Type of method to use, supports ' | |
f'{", ".join(ALL_SUPPORT_METHODS)}.') | |
parser.add_argument( | |
'--target-layers', | |
default=['neck.out_layers[2]'], | |
nargs='+', | |
type=str, | |
help='The target layers to get Box AM, if not set, the tool will ' | |
'specify the neck.out_layers[2]') | |
parser.add_argument( | |
'--out-dir', default='./output', help='Path to output file') | |
parser.add_argument( | |
'--show', action='store_true', help='Show the CAM results') | |
parser.add_argument( | |
'--device', default='cuda:0', help='Device used for inference') | |
parser.add_argument( | |
'--score-thr', type=float, default=0.3, help='Bbox score threshold') | |
parser.add_argument( | |
'--topk', | |
type=int, | |
default=-1, | |
help='Select topk predict resutls to show. -1 are mean all.') | |
parser.add_argument( | |
'--max-shape', | |
nargs='+', | |
type=int, | |
default=-1, | |
help='max shapes. Its purpose is to save GPU memory. ' | |
'The activation map is scaled and then evaluated. ' | |
'If set to -1, it means no scaling.') | |
parser.add_argument( | |
'--preview-model', | |
default=False, | |
action='store_true', | |
help='To preview all the model layers') | |
parser.add_argument( | |
'--norm-in-bbox', action='store_true', help='Norm in bbox of am image') | |
parser.add_argument( | |
'--cfg-options', | |
nargs='+', | |
action=DictAction, | |
help='override some settings in the used config, the key-value pair ' | |
'in xxx=yyy format will be merged into config file. If the value to ' | |
'be overwritten is a list, it should be like key="[a,b]" or key=a,b ' | |
'It also allows nested list/tuple values, e.g. key="[(a,b),(c,d)]" ' | |
'Note that the quotation marks are necessary and that no white space ' | |
'is allowed.') | |
# Only used by AblationCAM | |
parser.add_argument( | |
'--batch-size', | |
type=int, | |
default=1, | |
help='batch of inference of AblationCAM') | |
parser.add_argument( | |
'--ratio-channels-to-ablate', | |
type=int, | |
default=0.5, | |
help='Making it much faster of AblationCAM. ' | |
'The parameter controls how many channels should be ablated') | |
args = parser.parse_args() | |
return args | |
def init_detector_and_visualizer(args, cfg): | |
max_shape = args.max_shape | |
if not isinstance(max_shape, list): | |
max_shape = [args.max_shape] | |
assert len(max_shape) == 1 or len(max_shape) == 2 | |
model_wrapper = BoxAMDetectorWrapper( | |
cfg, args.checkpoint, args.score_thr, device=args.device) | |
if args.preview_model: | |
print(model_wrapper.detector) | |
print('\n Please remove `--preview-model` to get the BoxAM.') | |
return None, None | |
target_layers = [] | |
for target_layer in args.target_layers: | |
try: | |
target_layers.append( | |
eval(f'model_wrapper.detector.{target_layer}')) | |
except Exception as e: | |
print(model_wrapper.detector) | |
raise RuntimeError('layer does not exist', e) | |
ablationcam_extra_params = { | |
'batch_size': args.batch_size, | |
'ablation_layer': DetAblationLayer(), | |
'ratio_channels_to_ablate': args.ratio_channels_to_ablate | |
} | |
if args.method in GRAD_BASED_METHOD_MAP: | |
method_class = GRAD_BASED_METHOD_MAP[args.method] | |
is_need_grad = True | |
else: | |
method_class = GRAD_FREE_METHOD_MAP[args.method] | |
is_need_grad = False | |
boxam_detector_visualizer = BoxAMDetectorVisualizer( | |
method_class, | |
model_wrapper, | |
target_layers, | |
reshape_transform=partial( | |
reshape_transform, max_shape=max_shape, is_need_grad=is_need_grad), | |
is_need_grad=is_need_grad, | |
extra_params=ablationcam_extra_params) | |
return model_wrapper, boxam_detector_visualizer | |
def main(): | |
args = parse_args() | |
# hard code | |
ignore_loss_params = None | |
for param_keys in IGNORE_LOSS_PARAMS: | |
if param_keys in args.config: | |
print(f'The algorithm currently used is {param_keys}') | |
ignore_loss_params = IGNORE_LOSS_PARAMS[param_keys] | |
break | |
cfg = Config.fromfile(args.config) | |
if args.cfg_options is not None: | |
cfg.merge_from_dict(args.cfg_options) | |
if not os.path.exists(args.out_dir) and not args.show: | |
os.mkdir(args.out_dir) | |
model_wrapper, boxam_detector_visualizer = init_detector_and_visualizer( | |
args, cfg) | |
# get file list | |
image_list, source_type = get_file_list(args.img) | |
progress_bar = ProgressBar(len(image_list)) | |
for image_path in image_list: | |
image = cv2.imread(image_path) | |
model_wrapper.set_input_data(image) | |
# forward detection results | |
result = model_wrapper()[0] | |
pred_instances = result.pred_instances | |
# Get candidate predict info with score threshold | |
pred_instances = pred_instances[pred_instances.scores > args.score_thr] | |
if len(pred_instances) == 0: | |
warnings.warn('empty detection results! skip this') | |
continue | |
if args.topk > 0: | |
pred_instances = pred_instances[:args.topk] | |
targets = [ | |
DetBoxScoreTarget( | |
pred_instances, | |
device=args.device, | |
ignore_loss_params=ignore_loss_params) | |
] | |
if args.method in GRAD_BASED_METHOD_MAP: | |
model_wrapper.need_loss(True) | |
model_wrapper.set_input_data(image, pred_instances) | |
boxam_detector_visualizer.switch_activations_and_grads( | |
model_wrapper) | |
# get box am image | |
grayscale_boxam = boxam_detector_visualizer(image, targets=targets) | |
# draw cam on image | |
pred_instances = pred_instances.numpy() | |
image_with_bounding_boxes = boxam_detector_visualizer.show_am( | |
image, | |
pred_instances, | |
grayscale_boxam, | |
with_norm_in_bboxes=args.norm_in_bbox) | |
if source_type['is_dir']: | |
filename = os.path.relpath(image_path, args.img).replace('/', '_') | |
else: | |
filename = os.path.basename(image_path) | |
out_file = None if args.show else os.path.join(args.out_dir, filename) | |
if out_file: | |
mmcv.imwrite(image_with_bounding_boxes, out_file) | |
else: | |
cv2.namedWindow(filename, 0) | |
cv2.imshow(filename, image_with_bounding_boxes) | |
cv2.waitKey(0) | |
# switch | |
if args.method in GRAD_BASED_METHOD_MAP: | |
model_wrapper.need_loss(False) | |
boxam_detector_visualizer.switch_activations_and_grads( | |
model_wrapper) | |
progress_bar.update() | |
if not args.show: | |
print(f'All done!' | |
f'\nResults have been saved at {os.path.abspath(args.out_dir)}') | |
if __name__ == '__main__': | |
main() | |