Initial commit: DCNv4 custom op mirror setup
- Add enhanced README with project structure and quick start guide - Initialize repository with DCNv4 CUDA extension (PyTorch module) - Include classification, detection, and segmentation subdirectories - Reference upstream OpenGVLab DCNv4 implementation Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
8
detection/mmdet_custom/__init__.py
Normal file
8
detection/mmdet_custom/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
from .models import * # noqa: F401,F403
|
||||
from .datasets import *
|
||||
7
detection/mmdet_custom/datasets/__init__.py
Normal file
7
detection/mmdet_custom/datasets/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
from .crowd_human import CrowdHumanDataset
|
||||
529
detection/mmdet_custom/datasets/crowd_human.py
Normal file
529
detection/mmdet_custom/datasets/crowd_human.py
Normal file
@@ -0,0 +1,529 @@
|
||||
import itertools
|
||||
import logging
|
||||
import os.path as osp
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections import OrderedDict
|
||||
|
||||
import mmcv
|
||||
import numpy as np
|
||||
from mmcv.utils import print_log
|
||||
from terminaltables import AsciiTable
|
||||
|
||||
from mmdet.core import eval_recalls
|
||||
from mmdet.datasets.api_wrappers import COCO, COCOeval
|
||||
|
||||
from mmdet.datasets.custom import CustomDataset
|
||||
from mmdet.datasets.builder import DATASETS
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class CrowdHumanDataset(CustomDataset):
|
||||
|
||||
CLASSES = ('person', )
|
||||
|
||||
def load_annotations(self, ann_file):
|
||||
"""Load annotation from COCO style annotation file.
|
||||
Args:
|
||||
ann_file (str): Path of annotation file.
|
||||
Returns:
|
||||
list[dict]: Annotation info from COCO api.
|
||||
"""
|
||||
|
||||
self.coco = COCO(ann_file)
|
||||
# The order of returned `cat_ids` will not
|
||||
# change with the order of the CLASSES
|
||||
self.cat_ids = self.coco.get_cat_ids(cat_names=self.CLASSES)
|
||||
|
||||
self.cat2label = {cat_id: i for i, cat_id in enumerate(self.cat_ids)}
|
||||
self.img_ids = self.coco.get_img_ids()
|
||||
data_infos = []
|
||||
total_ann_ids = []
|
||||
for i in self.img_ids:
|
||||
info = self.coco.load_imgs([i])[0]
|
||||
info['filename'] = info['file_name']
|
||||
data_infos.append(info)
|
||||
ann_ids = self.coco.get_ann_ids(img_ids=[i])
|
||||
total_ann_ids.extend(ann_ids)
|
||||
assert len(set(total_ann_ids)) == len(
|
||||
total_ann_ids), f"Annotation ids in '{ann_file}' are not unique!"
|
||||
return data_infos
|
||||
|
||||
def get_ann_info(self, idx):
|
||||
"""Get COCO annotation by index.
|
||||
Args:
|
||||
idx (int): Index of data.
|
||||
Returns:
|
||||
dict: Annotation info of specified index.
|
||||
"""
|
||||
|
||||
img_id = self.data_infos[idx]['id']
|
||||
ann_ids = self.coco.get_ann_ids(img_ids=[img_id])
|
||||
ann_info = self.coco.load_anns(ann_ids)
|
||||
return self._parse_ann_info(self.data_infos[idx], ann_info)
|
||||
|
||||
def get_cat_ids(self, idx):
|
||||
"""Get COCO category ids by index.
|
||||
Args:
|
||||
idx (int): Index of data.
|
||||
Returns:
|
||||
list[int]: All categories in the image of specified index.
|
||||
"""
|
||||
|
||||
img_id = self.data_infos[idx]['id']
|
||||
ann_ids = self.coco.get_ann_ids(img_ids=[img_id])
|
||||
ann_info = self.coco.load_anns(ann_ids)
|
||||
return [ann['category_id'] for ann in ann_info]
|
||||
|
||||
def _filter_imgs(self, min_size=32):
|
||||
"""Filter images too small or without ground truths."""
|
||||
valid_inds = []
|
||||
# obtain images that contain annotation
|
||||
ids_with_ann = set(_['image_id'] for _ in self.coco.anns.values())
|
||||
# obtain images that contain annotations of the required categories
|
||||
ids_in_cat = set()
|
||||
for i, class_id in enumerate(self.cat_ids):
|
||||
ids_in_cat |= set(self.coco.cat_img_map[class_id])
|
||||
# merge the image id sets of the two conditions and use the merged set
|
||||
# to filter out images if self.filter_empty_gt=True
|
||||
ids_in_cat &= ids_with_ann
|
||||
|
||||
valid_img_ids = []
|
||||
for i, img_info in enumerate(self.data_infos):
|
||||
img_id = self.img_ids[i]
|
||||
if self.filter_empty_gt and img_id not in ids_in_cat:
|
||||
continue
|
||||
if min(img_info['width'], img_info['height']) >= min_size:
|
||||
valid_inds.append(i)
|
||||
valid_img_ids.append(img_id)
|
||||
self.img_ids = valid_img_ids
|
||||
return valid_inds
|
||||
|
||||
def _parse_ann_info(self, img_info, ann_info):
|
||||
"""Parse bbox and mask annotation.
|
||||
Args:
|
||||
ann_info (list[dict]): Annotation info of an image.
|
||||
with_mask (bool): Whether to parse mask annotations.
|
||||
Returns:
|
||||
dict: A dict containing the following keys: bboxes, bboxes_ignore,\
|
||||
labels, masks, seg_map. "masks" are raw annotations and not \
|
||||
decoded into binary masks.
|
||||
"""
|
||||
gt_bboxes = []
|
||||
gt_labels = []
|
||||
gt_bboxes_ignore = []
|
||||
gt_masks_ann = []
|
||||
for i, ann in enumerate(ann_info):
|
||||
if ann.get('ignore', False):
|
||||
continue
|
||||
x1, y1, w, h = ann['bbox']
|
||||
inter_w = max(0, min(x1 + w, img_info['width']) - max(x1, 0))
|
||||
inter_h = max(0, min(y1 + h, img_info['height']) - max(y1, 0))
|
||||
if inter_w * inter_h == 0:
|
||||
continue
|
||||
if ann['area'] <= 0 or w < 1 or h < 1:
|
||||
continue
|
||||
if ann['category_id'] not in self.cat_ids:
|
||||
continue
|
||||
bbox = [x1, y1, x1 + w, y1 + h]
|
||||
if ann.get('iscrowd', False):
|
||||
gt_bboxes_ignore.append(bbox)
|
||||
else:
|
||||
gt_bboxes.append(bbox)
|
||||
gt_labels.append(self.cat2label[ann['category_id']])
|
||||
gt_masks_ann.append(ann.get('segmentation', None))
|
||||
|
||||
if gt_bboxes:
|
||||
gt_bboxes = np.array(gt_bboxes, dtype=np.float32)
|
||||
gt_labels = np.array(gt_labels, dtype=np.int64)
|
||||
else:
|
||||
gt_bboxes = np.zeros((0, 4), dtype=np.float32)
|
||||
gt_labels = np.array([], dtype=np.int64)
|
||||
|
||||
if gt_bboxes_ignore:
|
||||
gt_bboxes_ignore = np.array(gt_bboxes_ignore, dtype=np.float32)
|
||||
else:
|
||||
gt_bboxes_ignore = np.zeros((0, 4), dtype=np.float32)
|
||||
|
||||
seg_map = img_info['filename'].replace('jpg', 'png')
|
||||
|
||||
ann = dict(
|
||||
bboxes=gt_bboxes,
|
||||
labels=gt_labels,
|
||||
bboxes_ignore=gt_bboxes_ignore,
|
||||
masks=gt_masks_ann,
|
||||
seg_map=seg_map)
|
||||
|
||||
return ann
|
||||
|
||||
def xyxy2xywh(self, bbox):
|
||||
"""Convert ``xyxy`` style bounding boxes to ``xywh`` style for COCO
|
||||
evaluation.
|
||||
Args:
|
||||
bbox (numpy.ndarray): The bounding boxes, shape (4, ), in
|
||||
``xyxy`` order.
|
||||
Returns:
|
||||
list[float]: The converted bounding boxes, in ``xywh`` order.
|
||||
"""
|
||||
|
||||
_bbox = bbox.tolist()
|
||||
return [
|
||||
_bbox[0],
|
||||
_bbox[1],
|
||||
_bbox[2] - _bbox[0],
|
||||
_bbox[3] - _bbox[1],
|
||||
]
|
||||
|
||||
def _proposal2json(self, results):
|
||||
"""Convert proposal results to COCO json style."""
|
||||
json_results = []
|
||||
for idx in range(len(self)):
|
||||
img_id = self.img_ids[idx]
|
||||
bboxes = results[idx]
|
||||
for i in range(bboxes.shape[0]):
|
||||
data = dict()
|
||||
data['image_id'] = img_id
|
||||
data['bbox'] = self.xyxy2xywh(bboxes[i])
|
||||
data['score'] = float(bboxes[i][4])
|
||||
data['category_id'] = 1
|
||||
json_results.append(data)
|
||||
return json_results
|
||||
|
||||
def _det2json(self, results):
|
||||
"""Convert detection results to COCO json style."""
|
||||
json_results = []
|
||||
for idx in range(len(self)):
|
||||
img_id = self.img_ids[idx]
|
||||
result = results[idx]
|
||||
for label in range(len(result)):
|
||||
bboxes = result[label]
|
||||
for i in range(bboxes.shape[0]):
|
||||
data = dict()
|
||||
data['image_id'] = img_id
|
||||
data['bbox'] = self.xyxy2xywh(bboxes[i])
|
||||
data['score'] = float(bboxes[i][4])
|
||||
data['category_id'] = self.cat_ids[label]
|
||||
json_results.append(data)
|
||||
return json_results
|
||||
|
||||
def _segm2json(self, results):
|
||||
"""Convert instance segmentation results to COCO json style."""
|
||||
bbox_json_results = []
|
||||
segm_json_results = []
|
||||
for idx in range(len(self)):
|
||||
img_id = self.img_ids[idx]
|
||||
det, seg = results[idx]
|
||||
for label in range(len(det)):
|
||||
# bbox results
|
||||
bboxes = det[label]
|
||||
for i in range(bboxes.shape[0]):
|
||||
data = dict()
|
||||
data['image_id'] = img_id
|
||||
data['bbox'] = self.xyxy2xywh(bboxes[i])
|
||||
data['score'] = float(bboxes[i][4])
|
||||
data['category_id'] = self.cat_ids[label]
|
||||
bbox_json_results.append(data)
|
||||
|
||||
# segm results
|
||||
# some detectors use different scores for bbox and mask
|
||||
if isinstance(seg, tuple):
|
||||
segms = seg[0][label]
|
||||
mask_score = seg[1][label]
|
||||
else:
|
||||
segms = seg[label]
|
||||
mask_score = [bbox[4] for bbox in bboxes]
|
||||
for i in range(bboxes.shape[0]):
|
||||
data = dict()
|
||||
data['image_id'] = img_id
|
||||
data['bbox'] = self.xyxy2xywh(bboxes[i])
|
||||
data['score'] = float(mask_score[i])
|
||||
data['category_id'] = self.cat_ids[label]
|
||||
if isinstance(segms[i]['counts'], bytes):
|
||||
segms[i]['counts'] = segms[i]['counts'].decode()
|
||||
data['segmentation'] = segms[i]
|
||||
segm_json_results.append(data)
|
||||
return bbox_json_results, segm_json_results
|
||||
|
||||
def results2json(self, results, outfile_prefix):
|
||||
"""Dump the detection results to a COCO style json file.
|
||||
There are 3 types of results: proposals, bbox predictions, mask
|
||||
predictions, and they have different data types. This method will
|
||||
automatically recognize the type, and dump them to json files.
|
||||
Args:
|
||||
results (list[list | tuple | ndarray]): Testing results of the
|
||||
dataset.
|
||||
outfile_prefix (str): The filename prefix of the json files. If the
|
||||
prefix is "somepath/xxx", the json files will be named
|
||||
"somepath/xxx.bbox.json", "somepath/xxx.segm.json",
|
||||
"somepath/xxx.proposal.json".
|
||||
Returns:
|
||||
dict[str: str]: Possible keys are "bbox", "segm", "proposal", and \
|
||||
values are corresponding filenames.
|
||||
"""
|
||||
result_files = dict()
|
||||
if isinstance(results[0], list):
|
||||
json_results = self._det2json(results)
|
||||
result_files['bbox'] = f'{outfile_prefix}.bbox.json'
|
||||
result_files['proposal'] = f'{outfile_prefix}.bbox.json'
|
||||
mmcv.dump(json_results, result_files['bbox'])
|
||||
elif isinstance(results[0], tuple):
|
||||
json_results = self._segm2json(results)
|
||||
result_files['bbox'] = f'{outfile_prefix}.bbox.json'
|
||||
result_files['proposal'] = f'{outfile_prefix}.bbox.json'
|
||||
result_files['segm'] = f'{outfile_prefix}.segm.json'
|
||||
mmcv.dump(json_results[0], result_files['bbox'])
|
||||
mmcv.dump(json_results[1], result_files['segm'])
|
||||
elif isinstance(results[0], np.ndarray):
|
||||
json_results = self._proposal2json(results)
|
||||
result_files['proposal'] = f'{outfile_prefix}.proposal.json'
|
||||
mmcv.dump(json_results, result_files['proposal'])
|
||||
else:
|
||||
raise TypeError('invalid type of results')
|
||||
return result_files
|
||||
|
||||
def fast_eval_recall(self, results, proposal_nums, iou_thrs, logger=None):
|
||||
gt_bboxes = []
|
||||
for i in range(len(self.img_ids)):
|
||||
ann_ids = self.coco.get_ann_ids(img_ids=self.img_ids[i])
|
||||
ann_info = self.coco.load_anns(ann_ids)
|
||||
if len(ann_info) == 0:
|
||||
gt_bboxes.append(np.zeros((0, 4)))
|
||||
continue
|
||||
bboxes = []
|
||||
for ann in ann_info:
|
||||
if ann.get('ignore', False) or ann['iscrowd']:
|
||||
continue
|
||||
x1, y1, w, h = ann['bbox']
|
||||
bboxes.append([x1, y1, x1 + w, y1 + h])
|
||||
bboxes = np.array(bboxes, dtype=np.float32)
|
||||
if bboxes.shape[0] == 0:
|
||||
bboxes = np.zeros((0, 4))
|
||||
gt_bboxes.append(bboxes)
|
||||
|
||||
recalls = eval_recalls(
|
||||
gt_bboxes, results, proposal_nums, iou_thrs, logger=logger)
|
||||
ar = recalls.mean(axis=1)
|
||||
return ar
|
||||
|
||||
def format_results(self, results, jsonfile_prefix=None, **kwargs):
|
||||
"""Format the results to json (standard format for COCO evaluation).
|
||||
Args:
|
||||
results (list[tuple | numpy.ndarray]): Testing results of the
|
||||
dataset.
|
||||
jsonfile_prefix (str | None): The prefix of json files. It includes
|
||||
the file path and the prefix of filename, e.g., "a/b/prefix".
|
||||
If not specified, a temp file will be created. Default: None.
|
||||
Returns:
|
||||
tuple: (result_files, tmp_dir), result_files is a dict containing \
|
||||
the json filepaths, tmp_dir is the temporal directory created \
|
||||
for saving json files when jsonfile_prefix is not specified.
|
||||
"""
|
||||
assert isinstance(results, list), 'results must be a list'
|
||||
assert len(results) == len(self), (
|
||||
'The length of results is not equal to the dataset len: {} != {}'.
|
||||
format(len(results), len(self)))
|
||||
|
||||
if jsonfile_prefix is None:
|
||||
tmp_dir = tempfile.TemporaryDirectory()
|
||||
jsonfile_prefix = osp.join(tmp_dir.name, 'results')
|
||||
else:
|
||||
tmp_dir = None
|
||||
result_files = self.results2json(results, jsonfile_prefix)
|
||||
return result_files, tmp_dir
|
||||
|
||||
def evaluate(self,
|
||||
results,
|
||||
metric='bbox',
|
||||
logger=None,
|
||||
jsonfile_prefix=None,
|
||||
classwise=False,
|
||||
proposal_nums=(100, 300, 1000),
|
||||
iou_thrs=None,
|
||||
metric_items=None):
|
||||
"""Evaluation in COCO protocol.
|
||||
Args:
|
||||
results (list[list | tuple]): Testing results of the dataset.
|
||||
metric (str | list[str]): Metrics to be evaluated. Options are
|
||||
'bbox', 'segm', 'proposal', 'proposal_fast'.
|
||||
logger (logging.Logger | str | None): Logger used for printing
|
||||
related information during evaluation. Default: None.
|
||||
jsonfile_prefix (str | None): The prefix of json files. It includes
|
||||
the file path and the prefix of filename, e.g., "a/b/prefix".
|
||||
If not specified, a temp file will be created. Default: None.
|
||||
classwise (bool): Whether to evaluating the AP for each class.
|
||||
proposal_nums (Sequence[int]): Proposal number used for evaluating
|
||||
recalls, such as recall@100, recall@1000.
|
||||
Default: (100, 300, 1000).
|
||||
iou_thrs (Sequence[float], optional): IoU threshold used for
|
||||
evaluating recalls/mAPs. If set to a list, the average of all
|
||||
IoUs will also be computed. If not specified, [0.50, 0.55,
|
||||
0.60, 0.65, 0.70, 0.75, 0.80, 0.85, 0.90, 0.95] will be used.
|
||||
Default: None.
|
||||
metric_items (list[str] | str, optional): Metric items that will
|
||||
be returned. If not specified, ``['AR@100', 'AR@300',
|
||||
'AR@1000', 'AR_s@1000', 'AR_m@1000', 'AR_l@1000' ]`` will be
|
||||
used when ``metric=='proposal'``, ``['mAP', 'mAP_50', 'mAP_75',
|
||||
'mAP_s', 'mAP_m', 'mAP_l']`` will be used when
|
||||
``metric=='bbox' or metric=='segm'``.
|
||||
Returns:
|
||||
dict[str, float]: COCO style evaluation metric.
|
||||
"""
|
||||
|
||||
metrics = metric if isinstance(metric, list) else [metric]
|
||||
allowed_metrics = ['bbox', 'segm', 'proposal', 'proposal_fast']
|
||||
for metric in metrics:
|
||||
if metric not in allowed_metrics:
|
||||
raise KeyError(f'metric {metric} is not supported')
|
||||
if iou_thrs is None:
|
||||
iou_thrs = np.linspace(
|
||||
.5, 0.95, int(np.round((0.95 - .5) / .05)) + 1, endpoint=True)
|
||||
if metric_items is not None:
|
||||
if not isinstance(metric_items, list):
|
||||
metric_items = [metric_items]
|
||||
|
||||
result_files, tmp_dir = self.format_results(results, jsonfile_prefix)
|
||||
|
||||
eval_results = OrderedDict()
|
||||
cocoGt = self.coco
|
||||
for metric in metrics:
|
||||
msg = f'Evaluating {metric}...'
|
||||
if logger is None:
|
||||
msg = '\n' + msg
|
||||
print_log(msg, logger=logger)
|
||||
|
||||
if metric == 'proposal_fast':
|
||||
ar = self.fast_eval_recall(
|
||||
results, proposal_nums, iou_thrs, logger='silent')
|
||||
log_msg = []
|
||||
for i, num in enumerate(proposal_nums):
|
||||
eval_results[f'AR@{num}'] = ar[i]
|
||||
log_msg.append(f'\nAR@{num}\t{ar[i]:.4f}')
|
||||
log_msg = ''.join(log_msg)
|
||||
print_log(log_msg, logger=logger)
|
||||
continue
|
||||
|
||||
iou_type = 'bbox' if metric == 'proposal' else metric
|
||||
if metric not in result_files:
|
||||
raise KeyError(f'{metric} is not in results')
|
||||
try:
|
||||
predictions = mmcv.load(result_files[metric])
|
||||
if iou_type == 'segm':
|
||||
# Refer to https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocotools/coco.py#L331 # noqa
|
||||
# When evaluating mask AP, if the results contain bbox,
|
||||
# cocoapi will use the box area instead of the mask area
|
||||
# for calculating the instance area. Though the overall AP
|
||||
# is not affected, this leads to different
|
||||
# small/medium/large mask AP results.
|
||||
for x in predictions:
|
||||
x.pop('bbox')
|
||||
warnings.simplefilter('once')
|
||||
warnings.warn(
|
||||
'The key "bbox" is deleted for more accurate mask AP '
|
||||
'of small/medium/large instances since v2.12.0. This '
|
||||
'does not change the overall mAP calculation.',
|
||||
UserWarning)
|
||||
cocoDt = cocoGt.loadRes(predictions)
|
||||
except IndexError:
|
||||
print_log(
|
||||
'The testing results of the whole dataset is empty.',
|
||||
logger=logger,
|
||||
level=logging.ERROR)
|
||||
break
|
||||
|
||||
cocoEval = COCOeval(cocoGt, cocoDt, iou_type)
|
||||
cocoEval.params.catIds = self.cat_ids
|
||||
cocoEval.params.imgIds = self.img_ids
|
||||
cocoEval.params.maxDets = list(proposal_nums)
|
||||
cocoEval.params.iouThrs = iou_thrs
|
||||
# mapping of cocoEval.stats
|
||||
coco_metric_names = {
|
||||
'mAP': 0,
|
||||
'mAP_50': 1,
|
||||
'mAP_75': 2,
|
||||
'mAP_s': 3,
|
||||
'mAP_m': 4,
|
||||
'mAP_l': 5,
|
||||
'AR@100': 6,
|
||||
'AR@300': 7,
|
||||
'AR@1000': 8,
|
||||
'AR_s@1000': 9,
|
||||
'AR_m@1000': 10,
|
||||
'AR_l@1000': 11
|
||||
}
|
||||
if metric_items is not None:
|
||||
for metric_item in metric_items:
|
||||
if metric_item not in coco_metric_names:
|
||||
raise KeyError(
|
||||
f'metric item {metric_item} is not supported')
|
||||
|
||||
if metric == 'proposal':
|
||||
cocoEval.params.useCats = 0
|
||||
cocoEval.evaluate()
|
||||
cocoEval.accumulate()
|
||||
cocoEval.summarize()
|
||||
if metric_items is None:
|
||||
metric_items = [
|
||||
'AR@100', 'AR@300', 'AR@1000', 'AR_s@1000',
|
||||
'AR_m@1000', 'AR_l@1000'
|
||||
]
|
||||
|
||||
for item in metric_items:
|
||||
val = float(
|
||||
f'{cocoEval.stats[coco_metric_names[item]]:.3f}')
|
||||
eval_results[item] = val
|
||||
else:
|
||||
cocoEval.evaluate()
|
||||
cocoEval.accumulate()
|
||||
cocoEval.summarize()
|
||||
if classwise: # Compute per-category AP
|
||||
# Compute per-category AP
|
||||
# from https://github.com/facebookresearch/detectron2/
|
||||
precisions = cocoEval.eval['precision']
|
||||
# precision: (iou, recall, cls, area range, max dets)
|
||||
assert len(self.cat_ids) == precisions.shape[2]
|
||||
|
||||
results_per_category = []
|
||||
for idx, catId in enumerate(self.cat_ids):
|
||||
# area range index 0: all area ranges
|
||||
# max dets index -1: typically 100 per image
|
||||
nm = self.coco.loadCats(catId)[0]
|
||||
precision = precisions[:, :, idx, 0, -1]
|
||||
precision = precision[precision > -1]
|
||||
if precision.size:
|
||||
ap = np.mean(precision)
|
||||
else:
|
||||
ap = float('nan')
|
||||
results_per_category.append(
|
||||
(f'{nm["name"]}', f'{float(ap):0.3f}'))
|
||||
|
||||
num_columns = min(6, len(results_per_category) * 2)
|
||||
results_flatten = list(
|
||||
itertools.chain(*results_per_category))
|
||||
headers = ['category', 'AP'] * (num_columns // 2)
|
||||
results_2d = itertools.zip_longest(*[
|
||||
results_flatten[i::num_columns]
|
||||
for i in range(num_columns)
|
||||
])
|
||||
table_data = [headers]
|
||||
table_data += [result for result in results_2d]
|
||||
table = AsciiTable(table_data)
|
||||
print_log('\n' + table.table, logger=logger)
|
||||
|
||||
if metric_items is None:
|
||||
metric_items = [
|
||||
'mAP', 'mAP_50', 'mAP_75', 'mAP_s', 'mAP_m', 'mAP_l'
|
||||
]
|
||||
|
||||
for metric_item in metric_items:
|
||||
key = f'{metric}_{metric_item}'
|
||||
val = float(
|
||||
f'{cocoEval.stats[coco_metric_names[metric_item]]:.3f}'
|
||||
)
|
||||
eval_results[key] = val
|
||||
ap = cocoEval.stats[:6]
|
||||
eval_results[f'{metric}_mAP_copypaste'] = (
|
||||
f'{ap[0]:.3f} {ap[1]:.3f} {ap[2]:.3f} {ap[3]:.3f} '
|
||||
f'{ap[4]:.3f} {ap[5]:.3f}')
|
||||
if tmp_dir is not None:
|
||||
tmp_dir.cleanup()
|
||||
return
|
||||
11
detection/mmdet_custom/models/__init__.py
Normal file
11
detection/mmdet_custom/models/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
from .backbones import * # noqa: F401,F403
|
||||
from .dense_heads import * # noqa: F401,F403
|
||||
from .detectors import * # noqa: F401,F403
|
||||
from .utils import * # noqa: F401,F403
|
||||
from .necks.fpn import *
|
||||
8
detection/mmdet_custom/models/backbones/__init__.py
Normal file
8
detection/mmdet_custom/models/backbones/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2023 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
from .flash_intern_image import FlashInternImage
|
||||
|
||||
__all__ = ['FlashInternImage']
|
||||
763
detection/mmdet_custom/models/backbones/flash_intern_image.py
Normal file
763
detection/mmdet_custom/models/backbones/flash_intern_image.py
Normal file
@@ -0,0 +1,763 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from collections import OrderedDict
|
||||
import torch.utils.checkpoint as checkpoint
|
||||
from timm.models.layers import trunc_normal_, DropPath
|
||||
from mmcv.runner import _load_checkpoint
|
||||
from mmcv.cnn import constant_init, trunc_normal_init
|
||||
from mmdet.utils import get_root_logger
|
||||
from mmdet.models.builder import BACKBONES
|
||||
import torch.nn.functional as F
|
||||
import DCNv4
|
||||
|
||||
|
||||
class to_channels_first(nn.Module):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def forward(self, x):
|
||||
return x.permute(0, 3, 1, 2)
|
||||
|
||||
|
||||
class to_channels_last(nn.Module):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def forward(self, x):
|
||||
return x.permute(0, 2, 3, 1)
|
||||
|
||||
|
||||
def build_norm_layer(dim,
|
||||
norm_layer,
|
||||
in_format='channels_last',
|
||||
out_format='channels_last',
|
||||
eps=1e-6):
|
||||
layers = []
|
||||
if norm_layer == 'BN':
|
||||
if in_format == 'channels_last':
|
||||
layers.append(to_channels_first())
|
||||
layers.append(nn.BatchNorm2d(dim))
|
||||
if out_format == 'channels_last':
|
||||
layers.append(to_channels_last())
|
||||
elif norm_layer == 'LN':
|
||||
if in_format == 'channels_first':
|
||||
layers.append(to_channels_last())
|
||||
layers.append(nn.LayerNorm(dim, eps=eps))
|
||||
if out_format == 'channels_first':
|
||||
layers.append(to_channels_first())
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f'build_norm_layer does not support {norm_layer}')
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
|
||||
def build_act_layer(act_layer):
|
||||
if act_layer == 'ReLU':
|
||||
return nn.ReLU(inplace=True)
|
||||
elif act_layer == 'SiLU':
|
||||
return nn.SiLU(inplace=True)
|
||||
elif act_layer == 'GELU':
|
||||
return nn.GELU()
|
||||
|
||||
raise NotImplementedError(f'build_act_layer does not support {act_layer}')
|
||||
|
||||
|
||||
class CrossAttention(nn.Module):
|
||||
r""" Cross Attention Module
|
||||
Args:
|
||||
dim (int): Number of input channels.
|
||||
num_heads (int): Number of attention heads. Default: 8
|
||||
qkv_bias (bool, optional): If True, add a learnable bias to q, k, v.
|
||||
Default: False.
|
||||
qk_scale (float | None, optional): Override default qk scale of
|
||||
head_dim ** -0.5 if set. Default: None.
|
||||
attn_drop (float, optional): Dropout ratio of attention weight.
|
||||
Default: 0.0
|
||||
proj_drop (float, optional): Dropout ratio of output. Default: 0.0
|
||||
attn_head_dim (int, optional): Dimension of attention head.
|
||||
out_dim (int, optional): Dimension of output.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
dim,
|
||||
num_heads=8,
|
||||
qkv_bias=False,
|
||||
qk_scale=None,
|
||||
attn_drop=0.,
|
||||
proj_drop=0.,
|
||||
attn_head_dim=None,
|
||||
out_dim=None):
|
||||
super().__init__()
|
||||
if out_dim is None:
|
||||
out_dim = dim
|
||||
self.num_heads = num_heads
|
||||
head_dim = dim // num_heads
|
||||
if attn_head_dim is not None:
|
||||
head_dim = attn_head_dim
|
||||
all_head_dim = head_dim * self.num_heads
|
||||
self.scale = qk_scale or head_dim ** -0.5
|
||||
assert all_head_dim == dim
|
||||
|
||||
self.q = nn.Linear(dim, all_head_dim, bias=False)
|
||||
self.k = nn.Linear(dim, all_head_dim, bias=False)
|
||||
self.v = nn.Linear(dim, all_head_dim, bias=False)
|
||||
|
||||
if qkv_bias:
|
||||
self.q_bias = nn.Parameter(torch.zeros(all_head_dim))
|
||||
self.k_bias = nn.Parameter(torch.zeros(all_head_dim))
|
||||
self.v_bias = nn.Parameter(torch.zeros(all_head_dim))
|
||||
else:
|
||||
self.q_bias = None
|
||||
self.k_bias = None
|
||||
self.v_bias = None
|
||||
|
||||
self.attn_drop = nn.Dropout(attn_drop)
|
||||
self.proj = nn.Linear(all_head_dim, out_dim)
|
||||
self.proj_drop = nn.Dropout(proj_drop)
|
||||
|
||||
def forward(self, x, k=None, v=None):
|
||||
B, N, C = x.shape
|
||||
N_k = k.shape[1]
|
||||
N_v = v.shape[1]
|
||||
|
||||
q_bias, k_bias, v_bias = None, None, None
|
||||
if self.q_bias is not None:
|
||||
q_bias = self.q_bias
|
||||
k_bias = self.k_bias
|
||||
v_bias = self.v_bias
|
||||
|
||||
q = F.linear(input=x, weight=self.q.weight, bias=q_bias)
|
||||
q = q.reshape(B, N, 1, self.num_heads,
|
||||
-1).permute(2, 0, 3, 1,
|
||||
4).squeeze(0) # (B, N_head, N_q, dim)
|
||||
|
||||
k = F.linear(input=k, weight=self.k.weight, bias=k_bias)
|
||||
k = k.reshape(B, N_k, 1, self.num_heads, -1).permute(2, 0, 3, 1,
|
||||
4).squeeze(0)
|
||||
|
||||
v = F.linear(input=v, weight=self.v.weight, bias=v_bias)
|
||||
v = v.reshape(B, N_v, 1, self.num_heads, -1).permute(2, 0, 3, 1,
|
||||
4).squeeze(0)
|
||||
|
||||
q = q * self.scale
|
||||
attn = (q @ k.transpose(-2, -1)) # (B, N_head, N_q, N_k)
|
||||
|
||||
attn = attn.softmax(dim=-1)
|
||||
attn = self.attn_drop(attn)
|
||||
|
||||
x = (attn @ v).transpose(1, 2).reshape(B, N, -1)
|
||||
x = self.proj(x)
|
||||
x = self.proj_drop(x)
|
||||
|
||||
return x
|
||||
|
||||
|
||||
class AttentiveBlock(nn.Module):
|
||||
r"""Attentive Block
|
||||
Args:
|
||||
dim (int): Number of input channels.
|
||||
num_heads (int): Number of attention heads. Default: 8
|
||||
qkv_bias (bool, optional): If True, add a learnable bias to q, k, v.
|
||||
Default: False.
|
||||
qk_scale (float | None, optional): Override default qk scale of
|
||||
head_dim ** -0.5 if set. Default: None.
|
||||
drop (float, optional): Dropout rate. Default: 0.0.
|
||||
attn_drop (float, optional): Attention dropout rate. Default: 0.0.
|
||||
drop_path (float | tuple[float], optional): Stochastic depth rate.
|
||||
Default: 0.0.
|
||||
norm_layer (nn.Module, optional): Normalization layer. Default: nn.LayerNorm.
|
||||
attn_head_dim (int, optional): Dimension of attention head. Default: None.
|
||||
out_dim (int, optional): Dimension of output. Default: None.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
dim,
|
||||
num_heads,
|
||||
qkv_bias=False,
|
||||
qk_scale=None,
|
||||
drop=0.,
|
||||
attn_drop=0.,
|
||||
drop_path=0.,
|
||||
norm_layer="LN",
|
||||
attn_head_dim=None,
|
||||
out_dim=None):
|
||||
super().__init__()
|
||||
|
||||
self.norm1_q = build_norm_layer(dim, norm_layer, eps=1e-6)
|
||||
self.norm1_k = build_norm_layer(dim, norm_layer, eps=1e-6)
|
||||
self.norm1_v = build_norm_layer(dim, norm_layer, eps=1e-6)
|
||||
self.cross_dcn = CrossAttention(dim,
|
||||
num_heads=num_heads,
|
||||
qkv_bias=qkv_bias,
|
||||
qk_scale=qk_scale,
|
||||
attn_drop=attn_drop,
|
||||
proj_drop=drop,
|
||||
attn_head_dim=attn_head_dim,
|
||||
out_dim=out_dim)
|
||||
|
||||
self.drop_path = DropPath(
|
||||
drop_path) if drop_path > 0. else nn.Identity()
|
||||
|
||||
def forward(self,
|
||||
x_q,
|
||||
x_kv,
|
||||
pos_q,
|
||||
pos_k,
|
||||
bool_masked_pos,
|
||||
rel_pos_bias=None):
|
||||
x_q = self.norm1_q(x_q + pos_q)
|
||||
x_k = self.norm1_k(x_kv + pos_k)
|
||||
x_v = self.norm1_v(x_kv)
|
||||
|
||||
x = self.cross_dcn(x_q, k=x_k, v=x_v)
|
||||
|
||||
return x
|
||||
|
||||
|
||||
class AttentionPoolingBlock(AttentiveBlock):
|
||||
|
||||
def forward(self, x):
|
||||
x_q = x.mean(1, keepdim=True)
|
||||
x_kv = x
|
||||
pos_q, pos_k = 0, 0
|
||||
x = super().forward(x_q, x_kv, pos_q, pos_k,
|
||||
bool_masked_pos=None,
|
||||
rel_pos_bias=None)
|
||||
x = x.squeeze(1)
|
||||
return x
|
||||
|
||||
|
||||
class StemLayer(nn.Module):
|
||||
r""" Stem layer of InternImage
|
||||
Args:
|
||||
in_chans (int): number of input channels
|
||||
out_chans (int): number of output channels
|
||||
act_layer (str): activation layer
|
||||
norm_layer (str): normalization layer
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_chans=3,
|
||||
out_chans=96,
|
||||
act_layer='GELU',
|
||||
norm_layer='BN'):
|
||||
super().__init__()
|
||||
self.conv1 = nn.Conv2d(in_chans,
|
||||
out_chans // 2,
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1)
|
||||
self.norm1 = build_norm_layer(out_chans // 2, norm_layer,
|
||||
'channels_first', 'channels_first')
|
||||
self.act = build_act_layer(act_layer)
|
||||
self.conv2 = nn.Conv2d(out_chans // 2,
|
||||
out_chans,
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1)
|
||||
self.norm2 = build_norm_layer(out_chans, norm_layer, 'channels_first',
|
||||
'channels_last')
|
||||
|
||||
def forward(self, x):
|
||||
x = self.conv1(x)
|
||||
x = self.norm1(x)
|
||||
x = self.act(x)
|
||||
x = self.conv2(x)
|
||||
x = self.norm2(x)
|
||||
return x
|
||||
|
||||
|
||||
|
||||
class DownsampleLayer(nn.Module):
|
||||
r""" Downsample layer of InternImage
|
||||
Args:
|
||||
channels (int): number of input channels
|
||||
norm_layer (str): normalization layer
|
||||
"""
|
||||
|
||||
def __init__(self, channels, norm_layer='LN'):
|
||||
super().__init__()
|
||||
self.conv = nn.Conv2d(channels,
|
||||
2 * channels,
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1,
|
||||
bias=False)
|
||||
self.norm = build_norm_layer(2 * channels, norm_layer,
|
||||
'channels_first', 'channels_first')
|
||||
|
||||
|
||||
def forward(self, x, shape=None):
|
||||
H, W = shape
|
||||
N, HW, C = x.shape
|
||||
|
||||
x = x.view(N, H, W, C)
|
||||
x = self.conv(x.permute(0, 3, 1, 2))
|
||||
x = self.norm(x) # B C H W
|
||||
H, W = x.size(2), x.size(3)
|
||||
x = x.flatten(2).permute(0, 2, 1)
|
||||
|
||||
return x, (H, W)
|
||||
|
||||
|
||||
|
||||
class MLPLayer(nn.Module):
|
||||
r""" MLP layer of InternImage
|
||||
Args:
|
||||
in_features (int): number of input features
|
||||
hidden_features (int): number of hidden features
|
||||
out_features (int): number of output features
|
||||
act_layer (str): activation layer
|
||||
drop (float): dropout rate
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_features,
|
||||
hidden_features=None,
|
||||
out_features=None,
|
||||
act_layer='GELU',
|
||||
mlp_fc2_bias=False,
|
||||
drop=0.):
|
||||
super().__init__()
|
||||
out_features = out_features or in_features
|
||||
hidden_features = hidden_features or in_features
|
||||
self.fc1 = nn.Linear(in_features, hidden_features, bias=True)
|
||||
self.act = build_act_layer(act_layer)
|
||||
self.fc2 = nn.Linear(hidden_features, out_features, bias=mlp_fc2_bias)
|
||||
self.drop = nn.Dropout(drop)
|
||||
|
||||
|
||||
def forward(self, x, shape, level_idx=0):
|
||||
x = self.fc1(x)
|
||||
x = self.act(x)
|
||||
x = self.drop(x)
|
||||
x = self.fc2(x)
|
||||
x = self.drop(x)
|
||||
return x
|
||||
|
||||
|
||||
class InternImageLayer(nn.Module):
|
||||
r""" Basic layer of InternImage
|
||||
Args:
|
||||
core_op (nn.Module): core operation of InternImage
|
||||
channels (int): number of input channels
|
||||
groups (list): Groups of each block.
|
||||
mlp_ratio (float): ratio of mlp hidden features to input channels
|
||||
drop (float): dropout rate
|
||||
drop_path (float): drop path rate
|
||||
act_layer (str): activation layer
|
||||
norm_layer (str): normalization layer
|
||||
post_norm (bool): whether to use post normalization
|
||||
layer_scale (float): layer scale
|
||||
offset_scale (float): offset scale
|
||||
with_cp (bool): whether to use checkpoint
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
core_op,
|
||||
channels,
|
||||
groups,
|
||||
mlp_ratio=4.,
|
||||
drop=0.,
|
||||
drop_path=0.,
|
||||
act_layer='GELU',
|
||||
norm_layer='LN',
|
||||
post_norm=False,
|
||||
layer_scale=None,
|
||||
offset_scale=1.0,
|
||||
with_cp=False,
|
||||
dcn_output_bias=False,
|
||||
mlp_fc2_bias=False,
|
||||
dw_kernel_size=None, # for InternImage-H/G
|
||||
res_post_norm=False, # for InternImage-H/G
|
||||
center_feature_scale=False): # for InternImage-H/G
|
||||
super().__init__()
|
||||
self.channels = channels
|
||||
self.groups = groups
|
||||
self.mlp_ratio = mlp_ratio
|
||||
self.with_cp = with_cp
|
||||
|
||||
self.norm1 = build_norm_layer(channels, 'LN')
|
||||
self.post_norm = post_norm
|
||||
self.dcn = core_op(
|
||||
channels=channels,
|
||||
group=groups,
|
||||
offset_scale=offset_scale,
|
||||
dw_kernel_size=dw_kernel_size,
|
||||
output_bias=dcn_output_bias,
|
||||
)
|
||||
self.drop_path = DropPath(drop_path) if drop_path > 0. \
|
||||
else nn.Identity()
|
||||
self.norm2 = build_norm_layer(channels, 'LN')
|
||||
self.mlp = MLPLayer(in_features=channels,
|
||||
hidden_features=int(channels * mlp_ratio),
|
||||
act_layer=act_layer,
|
||||
drop=drop,
|
||||
mlp_fc2_bias=mlp_fc2_bias
|
||||
)
|
||||
self.layer_scale = layer_scale is not None
|
||||
if self.layer_scale:
|
||||
self.gamma1 = nn.Parameter(layer_scale * torch.ones(channels),
|
||||
requires_grad=True)
|
||||
self.gamma2 = nn.Parameter(layer_scale * torch.ones(channels),
|
||||
requires_grad=True)
|
||||
self.res_post_norm = res_post_norm
|
||||
if res_post_norm:
|
||||
self.res_post_norm1 = build_norm_layer(channels, 'LN')
|
||||
self.res_post_norm2 = build_norm_layer(channels, 'LN')
|
||||
def forward(self, x, shape, level_idx=0):
|
||||
|
||||
def _inner_forward(x, shape, level_idx):
|
||||
if not self.layer_scale:
|
||||
if self.post_norm:
|
||||
x = x + self.drop_path(self.norm1(self.dcn(x, shape, level_idx)))
|
||||
x = x + self.drop_path(self.norm2(self.mlp(x, shape, level_idx)))
|
||||
elif self.res_post_norm: # for InternImage-H/G
|
||||
x = x + self.drop_path(self.res_post_norm1(self.dcn(self.norm1(x), shape, level_idx)))
|
||||
x = x + self.drop_path(self.res_post_norm2(self.mlp(self.norm2(x), shape, level_idx)))
|
||||
|
||||
else:
|
||||
x = x + self.drop_path(self.dcn(self.norm1(x), shape, level_idx))
|
||||
x = x + self.drop_path(self.mlp(self.norm2(x), shape, level_idx))
|
||||
return x
|
||||
if self.post_norm:
|
||||
x = x + self.drop_path(self.gamma1 * self.norm1(self.dcn(x, shape)))
|
||||
x = x + self.drop_path(self.gamma2 * self.norm2(self.mlp(x, shape, level_idx)))
|
||||
else:
|
||||
x = x + self.drop_path(self.gamma1 * self.dcn(self.norm1(x), shape))
|
||||
x = x + self.drop_path(self.gamma2 * self.mlp(self.norm2(x), shape, level_idx))
|
||||
return x
|
||||
|
||||
if self.with_cp and x.requires_grad:
|
||||
x = checkpoint.checkpoint(_inner_forward, x, shape, level_idx)
|
||||
else:
|
||||
x = _inner_forward(x, shape, level_idx)
|
||||
|
||||
return x
|
||||
|
||||
|
||||
class InternImageBlock(nn.Module):
|
||||
r""" Block of InternImage
|
||||
Args:
|
||||
core_op (nn.Module): core operation of InternImage
|
||||
channels (int): number of input channels
|
||||
depths (list): Depth of each block.
|
||||
groups (list): Groups of each block.
|
||||
mlp_ratio (float): ratio of mlp hidden features to input channels
|
||||
drop (float): dropout rate
|
||||
drop_path (float): drop path rate
|
||||
act_layer (str): activation layer
|
||||
norm_layer (str): normalization layer
|
||||
post_norm (bool): whether to use post normalization
|
||||
layer_scale (float): layer scale
|
||||
offset_scale (float): offset scale
|
||||
with_cp (bool): whether to use checkpoint
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
core_op,
|
||||
channels,
|
||||
depth,
|
||||
groups,
|
||||
downsample=True,
|
||||
downsample_layer=DownsampleLayer,
|
||||
mlp_ratio=4.,
|
||||
drop=0.,
|
||||
drop_path=0.,
|
||||
act_layer='GELU',
|
||||
norm_layer='LN',
|
||||
post_norm=False,
|
||||
offset_scale=0.5,
|
||||
layer_scale=None,
|
||||
with_cp=False,
|
||||
dcn_output_bias=False,
|
||||
mlp_fc2_bias=False,
|
||||
dw_kernel_size=None, # for InternImage-H/G
|
||||
post_norm_block_ids=None, # for InternImage-H/G
|
||||
res_post_norm=False, # for InternImage-H/G
|
||||
center_feature_scale=False): # for InternImage-H/G
|
||||
super().__init__()
|
||||
self.channels = channels
|
||||
self.depth = depth
|
||||
self.post_norm = post_norm
|
||||
self.center_feature_scale = center_feature_scale
|
||||
|
||||
self.blocks = nn.ModuleList([
|
||||
InternImageLayer(
|
||||
core_op=core_op,
|
||||
channels=channels,
|
||||
groups=groups,
|
||||
mlp_ratio=mlp_ratio,
|
||||
drop=drop,
|
||||
drop_path=drop_path[i] if isinstance(
|
||||
drop_path, list) else drop_path,
|
||||
act_layer=act_layer,
|
||||
norm_layer=norm_layer,
|
||||
post_norm=post_norm,
|
||||
layer_scale=layer_scale,
|
||||
offset_scale=offset_scale,
|
||||
with_cp=with_cp,
|
||||
dcn_output_bias=dcn_output_bias,
|
||||
mlp_fc2_bias=mlp_fc2_bias,
|
||||
dw_kernel_size=dw_kernel_size, # for InternImage-H/G
|
||||
res_post_norm=res_post_norm, # for InternImage-H/G
|
||||
center_feature_scale=center_feature_scale # for InternImage-H/G
|
||||
) for i in range(depth)
|
||||
])
|
||||
if not self.post_norm or center_feature_scale:
|
||||
self.norm = build_norm_layer(channels, 'LN')
|
||||
self.post_norm_block_ids = post_norm_block_ids
|
||||
if post_norm_block_ids is not None: # for InternImage-H/G
|
||||
self.post_norms = nn.ModuleList(
|
||||
[build_norm_layer(channels, 'LN', eps=1e-6) for _ in post_norm_block_ids]
|
||||
)
|
||||
self.downsample = downsample_layer(
|
||||
channels=channels, norm_layer=norm_layer) if downsample else None
|
||||
|
||||
|
||||
def forward(self, x, return_wo_downsample=False, shape=None, level_idx=0
|
||||
):
|
||||
for i, blk in enumerate(self.blocks):
|
||||
x = blk(x, shape=shape, level_idx=level_idx)
|
||||
if (self.post_norm_block_ids is not None) and (i in self.post_norm_block_ids):
|
||||
index = self.post_norm_block_ids.index(i)
|
||||
x = self.post_norms[index](x) # for InternImage-H/G
|
||||
if not self.post_norm or self.center_feature_scale:
|
||||
x = self.norm(x)
|
||||
if return_wo_downsample:
|
||||
x_ = x.clone()
|
||||
if self.downsample is not None:
|
||||
x, shape = self.downsample(x, shape=shape)
|
||||
|
||||
if return_wo_downsample:
|
||||
return x, x_, shape
|
||||
return x, shape
|
||||
|
||||
@BACKBONES.register_module()
|
||||
class FlashInternImage(nn.Module):
|
||||
r""" FlashInternImage
|
||||
A PyTorch impl based on :
|
||||
`InternImage: Exploring Large-Scale Vision Foundation Models with Deformable Convolutions` -
|
||||
https://arxiv.org/pdf/2103.14030
|
||||
'DCNv4': TODO: add arxiv
|
||||
Args:
|
||||
core_op (str): Core operator. Default: 'DCNv4'
|
||||
channels (int): Number of the first stage. Default: 64
|
||||
depths (list): Depth of each block. Default: [3, 4, 18, 5]
|
||||
groups (list): Groups of each block. Default: [3, 6, 12, 24]
|
||||
num_classes (int): Number of classes. Default: 1000
|
||||
mlp_ratio (float): Ratio of mlp hidden dim to embedding dim. Default: 4.
|
||||
drop_rate (float): Probability of an element to be zeroed. Default: 0.
|
||||
drop_path_rate (float): Stochastic depth rate. Default: 0.2
|
||||
act_layer (str): Activation layer. Default: 'GELU'
|
||||
norm_layer (str): Normalization layer. Default: 'LN'
|
||||
layer_scale (bool): Whether to use layer scale. Default: False
|
||||
cls_scale (bool): Whether to use class scale. Default: False
|
||||
with_cp (bool): Use checkpoint or not. Using checkpoint will save some
|
||||
dw_kernel_size (int): Size of the dwconv. Default: None
|
||||
use_clip_projector (bool): Whether to use clip projector. Default: False
|
||||
level2_post_norm (bool): Whether to use level2 post norm. Default: False
|
||||
level2_post_norm_block_ids (list): Indexes of post norm blocks. Default: None
|
||||
res_post_norm (bool): Whether to use res post norm. Default: False
|
||||
center_feature_scale (bool): Whether to use center feature scale. Default: False
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
core_op='DCNv4',
|
||||
channels=64,
|
||||
depths=[3, 4, 18, 5],
|
||||
groups=[3, 6, 12, 24],
|
||||
num_classes=1000,
|
||||
mlp_ratio=4.,
|
||||
drop_rate=0.,
|
||||
drop_path_rate=0.2,
|
||||
drop_path_type='linear',
|
||||
act_layer='GELU',
|
||||
norm_layer='LN',
|
||||
layer_scale=None,
|
||||
offset_scale=0.5,
|
||||
post_norm=False,
|
||||
with_cp=False,
|
||||
mlp_fc2_bias=False,
|
||||
dcn_output_bias=False,
|
||||
dw_kernel_size=None, # for InternImage-H/G
|
||||
level2_post_norm=False, # for InternImage-H/G
|
||||
level2_post_norm_block_ids=None, # for InternImage-H/G
|
||||
res_post_norm=False, # for InternImage-H/G
|
||||
center_feature_scale=False, # for InternImage-H/G
|
||||
out_indices=(0, 1, 2, 3),
|
||||
init_cfg=None,
|
||||
**kwargs):
|
||||
super().__init__()
|
||||
self.core_op = core_op
|
||||
self.num_levels = len(depths)
|
||||
self.depths = depths
|
||||
self.channels = channels
|
||||
self.num_features = int(channels * 2**(self.num_levels - 1))
|
||||
self.post_norm = post_norm
|
||||
self.mlp_ratio = mlp_ratio
|
||||
self.init_cfg = init_cfg
|
||||
self.out_indices = out_indices
|
||||
self.level2_post_norm_block_ids = level2_post_norm_block_ids
|
||||
logger = get_root_logger()
|
||||
logger.info(f'using core type: {core_op}')
|
||||
logger.info(f'using activation layer: {act_layer}')
|
||||
logger.info(f'using main norm layer: {norm_layer}')
|
||||
logger.info(f'using dpr: {drop_path_type}, {drop_path_rate}')
|
||||
logger.info(f"level2_post_norm: {level2_post_norm}")
|
||||
logger.info(f"level2_post_norm_block_ids: {level2_post_norm_block_ids}")
|
||||
logger.info(f"res_post_norm: {res_post_norm}")
|
||||
|
||||
in_chans = 3
|
||||
self.patch_embed = StemLayer(in_chans=in_chans,
|
||||
out_chans=channels,
|
||||
act_layer=act_layer,
|
||||
norm_layer=norm_layer)
|
||||
self.pos_drop = nn.Dropout(p=drop_rate)
|
||||
|
||||
dpr = [
|
||||
x.item() for x in torch.linspace(0, drop_path_rate, sum(depths))
|
||||
]
|
||||
if drop_path_type == 'uniform':
|
||||
for i in range(len(dpr)):
|
||||
dpr[i] = drop_path_rate
|
||||
|
||||
self.levels = nn.ModuleList()
|
||||
for i in range(self.num_levels):
|
||||
post_norm_block_ids = level2_post_norm_block_ids if level2_post_norm and (
|
||||
i == 2) else None # for InternImage-H/G
|
||||
|
||||
level = InternImageBlock(
|
||||
core_op=getattr(DCNv4, core_op),
|
||||
channels=int(channels * 2**i),
|
||||
depth=depths[i],
|
||||
groups=groups[i],
|
||||
mlp_ratio=self.mlp_ratio,
|
||||
drop=drop_rate,
|
||||
drop_path=dpr[sum(depths[:i]):sum(depths[:i + 1])],
|
||||
act_layer=act_layer,
|
||||
norm_layer=norm_layer,
|
||||
post_norm=post_norm,
|
||||
downsample=(i < self.num_levels - 1),
|
||||
downsample_layer = DownsampleLayer,
|
||||
layer_scale=layer_scale,
|
||||
offset_scale=offset_scale,
|
||||
with_cp=with_cp,
|
||||
mlp_fc2_bias=mlp_fc2_bias,
|
||||
dcn_output_bias=dcn_output_bias,
|
||||
dw_kernel_size=dw_kernel_size, # for InternImage-H/G
|
||||
post_norm_block_ids=post_norm_block_ids, # for InternImage-H/G
|
||||
res_post_norm=res_post_norm, # for InternImage-H/G
|
||||
center_feature_scale=center_feature_scale # for InternImage-H/G
|
||||
)
|
||||
self.levels.append(level)
|
||||
|
||||
self.num_layers = len(depths)
|
||||
self.apply(self._init_weights)
|
||||
self.apply(self._init_deform_weights)
|
||||
|
||||
def init_weights(self):
|
||||
logger = get_root_logger()
|
||||
if self.init_cfg is None:
|
||||
logger.warn(f'No pre-trained weights for '
|
||||
f'{self.__class__.__name__}, '
|
||||
f'training start from scratch')
|
||||
for m in self.modules():
|
||||
if isinstance(m, nn.Linear):
|
||||
trunc_normal_init(m, std=.02, bias=0.)
|
||||
elif isinstance(m, nn.LayerNorm):
|
||||
constant_init(m, 1.0)
|
||||
else:
|
||||
assert 'checkpoint' in self.init_cfg, f'Only support ' \
|
||||
f'specify `Pretrained` in ' \
|
||||
f'`init_cfg` in ' \
|
||||
f'{self.__class__.__name__} '
|
||||
ckpt = _load_checkpoint(self.init_cfg.checkpoint,
|
||||
logger=logger,
|
||||
map_location='cpu')
|
||||
if 'state_dict' in ckpt:
|
||||
_state_dict = ckpt['state_dict']
|
||||
elif 'model_ema' in ckpt:
|
||||
_state_dict = ckpt['model_ema']
|
||||
elif 'model' in ckpt:
|
||||
_state_dict = ckpt['model']
|
||||
else:
|
||||
_state_dict = ckpt
|
||||
|
||||
state_dict = OrderedDict()
|
||||
for k, v in _state_dict.items():
|
||||
if k.startswith('backbone.'):
|
||||
state_dict[k[9:]] = v
|
||||
else:
|
||||
state_dict[k] = v
|
||||
|
||||
# strip prefix of state_dict
|
||||
if list(state_dict.keys())[0].startswith('module.'):
|
||||
state_dict = {k[7:]: v for k, v in state_dict.items()}
|
||||
|
||||
# load state_dict
|
||||
meg = self.load_state_dict(state_dict, False)
|
||||
logger.info(meg)
|
||||
|
||||
def _init_weights(self, m):
|
||||
if isinstance(m, nn.Linear):
|
||||
trunc_normal_(m.weight, std=.02)
|
||||
if isinstance(m, nn.Linear) and m.bias is not None:
|
||||
nn.init.constant_(m.bias, 0)
|
||||
elif isinstance(m, nn.LayerNorm):
|
||||
nn.init.constant_(m.bias, 0)
|
||||
nn.init.constant_(m.weight, 1.0)
|
||||
|
||||
def _init_deform_weights(self, m):
|
||||
if isinstance(m, getattr(DCNv4, self.core_op)):
|
||||
m._reset_parameters()
|
||||
|
||||
@torch.jit.ignore
|
||||
def lr_decay_keywards(self, decay_ratio=0.87):
|
||||
lr_ratios = {}
|
||||
|
||||
# blocks
|
||||
idx = 0
|
||||
for i in range(4):
|
||||
layer_num = 3 - i # 3 2 1 0
|
||||
for j in range(self.depths[layer_num]):
|
||||
block_num = self.depths[layer_num] - j - 1
|
||||
tag = 'levels.{}.blocks.{}.'.format(layer_num, block_num)
|
||||
decay = 1.0 * (decay_ratio**idx)
|
||||
lr_ratios[tag] = decay
|
||||
idx += 1
|
||||
# patch_embed (before stage-1)
|
||||
lr_ratios["patch_embed"] = lr_ratios['levels.0.blocks.0.']
|
||||
# levels.0.downsample (between stage-1 and stage-2)
|
||||
lr_ratios["levels.0.downsample"] = lr_ratios['levels.1.blocks.0.']
|
||||
lr_ratios["levels.0.norm"] = lr_ratios['levels.1.blocks.0.']
|
||||
# levels.1.downsample (between stage-2 and stage-3)
|
||||
lr_ratios["levels.1.downsample"] = lr_ratios['levels.2.blocks.0.']
|
||||
lr_ratios["levels.1.norm"] = lr_ratios['levels.2.blocks.0.']
|
||||
# levels.2.downsample (between stage-3 and stage-4)
|
||||
lr_ratios["levels.2.downsample"] = lr_ratios['levels.3.blocks.0.']
|
||||
lr_ratios["levels.2.norm"] = lr_ratios['levels.3.blocks.0.']
|
||||
return lr_ratios
|
||||
|
||||
def forward(self, x):
|
||||
x = self.patch_embed(x)
|
||||
N, H, W, C = x.shape
|
||||
x = x.view(N, H*W, C)
|
||||
|
||||
shape=(H, W)
|
||||
seq_out = []
|
||||
for level_idx, level in enumerate(self.levels):
|
||||
old_shape = shape
|
||||
x, x_ , shape = level(x, return_wo_downsample=True, shape=shape, level_idx=level_idx)
|
||||
if level_idx in self.out_indices:
|
||||
h, w= old_shape
|
||||
seq_out.append(x_.reshape(N, h, w, -1).permute(0, 3, 1, 2))
|
||||
return seq_out
|
||||
13
detection/mmdet_custom/models/dense_heads/__init__.py
Normal file
13
detection/mmdet_custom/models/dense_heads/__init__.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
from .deformable_detr_head import DeformableDETRHead
|
||||
from .detr_head import DETRHead
|
||||
from .dino_head import DINOHead
|
||||
from .msda import FlashMultiScaleDeformableAttention
|
||||
from .bbox_head import DCNv4FCBBoxHead
|
||||
from .mask_rcnn import MaskRCNN_
|
||||
__all__ = ['DeformableDETRHead', 'DETRHead', 'DINOHead']
|
||||
222
detection/mmdet_custom/models/dense_heads/bbox_head.py
Normal file
222
detection/mmdet_custom/models/dense_heads/bbox_head.py
Normal file
@@ -0,0 +1,222 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch.nn as nn
|
||||
from mmcv.cnn import ConvModule
|
||||
|
||||
from mmdet.models.builder import HEADS
|
||||
from mmdet.models.utils import build_linear_layer
|
||||
from mmdet.models.roi_heads.bbox_heads.bbox_head import BBoxHead
|
||||
import DCNv4
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@HEADS.register_module()
|
||||
class DCNv4FCBBoxHead(BBoxHead):
|
||||
r"""More general bbox head, with shared conv and fc layers and two optional
|
||||
separated branches.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
/-> cls convs -> cls fcs -> cls
|
||||
shared convs -> shared fcs
|
||||
\-> reg convs -> reg fcs -> reg
|
||||
""" # noqa: W605
|
||||
|
||||
def __init__(self,
|
||||
num_shared_convs=0,
|
||||
num_shared_fcs=0,
|
||||
num_cls_convs=0,
|
||||
num_cls_fcs=0,
|
||||
num_reg_convs=0,
|
||||
num_reg_fcs=0,
|
||||
conv_out_channels=256,
|
||||
fc_out_channels=1024,
|
||||
conv_cfg=None,
|
||||
norm_cfg=None,
|
||||
init_cfg=None,
|
||||
with_dcn=True,
|
||||
short_cut=False,
|
||||
*args,
|
||||
**kwargs):
|
||||
super(DCNv4FCBBoxHead, self).__init__(
|
||||
*args, init_cfg=init_cfg, **kwargs)
|
||||
assert (num_shared_convs + num_shared_fcs + num_cls_convs +
|
||||
num_cls_fcs + num_reg_convs + num_reg_fcs > 0)
|
||||
if num_cls_convs > 0 or num_reg_convs > 0:
|
||||
assert num_shared_fcs == 0
|
||||
if not self.with_cls:
|
||||
assert num_cls_convs == 0 and num_cls_fcs == 0
|
||||
if not self.with_reg:
|
||||
assert num_reg_convs == 0 and num_reg_fcs == 0
|
||||
self.num_shared_convs = num_shared_convs
|
||||
self.num_shared_fcs = num_shared_fcs
|
||||
self.num_cls_convs = num_cls_convs
|
||||
self.num_cls_fcs = num_cls_fcs
|
||||
self.num_reg_convs = num_reg_convs
|
||||
self.num_reg_fcs = num_reg_fcs
|
||||
self.conv_out_channels = conv_out_channels
|
||||
self.fc_out_channels = fc_out_channels
|
||||
self.conv_cfg = conv_cfg
|
||||
self.norm_cfg = norm_cfg
|
||||
self.with_dcn = with_dcn
|
||||
self.short_cut = short_cut
|
||||
|
||||
# add shared convs and fcs
|
||||
self.shared_convs, self.shared_fcs, last_layer_dim = \
|
||||
self._add_conv_fc_branch(
|
||||
self.num_shared_convs, self.num_shared_fcs, self.in_channels,
|
||||
True)
|
||||
self.shared_out_channels = last_layer_dim
|
||||
|
||||
# add cls specific branch
|
||||
self.cls_convs, self.cls_fcs, self.cls_last_dim = \
|
||||
self._add_conv_fc_branch(
|
||||
self.num_cls_convs, self.num_cls_fcs, self.shared_out_channels)
|
||||
|
||||
# add reg specific branch
|
||||
self.reg_convs, self.reg_fcs, self.reg_last_dim = \
|
||||
self._add_conv_fc_branch(
|
||||
self.num_reg_convs, self.num_reg_fcs, self.shared_out_channels)
|
||||
|
||||
if self.num_shared_fcs == 0 and not self.with_avg_pool:
|
||||
if self.num_cls_fcs == 0:
|
||||
self.cls_last_dim *= self.roi_feat_area
|
||||
if self.num_reg_fcs == 0:
|
||||
self.reg_last_dim *= self.roi_feat_area
|
||||
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
# reconstruct fc_cls and fc_reg since input channels are changed
|
||||
if self.with_cls:
|
||||
if self.custom_cls_channels:
|
||||
cls_channels = self.loss_cls.get_cls_channels(self.num_classes)
|
||||
else:
|
||||
cls_channels = self.num_classes + 1
|
||||
self.fc_cls = build_linear_layer(
|
||||
self.cls_predictor_cfg,
|
||||
in_features=self.cls_last_dim,
|
||||
out_features=cls_channels)
|
||||
if self.with_reg:
|
||||
out_dim_reg = (4 if self.reg_class_agnostic else 4 *
|
||||
self.num_classes)
|
||||
self.fc_reg = build_linear_layer(
|
||||
self.reg_predictor_cfg,
|
||||
in_features=self.reg_last_dim,
|
||||
out_features=out_dim_reg)
|
||||
|
||||
if init_cfg is None:
|
||||
# when init_cfg is None,
|
||||
# It has been set to
|
||||
# [[dict(type='Normal', std=0.01, override=dict(name='fc_cls'))],
|
||||
# [dict(type='Normal', std=0.001, override=dict(name='fc_reg'))]
|
||||
# after `super(ConvFCBBoxHead, self).__init__()`
|
||||
# we only need to append additional configuration
|
||||
# for `shared_fcs`, `cls_fcs` and `reg_fcs`
|
||||
self.init_cfg += [
|
||||
dict(
|
||||
type='Xavier',
|
||||
distribution='uniform',
|
||||
override=[
|
||||
dict(name='shared_fcs'),
|
||||
dict(name='cls_fcs'),
|
||||
dict(name='reg_fcs')
|
||||
])
|
||||
]
|
||||
|
||||
def _add_conv_fc_branch(self,
|
||||
num_branch_convs,
|
||||
num_branch_fcs,
|
||||
in_channels,
|
||||
is_shared=False):
|
||||
"""Add shared or separable branch.
|
||||
|
||||
convs -> avg pool (optional) -> fcs
|
||||
"""
|
||||
last_layer_dim = in_channels
|
||||
# add branch specific conv layers
|
||||
branch_convs = nn.ModuleList()
|
||||
if num_branch_convs > 0:
|
||||
for i in range(num_branch_convs):
|
||||
conv_in_channels = (
|
||||
last_layer_dim if i == 0 else self.conv_out_channels)
|
||||
if self.with_dcn:
|
||||
assert False, 'TODO: support DCNv4 in the task head'
|
||||
conv = DCNv4.DCNv4(
|
||||
conv_in_channels,
|
||||
self.conv_out_channels,
|
||||
)
|
||||
else:
|
||||
conv = ConvModule(
|
||||
conv_in_channels,
|
||||
self.conv_out_channels,
|
||||
3,
|
||||
padding=1,
|
||||
conv_cfg=self.conv_cfg,
|
||||
norm_cfg=self.norm_cfg)
|
||||
|
||||
branch_convs.append(conv)
|
||||
last_layer_dim = self.conv_out_channels
|
||||
# add branch specific fc layers
|
||||
branch_fcs = nn.ModuleList()
|
||||
if num_branch_fcs > 0:
|
||||
# for shared branch, only consider self.with_avg_pool
|
||||
# for separated branches, also consider self.num_shared_fcs
|
||||
if (is_shared
|
||||
or self.num_shared_fcs == 0) and not self.with_avg_pool:
|
||||
last_layer_dim *= self.roi_feat_area
|
||||
for i in range(num_branch_fcs):
|
||||
fc_in_channels = (
|
||||
last_layer_dim if i == 0 else self.fc_out_channels)
|
||||
branch_fcs.append(
|
||||
nn.Linear(fc_in_channels, self.fc_out_channels))
|
||||
last_layer_dim = self.fc_out_channels
|
||||
return branch_convs, branch_fcs, last_layer_dim
|
||||
|
||||
def forward(self, x):
|
||||
# shared part
|
||||
if self.with_dcn:
|
||||
N, C, H, W = x.shape
|
||||
x = x.permute(0, 2, 3, 1).view(N, H*W, C)
|
||||
if self.num_shared_convs > 0:
|
||||
for conv in self.shared_convs:
|
||||
if self.short_cut:
|
||||
x = x + conv(x, shape=(H, W))
|
||||
else:
|
||||
x = conv(x, shape=(H, W))
|
||||
else:
|
||||
if self.num_shared_convs > 0:
|
||||
for conv in self.shared_convs:
|
||||
x = conv(x)
|
||||
if self.num_shared_fcs > 0:
|
||||
if self.with_avg_pool:
|
||||
x = self.avg_pool(x)
|
||||
x = x.flatten(1)
|
||||
|
||||
for fc in self.shared_fcs:
|
||||
x = self.relu(fc(x))
|
||||
# separate branches
|
||||
x_cls = x
|
||||
x_reg = x
|
||||
|
||||
for conv in self.cls_convs:
|
||||
x_cls = conv(x_cls)
|
||||
if x_cls.dim() > 2:
|
||||
if self.with_avg_pool:
|
||||
x_cls = self.avg_pool(x_cls)
|
||||
x_cls = x_cls.flatten(1)
|
||||
for fc in self.cls_fcs:
|
||||
x_cls = self.relu(fc(x_cls))
|
||||
|
||||
for conv in self.reg_convs:
|
||||
x_reg = conv(x_reg)
|
||||
if x_reg.dim() > 2:
|
||||
if self.with_avg_pool:
|
||||
x_reg = self.avg_pool(x_reg)
|
||||
x_reg = x_reg.flatten(1)
|
||||
for fc in self.reg_fcs:
|
||||
x_reg = self.relu(fc(x_reg))
|
||||
|
||||
cls_score = self.fc_cls(x_cls) if self.with_cls else None
|
||||
bbox_pred = self.fc_reg(x_reg) if self.with_reg else None
|
||||
return cls_score, bbox_pred
|
||||
@@ -0,0 +1,332 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import copy
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from mmcv.cnn import Linear, bias_init_with_prob, constant_init
|
||||
from mmcv.runner import force_fp32
|
||||
|
||||
from mmdet.core import multi_apply
|
||||
from mmdet.models.utils.transformer import inverse_sigmoid
|
||||
from mmdet.models.builder import HEADS
|
||||
from .detr_head import DETRHead
|
||||
|
||||
|
||||
@HEADS.register_module(force=True)
|
||||
class DeformableDETRHead(DETRHead):
|
||||
"""Head of DeformDETR: Deformable DETR: Deformable Transformers for End-to-
|
||||
End Object Detection.
|
||||
|
||||
Code is modified from the `official github repo
|
||||
<https://github.com/fundamentalvision/Deformable-DETR>`_.
|
||||
|
||||
More details can be found in the `paper
|
||||
<https://arxiv.org/abs/2010.04159>`_ .
|
||||
|
||||
Args:
|
||||
with_box_refine (bool): Whether to refine the reference points
|
||||
in the decoder. Defaults to False.
|
||||
as_two_stage (bool) : Whether to generate the proposal from
|
||||
the outputs of encoder.
|
||||
transformer (obj:`ConfigDict`): ConfigDict is used for building
|
||||
the Encoder and Decoder.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
*args,
|
||||
with_box_refine=False,
|
||||
as_two_stage=False,
|
||||
transformer=None,
|
||||
use_2fc_cls_branch=False,
|
||||
**kwargs):
|
||||
self.with_box_refine = with_box_refine
|
||||
self.as_two_stage = as_two_stage
|
||||
self.use_2fc_cls_branch = use_2fc_cls_branch
|
||||
if self.as_two_stage:
|
||||
transformer['as_two_stage'] = self.as_two_stage
|
||||
|
||||
super(DeformableDETRHead, self).__init__(
|
||||
*args, transformer=transformer, **kwargs)
|
||||
|
||||
def _init_layers(self):
|
||||
"""Initialize classification branch and regression branch of head."""
|
||||
|
||||
if not self.use_2fc_cls_branch:
|
||||
fc_cls = Linear(self.embed_dims, self.cls_out_channels)
|
||||
else:
|
||||
fc_cls = nn.Sequential(*[
|
||||
Linear(self.embed_dims, int(self.embed_dims * 1.5)),
|
||||
nn.LayerNorm(int(self.embed_dims * 1.5)),
|
||||
nn.GELU(),
|
||||
Linear(int(self.embed_dims * 1.5), self.cls_out_channels),
|
||||
])
|
||||
fc_cls.out_features = self.cls_out_channels
|
||||
|
||||
reg_branch = []
|
||||
for _ in range(self.num_reg_fcs):
|
||||
reg_branch.append(Linear(self.embed_dims, self.embed_dims))
|
||||
reg_branch.append(nn.ReLU())
|
||||
reg_branch.append(Linear(self.embed_dims, 4))
|
||||
reg_branch = nn.Sequential(*reg_branch)
|
||||
|
||||
def _get_clones(module, N):
|
||||
return nn.ModuleList([copy.deepcopy(module) for i in range(N)])
|
||||
|
||||
# last reg_branch is used to generate proposal from
|
||||
# encode feature map when as_two_stage is True.
|
||||
num_pred = (self.transformer.decoder.num_layers + 1) if \
|
||||
self.as_two_stage else self.transformer.decoder.num_layers
|
||||
|
||||
if self.with_box_refine:
|
||||
self.cls_branches = _get_clones(fc_cls, num_pred)
|
||||
self.reg_branches = _get_clones(reg_branch, num_pred)
|
||||
else:
|
||||
|
||||
self.cls_branches = nn.ModuleList(
|
||||
[fc_cls for _ in range(num_pred)])
|
||||
self.reg_branches = nn.ModuleList(
|
||||
[reg_branch for _ in range(num_pred)])
|
||||
|
||||
if not self.as_two_stage:
|
||||
self.query_embedding = nn.Embedding(
|
||||
self.num_query,
|
||||
self.embed_dims * 2)
|
||||
|
||||
def init_weights(self):
|
||||
"""Initialize weights of the DeformDETR head."""
|
||||
self.transformer.init_weights()
|
||||
if self.loss_cls.use_sigmoid:
|
||||
bias_init = bias_init_with_prob(0.01)
|
||||
if not self.use_2fc_cls_branch:
|
||||
for m in self.cls_branches:
|
||||
nn.init.constant_(m.bias, bias_init)
|
||||
for m in self.reg_branches:
|
||||
constant_init(m[-1], 0, bias=0)
|
||||
nn.init.constant_(self.reg_branches[0][-1].bias.data[2:], -2.0)
|
||||
if self.as_two_stage:
|
||||
for m in self.reg_branches:
|
||||
nn.init.constant_(m[-1].bias.data[2:], 0.0)
|
||||
|
||||
def forward(self, mlvl_feats, img_metas):
|
||||
"""Forward function.
|
||||
|
||||
Args:
|
||||
mlvl_feats (tuple[Tensor]): Features from the upstream
|
||||
network, each is a 4D-tensor with shape
|
||||
(N, C, H, W).
|
||||
img_metas (list[dict]): List of image information.
|
||||
|
||||
Returns:
|
||||
all_cls_scores (Tensor): Outputs from the classification head, \
|
||||
shape [nb_dec, bs, num_query, cls_out_channels]. Note \
|
||||
cls_out_channels should includes background.
|
||||
all_bbox_preds (Tensor): Sigmoid outputs from the regression \
|
||||
head with normalized coordinate format (cx, cy, w, h). \
|
||||
Shape [nb_dec, bs, num_query, 4].
|
||||
enc_outputs_class (Tensor): The score of each point on encode \
|
||||
feature map, has shape (N, h*w, num_class). Only when \
|
||||
as_two_stage is True it would be returned, otherwise \
|
||||
`None` would be returned.
|
||||
enc_outputs_coord (Tensor): The proposal generate from the \
|
||||
encode feature map, has shape (N, h*w, 4). Only when \
|
||||
as_two_stage is True it would be returned, otherwise \
|
||||
`None` would be returned.
|
||||
"""
|
||||
|
||||
batch_size = mlvl_feats[0].size(0)
|
||||
input_img_h, input_img_w = img_metas[0]['batch_input_shape']
|
||||
img_masks = mlvl_feats[0].new_ones(
|
||||
(batch_size, input_img_h, input_img_w))
|
||||
for img_id in range(batch_size):
|
||||
img_h, img_w, _ = img_metas[img_id]['img_shape']
|
||||
img_masks[img_id, :img_h, :img_w] = 0
|
||||
|
||||
mlvl_masks = []
|
||||
mlvl_positional_encodings = []
|
||||
for feat in mlvl_feats:
|
||||
mlvl_masks.append(
|
||||
F.interpolate(img_masks[None],
|
||||
size=feat.shape[-2:]).to(torch.bool).squeeze(0))
|
||||
mlvl_positional_encodings.append(
|
||||
self.positional_encoding(mlvl_masks[-1]))
|
||||
|
||||
query_embeds = None
|
||||
if not self.as_two_stage:
|
||||
query_embeds = self.query_embedding.weight
|
||||
hs, init_reference, inter_references, \
|
||||
enc_outputs_class, enc_outputs_coord = self.transformer(
|
||||
mlvl_feats,
|
||||
mlvl_masks,
|
||||
query_embeds,
|
||||
mlvl_positional_encodings,
|
||||
reg_branches=self.reg_branches if self.with_box_refine else None, # noqa:E501
|
||||
cls_branches=self.cls_branches if self.as_two_stage else None # noqa:E501
|
||||
)
|
||||
hs = hs.permute(0, 2, 1, 3)
|
||||
outputs_classes = []
|
||||
outputs_coords = []
|
||||
|
||||
for lvl in range(hs.shape[0]):
|
||||
if lvl == 0:
|
||||
reference = init_reference
|
||||
else:
|
||||
reference = inter_references[lvl - 1]
|
||||
reference = inverse_sigmoid(reference)
|
||||
outputs_class = self.cls_branches[lvl](hs[lvl])
|
||||
tmp = self.reg_branches[lvl](hs[lvl])
|
||||
if reference.shape[-1] == 4:
|
||||
tmp += reference
|
||||
else:
|
||||
assert reference.shape[-1] == 2
|
||||
tmp[..., :2] += reference
|
||||
outputs_coord = tmp.sigmoid()
|
||||
outputs_classes.append(outputs_class)
|
||||
outputs_coords.append(outputs_coord)
|
||||
|
||||
outputs_classes = torch.stack(outputs_classes)
|
||||
outputs_coords = torch.stack(outputs_coords)
|
||||
if self.as_two_stage:
|
||||
return outputs_classes, outputs_coords, \
|
||||
enc_outputs_class, \
|
||||
enc_outputs_coord.sigmoid()
|
||||
else:
|
||||
return outputs_classes, outputs_coords, \
|
||||
None, None
|
||||
|
||||
@force_fp32(apply_to=('all_cls_scores_list', 'all_bbox_preds_list'))
|
||||
def loss(self,
|
||||
all_cls_scores,
|
||||
all_bbox_preds,
|
||||
enc_cls_scores,
|
||||
enc_bbox_preds,
|
||||
gt_bboxes_list,
|
||||
gt_labels_list,
|
||||
img_metas,
|
||||
gt_bboxes_ignore=None):
|
||||
""""Loss function.
|
||||
|
||||
Args:
|
||||
all_cls_scores (Tensor): Classification score of all
|
||||
decoder layers, has shape
|
||||
[nb_dec, bs, num_query, cls_out_channels].
|
||||
all_bbox_preds (Tensor): Sigmoid regression
|
||||
outputs of all decode layers. Each is a 4D-tensor with
|
||||
normalized coordinate format (cx, cy, w, h) and shape
|
||||
[nb_dec, bs, num_query, 4].
|
||||
enc_cls_scores (Tensor): Classification scores of
|
||||
points on encode feature map , has shape
|
||||
(N, h*w, num_classes). Only be passed when as_two_stage is
|
||||
True, otherwise is None.
|
||||
enc_bbox_preds (Tensor): Regression results of each points
|
||||
on the encode feature map, has shape (N, h*w, 4). Only be
|
||||
passed when as_two_stage is True, otherwise is None.
|
||||
gt_bboxes_list (list[Tensor]): Ground truth bboxes for each image
|
||||
with shape (num_gts, 4) in [tl_x, tl_y, br_x, br_y] format.
|
||||
gt_labels_list (list[Tensor]): Ground truth class indices for each
|
||||
image with shape (num_gts, ).
|
||||
img_metas (list[dict]): List of image meta information.
|
||||
gt_bboxes_ignore (list[Tensor], optional): Bounding boxes
|
||||
which can be ignored for each image. Default None.
|
||||
|
||||
Returns:
|
||||
dict[str, Tensor]: A dictionary of loss components.
|
||||
"""
|
||||
assert gt_bboxes_ignore is None, \
|
||||
f'{self.__class__.__name__} only supports ' \
|
||||
f'for gt_bboxes_ignore setting to None.'
|
||||
|
||||
num_dec_layers = len(all_cls_scores)
|
||||
all_gt_bboxes_list = [gt_bboxes_list for _ in range(num_dec_layers)]
|
||||
all_gt_labels_list = [gt_labels_list for _ in range(num_dec_layers)]
|
||||
all_gt_bboxes_ignore_list = [
|
||||
gt_bboxes_ignore for _ in range(num_dec_layers)
|
||||
]
|
||||
img_metas_list = [img_metas for _ in range(num_dec_layers)]
|
||||
|
||||
losses_cls, losses_bbox, losses_iou = multi_apply(
|
||||
self.loss_single, all_cls_scores, all_bbox_preds,
|
||||
all_gt_bboxes_list, all_gt_labels_list, img_metas_list,
|
||||
all_gt_bboxes_ignore_list)
|
||||
|
||||
loss_dict = dict()
|
||||
# loss of proposal generated from encode feature map.
|
||||
if enc_cls_scores is not None:
|
||||
binary_labels_list = [
|
||||
torch.zeros_like(gt_labels_list[i])
|
||||
for i in range(len(img_metas))
|
||||
]
|
||||
enc_loss_cls, enc_losses_bbox, enc_losses_iou = \
|
||||
self.loss_single(enc_cls_scores, enc_bbox_preds,
|
||||
gt_bboxes_list, binary_labels_list,
|
||||
img_metas, gt_bboxes_ignore)
|
||||
loss_dict['enc_loss_cls'] = enc_loss_cls
|
||||
loss_dict['enc_loss_bbox'] = enc_losses_bbox
|
||||
loss_dict['enc_loss_iou'] = enc_losses_iou
|
||||
|
||||
# loss from the last decoder layer
|
||||
loss_dict['loss_cls'] = losses_cls[-1]
|
||||
loss_dict['loss_bbox'] = losses_bbox[-1]
|
||||
loss_dict['loss_iou'] = losses_iou[-1]
|
||||
# loss from other decoder layers
|
||||
num_dec_layer = 0
|
||||
for loss_cls_i, loss_bbox_i, loss_iou_i in zip(losses_cls[:-1],
|
||||
losses_bbox[:-1],
|
||||
losses_iou[:-1]):
|
||||
loss_dict[f'd{num_dec_layer}.loss_cls'] = loss_cls_i
|
||||
loss_dict[f'd{num_dec_layer}.loss_bbox'] = loss_bbox_i
|
||||
loss_dict[f'd{num_dec_layer}.loss_iou'] = loss_iou_i
|
||||
num_dec_layer += 1
|
||||
return loss_dict
|
||||
|
||||
@force_fp32(apply_to=('all_cls_scores_list', 'all_bbox_preds_list'))
|
||||
def get_bboxes(self,
|
||||
all_cls_scores,
|
||||
all_bbox_preds,
|
||||
enc_cls_scores,
|
||||
enc_bbox_preds,
|
||||
img_metas,
|
||||
rescale=False):
|
||||
"""Transform network outputs for a batch into bbox predictions.
|
||||
|
||||
Args:
|
||||
all_cls_scores (Tensor): Classification score of all
|
||||
decoder layers, has shape
|
||||
[nb_dec, bs, num_query, cls_out_channels].
|
||||
all_bbox_preds (Tensor): Sigmoid regression
|
||||
outputs of all decode layers. Each is a 4D-tensor with
|
||||
normalized coordinate format (cx, cy, w, h) and shape
|
||||
[nb_dec, bs, num_query, 4].
|
||||
enc_cls_scores (Tensor): Classification scores of
|
||||
points on encode feature map , has shape
|
||||
(N, h*w, num_classes). Only be passed when as_two_stage is
|
||||
True, otherwise is None.
|
||||
enc_bbox_preds (Tensor): Regression results of each points
|
||||
on the encode feature map, has shape (N, h*w, 4). Only be
|
||||
passed when as_two_stage is True, otherwise is None.
|
||||
img_metas (list[dict]): Meta information of each image.
|
||||
rescale (bool, optional): If True, return boxes in original
|
||||
image space. Default False.
|
||||
|
||||
Returns:
|
||||
list[list[Tensor, Tensor]]: Each item in result_list is 2-tuple. \
|
||||
The first item is an (n, 5) tensor, where the first 4 columns \
|
||||
are bounding box positions (tl_x, tl_y, br_x, br_y) and the \
|
||||
5-th column is a score between 0 and 1. The second item is a \
|
||||
(n,) tensor where each item is the predicted class label of \
|
||||
the corresponding box.
|
||||
"""
|
||||
cls_scores = all_cls_scores[-1]
|
||||
bbox_preds = all_bbox_preds[-1]
|
||||
|
||||
result_list = []
|
||||
for img_id in range(len(img_metas)):
|
||||
cls_score = cls_scores[img_id]
|
||||
bbox_pred = bbox_preds[img_id]
|
||||
img_shape = img_metas[img_id]['img_shape']
|
||||
scale_factor = img_metas[img_id]['scale_factor']
|
||||
proposals = self._get_bboxes_single(cls_score, bbox_pred,
|
||||
img_shape, scale_factor,
|
||||
rescale)
|
||||
result_list.append(proposals)
|
||||
return result_list
|
||||
954
detection/mmdet_custom/models/dense_heads/detr_head.py
Normal file
954
detection/mmdet_custom/models/dense_heads/detr_head.py
Normal file
@@ -0,0 +1,954 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from mmcv.cnn import Conv2d, Linear, build_activation_layer
|
||||
from mmcv.cnn.bricks.transformer import FFN, build_positional_encoding
|
||||
from mmcv.runner import force_fp32
|
||||
|
||||
from mmdet.core import (bbox_cxcywh_to_xyxy, bbox_xyxy_to_cxcywh,
|
||||
build_assigner, build_sampler, multi_apply,
|
||||
reduce_mean)
|
||||
from mmdet.models.utils import build_transformer
|
||||
from mmdet.models.builder import HEADS, build_loss
|
||||
from mmdet.models.dense_heads.anchor_free_head import AnchorFreeHead
|
||||
import numpy as np
|
||||
|
||||
|
||||
@HEADS.register_module(force=True)
|
||||
class DETRHead(AnchorFreeHead):
|
||||
"""Implements the DETR transformer head.
|
||||
|
||||
See `paper: End-to-End Object Detection with Transformers
|
||||
<https://arxiv.org/pdf/2005.12872>`_ for details.
|
||||
|
||||
Args:
|
||||
num_classes (int): Number of categories excluding the background.
|
||||
in_channels (int): Number of channels in the input feature map.
|
||||
num_query (int): Number of query in Transformer.
|
||||
num_reg_fcs (int, optional): Number of fully-connected layers used in
|
||||
`FFN`, which is then used for the regression head. Default 2.
|
||||
transformer (obj:`mmcv.ConfigDict`|dict): Config for transformer.
|
||||
Default: None.
|
||||
sync_cls_avg_factor (bool): Whether to sync the avg_factor of
|
||||
all ranks. Default to False.
|
||||
positional_encoding (obj:`mmcv.ConfigDict`|dict):
|
||||
Config for position encoding.
|
||||
loss_cls (obj:`mmcv.ConfigDict`|dict): Config of the
|
||||
classification loss. Default `CrossEntropyLoss`.
|
||||
loss_bbox (obj:`mmcv.ConfigDict`|dict): Config of the
|
||||
regression loss. Default `L1Loss`.
|
||||
loss_iou (obj:`mmcv.ConfigDict`|dict): Config of the
|
||||
regression iou loss. Default `GIoULoss`.
|
||||
tran_cfg (obj:`mmcv.ConfigDict`|dict): Training config of
|
||||
transformer head.
|
||||
test_cfg (obj:`mmcv.ConfigDict`|dict): Testing config of
|
||||
transformer head.
|
||||
init_cfg (dict or list[dict], optional): Initialization config dict.
|
||||
Default: None
|
||||
"""
|
||||
|
||||
_version = 2
|
||||
|
||||
def __init__(self,
|
||||
num_classes,
|
||||
in_channels,
|
||||
num_query=100,
|
||||
num_reg_fcs=2,
|
||||
transformer=None,
|
||||
sync_cls_avg_factor=False,
|
||||
positional_encoding=dict(
|
||||
type='SinePositionalEncoding',
|
||||
num_feats=128,
|
||||
normalize=True),
|
||||
loss_cls=dict(
|
||||
type='CrossEntropyLoss',
|
||||
bg_cls_weight=0.1,
|
||||
use_sigmoid=False,
|
||||
loss_weight=1.0,
|
||||
class_weight=1.0),
|
||||
loss_bbox=dict(type='L1Loss', loss_weight=5.0),
|
||||
loss_iou=dict(type='GIoULoss', loss_weight=2.0),
|
||||
train_cfg=dict(
|
||||
assigner=dict(
|
||||
type='HungarianAssigner',
|
||||
cls_cost=dict(type='ClassificationCost', weight=1.),
|
||||
reg_cost=dict(type='BBoxL1Cost', weight=5.0),
|
||||
iou_cost=dict(
|
||||
type='IoUCost', iou_mode='giou', weight=2.0))),
|
||||
test_cfg=dict(max_per_img=100),
|
||||
init_cfg=None,
|
||||
**kwargs):
|
||||
# NOTE here use `AnchorFreeHead` instead of `TransformerHead`,
|
||||
# since it brings inconvenience when the initialization of
|
||||
# `AnchorFreeHead` is called.
|
||||
super(AnchorFreeHead, self).__init__(init_cfg)
|
||||
|
||||
self.bg_cls_weight = 0
|
||||
self.sync_cls_avg_factor = sync_cls_avg_factor
|
||||
class_weight = loss_cls.get('class_weight', None)
|
||||
if class_weight is not None and (self.__class__ is DETRHead):
|
||||
# assert isinstance(class_weight, float), 'Expected ' \
|
||||
# 'class_weight to have type float. Found ' \
|
||||
# f'{type(class_weight)}.'
|
||||
|
||||
# NOTE following the official DETR rep0, bg_cls_weight means
|
||||
# relative classification weight of the no-object class.
|
||||
bg_cls_weight = loss_cls.get('bg_cls_weight', class_weight)
|
||||
|
||||
assert isinstance(bg_cls_weight, float), 'Expected ' \
|
||||
'bg_cls_weight to have type float. Found ' \
|
||||
f'{type(bg_cls_weight)}.'
|
||||
if isinstance(class_weight, list):
|
||||
class_weight.append(bg_cls_weight)
|
||||
class_weight = np.array(class_weight)
|
||||
class_weight = torch.from_numpy(class_weight)
|
||||
class_weight = torch.ones(num_classes + 1) * class_weight
|
||||
elif isinstance(class_weight, float):
|
||||
class_weight = torch.ones(num_classes + 1) * class_weight
|
||||
# set background class as the last indice
|
||||
class_weight[num_classes] = bg_cls_weight
|
||||
loss_cls.update({'class_weight': class_weight})
|
||||
if 'bg_cls_weight' in loss_cls:
|
||||
loss_cls.pop('bg_cls_weight')
|
||||
self.bg_cls_weight = bg_cls_weight
|
||||
|
||||
if train_cfg:
|
||||
assert 'assigner' in train_cfg, 'assigner should be provided ' \
|
||||
'when train_cfg is set.'
|
||||
assigner = train_cfg['assigner']
|
||||
# assert loss_cls['loss_weight'] == assigner['cls_cost']['weight'],
|
||||
# 'The classification weight for loss and matcher should be' \
|
||||
# 'exactly the same.'
|
||||
# assert loss_bbox['loss_weight'] == assigner['reg_cost'][
|
||||
# 'weight'], 'The regression L1 weight for loss and matcher '\
|
||||
# 'should be exactly the same.'
|
||||
# assert loss_iou['loss_weight'] == assigner['iou_cost']['weight'],
|
||||
# 'The regression iou weight for loss and matcher should be' \
|
||||
# 'exactly the same.'
|
||||
self.assigner = build_assigner(assigner)
|
||||
# DETR sampling=False, so use PseudoSampler
|
||||
sampler_cfg = dict(type='PseudoSampler')
|
||||
self.sampler = build_sampler(sampler_cfg, context=self)
|
||||
|
||||
self.num_query = num_query
|
||||
self.num_classes = num_classes
|
||||
self.in_channels = in_channels
|
||||
self.num_reg_fcs = num_reg_fcs
|
||||
self.train_cfg = train_cfg
|
||||
self.test_cfg = test_cfg
|
||||
self.fp16_enabled = False
|
||||
self.loss_cls = build_loss(loss_cls)
|
||||
self.loss_bbox = build_loss(loss_bbox)
|
||||
self.loss_iou = build_loss(loss_iou)
|
||||
|
||||
if self.loss_cls.use_sigmoid:
|
||||
self.cls_out_channels = num_classes
|
||||
else:
|
||||
self.cls_out_channels = num_classes + 1
|
||||
self.act_cfg = transformer.get('act_cfg',
|
||||
dict(type='ReLU', inplace=True))
|
||||
self.activate = build_activation_layer(self.act_cfg)
|
||||
self.positional_encoding = build_positional_encoding(
|
||||
positional_encoding)
|
||||
self.transformer = build_transformer(transformer)
|
||||
self.embed_dims = self.transformer.embed_dims
|
||||
assert 'num_feats' in positional_encoding
|
||||
num_feats = positional_encoding['num_feats']
|
||||
assert num_feats * 2 == self.embed_dims, 'embed_dims should' \
|
||||
f' be exactly 2 times of num_feats. Found {self.embed_dims}' \
|
||||
f' and {num_feats}.'
|
||||
|
||||
self._init_layers()
|
||||
|
||||
def _init_layers(self):
|
||||
"""Initialize layers of the transformer head."""
|
||||
self.input_proj = Conv2d(
|
||||
self.in_channels, self.embed_dims, kernel_size=1)
|
||||
self.fc_cls = Linear(self.embed_dims, self.cls_out_channels)
|
||||
self.reg_ffn = FFN(
|
||||
self.embed_dims,
|
||||
self.embed_dims,
|
||||
self.num_reg_fcs,
|
||||
self.act_cfg,
|
||||
dropout=0.0,
|
||||
add_residual=False)
|
||||
self.fc_reg = Linear(self.embed_dims, 4)
|
||||
self.query_embedding = nn.Embedding(self.num_query, self.embed_dims)
|
||||
|
||||
def init_weights(self):
|
||||
"""Initialize weights of the transformer head."""
|
||||
# The initialization for transformer is important
|
||||
self.transformer.init_weights()
|
||||
|
||||
def _load_from_state_dict(self, state_dict, prefix, local_metadata, strict,
|
||||
missing_keys, unexpected_keys, error_msgs):
|
||||
"""load checkpoints."""
|
||||
# NOTE here use `AnchorFreeHead` instead of `TransformerHead`,
|
||||
# since `AnchorFreeHead._load_from_state_dict` should not be
|
||||
# called here. Invoking the default `Module._load_from_state_dict`
|
||||
# is enough.
|
||||
|
||||
# Names of some parameters in has been changed.
|
||||
version = local_metadata.get('version', None)
|
||||
if (version is None or version < 2) and self.__class__ is DETRHead:
|
||||
convert_dict = {
|
||||
'.self_attn.': '.attentions.0.',
|
||||
'.ffn.': '.ffns.0.',
|
||||
'.multihead_attn.': '.attentions.1.',
|
||||
'.decoder.norm.': '.decoder.post_norm.'
|
||||
}
|
||||
state_dict_keys = list(state_dict.keys())
|
||||
for k in state_dict_keys:
|
||||
for ori_key, convert_key in convert_dict.items():
|
||||
if ori_key in k:
|
||||
convert_key = k.replace(ori_key, convert_key)
|
||||
state_dict[convert_key] = state_dict[k]
|
||||
del state_dict[k]
|
||||
|
||||
super(AnchorFreeHead,
|
||||
self)._load_from_state_dict(state_dict, prefix, local_metadata,
|
||||
strict, missing_keys,
|
||||
unexpected_keys, error_msgs)
|
||||
|
||||
def forward(self, feats, img_metas):
|
||||
"""Forward function.
|
||||
|
||||
Args:
|
||||
feats (tuple[Tensor]): Features from the upstream network, each is
|
||||
a 4D-tensor.
|
||||
img_metas (list[dict]): List of image information.
|
||||
|
||||
Returns:
|
||||
tuple[list[Tensor], list[Tensor]]: Outputs for all scale levels.
|
||||
|
||||
- all_cls_scores_list (list[Tensor]): Classification scores \
|
||||
for each scale level. Each is a 4D-tensor with shape \
|
||||
[nb_dec, bs, num_query, cls_out_channels]. Note \
|
||||
`cls_out_channels` should includes background.
|
||||
- all_bbox_preds_list (list[Tensor]): Sigmoid regression \
|
||||
outputs for each scale level. Each is a 4D-tensor with \
|
||||
normalized coordinate format (cx, cy, w, h) and shape \
|
||||
[nb_dec, bs, num_query, 4].
|
||||
"""
|
||||
num_levels = len(feats)
|
||||
img_metas_list = [img_metas for _ in range(num_levels)]
|
||||
return multi_apply(self.forward_single, feats, img_metas_list)
|
||||
|
||||
def forward_single(self, x, img_metas):
|
||||
""""Forward function for a single feature level.
|
||||
|
||||
Args:
|
||||
x (Tensor): Input feature from backbone's single stage, shape
|
||||
[bs, c, h, w].
|
||||
img_metas (list[dict]): List of image information.
|
||||
|
||||
Returns:
|
||||
all_cls_scores (Tensor): Outputs from the classification head,
|
||||
shape [nb_dec, bs, num_query, cls_out_channels]. Note
|
||||
cls_out_channels should includes background.
|
||||
all_bbox_preds (Tensor): Sigmoid outputs from the regression
|
||||
head with normalized coordinate format (cx, cy, w, h).
|
||||
Shape [nb_dec, bs, num_query, 4].
|
||||
"""
|
||||
# construct binary masks which used for the transformer.
|
||||
# NOTE following the official DETR repo, non-zero values representing
|
||||
# ignored positions, while zero values means valid positions.
|
||||
batch_size = x.size(0)
|
||||
input_img_h, input_img_w = img_metas[0]['batch_input_shape']
|
||||
masks = x.new_ones((batch_size, input_img_h, input_img_w))
|
||||
for img_id in range(batch_size):
|
||||
img_h, img_w, _ = img_metas[img_id]['img_shape']
|
||||
masks[img_id, :img_h, :img_w] = 0
|
||||
|
||||
x = self.input_proj(x)
|
||||
# interpolate masks to have the same spatial shape with x
|
||||
masks = F.interpolate(
|
||||
masks.unsqueeze(1), size=x.shape[-2:]).to(torch.bool).squeeze(1)
|
||||
# position encoding
|
||||
pos_embed = self.positional_encoding(masks) # [bs, embed_dim, h, w]
|
||||
# outs_dec: [nb_dec, bs, num_query, embed_dim]
|
||||
outs_dec, _ = self.transformer(x, masks, self.query_embedding.weight,
|
||||
pos_embed)
|
||||
|
||||
all_cls_scores = self.fc_cls(outs_dec)
|
||||
all_bbox_preds = self.fc_reg(self.activate(
|
||||
self.reg_ffn(outs_dec))).sigmoid()
|
||||
return all_cls_scores, all_bbox_preds
|
||||
|
||||
@force_fp32(apply_to=('all_cls_scores_list', 'all_bbox_preds_list'))
|
||||
def loss(self,
|
||||
all_cls_scores_list,
|
||||
all_bbox_preds_list,
|
||||
gt_bboxes_list,
|
||||
gt_labels_list,
|
||||
img_metas,
|
||||
gt_bboxes_ignore=None):
|
||||
""""Loss function.
|
||||
|
||||
Only outputs from the last feature level are used for computing
|
||||
losses by default.
|
||||
|
||||
Args:
|
||||
all_cls_scores_list (list[Tensor]): Classification outputs
|
||||
for each feature level. Each is a 4D-tensor with shape
|
||||
[nb_dec, bs, num_query, cls_out_channels].
|
||||
all_bbox_preds_list (list[Tensor]): Sigmoid regression
|
||||
outputs for each feature level. Each is a 4D-tensor with
|
||||
normalized coordinate format (cx, cy, w, h) and shape
|
||||
[nb_dec, bs, num_query, 4].
|
||||
gt_bboxes_list (list[Tensor]): Ground truth bboxes for each image
|
||||
with shape (num_gts, 4) in [tl_x, tl_y, br_x, br_y] format.
|
||||
gt_labels_list (list[Tensor]): Ground truth class indices for each
|
||||
image with shape (num_gts, ).
|
||||
img_metas (list[dict]): List of image meta information.
|
||||
gt_bboxes_ignore (list[Tensor], optional): Bounding boxes
|
||||
which can be ignored for each image. Default None.
|
||||
|
||||
Returns:
|
||||
dict[str, Tensor]: A dictionary of loss components.
|
||||
"""
|
||||
# NOTE defaultly only the outputs from the last feature scale is used.
|
||||
all_cls_scores = all_cls_scores_list[-1]
|
||||
all_bbox_preds = all_bbox_preds_list[-1]
|
||||
assert gt_bboxes_ignore is None, \
|
||||
'Only supports for gt_bboxes_ignore setting to None.'
|
||||
|
||||
num_dec_layers = len(all_cls_scores)
|
||||
all_gt_bboxes_list = [gt_bboxes_list for _ in range(num_dec_layers)]
|
||||
all_gt_labels_list = [gt_labels_list for _ in range(num_dec_layers)]
|
||||
all_gt_bboxes_ignore_list = [
|
||||
gt_bboxes_ignore for _ in range(num_dec_layers)
|
||||
]
|
||||
img_metas_list = [img_metas for _ in range(num_dec_layers)]
|
||||
|
||||
losses_cls, losses_bbox, losses_iou = multi_apply(
|
||||
self.loss_single, all_cls_scores, all_bbox_preds,
|
||||
all_gt_bboxes_list, all_gt_labels_list, img_metas_list,
|
||||
all_gt_bboxes_ignore_list)
|
||||
|
||||
loss_dict = dict()
|
||||
# loss from the last decoder layer
|
||||
loss_dict['loss_cls'] = losses_cls[-1]
|
||||
loss_dict['loss_bbox'] = losses_bbox[-1]
|
||||
loss_dict['loss_iou'] = losses_iou[-1]
|
||||
# loss from other decoder layers
|
||||
num_dec_layer = 0
|
||||
for loss_cls_i, loss_bbox_i, loss_iou_i in zip(losses_cls[:-1],
|
||||
losses_bbox[:-1],
|
||||
losses_iou[:-1]):
|
||||
loss_dict[f'd{num_dec_layer}.loss_cls'] = loss_cls_i
|
||||
loss_dict[f'd{num_dec_layer}.loss_bbox'] = loss_bbox_i
|
||||
loss_dict[f'd{num_dec_layer}.loss_iou'] = loss_iou_i
|
||||
num_dec_layer += 1
|
||||
return loss_dict
|
||||
|
||||
def get_fed_loss_classes(self, gt_classes, num_fed_loss_classes, num_classes, weight):
|
||||
"""
|
||||
Args:
|
||||
gt_classes: a long tensor of shape R that contains the gt class label of each proposal.
|
||||
num_fed_loss_classes: minimum number of classes to keep when calculating federated loss.
|
||||
Will sample negative classes if number of unique gt_classes is smaller than this value.
|
||||
num_classes: number of foreground classes
|
||||
weight: probabilities used to sample negative classes
|
||||
Returns:
|
||||
Tensor:
|
||||
classes to keep when calculating the federated loss, including both unique gt
|
||||
classes and sampled negative classes.
|
||||
"""
|
||||
unique_gt_classes = torch.unique(gt_classes)
|
||||
prob = unique_gt_classes.new_ones(num_classes + 1).float()
|
||||
prob[-1] = 0
|
||||
if len(unique_gt_classes) < num_fed_loss_classes:
|
||||
prob[:num_classes] = weight.float().clone()
|
||||
prob[unique_gt_classes] = 0
|
||||
sampled_negative_classes = torch.multinomial(
|
||||
prob, num_fed_loss_classes - len(unique_gt_classes), replacement=False
|
||||
)
|
||||
fed_loss_classes = torch.cat([unique_gt_classes, sampled_negative_classes])
|
||||
else:
|
||||
fed_loss_classes = unique_gt_classes
|
||||
return fed_loss_classes
|
||||
|
||||
def loss_single(self,
|
||||
cls_scores,
|
||||
bbox_preds,
|
||||
gt_bboxes_list,
|
||||
gt_labels_list,
|
||||
img_metas,
|
||||
gt_bboxes_ignore_list=None):
|
||||
""""Loss function for outputs from a single decoder layer of a single
|
||||
feature level.
|
||||
|
||||
Args:
|
||||
cls_scores (Tensor): Box score logits from a single decoder layer
|
||||
for all images. Shape [bs, num_query, cls_out_channels].
|
||||
bbox_preds (Tensor): Sigmoid outputs from a single decoder layer
|
||||
for all images, with normalized coordinate (cx, cy, w, h) and
|
||||
shape [bs, num_query, 4].
|
||||
gt_bboxes_list (list[Tensor]): Ground truth bboxes for each image
|
||||
with shape (num_gts, 4) in [tl_x, tl_y, br_x, br_y] format.
|
||||
gt_labels_list (list[Tensor]): Ground truth class indices for each
|
||||
image with shape (num_gts, ).
|
||||
img_metas (list[dict]): List of image meta information.
|
||||
gt_bboxes_ignore_list (list[Tensor], optional): Bounding
|
||||
boxes which can be ignored for each image. Default None.
|
||||
|
||||
Returns:
|
||||
dict[str, Tensor]: A dictionary of loss components for outputs from
|
||||
a single decoder layer.
|
||||
"""
|
||||
num_imgs = cls_scores.size(0)
|
||||
cls_scores_list = [cls_scores[i] for i in range(num_imgs)]
|
||||
bbox_preds_list = [bbox_preds[i] for i in range(num_imgs)]
|
||||
cls_reg_targets = self.get_targets(cls_scores_list, bbox_preds_list,
|
||||
gt_bboxes_list, gt_labels_list,
|
||||
img_metas, gt_bboxes_ignore_list)
|
||||
(labels_list, label_weights_list, bbox_targets_list, bbox_weights_list,
|
||||
num_total_pos, num_total_neg) = cls_reg_targets
|
||||
|
||||
labels = torch.cat(labels_list, 0)
|
||||
label_weights = torch.cat(label_weights_list, 0)
|
||||
bbox_targets = torch.cat(bbox_targets_list, 0)
|
||||
bbox_weights = torch.cat(bbox_weights_list, 0)
|
||||
|
||||
# classification loss
|
||||
cls_scores = cls_scores.reshape(-1, self.cls_out_channels)
|
||||
# construct weighted avg_factor to match with the official DETR repo
|
||||
cls_avg_factor = num_total_pos * 1.0 + \
|
||||
num_total_neg * self.bg_cls_weight
|
||||
if self.sync_cls_avg_factor:
|
||||
cls_avg_factor = reduce_mean(
|
||||
cls_scores.new_tensor([cls_avg_factor]))
|
||||
cls_avg_factor = max(cls_avg_factor, 1)
|
||||
|
||||
loss_cls = self.loss_cls(
|
||||
cls_scores, labels, label_weights, avg_factor=cls_avg_factor)
|
||||
|
||||
# Compute the average number of gt boxes across all gpus, for
|
||||
# normalization purposes
|
||||
num_total_pos = loss_cls.new_tensor([num_total_pos])
|
||||
num_total_pos = torch.clamp(reduce_mean(num_total_pos), min=1).item()
|
||||
|
||||
# construct factors used for rescale bboxes
|
||||
factors = []
|
||||
for img_meta, bbox_pred in zip(img_metas, bbox_preds):
|
||||
img_h, img_w, _ = img_meta['img_shape']
|
||||
factor = bbox_pred.new_tensor([img_w, img_h, img_w,
|
||||
img_h]).unsqueeze(0).repeat(
|
||||
bbox_pred.size(0), 1)
|
||||
factors.append(factor)
|
||||
factors = torch.cat(factors, 0)
|
||||
|
||||
# DETR regress the relative position of boxes (cxcywh) in the image,
|
||||
# thus the learning target is normalized by the image size. So here
|
||||
# we need to re-scale them for calculating IoU loss
|
||||
bbox_preds = bbox_preds.reshape(-1, 4)
|
||||
bboxes = bbox_cxcywh_to_xyxy(bbox_preds) * factors
|
||||
bboxes_gt = bbox_cxcywh_to_xyxy(bbox_targets) * factors
|
||||
|
||||
# regression IoU loss, defaultly GIoU loss
|
||||
loss_iou = self.loss_iou(
|
||||
bboxes, bboxes_gt, bbox_weights, avg_factor=num_total_pos)
|
||||
|
||||
# regression L1 loss
|
||||
loss_bbox = self.loss_bbox(
|
||||
bbox_preds, bbox_targets, bbox_weights, avg_factor=num_total_pos)
|
||||
return loss_cls, loss_bbox, loss_iou
|
||||
|
||||
def get_targets(self,
|
||||
cls_scores_list,
|
||||
bbox_preds_list,
|
||||
gt_bboxes_list,
|
||||
gt_labels_list,
|
||||
img_metas,
|
||||
gt_bboxes_ignore_list=None):
|
||||
""""Compute regression and classification targets for a batch image.
|
||||
|
||||
Outputs from a single decoder layer of a single feature level are used.
|
||||
|
||||
Args:
|
||||
cls_scores_list (list[Tensor]): Box score logits from a single
|
||||
decoder layer for each image with shape [num_query,
|
||||
cls_out_channels].
|
||||
bbox_preds_list (list[Tensor]): Sigmoid outputs from a single
|
||||
decoder layer for each image, with normalized coordinate
|
||||
(cx, cy, w, h) and shape [num_query, 4].
|
||||
gt_bboxes_list (list[Tensor]): Ground truth bboxes for each image
|
||||
with shape (num_gts, 4) in [tl_x, tl_y, br_x, br_y] format.
|
||||
gt_labels_list (list[Tensor]): Ground truth class indices for each
|
||||
image with shape (num_gts, ).
|
||||
img_metas (list[dict]): List of image meta information.
|
||||
gt_bboxes_ignore_list (list[Tensor], optional): Bounding
|
||||
boxes which can be ignored for each image. Default None.
|
||||
|
||||
Returns:
|
||||
tuple: a tuple containing the following targets.
|
||||
|
||||
- labels_list (list[Tensor]): Labels for all images.
|
||||
- label_weights_list (list[Tensor]): Label weights for all \
|
||||
images.
|
||||
- bbox_targets_list (list[Tensor]): BBox targets for all \
|
||||
images.
|
||||
- bbox_weights_list (list[Tensor]): BBox weights for all \
|
||||
images.
|
||||
- num_total_pos (int): Number of positive samples in all \
|
||||
images.
|
||||
- num_total_neg (int): Number of negative samples in all \
|
||||
images.
|
||||
"""
|
||||
assert gt_bboxes_ignore_list is None, \
|
||||
'Only supports for gt_bboxes_ignore setting to None.'
|
||||
num_imgs = len(cls_scores_list)
|
||||
gt_bboxes_ignore_list = [
|
||||
gt_bboxes_ignore_list for _ in range(num_imgs)
|
||||
]
|
||||
|
||||
(labels_list, label_weights_list, bbox_targets_list,
|
||||
bbox_weights_list, pos_inds_list, neg_inds_list) = multi_apply(
|
||||
self._get_target_single, cls_scores_list, bbox_preds_list,
|
||||
gt_bboxes_list, gt_labels_list, img_metas, gt_bboxes_ignore_list)
|
||||
num_total_pos = sum((inds.numel() for inds in pos_inds_list))
|
||||
num_total_neg = sum((inds.numel() for inds in neg_inds_list))
|
||||
return (labels_list, label_weights_list, bbox_targets_list,
|
||||
bbox_weights_list, num_total_pos, num_total_neg)
|
||||
|
||||
def _get_area_thr(self, img_shape, type):
|
||||
MIN_V = 0
|
||||
MAX_V = 1e10
|
||||
short_edge = min(img_shape[0], img_shape[1])
|
||||
if type == 'v1':
|
||||
DELTA = 4
|
||||
if short_edge <= 600:
|
||||
min_edge = 128 - DELTA
|
||||
max_edge = MAX_V
|
||||
elif 600 < short_edge <= 800:
|
||||
min_edge = 96 - DELTA
|
||||
max_edge = MAX_V
|
||||
elif 800 < short_edge <= 1000:
|
||||
min_edge = 64 - DELTA
|
||||
max_edge = MAX_V
|
||||
elif 1000 < short_edge <= 1200:
|
||||
min_edge = 32 - DELTA
|
||||
max_edge = MAX_V
|
||||
elif 1200 < short_edge <= 1400:
|
||||
min_edge = MIN_V
|
||||
max_edge = MAX_V
|
||||
else:
|
||||
min_edge = MIN_V
|
||||
max_edge = 2 + DELTA
|
||||
elif type == 'v2':
|
||||
if short_edge <= 1000:
|
||||
min_edge = 112
|
||||
max_edge = MAX_V
|
||||
elif 1000 < short_edge <= 1400:
|
||||
min_edge = 32
|
||||
max_edge = 160
|
||||
elif short_edge > 1400:
|
||||
min_edge = 0
|
||||
max_edge = 80
|
||||
elif type == 'v3':
|
||||
if short_edge <= 800:
|
||||
min_edge = 96
|
||||
max_edge = MAX_V
|
||||
elif 800 < short_edge <= 1000:
|
||||
min_edge = 64
|
||||
max_edge = MAX_V
|
||||
elif 1000 < short_edge <= 1400:
|
||||
min_edge = MIN_V
|
||||
max_edge = MAX_V
|
||||
elif 1400 < short_edge <= 1600:
|
||||
min_edge = MIN_V
|
||||
max_edge = 96
|
||||
elif short_edge > 1600:
|
||||
min_edge = MIN_V
|
||||
max_edge = 64
|
||||
elif type == 'v4':
|
||||
DELTA = 4
|
||||
if short_edge <= 800:
|
||||
min_edge = 96 - DELTA
|
||||
max_edge = MAX_V
|
||||
elif 800 < short_edge <= 1000:
|
||||
min_edge = 64 - DELTA
|
||||
max_edge = MAX_V
|
||||
elif 1000 < short_edge <= 1400:
|
||||
min_edge = MIN_V
|
||||
max_edge = MAX_V
|
||||
elif 1400 < short_edge <= 1600:
|
||||
min_edge = MIN_V
|
||||
max_edge = 64 + DELTA
|
||||
elif short_edge > 1600:
|
||||
min_edge = MIN_V
|
||||
max_edge = 32 + DELTA
|
||||
|
||||
return min_edge ** 2, max_edge ** 2
|
||||
|
||||
def _get_target_single(self,
|
||||
cls_score,
|
||||
bbox_pred,
|
||||
gt_bboxes,
|
||||
gt_labels,
|
||||
img_meta,
|
||||
gt_bboxes_ignore=None):
|
||||
""""Compute regression and classification targets for one image.
|
||||
|
||||
Outputs from a single decoder layer of a single feature level are used.
|
||||
|
||||
Args:
|
||||
cls_score (Tensor): Box score logits from a single decoder layer
|
||||
for one image. Shape [num_query, cls_out_channels].
|
||||
bbox_pred (Tensor): Sigmoid outputs from a single decoder layer
|
||||
for one image, with normalized coordinate (cx, cy, w, h) and
|
||||
shape [num_query, 4].
|
||||
gt_bboxes (Tensor): Ground truth bboxes for one image with
|
||||
shape (num_gts, 4) in [tl_x, tl_y, br_x, br_y] format.
|
||||
gt_labels (Tensor): Ground truth class indices for one image
|
||||
with shape (num_gts, ).
|
||||
img_meta (dict): Meta information for one image.
|
||||
gt_bboxes_ignore (Tensor, optional): Bounding boxes
|
||||
which can be ignored. Default None.
|
||||
|
||||
Returns:
|
||||
tuple[Tensor]: a tuple containing the following for one image.
|
||||
|
||||
- labels (Tensor): Labels of each image.
|
||||
- label_weights (Tensor]): Label weights of each image.
|
||||
- bbox_targets (Tensor): BBox targets of each image.
|
||||
- bbox_weights (Tensor): BBox weights of each image.
|
||||
- pos_inds (Tensor): Sampled positive indices for each image.
|
||||
- neg_inds (Tensor): Sampled negative indices for each image.
|
||||
"""
|
||||
|
||||
num_bboxes = bbox_pred.size(0)
|
||||
# assigner and sampler
|
||||
assign_result = self.assigner.assign(bbox_pred, cls_score, gt_bboxes,
|
||||
gt_labels, img_meta,
|
||||
gt_bboxes_ignore)
|
||||
sampling_result = self.sampler.sample(assign_result, bbox_pred,
|
||||
gt_bboxes)
|
||||
pos_inds = sampling_result.pos_inds
|
||||
neg_inds = sampling_result.neg_inds
|
||||
|
||||
# label targets
|
||||
labels = gt_bboxes.new_full((num_bboxes, ),
|
||||
self.num_classes,
|
||||
dtype=torch.long)
|
||||
labels[pos_inds] = gt_labels[sampling_result.pos_assigned_gt_inds]
|
||||
label_weights = gt_bboxes.new_ones(num_bboxes)
|
||||
|
||||
# bbox targets
|
||||
bbox_targets = torch.zeros_like(bbox_pred)
|
||||
bbox_weights = torch.zeros_like(bbox_pred)
|
||||
bbox_weights[pos_inds] = 1.0
|
||||
img_h, img_w, _ = img_meta['img_shape']
|
||||
|
||||
# DETR regress the relative position of boxes (cxcywh) in the image.
|
||||
# Thus the learning target should be normalized by the image size, also
|
||||
# the box format should be converted from defaultly x1y1x2y2 to cxcywh.
|
||||
factor = bbox_pred.new_tensor([img_w, img_h, img_w,
|
||||
img_h]).unsqueeze(0)
|
||||
pos_gt_bboxes_normalized = sampling_result.pos_gt_bboxes / factor
|
||||
pos_gt_bboxes_targets = bbox_xyxy_to_cxcywh(pos_gt_bboxes_normalized)
|
||||
bbox_targets[pos_inds] = pos_gt_bboxes_targets
|
||||
return (labels, label_weights, bbox_targets, bbox_weights, pos_inds,
|
||||
neg_inds)
|
||||
|
||||
# over-write because img_metas are needed as inputs for bbox_head.
|
||||
def forward_train(self,
|
||||
x,
|
||||
img_metas,
|
||||
gt_bboxes,
|
||||
gt_labels=None,
|
||||
gt_bboxes_ignore=None,
|
||||
proposal_cfg=None,
|
||||
**kwargs):
|
||||
"""Forward function for training mode.
|
||||
|
||||
Args:
|
||||
x (list[Tensor]): Features from backbone.
|
||||
img_metas (list[dict]): Meta information of each image, e.g.,
|
||||
image size, scaling factor, etc.
|
||||
gt_bboxes (Tensor): Ground truth bboxes of the image,
|
||||
shape (num_gts, 4).
|
||||
gt_labels (Tensor): Ground truth labels of each box,
|
||||
shape (num_gts,).
|
||||
gt_bboxes_ignore (Tensor): Ground truth bboxes to be
|
||||
ignored, shape (num_ignored_gts, 4).
|
||||
proposal_cfg (mmcv.Config): Test / postprocessing configuration,
|
||||
if None, test_cfg would be used.
|
||||
|
||||
Returns:
|
||||
dict[str, Tensor]: A dictionary of loss components.
|
||||
"""
|
||||
assert proposal_cfg is None, '"proposal_cfg" must be None'
|
||||
outs = self(x, img_metas)
|
||||
if gt_labels is None:
|
||||
loss_inputs = outs + (gt_bboxes, img_metas)
|
||||
else:
|
||||
loss_inputs = outs + (gt_bboxes, gt_labels, img_metas)
|
||||
losses = self.loss(*loss_inputs, gt_bboxes_ignore=gt_bboxes_ignore)
|
||||
return losses
|
||||
|
||||
@force_fp32(apply_to=('all_cls_scores_list', 'all_bbox_preds_list'))
|
||||
def get_bboxes(self,
|
||||
all_cls_scores_list,
|
||||
all_bbox_preds_list,
|
||||
img_metas,
|
||||
rescale=False):
|
||||
"""Transform network outputs for a batch into bbox predictions.
|
||||
|
||||
Args:
|
||||
all_cls_scores_list (list[Tensor]): Classification outputs
|
||||
for each feature level. Each is a 4D-tensor with shape
|
||||
[nb_dec, bs, num_query, cls_out_channels].
|
||||
all_bbox_preds_list (list[Tensor]): Sigmoid regression
|
||||
outputs for each feature level. Each is a 4D-tensor with
|
||||
normalized coordinate format (cx, cy, w, h) and shape
|
||||
[nb_dec, bs, num_query, 4].
|
||||
img_metas (list[dict]): Meta information of each image.
|
||||
rescale (bool, optional): If True, return boxes in original
|
||||
image space. Default False.
|
||||
|
||||
Returns:
|
||||
list[list[Tensor, Tensor]]: Each item in result_list is 2-tuple. \
|
||||
The first item is an (n, 5) tensor, where the first 4 columns \
|
||||
are bounding box positions (tl_x, tl_y, br_x, br_y) and the \
|
||||
5-th column is a score between 0 and 1. The second item is a \
|
||||
(n,) tensor where each item is the predicted class label of \
|
||||
the corresponding box.
|
||||
"""
|
||||
# NOTE defaultly only using outputs from the last feature level,
|
||||
# and only the outputs from the last decoder layer is used.
|
||||
cls_scores = all_cls_scores_list[-1][-1]
|
||||
bbox_preds = all_bbox_preds_list[-1][-1]
|
||||
|
||||
result_list = []
|
||||
for img_id in range(len(img_metas)):
|
||||
cls_score = cls_scores[img_id]
|
||||
bbox_pred = bbox_preds[img_id]
|
||||
img_shape = img_metas[img_id]['img_shape']
|
||||
scale_factor = img_metas[img_id]['scale_factor']
|
||||
proposals = self._get_bboxes_single(cls_score, bbox_pred,
|
||||
img_shape, scale_factor,
|
||||
rescale)
|
||||
result_list.append(proposals)
|
||||
|
||||
return result_list
|
||||
|
||||
def _get_bboxes_single(self,
|
||||
cls_score,
|
||||
bbox_pred,
|
||||
img_shape,
|
||||
scale_factor,
|
||||
rescale=False):
|
||||
"""Transform outputs from the last decoder layer into bbox predictions
|
||||
for each image.
|
||||
|
||||
Args:
|
||||
cls_score (Tensor): Box score logits from the last decoder layer
|
||||
for each image. Shape [num_query, cls_out_channels].
|
||||
bbox_pred (Tensor): Sigmoid outputs from the last decoder layer
|
||||
for each image, with coordinate format (cx, cy, w, h) and
|
||||
shape [num_query, 4].
|
||||
img_shape (tuple[int]): Shape of input image, (height, width, 3).
|
||||
scale_factor (ndarray, optional): Scale factor of the image arange
|
||||
as (w_scale, h_scale, w_scale, h_scale).
|
||||
rescale (bool, optional): If True, return boxes in original image
|
||||
space. Default False.
|
||||
|
||||
Returns:
|
||||
tuple[Tensor]: Results of detected bboxes and labels.
|
||||
|
||||
- det_bboxes: Predicted bboxes with shape [num_query, 5], \
|
||||
where the first 4 columns are bounding box positions \
|
||||
(tl_x, tl_y, br_x, br_y) and the 5-th column are scores \
|
||||
between 0 and 1.
|
||||
- det_labels: Predicted labels of the corresponding box with \
|
||||
shape [num_query].
|
||||
"""
|
||||
assert len(cls_score) == len(bbox_pred)
|
||||
max_per_img = self.test_cfg.get('max_per_img', self.num_query)
|
||||
# exclude background
|
||||
if self.loss_cls.use_sigmoid:
|
||||
cls_score = cls_score.sigmoid()
|
||||
scores, indexes = cls_score.view(-1).topk(max_per_img)
|
||||
det_labels = indexes % self.num_classes
|
||||
bbox_index = indexes // self.num_classes
|
||||
bbox_pred = bbox_pred[bbox_index]
|
||||
else:
|
||||
scores, det_labels = F.softmax(cls_score, dim=-1)[..., :-1].max(-1)
|
||||
scores, bbox_index = scores.topk(max_per_img)
|
||||
bbox_pred = bbox_pred[bbox_index]
|
||||
det_labels = det_labels[bbox_index]
|
||||
|
||||
det_bboxes = bbox_cxcywh_to_xyxy(bbox_pred)
|
||||
det_bboxes[:, 0::2] = det_bboxes[:, 0::2] * img_shape[1]
|
||||
det_bboxes[:, 1::2] = det_bboxes[:, 1::2] * img_shape[0]
|
||||
det_bboxes[:, 0::2].clamp_(min=0, max=img_shape[1])
|
||||
det_bboxes[:, 1::2].clamp_(min=0, max=img_shape[0])
|
||||
if rescale:
|
||||
det_bboxes /= det_bboxes.new_tensor(scale_factor)
|
||||
det_bboxes = torch.cat((det_bboxes, scores.unsqueeze(1)), -1)
|
||||
|
||||
return det_bboxes, det_labels
|
||||
|
||||
def simple_test_bboxes(self, feats, img_metas, rescale=False):
|
||||
"""Test det bboxes without test-time augmentation.
|
||||
|
||||
Args:
|
||||
feats (tuple[torch.Tensor]): Multi-level features from the
|
||||
upstream network, each is a 4D-tensor.
|
||||
img_metas (list[dict]): List of image information.
|
||||
rescale (bool, optional): Whether to rescale the results.
|
||||
Defaults to False.
|
||||
|
||||
Returns:
|
||||
list[tuple[Tensor, Tensor]]: Each item in result_list is 2-tuple.
|
||||
The first item is ``bboxes`` with shape (n, 5),
|
||||
where 5 represent (tl_x, tl_y, br_x, br_y, score).
|
||||
The shape of the second tensor in the tuple is ``labels``
|
||||
with shape (n,)
|
||||
"""
|
||||
# forward of this head requires img_metas
|
||||
outs = self.forward(feats, img_metas)
|
||||
results_list = self.get_bboxes(*outs, img_metas, rescale=rescale)
|
||||
return results_list
|
||||
|
||||
def forward_onnx(self, feats, img_metas):
|
||||
"""Forward function for exporting to ONNX.
|
||||
|
||||
Over-write `forward` because: `masks` is directly created with
|
||||
zero (valid position tag) and has the same spatial size as `x`.
|
||||
Thus the construction of `masks` is different from that in `forward`.
|
||||
|
||||
Args:
|
||||
feats (tuple[Tensor]): Features from the upstream network, each is
|
||||
a 4D-tensor.
|
||||
img_metas (list[dict]): List of image information.
|
||||
|
||||
Returns:
|
||||
tuple[list[Tensor], list[Tensor]]: Outputs for all scale levels.
|
||||
|
||||
- all_cls_scores_list (list[Tensor]): Classification scores \
|
||||
for each scale level. Each is a 4D-tensor with shape \
|
||||
[nb_dec, bs, num_query, cls_out_channels]. Note \
|
||||
`cls_out_channels` should includes background.
|
||||
- all_bbox_preds_list (list[Tensor]): Sigmoid regression \
|
||||
outputs for each scale level. Each is a 4D-tensor with \
|
||||
normalized coordinate format (cx, cy, w, h) and shape \
|
||||
[nb_dec, bs, num_query, 4].
|
||||
"""
|
||||
num_levels = len(feats)
|
||||
img_metas_list = [img_metas for _ in range(num_levels)]
|
||||
return multi_apply(self.forward_single_onnx, feats, img_metas_list)
|
||||
|
||||
def forward_single_onnx(self, x, img_metas):
|
||||
""""Forward function for a single feature level with ONNX exportation.
|
||||
|
||||
Args:
|
||||
x (Tensor): Input feature from backbone's single stage, shape
|
||||
[bs, c, h, w].
|
||||
img_metas (list[dict]): List of image information.
|
||||
|
||||
Returns:
|
||||
all_cls_scores (Tensor): Outputs from the classification head,
|
||||
shape [nb_dec, bs, num_query, cls_out_channels]. Note
|
||||
cls_out_channels should includes background.
|
||||
all_bbox_preds (Tensor): Sigmoid outputs from the regression
|
||||
head with normalized coordinate format (cx, cy, w, h).
|
||||
Shape [nb_dec, bs, num_query, 4].
|
||||
"""
|
||||
# Note `img_shape` is not dynamically traceable to ONNX,
|
||||
# since the related augmentation was done with numpy under
|
||||
# CPU. Thus `masks` is directly created with zeros (valid tag)
|
||||
# and the same spatial shape as `x`.
|
||||
# The difference between torch and exported ONNX model may be
|
||||
# ignored, since the same performance is achieved (e.g.
|
||||
# 40.1 vs 40.1 for DETR)
|
||||
batch_size = x.size(0)
|
||||
h, w = x.size()[-2:]
|
||||
masks = x.new_zeros((batch_size, h, w)) # [B,h,w]
|
||||
|
||||
x = self.input_proj(x)
|
||||
# interpolate masks to have the same spatial shape with x
|
||||
masks = F.interpolate(
|
||||
masks.unsqueeze(1), size=x.shape[-2:]).to(torch.bool).squeeze(1)
|
||||
pos_embed = self.positional_encoding(masks)
|
||||
outs_dec, _ = self.transformer(x, masks, self.query_embedding.weight,
|
||||
pos_embed)
|
||||
|
||||
all_cls_scores = self.fc_cls(outs_dec)
|
||||
all_bbox_preds = self.fc_reg(self.activate(
|
||||
self.reg_ffn(outs_dec))).sigmoid()
|
||||
return all_cls_scores, all_bbox_preds
|
||||
|
||||
def onnx_export(self, all_cls_scores_list, all_bbox_preds_list, img_metas):
|
||||
"""Transform network outputs into bbox predictions, with ONNX
|
||||
exportation.
|
||||
|
||||
Args:
|
||||
all_cls_scores_list (list[Tensor]): Classification outputs
|
||||
for each feature level. Each is a 4D-tensor with shape
|
||||
[nb_dec, bs, num_query, cls_out_channels].
|
||||
all_bbox_preds_list (list[Tensor]): Sigmoid regression
|
||||
outputs for each feature level. Each is a 4D-tensor with
|
||||
normalized coordinate format (cx, cy, w, h) and shape
|
||||
[nb_dec, bs, num_query, 4].
|
||||
img_metas (list[dict]): Meta information of each image.
|
||||
|
||||
Returns:
|
||||
tuple[Tensor, Tensor]: dets of shape [N, num_det, 5]
|
||||
and class labels of shape [N, num_det].
|
||||
"""
|
||||
assert len(img_metas) == 1, \
|
||||
'Only support one input image while in exporting to ONNX'
|
||||
|
||||
cls_scores = all_cls_scores_list[-1][-1]
|
||||
bbox_preds = all_bbox_preds_list[-1][-1]
|
||||
|
||||
# Note `img_shape` is not dynamically traceable to ONNX,
|
||||
# here `img_shape_for_onnx` (padded shape of image tensor)
|
||||
# is used.
|
||||
img_shape = img_metas[0]['img_shape_for_onnx']
|
||||
max_per_img = self.test_cfg.get('max_per_img', self.num_query)
|
||||
batch_size = cls_scores.size(0)
|
||||
# `batch_index_offset` is used for the gather of concatenated tensor
|
||||
batch_index_offset = torch.arange(batch_size).to(
|
||||
cls_scores.device) * max_per_img
|
||||
batch_index_offset = batch_index_offset.unsqueeze(1).expand(
|
||||
batch_size, max_per_img)
|
||||
|
||||
# supports dynamical batch inference
|
||||
if self.loss_cls.use_sigmoid:
|
||||
cls_scores = cls_scores.sigmoid()
|
||||
scores, indexes = cls_scores.view(batch_size, -1).topk(
|
||||
max_per_img, dim=1)
|
||||
det_labels = indexes % self.num_classes
|
||||
bbox_index = indexes // self.num_classes
|
||||
bbox_index = (bbox_index + batch_index_offset).view(-1)
|
||||
bbox_preds = bbox_preds.view(-1, 4)[bbox_index]
|
||||
bbox_preds = bbox_preds.view(batch_size, -1, 4)
|
||||
else:
|
||||
scores, det_labels = F.softmax(
|
||||
cls_scores, dim=-1)[..., :-1].max(-1)
|
||||
scores, bbox_index = scores.topk(max_per_img, dim=1)
|
||||
bbox_index = (bbox_index + batch_index_offset).view(-1)
|
||||
bbox_preds = bbox_preds.view(-1, 4)[bbox_index]
|
||||
det_labels = det_labels.view(-1)[bbox_index]
|
||||
bbox_preds = bbox_preds.view(batch_size, -1, 4)
|
||||
det_labels = det_labels.view(batch_size, -1)
|
||||
|
||||
det_bboxes = bbox_cxcywh_to_xyxy(bbox_preds)
|
||||
# use `img_shape_tensor` for dynamically exporting to ONNX
|
||||
img_shape_tensor = img_shape.flip(0).repeat(2) # [w,h,w,h]
|
||||
img_shape_tensor = img_shape_tensor.unsqueeze(0).unsqueeze(0).expand(
|
||||
batch_size, det_bboxes.size(1), 4)
|
||||
det_bboxes = det_bboxes * img_shape_tensor
|
||||
# dynamically clip bboxes
|
||||
x1, y1, x2, y2 = det_bboxes.split((1, 1, 1, 1), dim=-1)
|
||||
from mmdet.core.export import dynamic_clip_for_onnx
|
||||
x1, y1, x2, y2 = dynamic_clip_for_onnx(x1, y1, x2, y2, img_shape)
|
||||
det_bboxes = torch.cat([x1, y1, x2, y2], dim=-1)
|
||||
det_bboxes = torch.cat((det_bboxes, scores.unsqueeze(-1)), -1)
|
||||
|
||||
return det_bboxes, det_labels
|
||||
365
detection/mmdet_custom/models/dense_heads/dino_head.py
Normal file
365
detection/mmdet_custom/models/dense_heads/dino_head.py
Normal file
@@ -0,0 +1,365 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from mmdet.core import (bbox_cxcywh_to_xyxy, bbox_xyxy_to_cxcywh, multi_apply,
|
||||
reduce_mean)
|
||||
from ..utils import build_dn_generator
|
||||
from mmdet.models.utils.transformer import inverse_sigmoid
|
||||
from mmdet.models.builder import HEADS
|
||||
from .deformable_detr_head import DeformableDETRHead
|
||||
from mmcv.runner import force_fp32
|
||||
|
||||
|
||||
@HEADS.register_module()
|
||||
class DINOHead(DeformableDETRHead):
|
||||
|
||||
def __init__(self, *args, dn_cfg=None, **kwargs):
|
||||
super(DINOHead, self).__init__(*args, **kwargs)
|
||||
self._init_layers()
|
||||
self.init_denoising(dn_cfg)
|
||||
assert self.as_two_stage, \
|
||||
'as_two_stage must be True for DINO'
|
||||
assert self.with_box_refine, \
|
||||
'with_box_refine must be True for DINO'
|
||||
|
||||
def _init_layers(self):
|
||||
super()._init_layers()
|
||||
# NOTE The original repo of DINO set the num_embeddings 92 for coco,
|
||||
# 91 (0~90) of which represents target classes and the 92 (91)
|
||||
# indicates [Unknown] class. However, the embedding of unknown class
|
||||
# is not used in the original DINO
|
||||
self.label_embedding = nn.Embedding(self.cls_out_channels,
|
||||
self.embed_dims)
|
||||
|
||||
def init_denoising(self, dn_cfg):
|
||||
if dn_cfg is not None:
|
||||
dn_cfg['num_classes'] = self.num_classes
|
||||
dn_cfg['num_queries'] = self.num_query
|
||||
dn_cfg['hidden_dim'] = self.embed_dims
|
||||
self.dn_generator = build_dn_generator(dn_cfg)
|
||||
|
||||
def forward_train(self,
|
||||
x,
|
||||
img_metas,
|
||||
gt_bboxes,
|
||||
gt_labels=None,
|
||||
gt_bboxes_ignore=None,
|
||||
proposal_cfg=None,
|
||||
**kwargs):
|
||||
assert proposal_cfg is None, '"proposal_cfg" must be None'
|
||||
assert self.dn_generator is not None, '"dn_cfg" must be set'
|
||||
dn_label_query, dn_bbox_query, attn_mask, dn_meta = \
|
||||
self.dn_generator(gt_bboxes, gt_labels,
|
||||
self.label_embedding, img_metas)
|
||||
outs = self(x, img_metas, dn_label_query, dn_bbox_query, attn_mask)
|
||||
if gt_labels is None:
|
||||
loss_inputs = outs + (gt_bboxes, img_metas, dn_meta)
|
||||
else:
|
||||
loss_inputs = outs + (gt_bboxes, gt_labels, img_metas, dn_meta)
|
||||
losses = self.loss(*loss_inputs, gt_bboxes_ignore=gt_bboxes_ignore)
|
||||
return losses
|
||||
|
||||
def forward(self,
|
||||
mlvl_feats,
|
||||
img_metas,
|
||||
dn_label_query=None,
|
||||
dn_bbox_query=None,
|
||||
attn_mask=None):
|
||||
batch_size = mlvl_feats[0].size(0)
|
||||
input_img_h, input_img_w = img_metas[0]['batch_input_shape']
|
||||
img_masks = mlvl_feats[0].new_ones(
|
||||
(batch_size, input_img_h, input_img_w))
|
||||
for img_id in range(batch_size):
|
||||
if img_id >= len(img_metas): img_id = 0
|
||||
img_h, img_w, _ = img_metas[img_id]['img_shape']
|
||||
img_masks[img_id, :img_h, :img_w] = 0
|
||||
|
||||
mlvl_masks = []
|
||||
mlvl_positional_encodings = []
|
||||
for feat in mlvl_feats:
|
||||
mlvl_masks.append(
|
||||
F.interpolate(
|
||||
img_masks[None],
|
||||
size=feat.shape[-2:]).to(torch.bool).squeeze(0))
|
||||
mlvl_positional_encodings.append(
|
||||
self.positional_encoding(mlvl_masks[-1]))
|
||||
|
||||
query_embeds = None
|
||||
hs, inter_references, topk_score, topk_anchor = \
|
||||
self.transformer(
|
||||
mlvl_feats,
|
||||
mlvl_masks,
|
||||
query_embeds,
|
||||
mlvl_positional_encodings,
|
||||
dn_label_query,
|
||||
dn_bbox_query,
|
||||
attn_mask,
|
||||
reg_branches=self.reg_branches if self.with_box_refine else None, # noqa:E501
|
||||
cls_branches=self.cls_branches if self.as_two_stage else None # noqa:E501
|
||||
)
|
||||
hs = hs.permute(0, 2, 1, 3)
|
||||
|
||||
if dn_label_query is not None and dn_label_query.size(1) == 0:
|
||||
# NOTE: If there is no target in the image, the parameters of
|
||||
# label_embedding won't be used in producing loss, which raises
|
||||
# RuntimeError when using distributed mode.
|
||||
hs[0] += self.label_embedding.weight[0, 0] * 0.0
|
||||
|
||||
outputs_classes = []
|
||||
outputs_coords = []
|
||||
|
||||
for lvl in range(hs.shape[0]):
|
||||
reference = inter_references[lvl]
|
||||
reference = inverse_sigmoid(reference, eps=1e-3)
|
||||
outputs_class = self.cls_branches[lvl](hs[lvl])
|
||||
tmp = self.reg_branches[lvl](hs[lvl])
|
||||
if reference.shape[-1] == 4:
|
||||
tmp += reference
|
||||
else:
|
||||
assert reference.shape[-1] == 2
|
||||
tmp[..., :2] += reference
|
||||
outputs_coord = tmp.sigmoid()
|
||||
outputs_classes.append(outputs_class)
|
||||
outputs_coords.append(outputs_coord)
|
||||
|
||||
outputs_classes = torch.stack(outputs_classes)
|
||||
outputs_coords = torch.stack(outputs_coords)
|
||||
|
||||
return outputs_classes, outputs_coords, topk_score, topk_anchor
|
||||
|
||||
@force_fp32(apply_to=('all_cls_scores', 'all_bbox_preds'))
|
||||
def loss(self,
|
||||
all_cls_scores,
|
||||
all_bbox_preds,
|
||||
enc_topk_scores,
|
||||
enc_topk_anchors,
|
||||
gt_bboxes_list,
|
||||
gt_labels_list,
|
||||
img_metas,
|
||||
dn_meta=None,
|
||||
gt_bboxes_ignore=None):
|
||||
assert gt_bboxes_ignore is None, \
|
||||
f'{self.__class__.__name__} only supports ' \
|
||||
f'for gt_bboxes_ignore setting to None.'
|
||||
|
||||
loss_dict = dict()
|
||||
|
||||
# extract denoising and matching part of outputs
|
||||
all_cls_scores, all_bbox_preds, dn_cls_scores, dn_bbox_preds = \
|
||||
self.extract_dn_outputs(all_cls_scores, all_bbox_preds, dn_meta)
|
||||
|
||||
if enc_topk_scores is not None:
|
||||
# calculate loss from encode feature maps
|
||||
# NOTE The DeformDETR calculate binary cls loss
|
||||
# for all encoder embeddings, while DINO calculate
|
||||
# multi-class loss for topk embeddings.
|
||||
enc_loss_cls, enc_losses_bbox, enc_losses_iou = \
|
||||
self.loss_single(enc_topk_scores, enc_topk_anchors,
|
||||
gt_bboxes_list, gt_labels_list,
|
||||
img_metas, gt_bboxes_ignore)
|
||||
|
||||
# collate loss from encode feature maps
|
||||
loss_dict['interm_loss_cls'] = enc_loss_cls
|
||||
loss_dict['interm_loss_bbox'] = enc_losses_bbox
|
||||
loss_dict['interm_loss_iou'] = enc_losses_iou
|
||||
|
||||
# calculate loss from all decoder layers
|
||||
num_dec_layers = len(all_cls_scores)
|
||||
all_gt_bboxes_list = [gt_bboxes_list for _ in range(num_dec_layers)]
|
||||
all_gt_labels_list = [gt_labels_list for _ in range(num_dec_layers)]
|
||||
all_gt_bboxes_ignore_list = [
|
||||
gt_bboxes_ignore for _ in range(num_dec_layers)
|
||||
]
|
||||
img_metas_list = [img_metas for _ in range(num_dec_layers)]
|
||||
losses_cls, losses_bbox, losses_iou = multi_apply(
|
||||
self.loss_single, all_cls_scores, all_bbox_preds,
|
||||
all_gt_bboxes_list, all_gt_labels_list, img_metas_list,
|
||||
all_gt_bboxes_ignore_list)
|
||||
|
||||
# collate loss from the last decoder layer
|
||||
loss_dict['loss_cls'] = losses_cls[-1]
|
||||
loss_dict['loss_bbox'] = losses_bbox[-1]
|
||||
loss_dict['loss_iou'] = losses_iou[-1]
|
||||
|
||||
# collate loss from other decoder layers
|
||||
num_dec_layer = 0
|
||||
for loss_cls_i, loss_bbox_i, loss_iou_i in zip(losses_cls[:-1],
|
||||
losses_bbox[:-1],
|
||||
losses_iou[:-1]):
|
||||
loss_dict[f'd{num_dec_layer}.loss_cls'] = loss_cls_i
|
||||
loss_dict[f'd{num_dec_layer}.loss_bbox'] = loss_bbox_i
|
||||
loss_dict[f'd{num_dec_layer}.loss_iou'] = loss_iou_i
|
||||
num_dec_layer += 1
|
||||
|
||||
if dn_cls_scores is not None:
|
||||
# calculate denoising loss from all decoder layers
|
||||
dn_meta = [dn_meta for _ in img_metas]
|
||||
dn_losses_cls, dn_losses_bbox, dn_losses_iou = self.loss_dn(
|
||||
dn_cls_scores, dn_bbox_preds, gt_bboxes_list, gt_labels_list,
|
||||
img_metas, dn_meta)
|
||||
|
||||
# collate denoising loss
|
||||
loss_dict['dn_loss_cls'] = dn_losses_cls[-1]
|
||||
loss_dict['dn_loss_bbox'] = dn_losses_bbox[-1]
|
||||
loss_dict['dn_loss_iou'] = dn_losses_iou[-1]
|
||||
num_dec_layer = 0
|
||||
for loss_cls_i, loss_bbox_i, loss_iou_i in zip(
|
||||
dn_losses_cls[:-1], dn_losses_bbox[:-1],
|
||||
dn_losses_iou[:-1]):
|
||||
loss_dict[f'd{num_dec_layer}.dn_loss_cls'] = loss_cls_i
|
||||
loss_dict[f'd{num_dec_layer}.dn_loss_bbox'] = loss_bbox_i
|
||||
loss_dict[f'd{num_dec_layer}.dn_loss_iou'] = loss_iou_i
|
||||
num_dec_layer += 1
|
||||
|
||||
return loss_dict
|
||||
|
||||
def loss_dn(self, dn_cls_scores, dn_bbox_preds, gt_bboxes_list,
|
||||
gt_labels_list, img_metas, dn_meta):
|
||||
num_dec_layers = len(dn_cls_scores)
|
||||
all_gt_bboxes_list = [gt_bboxes_list for _ in range(num_dec_layers)]
|
||||
all_gt_labels_list = [gt_labels_list for _ in range(num_dec_layers)]
|
||||
img_metas_list = [img_metas for _ in range(num_dec_layers)]
|
||||
dn_meta_list = [dn_meta for _ in range(num_dec_layers)]
|
||||
return multi_apply(self.loss_dn_single, dn_cls_scores, dn_bbox_preds,
|
||||
all_gt_bboxes_list, all_gt_labels_list,
|
||||
img_metas_list, dn_meta_list)
|
||||
|
||||
def loss_dn_single(self, dn_cls_scores, dn_bbox_preds, gt_bboxes_list,
|
||||
gt_labels_list, img_metas, dn_meta):
|
||||
num_imgs = dn_cls_scores.size(0)
|
||||
bbox_preds_list = [dn_bbox_preds[i] for i in range(num_imgs)]
|
||||
cls_reg_targets = self.get_dn_target(bbox_preds_list, gt_bboxes_list,
|
||||
gt_labels_list, img_metas,
|
||||
dn_meta)
|
||||
(labels_list, label_weights_list, bbox_targets_list, bbox_weights_list,
|
||||
num_total_pos, num_total_neg) = cls_reg_targets
|
||||
labels = torch.cat(labels_list, 0)
|
||||
label_weights = torch.cat(label_weights_list, 0)
|
||||
bbox_targets = torch.cat(bbox_targets_list, 0)
|
||||
bbox_weights = torch.cat(bbox_weights_list, 0)
|
||||
|
||||
# classification loss
|
||||
cls_scores = dn_cls_scores.reshape(-1, self.cls_out_channels)
|
||||
# construct weighted avg_factor to match with the official DETR repo
|
||||
cls_avg_factor = \
|
||||
num_total_pos * 1.0 + num_total_neg * self.bg_cls_weight
|
||||
if self.sync_cls_avg_factor:
|
||||
cls_avg_factor = reduce_mean(
|
||||
cls_scores.new_tensor([cls_avg_factor]))
|
||||
cls_avg_factor = max(cls_avg_factor, 1)
|
||||
|
||||
if len(cls_scores) > 0:
|
||||
loss_cls = self.loss_cls(
|
||||
cls_scores, labels, label_weights, avg_factor=cls_avg_factor)
|
||||
else:
|
||||
loss_cls = torch.zeros( # TODO: How to better return zero loss
|
||||
1,
|
||||
dtype=cls_scores.dtype,
|
||||
device=cls_scores.device)
|
||||
|
||||
# Compute the average number of gt boxes across all gpus, for
|
||||
# normalization purposes
|
||||
num_total_pos = loss_cls.new_tensor([num_total_pos])
|
||||
num_total_pos = torch.clamp(reduce_mean(num_total_pos), min=1).item()
|
||||
|
||||
# construct factors used for rescale bboxes
|
||||
factors = []
|
||||
for img_meta, bbox_pred in zip(img_metas, dn_bbox_preds):
|
||||
img_h, img_w, _ = img_meta['img_shape']
|
||||
factor = bbox_pred.new_tensor([img_w, img_h, img_w,
|
||||
img_h]).unsqueeze(0).repeat(
|
||||
bbox_pred.size(0), 1)
|
||||
factors.append(factor)
|
||||
factors = torch.cat(factors, 0)
|
||||
|
||||
# DETR regress the relative position of boxes (cxcywh) in the image,
|
||||
# thus the learning target is normalized by the image size. So here
|
||||
# we need to re-scale them for calculating IoU loss
|
||||
bbox_preds = dn_bbox_preds.reshape(-1, 4)
|
||||
bboxes = bbox_cxcywh_to_xyxy(bbox_preds) * factors
|
||||
bboxes_gt = bbox_cxcywh_to_xyxy(bbox_targets) * factors
|
||||
|
||||
# regression IoU loss, defaultly GIoU loss
|
||||
loss_iou = self.loss_iou(
|
||||
bboxes, bboxes_gt, bbox_weights, avg_factor=num_total_pos)
|
||||
|
||||
# regression L1 loss
|
||||
loss_bbox = self.loss_bbox(
|
||||
bbox_preds, bbox_targets, bbox_weights, avg_factor=num_total_pos)
|
||||
return loss_cls, loss_bbox, loss_iou
|
||||
|
||||
def get_dn_target(self, dn_bbox_preds_list, gt_bboxes_list, gt_labels_list,
|
||||
img_metas, dn_meta):
|
||||
(labels_list, label_weights_list, bbox_targets_list, bbox_weights_list,
|
||||
pos_inds_list,
|
||||
neg_inds_list) = multi_apply(self._get_dn_target_single,
|
||||
dn_bbox_preds_list, gt_bboxes_list,
|
||||
gt_labels_list, img_metas, dn_meta)
|
||||
num_total_pos = sum((inds.numel() for inds in pos_inds_list))
|
||||
num_total_neg = sum((inds.numel() for inds in neg_inds_list))
|
||||
return (labels_list, label_weights_list, bbox_targets_list,
|
||||
bbox_weights_list, num_total_pos, num_total_neg)
|
||||
|
||||
def _get_dn_target_single(self, dn_bbox_pred, gt_bboxes, gt_labels,
|
||||
img_meta, dn_meta):
|
||||
num_groups = dn_meta['num_dn_group']
|
||||
pad_size = dn_meta['pad_size']
|
||||
assert pad_size % num_groups == 0
|
||||
single_pad = pad_size // num_groups
|
||||
num_bboxes = dn_bbox_pred.size(0)
|
||||
|
||||
if len(gt_labels) > 0:
|
||||
t = torch.range(0, len(gt_labels) - 1).long().cuda()
|
||||
t = t.unsqueeze(0).repeat(num_groups, 1)
|
||||
pos_assigned_gt_inds = t.flatten()
|
||||
pos_inds = (torch.tensor(range(num_groups)) *
|
||||
single_pad).long().cuda().unsqueeze(1) + t
|
||||
pos_inds = pos_inds.flatten()
|
||||
else:
|
||||
pos_inds = pos_assigned_gt_inds = torch.tensor([]).long().cuda()
|
||||
neg_inds = pos_inds + single_pad // 2
|
||||
|
||||
# label targets
|
||||
labels = gt_bboxes.new_full((num_bboxes, ),
|
||||
self.num_classes,
|
||||
dtype=torch.long)
|
||||
labels[pos_inds] = gt_labels[pos_assigned_gt_inds]
|
||||
label_weights = gt_bboxes.new_ones(num_bboxes)
|
||||
|
||||
# bbox targets
|
||||
bbox_targets = torch.zeros_like(dn_bbox_pred)
|
||||
bbox_weights = torch.zeros_like(dn_bbox_pred)
|
||||
bbox_weights[pos_inds] = 1.0
|
||||
img_h, img_w, _ = img_meta['img_shape']
|
||||
|
||||
# DETR regress the relative position of boxes (cxcywh) in the image.
|
||||
# Thus the learning target should be normalized by the image size, also
|
||||
# the box format should be converted from defaultly x1y1x2y2 to cxcywh.
|
||||
factor = dn_bbox_pred.new_tensor([img_w, img_h, img_w,
|
||||
img_h]).unsqueeze(0)
|
||||
gt_bboxes_normalized = gt_bboxes / factor
|
||||
gt_bboxes_targets = bbox_xyxy_to_cxcywh(gt_bboxes_normalized)
|
||||
bbox_targets[pos_inds] = gt_bboxes_targets.repeat([num_groups, 1])
|
||||
|
||||
return (labels, label_weights, bbox_targets, bbox_weights, pos_inds,
|
||||
neg_inds)
|
||||
|
||||
@staticmethod
|
||||
def extract_dn_outputs(all_cls_scores, all_bbox_preds, dn_meta):
|
||||
# if dn_meta and dn_meta['pad_size'] > 0:
|
||||
if dn_meta is not None:
|
||||
denoising_cls_scores = all_cls_scores[:, :, :
|
||||
dn_meta['pad_size'], :]
|
||||
denoising_bbox_preds = all_bbox_preds[:, :, :
|
||||
dn_meta['pad_size'], :]
|
||||
matching_cls_scores = all_cls_scores[:, :, dn_meta['pad_size']:, :]
|
||||
matching_bbox_preds = all_bbox_preds[:, :, dn_meta['pad_size']:, :]
|
||||
else:
|
||||
denoising_cls_scores = None
|
||||
denoising_bbox_preds = None
|
||||
matching_cls_scores = all_cls_scores
|
||||
matching_bbox_preds = all_bbox_preds
|
||||
return (matching_cls_scores, matching_bbox_preds, denoising_cls_scores,
|
||||
denoising_bbox_preds)
|
||||
27
detection/mmdet_custom/models/dense_heads/mask_rcnn.py
Normal file
27
detection/mmdet_custom/models/dense_heads/mask_rcnn.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from mmdet.models.builder import DETECTORS
|
||||
from .two_stage import TwoStageDetector
|
||||
|
||||
|
||||
@DETECTORS.register_module()
|
||||
class MaskRCNN_(TwoStageDetector):
|
||||
"""Implementation of `Mask R-CNN <https://arxiv.org/abs/1703.06870>`_"""
|
||||
|
||||
def __init__(self,
|
||||
backbone,
|
||||
rpn_head,
|
||||
roi_head,
|
||||
train_cfg,
|
||||
test_cfg,
|
||||
neck=None,
|
||||
pretrained=None,
|
||||
init_cfg=None):
|
||||
super(MaskRCNN_, self).__init__(
|
||||
backbone=backbone,
|
||||
neck=neck,
|
||||
rpn_head=rpn_head,
|
||||
roi_head=roi_head,
|
||||
train_cfg=train_cfg,
|
||||
test_cfg=test_cfg,
|
||||
pretrained=pretrained,
|
||||
init_cfg=init_cfg)
|
||||
369
detection/mmdet_custom/models/dense_heads/msda.py
Normal file
369
detection/mmdet_custom/models/dense_heads/msda.py
Normal file
@@ -0,0 +1,369 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
import torch
|
||||
from torch.cuda.amp import custom_bwd, custom_fwd
|
||||
from torch.autograd.function import Function, once_differentiable
|
||||
from mmcv.utils import ext_loader
|
||||
ext_module = ext_loader.load_ext(
|
||||
'_ext', ['ms_deform_attn_backward', 'ms_deform_attn_forward'])
|
||||
|
||||
class MultiScaleDeformableAttnFunction_fp16(Function):
|
||||
|
||||
@staticmethod
|
||||
@custom_fwd(cast_inputs=torch.float16)
|
||||
def forward(ctx, value, value_spatial_shapes, value_level_start_index,
|
||||
sampling_locations, attention_weights, im2col_step):
|
||||
"""GPU version of multi-scale deformable attention.
|
||||
|
||||
Args:
|
||||
value (Tensor): The value has shape
|
||||
(bs, num_keys, mum_heads, embed_dims//num_heads)
|
||||
value_spatial_shapes (Tensor): Spatial shape of
|
||||
each feature map, has shape (num_levels, 2),
|
||||
last dimension 2 represent (h, w)
|
||||
sampling_locations (Tensor): The location of sampling points,
|
||||
has shape
|
||||
(bs ,num_queries, num_heads, num_levels, num_points, 2),
|
||||
the last dimension 2 represent (x, y).
|
||||
attention_weights (Tensor): The weight of sampling points used
|
||||
when calculate the attention, has shape
|
||||
(bs ,num_queries, num_heads, num_levels, num_points),
|
||||
im2col_step (Tensor): The step used in image to column.
|
||||
|
||||
Returns:
|
||||
Tensor: has shape (bs, num_queries, embed_dims)
|
||||
"""
|
||||
ctx.im2col_step = im2col_step
|
||||
output = ext_module.ms_deform_attn_forward(
|
||||
value,
|
||||
value_spatial_shapes,
|
||||
value_level_start_index,
|
||||
sampling_locations,
|
||||
attention_weights,
|
||||
im2col_step=ctx.im2col_step)
|
||||
ctx.save_for_backward(value, value_spatial_shapes,
|
||||
value_level_start_index, sampling_locations,
|
||||
attention_weights)
|
||||
return output
|
||||
|
||||
@staticmethod
|
||||
@once_differentiable
|
||||
@custom_bwd
|
||||
def backward(ctx, grad_output):
|
||||
"""GPU version of backward function.
|
||||
|
||||
Args:
|
||||
grad_output (Tensor): Gradient
|
||||
of output tensor of forward.
|
||||
|
||||
Returns:
|
||||
Tuple[Tensor]: Gradient
|
||||
of input tensors in forward.
|
||||
"""
|
||||
value, value_spatial_shapes, value_level_start_index, \
|
||||
sampling_locations, attention_weights = ctx.saved_tensors
|
||||
grad_value = torch.zeros_like(value)
|
||||
grad_sampling_loc = torch.zeros_like(sampling_locations)
|
||||
grad_attn_weight = torch.zeros_like(attention_weights)
|
||||
|
||||
ext_module.ms_deform_attn_backward(
|
||||
value,
|
||||
value_spatial_shapes,
|
||||
value_level_start_index,
|
||||
sampling_locations,
|
||||
attention_weights,
|
||||
grad_output.contiguous(),
|
||||
grad_value,
|
||||
grad_sampling_loc,
|
||||
grad_attn_weight,
|
||||
im2col_step=ctx.im2col_step)
|
||||
|
||||
return grad_value, None, None, \
|
||||
grad_sampling_loc, grad_attn_weight, None
|
||||
|
||||
|
||||
|
||||
shm_size_dict = {
|
||||
"8.0": 163000,
|
||||
"8.6": 99000,
|
||||
"8.7": 163000,
|
||||
"8.9": 99000,
|
||||
"9.0": 227000,
|
||||
"7.5": 64000,
|
||||
"7.0": 96000,
|
||||
}
|
||||
|
||||
cuda_capability = f"{torch.cuda.get_device_properties(0).major}.{torch.cuda.get_device_properties(0).minor}"
|
||||
|
||||
if cuda_capability not in shm_size_dict:
|
||||
raise NotImplementedError
|
||||
|
||||
shm_size_cap = shm_size_dict[cuda_capability]
|
||||
|
||||
|
||||
class MultiScaleDeformableAttnFunction_fp32_old(Function):
|
||||
|
||||
@staticmethod
|
||||
@custom_fwd(cast_inputs=torch.float32)
|
||||
def forward(ctx, value, value_spatial_shapes, value_level_start_index,
|
||||
sampling_locations, attention_weights, im2col_step):
|
||||
"""GPU version of multi-scale deformable attention.
|
||||
|
||||
Args:
|
||||
value (Tensor): The value has shape
|
||||
(bs, num_keys, mum_heads, embed_dims//num_heads)
|
||||
value_spatial_shapes (Tensor): Spatial shape of
|
||||
each feature map, has shape (num_levels, 2),
|
||||
last dimension 2 represent (h, w)
|
||||
sampling_locations (Tensor): The location of sampling points,
|
||||
has shape
|
||||
(bs ,num_queries, num_heads, num_levels, num_points, 2),
|
||||
the last dimension 2 represent (x, y).
|
||||
attention_weights (Tensor): The weight of sampling points used
|
||||
when calculate the attention, has shape
|
||||
(bs ,num_queries, num_heads, num_levels, num_points),
|
||||
im2col_step (Tensor): The step used in image to column.
|
||||
|
||||
Returns:
|
||||
Tensor: has shape (bs, num_queries, embed_dims)
|
||||
"""
|
||||
|
||||
ctx.im2col_step = im2col_step
|
||||
output = ext_module.ms_deform_attn_forward(
|
||||
value,
|
||||
value_spatial_shapes,
|
||||
value_level_start_index,
|
||||
sampling_locations,
|
||||
attention_weights,
|
||||
im2col_step=ctx.im2col_step)
|
||||
ctx.save_for_backward(value, value_spatial_shapes,
|
||||
value_level_start_index, sampling_locations,
|
||||
attention_weights)
|
||||
return output
|
||||
|
||||
@staticmethod
|
||||
@once_differentiable
|
||||
@custom_bwd
|
||||
def backward(ctx, grad_output):
|
||||
"""GPU version of backward function.
|
||||
|
||||
Args:
|
||||
grad_output (Tensor): Gradient
|
||||
of output tensor of forward.
|
||||
|
||||
Returns:
|
||||
Tuple[Tensor]: Gradient
|
||||
of input tensors in forward.
|
||||
"""
|
||||
value, value_spatial_shapes, value_level_start_index, \
|
||||
sampling_locations, attention_weights = ctx.saved_tensors
|
||||
grad_value = torch.zeros_like(value)
|
||||
grad_sampling_loc = torch.zeros_like(sampling_locations)
|
||||
grad_attn_weight = torch.zeros_like(attention_weights)
|
||||
|
||||
ext_module.ms_deform_attn_backward(
|
||||
value,
|
||||
value_spatial_shapes,
|
||||
value_level_start_index,
|
||||
sampling_locations,
|
||||
attention_weights,
|
||||
grad_output.contiguous(),
|
||||
grad_value,
|
||||
grad_sampling_loc,
|
||||
grad_attn_weight,
|
||||
im2col_step=ctx.im2col_step)
|
||||
|
||||
return grad_value, None, None, \
|
||||
grad_sampling_loc, grad_attn_weight, None
|
||||
|
||||
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import math
|
||||
import warnings
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from torch.autograd.function import Function, once_differentiable
|
||||
|
||||
from mmcv import deprecated_api_warning
|
||||
from mmcv.cnn import constant_init, xavier_init
|
||||
from mmcv.cnn.bricks.registry import ATTENTION
|
||||
from mmcv.runner import BaseModule
|
||||
|
||||
ext_module = ext_loader.load_ext(
|
||||
'_ext', ['ms_deform_attn_backward', 'ms_deform_attn_forward'])
|
||||
import functools
|
||||
import time
|
||||
from collections import defaultdict
|
||||
import torch
|
||||
from mmcv.ops import MultiScaleDeformableAttention
|
||||
@ATTENTION.register_module()
|
||||
class FlashMultiScaleDeformableAttention(MultiScaleDeformableAttention):
|
||||
"""An attention module used in Deformable-Detr.
|
||||
|
||||
`Deformable DETR: Deformable Transformers for End-to-End Object Detection.
|
||||
<https://arxiv.org/pdf/2010.04159.pdf>`_.
|
||||
|
||||
Args:
|
||||
embed_dims (int): The embedding dimension of Attention.
|
||||
Default: 256.
|
||||
num_heads (int): Parallel attention heads. Default: 64.
|
||||
num_levels (int): The number of feature map used in
|
||||
Attention. Default: 4.
|
||||
num_points (int): The number of sampling points for
|
||||
each query in each head. Default: 4.
|
||||
im2col_step (int): The step used in image_to_column.
|
||||
Default: 64.
|
||||
dropout (float): A Dropout layer on `inp_identity`.
|
||||
Default: 0.1.
|
||||
batch_first (bool): Key, Query and Value are shape of
|
||||
(batch, n, embed_dim)
|
||||
or (n, batch, embed_dim). Default to False.
|
||||
norm_cfg (dict): Config dict for normalization layer.
|
||||
Default: None.
|
||||
init_cfg (obj:`mmcv.ConfigDict`): The Config for initialization.
|
||||
Default: None.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
use_flash=False,
|
||||
use_softmax=True,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.use_flash = use_flash
|
||||
self.use_softmax = use_softmax
|
||||
|
||||
@deprecated_api_warning({'residual': 'identity'},
|
||||
cls_name='FlashMultiScaleDeformableAttention')
|
||||
# @run_time('ms_attention')
|
||||
def forward(self,
|
||||
query,
|
||||
key=None,
|
||||
value=None,
|
||||
identity=None,
|
||||
query_pos=None,
|
||||
key_padding_mask=None,
|
||||
reference_points=None,
|
||||
spatial_shapes=None,
|
||||
level_start_index=None,
|
||||
**kwargs):
|
||||
"""Forward Function of MultiScaleDeformAttention.
|
||||
|
||||
Args:
|
||||
query (torch.Tensor): Query of Transformer with shape
|
||||
(num_query, bs, embed_dims).
|
||||
key (torch.Tensor): The key tensor with shape
|
||||
`(num_key, bs, embed_dims)`.
|
||||
value (torch.Tensor): The value tensor with shape
|
||||
`(num_key, bs, embed_dims)`.
|
||||
identity (torch.Tensor): The tensor used for addition, with the
|
||||
same shape as `query`. Default None. If None,
|
||||
`query` will be used.
|
||||
query_pos (torch.Tensor): The positional encoding for `query`.
|
||||
Default: None.
|
||||
key_pos (torch.Tensor): The positional encoding for `key`. Default
|
||||
None.
|
||||
reference_points (torch.Tensor): The normalized reference
|
||||
points with shape (bs, num_query, num_levels, 2),
|
||||
all elements is range in [0, 1], top-left (0,0),
|
||||
bottom-right (1, 1), including padding area.
|
||||
or (N, Length_{query}, num_levels, 4), add
|
||||
additional two dimensions is (w, h) to
|
||||
form reference boxes.
|
||||
key_padding_mask (torch.Tensor): ByteTensor for `query`, with
|
||||
shape [bs, num_key].
|
||||
spatial_shapes (torch.Tensor): Spatial shape of features in
|
||||
different levels. With shape (num_levels, 2),
|
||||
last dimension represents (h, w).
|
||||
level_start_index (torch.Tensor): The start index of each level.
|
||||
A tensor has shape ``(num_levels, )`` and can be represented
|
||||
as [0, h_0*w_0, h_0*w_0+h_1*w_1, ...].
|
||||
|
||||
Returns:
|
||||
torch.Tensor: forwarded results with shape
|
||||
[num_query, bs, embed_dims].
|
||||
"""
|
||||
|
||||
if value is None:
|
||||
value = query
|
||||
|
||||
if identity is None:
|
||||
identity = query
|
||||
if query_pos is not None:
|
||||
query = query + query_pos
|
||||
if not self.batch_first:
|
||||
# change to (bs, num_query ,embed_dims)
|
||||
query = query.permute(1, 0, 2)
|
||||
value = value.permute(1, 0, 2)
|
||||
|
||||
bs, num_query, _ = query.shape
|
||||
bs, num_value, _ = value.shape
|
||||
assert (spatial_shapes[:, 0] * spatial_shapes[:, 1]).sum() == num_value
|
||||
|
||||
value = self.value_proj(value)
|
||||
if key_padding_mask is not None:
|
||||
value = value.masked_fill(key_padding_mask[..., None], 0.0)
|
||||
value = value.view(bs, num_value, self.num_heads, -1)
|
||||
sampling_offsets = self.sampling_offsets(query).view(
|
||||
bs, num_query, self.num_heads, self.num_levels, self.num_points, 2)
|
||||
attention_weights = self.attention_weights(query).view(
|
||||
bs, num_query, self.num_heads, self.num_levels * self.num_points)
|
||||
|
||||
if not self.use_flash:
|
||||
if self.use_softmax:
|
||||
attention_weights = attention_weights.softmax(-1)
|
||||
attention_weights = attention_weights.view(bs, num_query,
|
||||
self.num_heads,
|
||||
self.num_levels,
|
||||
self.num_points)
|
||||
|
||||
else:
|
||||
attention_weights = attention_weights.view(bs, num_query,
|
||||
self.num_heads,
|
||||
self.num_levels,
|
||||
self.num_points, 1)
|
||||
|
||||
if reference_points.shape[-1] == 2:
|
||||
offset_normalizer = torch.stack(
|
||||
[spatial_shapes[..., 1], spatial_shapes[..., 0]], -1)
|
||||
sampling_locations = reference_points[:, :, None, :, None, :] \
|
||||
+ sampling_offsets \
|
||||
/ offset_normalizer[None, None, None, :, None, :]
|
||||
elif reference_points.shape[-1] == 4:
|
||||
sampling_locations = reference_points[:, :, None, :, None, :2] \
|
||||
+ sampling_offsets / self.num_points \
|
||||
* reference_points[:, :, None, :, None, 2:] \
|
||||
* 0.5
|
||||
else:
|
||||
raise ValueError(
|
||||
f'Last dim of reference_points must be'
|
||||
f' 2 or 4, but get {reference_points.shape[-1]} instead.')
|
||||
sampling_locations = sampling_locations.to(sampling_offsets.dtype)
|
||||
if torch.cuda.is_available() and value.is_cuda:
|
||||
if self.use_flash:
|
||||
assert False
|
||||
else:
|
||||
MultiScaleDeformableAttnFunction = MultiScaleDeformableAttnFunction_fp32_old
|
||||
|
||||
output = MultiScaleDeformableAttnFunction.apply(
|
||||
value, spatial_shapes, level_start_index, sampling_locations,
|
||||
attention_weights, self.im2col_step)
|
||||
|
||||
else:
|
||||
output = multi_scale_deformable_attn_pytorch(
|
||||
value, spatial_shapes, sampling_locations, attention_weights)
|
||||
|
||||
output = self.output_proj(output)
|
||||
|
||||
if not self.batch_first:
|
||||
# (num_query, bs ,embed_dims)
|
||||
output = output.permute(1, 0, 2)
|
||||
|
||||
return self.dropout(output) + identity
|
||||
225
detection/mmdet_custom/models/dense_heads/two_stage.py
Normal file
225
detection/mmdet_custom/models/dense_heads/two_stage.py
Normal file
@@ -0,0 +1,225 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import warnings
|
||||
|
||||
import torch
|
||||
|
||||
from mmdet.models.builder import DETECTORS, build_backbone, build_head, build_neck
|
||||
from mmdet.models.detectors.base import BaseDetector
|
||||
from mmcv.runner import BaseModule, force_fp32, auto_fp16
|
||||
import functools
|
||||
import time
|
||||
from collections import defaultdict
|
||||
import torch
|
||||
|
||||
|
||||
|
||||
# DETECTORS.register_module()
|
||||
class TwoStageDetector(BaseDetector):
|
||||
"""Base class for two-stage detectors.
|
||||
|
||||
Two-stage detectors typically consisting of a region proposal network and a
|
||||
task-specific regression head.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
backbone,
|
||||
neck=None,
|
||||
rpn_head=None,
|
||||
roi_head=None,
|
||||
train_cfg=None,
|
||||
test_cfg=None,
|
||||
pretrained=None,
|
||||
init_cfg=None):
|
||||
super(TwoStageDetector, self).__init__(init_cfg)
|
||||
if pretrained:
|
||||
warnings.warn('DeprecationWarning: pretrained is deprecated, '
|
||||
'please use "init_cfg" instead')
|
||||
backbone.pretrained = pretrained
|
||||
self.backbone = build_backbone(backbone)
|
||||
|
||||
if neck is not None:
|
||||
self.neck = build_neck(neck)
|
||||
|
||||
if rpn_head is not None:
|
||||
rpn_train_cfg = train_cfg.rpn if train_cfg is not None else None
|
||||
rpn_head_ = rpn_head.copy()
|
||||
rpn_head_.update(train_cfg=rpn_train_cfg, test_cfg=test_cfg.rpn)
|
||||
self.rpn_head = build_head(rpn_head_)
|
||||
|
||||
if roi_head is not None:
|
||||
# update train and test cfg here for now
|
||||
# TODO: refactor assigner & sampler
|
||||
rcnn_train_cfg = train_cfg.rcnn if train_cfg is not None else None
|
||||
roi_head.update(train_cfg=rcnn_train_cfg)
|
||||
roi_head.update(test_cfg=test_cfg.rcnn)
|
||||
roi_head.pretrained = pretrained
|
||||
self.roi_head = build_head(roi_head)
|
||||
|
||||
self.train_cfg = train_cfg
|
||||
self.test_cfg = test_cfg
|
||||
|
||||
@property
|
||||
def with_rpn(self):
|
||||
"""bool: whether the detector has RPN"""
|
||||
return hasattr(self, 'rpn_head') and self.rpn_head is not None
|
||||
|
||||
@property
|
||||
def with_roi_head(self):
|
||||
"""bool: whether the detector has a RoI head"""
|
||||
return hasattr(self, 'roi_head') and self.roi_head is not None
|
||||
|
||||
def use_backbone(self, img):
|
||||
return self.backbone(img)
|
||||
|
||||
# @auto_fp16(apply_to=('img',))
|
||||
def use_neck(self, img):
|
||||
# x = self.neck([each.float() for each in img])
|
||||
return self.neck(img)
|
||||
|
||||
def extract_feat(self, img):
|
||||
"""Directly extract features from the backbone+neck."""
|
||||
x = self.use_backbone(img)
|
||||
if self.with_neck:
|
||||
x = self.use_neck(x)
|
||||
return x
|
||||
|
||||
def forward_dummy(self, img):
|
||||
"""Used for computing network flops.
|
||||
|
||||
See `mmdetection/tools/analysis_tools/get_flops.py`
|
||||
"""
|
||||
outs = ()
|
||||
# backbone
|
||||
x = self.extract_feat(img)
|
||||
# rpn
|
||||
if self.with_rpn:
|
||||
rpn_outs = self.rpn_head(x)
|
||||
outs = outs + (rpn_outs, )
|
||||
proposals = torch.randn(1000, 4).to(img.device)
|
||||
# roi_head
|
||||
roi_outs = self.roi_head.forward_dummy(x, proposals)
|
||||
outs = outs + (roi_outs, )
|
||||
return outs
|
||||
|
||||
def forward_train(self,
|
||||
img,
|
||||
img_metas,
|
||||
gt_bboxes,
|
||||
gt_labels,
|
||||
gt_bboxes_ignore=None,
|
||||
gt_masks=None,
|
||||
proposals=None,
|
||||
**kwargs):
|
||||
"""
|
||||
Args:
|
||||
img (Tensor): of shape (N, C, H, W) encoding input images.
|
||||
Typically these should be mean centered and std scaled.
|
||||
|
||||
img_metas (list[dict]): list of image info dict where each dict
|
||||
has: 'img_shape', 'scale_factor', 'flip', and may also contain
|
||||
'filename', 'ori_shape', 'pad_shape', and 'img_norm_cfg'.
|
||||
For details on the values of these keys see
|
||||
`mmdet/datasets/pipelines/formatting.py:Collect`.
|
||||
|
||||
gt_bboxes (list[Tensor]): Ground truth bboxes for each image with
|
||||
shape (num_gts, 4) in [tl_x, tl_y, br_x, br_y] format.
|
||||
|
||||
gt_labels (list[Tensor]): class indices corresponding to each box
|
||||
|
||||
gt_bboxes_ignore (None | list[Tensor]): specify which bounding
|
||||
boxes can be ignored when computing the loss.
|
||||
|
||||
gt_masks (None | Tensor) : true segmentation masks for each box
|
||||
used if the architecture supports a segmentation task.
|
||||
|
||||
proposals : override rpn proposals with custom proposals. Use when
|
||||
`with_rpn` is False.
|
||||
|
||||
Returns:
|
||||
dict[str, Tensor]: a dictionary of loss components
|
||||
"""
|
||||
x = self.extract_feat(img)
|
||||
|
||||
losses = dict()
|
||||
|
||||
# RPN forward and loss
|
||||
if self.with_rpn:
|
||||
proposal_cfg = self.train_cfg.get('rpn_proposal',
|
||||
self.test_cfg.rpn)
|
||||
rpn_losses, proposal_list = self.rpn_head.forward_train(
|
||||
x,
|
||||
img_metas,
|
||||
gt_bboxes,
|
||||
gt_labels=None,
|
||||
gt_bboxes_ignore=gt_bboxes_ignore,
|
||||
proposal_cfg=proposal_cfg,
|
||||
**kwargs)
|
||||
losses.update(rpn_losses)
|
||||
else:
|
||||
proposal_list = proposals
|
||||
|
||||
roi_losses = self.roi_head.forward_train(x, img_metas, proposal_list,
|
||||
gt_bboxes, gt_labels,
|
||||
gt_bboxes_ignore, gt_masks,
|
||||
**kwargs)
|
||||
losses.update(roi_losses)
|
||||
|
||||
return losses
|
||||
|
||||
async def async_simple_test(self,
|
||||
img,
|
||||
img_meta,
|
||||
proposals=None,
|
||||
rescale=False):
|
||||
"""Async test without augmentation."""
|
||||
assert self.with_bbox, 'Bbox head must be implemented.'
|
||||
x = self.extract_feat(img)
|
||||
|
||||
if proposals is None:
|
||||
proposal_list = await self.rpn_head.async_simple_test_rpn(
|
||||
x, img_meta)
|
||||
else:
|
||||
proposal_list = proposals
|
||||
|
||||
return await self.roi_head.async_simple_test(
|
||||
x, proposal_list, img_meta, rescale=rescale)
|
||||
|
||||
def simple_test(self, img, img_metas, proposals=None, rescale=False):
|
||||
"""Test without augmentation."""
|
||||
|
||||
assert self.with_bbox, 'Bbox head must be implemented.'
|
||||
x = self.extract_feat(img)
|
||||
if proposals is None:
|
||||
proposal_list = self.rpn_head.simple_test_rpn(x, img_metas)
|
||||
else:
|
||||
proposal_list = proposals
|
||||
|
||||
return self.roi_head.simple_test(
|
||||
x, proposal_list, img_metas, rescale=rescale)
|
||||
|
||||
def aug_test(self, imgs, img_metas, rescale=False):
|
||||
"""Test with augmentations.
|
||||
|
||||
If rescale is False, then returned bboxes and masks will fit the scale
|
||||
of imgs[0].
|
||||
"""
|
||||
x = self.extract_feats(imgs)
|
||||
proposal_list = self.rpn_head.aug_test_rpn(x, img_metas)
|
||||
return self.roi_head.aug_test(
|
||||
x, proposal_list, img_metas, rescale=rescale)
|
||||
|
||||
def onnx_export(self, img, img_metas):
|
||||
|
||||
img_shape = torch._shape_as_tensor(img)[2:]
|
||||
img_metas[0]['img_shape_for_onnx'] = img_shape
|
||||
x = self.extract_feat(img)
|
||||
proposals = self.rpn_head.onnx_export(x, img_metas)
|
||||
if hasattr(self.roi_head, 'onnx_export'):
|
||||
return self.roi_head.onnx_export(x, proposals, img_metas)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f'{self.__class__.__name__} can not '
|
||||
f'be exported to ONNX. Please refer to the '
|
||||
f'list of supported models,'
|
||||
f'https://mmdetection.readthedocs.io/en/latest/tutorials/pytorch2onnx.html#list-of-supported-models-exportable-to-onnx' # noqa E501
|
||||
)
|
||||
9
detection/mmdet_custom/models/detectors/__init__.py
Normal file
9
detection/mmdet_custom/models/detectors/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
from .dino import DINO
|
||||
|
||||
__all__ = ['DINO']
|
||||
10
detection/mmdet_custom/models/detectors/dino.py
Normal file
10
detection/mmdet_custom/models/detectors/dino.py
Normal file
@@ -0,0 +1,10 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from mmdet.models.builder import DETECTORS
|
||||
from mmdet.models.detectors.detr import DETR
|
||||
|
||||
|
||||
@DETECTORS.register_module()
|
||||
class DINO(DETR):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DETR, self).__init__(*args, **kwargs)
|
||||
207
detection/mmdet_custom/models/necks/fpn.py
Normal file
207
detection/mmdet_custom/models/necks/fpn.py
Normal file
@@ -0,0 +1,207 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from mmcv.cnn import ConvModule
|
||||
from mmcv.runner import BaseModule, auto_fp16
|
||||
from ..utils import ConvModule_Norm
|
||||
|
||||
from mmdet.models.builder import NECKS
|
||||
|
||||
|
||||
@NECKS.register_module()
|
||||
class FPN_vitdet(BaseModule):
|
||||
r"""Feature Pyramid Network.
|
||||
|
||||
This is an implementation of paper `Feature Pyramid Networks for Object
|
||||
Detection <https://arxiv.org/abs/1612.03144>`_.
|
||||
|
||||
Args:
|
||||
in_channels (List[int]): Number of input channels per scale.
|
||||
out_channels (int): Number of output channels (used at each scale)
|
||||
num_outs (int): Number of output scales.
|
||||
start_level (int): Index of the start input backbone level used to
|
||||
build the feature pyramid. Default: 0.
|
||||
end_level (int): Index of the end input backbone level (exclusive) to
|
||||
build the feature pyramid. Default: -1, which means the last level.
|
||||
add_extra_convs (bool | str): If bool, it decides whether to add conv
|
||||
layers on top of the original feature maps. Default to False.
|
||||
If True, it is equivalent to `add_extra_convs='on_input'`.
|
||||
If str, it specifies the source feature map of the extra convs.
|
||||
Only the following options are allowed
|
||||
|
||||
- 'on_input': Last feat map of neck inputs (i.e. backbone feature).
|
||||
- 'on_lateral': Last feature map after lateral convs.
|
||||
- 'on_output': The last output feature map after fpn convs.
|
||||
relu_before_extra_convs (bool): Whether to apply relu before the extra
|
||||
conv. Default: False.
|
||||
no_norm_on_lateral (bool): Whether to apply norm on lateral.
|
||||
Default: False.
|
||||
conv_cfg (dict): Config dict for convolution layer. Default: None.
|
||||
norm_cfg (dict): Config dict for normalization layer. Default: None.
|
||||
act_cfg (str): Config dict for activation layer in ConvModule.
|
||||
Default: None.
|
||||
upsample_cfg (dict): Config dict for interpolate layer.
|
||||
Default: `dict(mode='nearest')`
|
||||
init_cfg (dict or list[dict], optional): Initialization config dict.
|
||||
|
||||
Example:
|
||||
>>> import torch
|
||||
>>> in_channels = [2, 3, 5, 7]
|
||||
>>> scales = [340, 170, 84, 43]
|
||||
>>> inputs = [torch.rand(1, c, s, s)
|
||||
... for c, s in zip(in_channels, scales)]
|
||||
>>> self = FPN(in_channels, 11, len(in_channels)).eval()
|
||||
>>> outputs = self.forward(inputs)
|
||||
>>> for i in range(len(outputs)):
|
||||
... print(f'outputs[{i}].shape = {outputs[i].shape}')
|
||||
outputs[0].shape = torch.Size([1, 11, 340, 340])
|
||||
outputs[1].shape = torch.Size([1, 11, 170, 170])
|
||||
outputs[2].shape = torch.Size([1, 11, 84, 84])
|
||||
outputs[3].shape = torch.Size([1, 11, 43, 43])
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
num_outs,
|
||||
start_level=0,
|
||||
end_level=-1,
|
||||
add_extra_convs=False,
|
||||
relu_before_extra_convs=False,
|
||||
no_norm_on_lateral=False,
|
||||
conv_cfg=None,
|
||||
norm_cfg=None,
|
||||
act_cfg=None,
|
||||
use_residual=True,
|
||||
upsample_cfg=dict(mode='nearest'),
|
||||
init_cfg=dict(
|
||||
type='Xavier', layer='Conv2d', distribution='uniform')):
|
||||
super(FPN_vitdet, self).__init__(init_cfg)
|
||||
assert isinstance(in_channels, list)
|
||||
self.in_channels = in_channels
|
||||
self.out_channels = out_channels
|
||||
self.num_ins = len(in_channels)
|
||||
self.num_outs = num_outs
|
||||
self.relu_before_extra_convs = relu_before_extra_convs
|
||||
self.no_norm_on_lateral = no_norm_on_lateral
|
||||
self.fp16_enabled = False
|
||||
self.upsample_cfg = upsample_cfg.copy()
|
||||
self.use_residual = use_residual
|
||||
|
||||
if end_level == -1:
|
||||
self.backbone_end_level = self.num_ins
|
||||
assert num_outs >= self.num_ins - start_level
|
||||
else:
|
||||
# if end_level < inputs, no extra level is allowed
|
||||
self.backbone_end_level = end_level
|
||||
assert end_level <= len(in_channels)
|
||||
assert num_outs == end_level - start_level
|
||||
self.start_level = start_level
|
||||
self.end_level = end_level
|
||||
self.add_extra_convs = add_extra_convs
|
||||
assert isinstance(add_extra_convs, (str, bool))
|
||||
if isinstance(add_extra_convs, str):
|
||||
# Extra_convs_source choices: 'on_input', 'on_lateral', 'on_output'
|
||||
assert add_extra_convs in ('on_input', 'on_lateral', 'on_output')
|
||||
elif add_extra_convs: # True
|
||||
self.add_extra_convs = 'on_input'
|
||||
|
||||
self.lateral_convs = nn.ModuleList()
|
||||
self.fpn_convs = nn.ModuleList()
|
||||
|
||||
for i in range(self.start_level, self.backbone_end_level):
|
||||
l_conv = ConvModule_Norm(
|
||||
in_channels[i],
|
||||
out_channels,
|
||||
1,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg if not self.no_norm_on_lateral else None,
|
||||
act_cfg=act_cfg,
|
||||
inplace=False)
|
||||
fpn_conv = ConvModule_Norm(
|
||||
out_channels,
|
||||
out_channels,
|
||||
3,
|
||||
padding=1,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=act_cfg,
|
||||
inplace=False)
|
||||
|
||||
self.lateral_convs.append(l_conv)
|
||||
self.fpn_convs.append(fpn_conv)
|
||||
|
||||
# add extra conv layers (e.g., RetinaNet)
|
||||
extra_levels = num_outs - self.backbone_end_level + self.start_level
|
||||
if self.add_extra_convs and extra_levels >= 1:
|
||||
for i in range(extra_levels):
|
||||
if i == 0 and self.add_extra_convs == 'on_input':
|
||||
in_channels = self.in_channels[self.backbone_end_level - 1]
|
||||
else:
|
||||
in_channels = out_channels
|
||||
extra_fpn_conv = ConvModule_Norm(
|
||||
in_channels,
|
||||
out_channels,
|
||||
3,
|
||||
stride=2,
|
||||
padding=1,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=act_cfg,
|
||||
inplace=False)
|
||||
self.fpn_convs.append(extra_fpn_conv)
|
||||
|
||||
@auto_fp16()
|
||||
def forward(self, inputs):
|
||||
"""Forward function."""
|
||||
assert len(inputs) == len(self.in_channels)
|
||||
|
||||
# build laterals
|
||||
laterals = [
|
||||
lateral_conv(inputs[i + self.start_level])
|
||||
for i, lateral_conv in enumerate(self.lateral_convs)
|
||||
]
|
||||
|
||||
# build top-down path
|
||||
used_backbone_levels = len(laterals)
|
||||
if self.use_residual:
|
||||
for i in range(used_backbone_levels - 1, 0, -1):
|
||||
# In some cases, fixing `scale factor` (e.g. 2) is preferred, but
|
||||
# it cannot co-exist with `size` in `F.interpolate`.
|
||||
if 'scale_factor' in self.upsample_cfg:
|
||||
laterals[i - 1] += F.interpolate(laterals[i],
|
||||
**self.upsample_cfg)
|
||||
else:
|
||||
prev_shape = laterals[i - 1].shape[2:]
|
||||
laterals[i - 1] += F.interpolate(
|
||||
laterals[i], size=prev_shape, **self.upsample_cfg)
|
||||
|
||||
# build outputs
|
||||
# part 1: from original levels
|
||||
outs = [
|
||||
self.fpn_convs[i](laterals[i]) for i in range(used_backbone_levels)
|
||||
]
|
||||
# part 2: add extra levels
|
||||
if self.num_outs > len(outs):
|
||||
# use max pool to get more levels on top of outputs
|
||||
# (e.g., Faster R-CNN, Mask R-CNN)
|
||||
if not self.add_extra_convs:
|
||||
for i in range(self.num_outs - used_backbone_levels):
|
||||
outs.append(F.max_pool2d(outs[-1], 1, stride=2))
|
||||
# add conv layers on top of original feature maps (RetinaNet)
|
||||
else:
|
||||
if self.add_extra_convs == 'on_input':
|
||||
extra_source = inputs[self.backbone_end_level - 1]
|
||||
elif self.add_extra_convs == 'on_lateral':
|
||||
extra_source = laterals[-1]
|
||||
elif self.add_extra_convs == 'on_output':
|
||||
extra_source = outs[-1]
|
||||
else:
|
||||
raise NotImplementedError
|
||||
outs.append(self.fpn_convs[used_backbone_levels](extra_source))
|
||||
for i in range(used_backbone_levels + 1, self.num_outs):
|
||||
if self.relu_before_extra_convs:
|
||||
outs.append(self.fpn_convs[i](F.relu(outs[-1])))
|
||||
else:
|
||||
outs.append(self.fpn_convs[i](outs[-1]))
|
||||
return tuple(outs)
|
||||
6
detection/mmdet_custom/models/utils/__init__.py
Normal file
6
detection/mmdet_custom/models/utils/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from .query_denoising import build_dn_generator
|
||||
from .transformer import (DinoTransformer, DinoTransformerDecoder)
|
||||
from .convModule_norm import ConvModule_Norm
|
||||
|
||||
|
||||
__all__ = ['build_dn_generator', 'DinoTransformer', 'DinoTransformerDecoder']
|
||||
34
detection/mmdet_custom/models/utils/convModule_norm.py
Normal file
34
detection/mmdet_custom/models/utils/convModule_norm.py
Normal file
@@ -0,0 +1,34 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
from mmcv.cnn.bricks.conv_module import ConvModule
|
||||
|
||||
class ConvModule_Norm(ConvModule):
|
||||
def __init__(self, in_channels,
|
||||
out_channels,
|
||||
kernel, **kwargs):
|
||||
super().__init__(in_channels, out_channels, kernel, **kwargs)
|
||||
|
||||
self.normType = kwargs.get('norm_cfg', {'type':''})
|
||||
if self.normType is not None:
|
||||
self.normType = self.normType['type']
|
||||
|
||||
def forward(self, x, activate=True, norm=True):
|
||||
for layer in self.order:
|
||||
if layer == 'conv':
|
||||
if self.with_explicit_padding:
|
||||
x = self.padding_layer(x)
|
||||
x = self.conv(x)
|
||||
elif layer == 'norm' and norm and self.with_norm:
|
||||
if 'LN' in self.normType:
|
||||
x = x.permute(0, 2, 3, 1)
|
||||
x = self.norm(x)
|
||||
x = x.permute(0, 3, 1, 2).contiguous()
|
||||
else:
|
||||
x = self.norm(x)
|
||||
elif layer == 'act' and activate and self.with_activation:
|
||||
x = self.activate(x)
|
||||
return x
|
||||
234
detection/mmdet_custom/models/utils/query_denoising.py
Normal file
234
detection/mmdet_custom/models/utils/query_denoising.py
Normal file
@@ -0,0 +1,234 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch
|
||||
from mmcv.runner import BaseModule
|
||||
|
||||
from mmdet.core import bbox_xyxy_to_cxcywh
|
||||
from mmdet.models.utils.transformer import inverse_sigmoid
|
||||
|
||||
|
||||
class DnQueryGenerator(BaseModule):
|
||||
|
||||
def __init__(self,
|
||||
num_queries,
|
||||
hidden_dim,
|
||||
num_classes,
|
||||
noise_scale=dict(label=0.5, box=0.4),
|
||||
group_cfg=dict(
|
||||
dynamic=True, num_groups=None, num_dn_queries=None)):
|
||||
super(DnQueryGenerator, self).__init__()
|
||||
self.num_queries = num_queries
|
||||
self.hidden_dim = hidden_dim
|
||||
self.num_classes = num_classes
|
||||
self.label_noise_scale = noise_scale['label']
|
||||
self.box_noise_scale = noise_scale['box']
|
||||
self.dynamic_dn_groups = group_cfg.get('dynamic', False)
|
||||
if self.dynamic_dn_groups:
|
||||
assert 'num_dn_queries' in group_cfg, \
|
||||
'num_dn_queries should be set when using ' \
|
||||
'dynamic dn groups'
|
||||
self.num_dn = group_cfg['num_dn_queries']
|
||||
else:
|
||||
assert 'num_groups' in group_cfg, \
|
||||
'num_groups should be set when using ' \
|
||||
'static dn groups'
|
||||
self.num_dn = group_cfg['num_groups']
|
||||
assert isinstance(self.num_dn, int) and self.num_dn >= 1, \
|
||||
f'Expected the num in group_cfg to have type int. ' \
|
||||
f'Found {type(self.num_dn)} '
|
||||
|
||||
def get_num_groups(self, group_queries=None):
|
||||
"""
|
||||
Args:
|
||||
group_queries (int): Number of dn queries in one group.
|
||||
"""
|
||||
if self.dynamic_dn_groups:
|
||||
assert group_queries is not None, \
|
||||
'group_queries should be provided when using ' \
|
||||
'dynamic dn groups'
|
||||
if group_queries == 0:
|
||||
num_groups = 1
|
||||
else:
|
||||
num_groups = self.num_dn // group_queries
|
||||
else:
|
||||
num_groups = self.num_dn
|
||||
if num_groups < 1: # avoid num_groups < 1 in query generator
|
||||
num_groups = 1
|
||||
return int(num_groups)
|
||||
|
||||
def forward(self,
|
||||
gt_bboxes,
|
||||
gt_labels=None,
|
||||
label_enc=None,
|
||||
img_metas=None):
|
||||
"""
|
||||
Args:
|
||||
gt_bboxes (List[Tensor]): List of ground truth bboxes
|
||||
of the image, shape of each (num_gts, 4).
|
||||
gt_labels (List[Tensor]): List of ground truth labels
|
||||
of the image, shape of each (num_gts,), if None,
|
||||
TODO:noisy_label would be None.
|
||||
Returns:
|
||||
TODO
|
||||
"""
|
||||
# TODO: temp only support for CDN
|
||||
# TODO: temp assert gt_labels is not None and label_enc is not None
|
||||
|
||||
if self.training:
|
||||
if gt_labels is not None:
|
||||
assert len(gt_bboxes) == len(gt_labels), \
|
||||
f'the length of provided gt_labels ' \
|
||||
f'{len(gt_labels)} should be equal to' \
|
||||
f' that of gt_bboxes {len(gt_bboxes)}'
|
||||
assert gt_labels is not None \
|
||||
and label_enc is not None \
|
||||
and img_metas is not None # TODO: adjust args
|
||||
batch_size = len(gt_bboxes)
|
||||
|
||||
# convert bbox
|
||||
gt_bboxes_list = []
|
||||
for img_meta, bboxes in zip(img_metas, gt_bboxes):
|
||||
img_h, img_w, _ = img_meta['img_shape']
|
||||
factor = bboxes.new_tensor([img_w, img_h, img_w,
|
||||
img_h]).unsqueeze(0)
|
||||
bboxes_normalized = bbox_xyxy_to_cxcywh(bboxes) / factor
|
||||
gt_bboxes_list.append(bboxes_normalized)
|
||||
gt_bboxes = gt_bboxes_list
|
||||
|
||||
known = [torch.ones_like(labels) for labels in gt_labels]
|
||||
known_num = [sum(k) for k in known]
|
||||
|
||||
num_groups = self.get_num_groups(int(max(known_num)))
|
||||
|
||||
unmask_bbox = unmask_label = torch.cat(known)
|
||||
labels = torch.cat(gt_labels)
|
||||
boxes = torch.cat(gt_bboxes)
|
||||
batch_idx = torch.cat([
|
||||
torch.full_like(t.long(), i) for i, t in enumerate(gt_labels)
|
||||
])
|
||||
|
||||
known_indice = torch.nonzero(unmask_label + unmask_bbox)
|
||||
known_indice = known_indice.view(-1)
|
||||
|
||||
known_indice = known_indice.repeat(2 * num_groups, 1).view(-1)
|
||||
known_labels = labels.repeat(2 * num_groups, 1).view(-1)
|
||||
known_bid = batch_idx.repeat(2 * num_groups, 1).view(-1)
|
||||
known_bboxs = boxes.repeat(2 * num_groups, 1)
|
||||
known_labels_expand = known_labels.clone()
|
||||
known_bbox_expand = known_bboxs.clone()
|
||||
|
||||
if self.label_noise_scale > 0:
|
||||
p = torch.rand_like(known_labels_expand.float())
|
||||
chosen_indice = torch.nonzero(
|
||||
p < (self.label_noise_scale * 0.5)).view(-1)
|
||||
new_label = torch.randint_like(chosen_indice, 0,
|
||||
self.num_classes)
|
||||
known_labels_expand.scatter_(0, chosen_indice, new_label)
|
||||
single_pad = int(max(known_num)) # TODO
|
||||
|
||||
pad_size = int(single_pad * 2 * num_groups)
|
||||
positive_idx = torch.tensor(range(
|
||||
len(boxes))).long().cuda().unsqueeze(0).repeat(num_groups, 1)
|
||||
positive_idx += (torch.tensor(range(num_groups)) * len(boxes) *
|
||||
2).long().cuda().unsqueeze(1)
|
||||
positive_idx = positive_idx.flatten()
|
||||
negative_idx = positive_idx + len(boxes)
|
||||
if self.box_noise_scale > 0:
|
||||
known_bbox_ = torch.zeros_like(known_bboxs)
|
||||
known_bbox_[:, : 2] = \
|
||||
known_bboxs[:, : 2] - known_bboxs[:, 2:] / 2
|
||||
known_bbox_[:, 2:] = \
|
||||
known_bboxs[:, :2] + known_bboxs[:, 2:] / 2
|
||||
|
||||
diff = torch.zeros_like(known_bboxs)
|
||||
diff[:, :2] = known_bboxs[:, 2:] / 2
|
||||
diff[:, 2:] = known_bboxs[:, 2:] / 2
|
||||
|
||||
rand_sign = torch.randint_like(
|
||||
known_bboxs, low=0, high=2, dtype=torch.float32)
|
||||
rand_sign = rand_sign * 2.0 - 1.0
|
||||
rand_part = torch.rand_like(known_bboxs)
|
||||
rand_part[negative_idx] += 1.0
|
||||
rand_part *= rand_sign
|
||||
known_bbox_ += \
|
||||
torch.mul(rand_part, diff).cuda() * self.box_noise_scale
|
||||
known_bbox_ = known_bbox_.clamp(min=0.0, max=1.0)
|
||||
known_bbox_expand[:, :2] = \
|
||||
(known_bbox_[:, :2] + known_bbox_[:, 2:]) / 2
|
||||
known_bbox_expand[:, 2:] = \
|
||||
known_bbox_[:, 2:] - known_bbox_[:, :2]
|
||||
|
||||
m = known_labels_expand.long().to('cuda')
|
||||
input_label_embed = label_enc(m)
|
||||
input_bbox_embed = inverse_sigmoid(known_bbox_expand, eps=1e-3)
|
||||
|
||||
padding_label = torch.zeros(pad_size, self.hidden_dim).cuda()
|
||||
padding_bbox = torch.zeros(pad_size, 4).cuda()
|
||||
|
||||
input_query_label = padding_label.repeat(batch_size, 1, 1)
|
||||
input_query_bbox = padding_bbox.repeat(batch_size, 1, 1)
|
||||
|
||||
map_known_indice = torch.tensor([]).to('cuda')
|
||||
if len(known_num):
|
||||
map_known_indice = torch.cat(
|
||||
[torch.tensor(range(num)) for num in known_num])
|
||||
map_known_indice = torch.cat([
|
||||
map_known_indice + single_pad * i
|
||||
for i in range(2 * num_groups)
|
||||
]).long()
|
||||
if len(known_bid):
|
||||
input_query_label[(known_bid.long(),
|
||||
map_known_indice)] = input_label_embed
|
||||
input_query_bbox[(known_bid.long(),
|
||||
map_known_indice)] = input_bbox_embed
|
||||
|
||||
tgt_size = pad_size + self.num_queries
|
||||
attn_mask = torch.ones(tgt_size, tgt_size).to('cuda') < 0
|
||||
# match query cannot see the reconstruct
|
||||
attn_mask[pad_size:, :pad_size] = True
|
||||
# reconstruct cannot see each other
|
||||
for i in range(num_groups):
|
||||
if i == 0:
|
||||
attn_mask[single_pad * 2 * i:single_pad * 2 * (i + 1),
|
||||
single_pad * 2 * (i + 1):pad_size] = True
|
||||
if i == num_groups - 1:
|
||||
attn_mask[single_pad * 2 * i:single_pad * 2 *
|
||||
(i + 1), :single_pad * i * 2] = True
|
||||
else:
|
||||
attn_mask[single_pad * 2 * i:single_pad * 2 * (i + 1),
|
||||
single_pad * 2 * (i + 1):pad_size] = True
|
||||
attn_mask[single_pad * 2 * i:single_pad * 2 *
|
||||
(i + 1), :single_pad * 2 * i] = True
|
||||
|
||||
dn_meta = {
|
||||
'pad_size': pad_size,
|
||||
'num_dn_group': num_groups,
|
||||
}
|
||||
else:
|
||||
input_query_label = None
|
||||
input_query_bbox = None
|
||||
attn_mask = None
|
||||
dn_meta = None
|
||||
return input_query_label, input_query_bbox, attn_mask, dn_meta
|
||||
|
||||
|
||||
class CdnQueryGenerator(DnQueryGenerator):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CdnQueryGenerator, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
def build_dn_generator(dn_args):
|
||||
"""
|
||||
Args:
|
||||
dn_args (dict):
|
||||
Returns:
|
||||
"""
|
||||
if dn_args is None:
|
||||
return None
|
||||
type = dn_args.pop('type')
|
||||
if type == 'DnQueryGenerator':
|
||||
return DnQueryGenerator(**dn_args)
|
||||
elif type == 'CdnQueryGenerator':
|
||||
return CdnQueryGenerator(**dn_args)
|
||||
else:
|
||||
raise NotImplementedError(f'{type} is not supported yet')
|
||||
278
detection/mmdet_custom/models/utils/transformer.py
Normal file
278
detection/mmdet_custom/models/utils/transformer.py
Normal file
@@ -0,0 +1,278 @@
|
||||
import math
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from mmdet.models.utils.builder import TRANSFORMER
|
||||
from mmcv.cnn.bricks.registry import (
|
||||
TRANSFORMER_LAYER_SEQUENCE, FEEDFORWARD_NETWORK, DROPOUT_LAYERS)
|
||||
from mmdet.models.utils.transformer import (inverse_sigmoid,
|
||||
DeformableDetrTransformerDecoder,
|
||||
DeformableDetrTransformer)
|
||||
|
||||
|
||||
def build_MLP(input_dim, hidden_dim, output_dim, num_layers):
|
||||
# TODO: It can be implemented by add an out_channel arg of
|
||||
# mmcv.cnn.bricks.transformer.FFN
|
||||
assert num_layers > 1, \
|
||||
f'num_layers should be greater than 1 but got {num_layers}'
|
||||
h = [hidden_dim] * (num_layers - 1)
|
||||
layers = list()
|
||||
for n, k in zip([input_dim] + h[:-1], h):
|
||||
layers.extend((nn.Linear(n, k), nn.ReLU()))
|
||||
# Note that the relu func of MLP in original DETR repo is set
|
||||
# 'inplace=False', however the ReLU cfg of FFN in mmdet is set
|
||||
# 'inplace=True' by default.
|
||||
layers.append(nn.Linear(hidden_dim, output_dim))
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
|
||||
@TRANSFORMER_LAYER_SEQUENCE.register_module()
|
||||
class DinoTransformerDecoder(DeformableDetrTransformerDecoder):
|
||||
|
||||
def __init__(self, *args, with_rp_noise=False, **kwargs):
|
||||
super(DinoTransformerDecoder, self).__init__(*args, **kwargs)
|
||||
self.with_rp_noise = with_rp_noise
|
||||
self._init_layers()
|
||||
|
||||
def _init_layers(self):
|
||||
self.ref_point_head = build_MLP(
|
||||
self.embed_dims * 2,
|
||||
self.embed_dims,
|
||||
self.embed_dims,
|
||||
2)
|
||||
self.norm = nn.LayerNorm(self.embed_dims)
|
||||
|
||||
# @staticmethod
|
||||
def gen_sineembed_for_position(self, pos_tensor):
|
||||
# n_query, bs, _ = pos_tensor.size()
|
||||
# sineembed_tensor = torch.zeros(n_query, bs, 256)
|
||||
scale = 2 * math.pi
|
||||
dim_t = torch.arange(
|
||||
self.embed_dims//2, dtype=torch.float32, device=pos_tensor.device)
|
||||
dim_t = 10000**(2 * (dim_t // 2) / (self.embed_dims//2))
|
||||
x_embed = pos_tensor[:, :, 0] * scale
|
||||
y_embed = pos_tensor[:, :, 1] * scale
|
||||
pos_x = x_embed[:, :, None] / dim_t
|
||||
pos_y = y_embed[:, :, None] / dim_t
|
||||
pos_x = torch.stack((pos_x[:, :, 0::2].sin(), pos_x[:, :, 1::2].cos()),
|
||||
dim=3).flatten(2)
|
||||
pos_y = torch.stack((pos_y[:, :, 0::2].sin(), pos_y[:, :, 1::2].cos()),
|
||||
dim=3).flatten(2)
|
||||
if pos_tensor.size(-1) == 2:
|
||||
pos = torch.cat((pos_y, pos_x), dim=2)
|
||||
elif pos_tensor.size(-1) == 4:
|
||||
w_embed = pos_tensor[:, :, 2] * scale
|
||||
pos_w = w_embed[:, :, None] / dim_t
|
||||
pos_w = torch.stack(
|
||||
(pos_w[:, :, 0::2].sin(), pos_w[:, :, 1::2].cos()),
|
||||
dim=3).flatten(2)
|
||||
|
||||
h_embed = pos_tensor[:, :, 3] * scale
|
||||
pos_h = h_embed[:, :, None] / dim_t
|
||||
pos_h = torch.stack(
|
||||
(pos_h[:, :, 0::2].sin(), pos_h[:, :, 1::2].cos()),
|
||||
dim=3).flatten(2)
|
||||
|
||||
pos = torch.cat((pos_y, pos_x, pos_w, pos_h), dim=2)
|
||||
else:
|
||||
raise ValueError('Unknown pos_tensor shape(-1):{}'.format(
|
||||
pos_tensor.size(-1)))
|
||||
return pos
|
||||
|
||||
def forward(self,
|
||||
query,
|
||||
*args,
|
||||
reference_points=None,
|
||||
valid_ratios=None,
|
||||
reg_branches=None,
|
||||
**kwargs):
|
||||
output = query
|
||||
intermediate = []
|
||||
intermediate_reference_points = [reference_points]
|
||||
for lid, layer in enumerate(self.layers):
|
||||
if reference_points.shape[-1] == 4:
|
||||
reference_points_input = \
|
||||
reference_points[:, :, None] * torch.cat(
|
||||
[valid_ratios, valid_ratios], -1)[:, None]
|
||||
else:
|
||||
assert reference_points.shape[-1] == 2
|
||||
reference_points_input = \
|
||||
reference_points[:, :, None] * valid_ratios[:, None]
|
||||
|
||||
if self.with_rp_noise and self.training:
|
||||
device = reference_points.device
|
||||
b, n, d = reference_points.size()
|
||||
noise = torch.rand(b, n, d).to(device) * 0.02 - 0.01
|
||||
reference_points = (reference_points + noise).clamp(0, 1)
|
||||
|
||||
query_sine_embed = self.gen_sineembed_for_position(
|
||||
reference_points_input[:, :, 0, :])
|
||||
query_pos = self.ref_point_head(query_sine_embed)
|
||||
|
||||
query_pos = query_pos.permute(1, 0, 2)
|
||||
output = layer(
|
||||
output,
|
||||
*args,
|
||||
query_pos=query_pos,
|
||||
reference_points=reference_points_input,
|
||||
**kwargs)
|
||||
output = output.permute(1, 0, 2)
|
||||
|
||||
if reg_branches is not None:
|
||||
tmp = reg_branches[lid](output)
|
||||
assert reference_points.shape[-1] == 4
|
||||
new_reference_points = tmp + inverse_sigmoid(
|
||||
reference_points, eps=1e-3)
|
||||
new_reference_points = new_reference_points.sigmoid()
|
||||
reference_points = new_reference_points.detach()
|
||||
|
||||
output = output.permute(1, 0, 2)
|
||||
if self.return_intermediate:
|
||||
intermediate.append(self.norm(output))
|
||||
intermediate_reference_points.append(new_reference_points)
|
||||
# NOTE this is for the "Look Forward Twice" module,
|
||||
# in the DeformDETR, reference_points was appended.
|
||||
|
||||
if self.return_intermediate:
|
||||
return torch.stack(intermediate), torch.stack(
|
||||
intermediate_reference_points)
|
||||
|
||||
return output, reference_points
|
||||
|
||||
|
||||
@TRANSFORMER.register_module()
|
||||
class DinoTransformer(DeformableDetrTransformer):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DinoTransformer, self).__init__(*args, **kwargs)
|
||||
|
||||
def init_layers(self):
|
||||
"""Initialize layers of the DinoTransformer."""
|
||||
self.level_embeds = nn.Parameter(
|
||||
torch.Tensor(self.num_feature_levels, self.embed_dims))
|
||||
self.enc_output = nn.Linear(self.embed_dims, self.embed_dims)
|
||||
self.enc_output_norm = nn.LayerNorm(self.embed_dims)
|
||||
self.query_embed = nn.Embedding(self.two_stage_num_proposals,
|
||||
self.embed_dims)
|
||||
|
||||
def init_weights(self):
|
||||
super().init_weights()
|
||||
nn.init.normal_(self.query_embed.weight.data)
|
||||
|
||||
def forward(self,
|
||||
mlvl_feats,
|
||||
mlvl_masks,
|
||||
query_embed,
|
||||
mlvl_pos_embeds,
|
||||
dn_label_query,
|
||||
dn_bbox_query,
|
||||
attn_mask,
|
||||
reg_branches=None,
|
||||
cls_branches=None,
|
||||
**kwargs):
|
||||
assert self.as_two_stage and query_embed is None, \
|
||||
'as_two_stage must be True for DINO'
|
||||
|
||||
feat_flatten = []
|
||||
mask_flatten = []
|
||||
lvl_pos_embed_flatten = []
|
||||
spatial_shapes = []
|
||||
for lvl, (feat, mask, pos_embed) in enumerate(
|
||||
zip(mlvl_feats, mlvl_masks, mlvl_pos_embeds)):
|
||||
bs, c, h, w = feat.shape
|
||||
spatial_shape = (h, w)
|
||||
spatial_shapes.append(spatial_shape)
|
||||
feat = feat.flatten(2).transpose(1, 2)
|
||||
mask = mask.flatten(1)
|
||||
pos_embed = pos_embed.flatten(2).transpose(1, 2)
|
||||
lvl_pos_embed = pos_embed + self.level_embeds[lvl].view(1, 1, -1)
|
||||
lvl_pos_embed_flatten.append(lvl_pos_embed)
|
||||
feat_flatten.append(feat)
|
||||
mask_flatten.append(mask)
|
||||
feat_flatten = torch.cat(feat_flatten, 1)
|
||||
mask_flatten = torch.cat(mask_flatten, 1)
|
||||
lvl_pos_embed_flatten = torch.cat(lvl_pos_embed_flatten, 1)
|
||||
spatial_shapes = torch.as_tensor(
|
||||
spatial_shapes, dtype=torch.long, device=feat_flatten.device)
|
||||
level_start_index = torch.cat((spatial_shapes.new_zeros(
|
||||
(1, )), spatial_shapes.prod(1).cumsum(0)[:-1]))
|
||||
valid_ratios = torch.stack(
|
||||
[self.get_valid_ratio(m) for m in mlvl_masks], 1)
|
||||
|
||||
reference_points = self.get_reference_points(
|
||||
spatial_shapes, valid_ratios, device=feat.device)
|
||||
|
||||
feat_flatten = feat_flatten.permute(1, 0, 2) # (H*W, bs, embed_dims)
|
||||
lvl_pos_embed_flatten = lvl_pos_embed_flatten.permute(
|
||||
1, 0, 2) # (H*W, bs, embed_dims)
|
||||
memory = self.encoder(
|
||||
query=feat_flatten,
|
||||
key=None,
|
||||
value=None,
|
||||
query_pos=lvl_pos_embed_flatten,
|
||||
query_key_padding_mask=mask_flatten,
|
||||
spatial_shapes=spatial_shapes,
|
||||
reference_points=reference_points,
|
||||
level_start_index=level_start_index,
|
||||
valid_ratios=valid_ratios,
|
||||
**kwargs)
|
||||
|
||||
memory = memory.permute(1, 0, 2)
|
||||
bs, _, c = memory.shape
|
||||
|
||||
output_memory, output_proposals = self.gen_encoder_output_proposals(
|
||||
memory, mask_flatten, spatial_shapes)
|
||||
enc_outputs_class = cls_branches[self.decoder.num_layers](
|
||||
output_memory)
|
||||
enc_outputs_coord_unact = reg_branches[self.decoder.num_layers](
|
||||
output_memory) + output_proposals
|
||||
cls_out_features = cls_branches[self.decoder.num_layers].out_features
|
||||
topk = self.two_stage_num_proposals
|
||||
# NOTE In DeformDETR, enc_outputs_class[..., 0] is used for topk TODO
|
||||
topk_indices = torch.topk(enc_outputs_class.max(-1)[0], topk, dim=1)[1]
|
||||
# topk_proposal = torch.gather(
|
||||
# output_proposals, 1,
|
||||
# topk_indices.unsqueeze(-1).repeat(1, 1, 4)).sigmoid()
|
||||
# topk_memory = torch.gather(
|
||||
# output_memory, 1,
|
||||
# topk_indices.unsqueeze(-1).repeat(1, 1, self.embed_dims))
|
||||
topk_score = torch.gather(
|
||||
enc_outputs_class, 1,
|
||||
topk_indices.unsqueeze(-1).repeat(1, 1, cls_out_features))
|
||||
topk_coords_unact = torch.gather(
|
||||
enc_outputs_coord_unact, 1,
|
||||
topk_indices.unsqueeze(-1).repeat(1, 1, 4))
|
||||
topk_anchor = topk_coords_unact.sigmoid()
|
||||
# NOTE In the original DeformDETR, init_reference_out is obtained
|
||||
# from detached topk_coords_unact, which is different with DINO. TODO
|
||||
topk_coords_unact = topk_coords_unact.detach()
|
||||
|
||||
query = self.query_embed.weight[:, None, :].repeat(1, bs,
|
||||
1).transpose(0, 1)
|
||||
if dn_label_query is not None:
|
||||
query = torch.cat([dn_label_query, query], dim=1)
|
||||
if dn_bbox_query is not None:
|
||||
reference_points = torch.cat([dn_bbox_query, topk_coords_unact],
|
||||
dim=1)
|
||||
else:
|
||||
reference_points = topk_coords_unact
|
||||
reference_points = reference_points.sigmoid()
|
||||
|
||||
# decoder
|
||||
query = query.permute(1, 0, 2)
|
||||
memory = memory.permute(1, 0, 2)
|
||||
inter_states, inter_references = self.decoder(
|
||||
query=query,
|
||||
key=None,
|
||||
value=memory,
|
||||
attn_masks=attn_mask,
|
||||
key_padding_mask=mask_flatten,
|
||||
reference_points=reference_points,
|
||||
spatial_shapes=spatial_shapes,
|
||||
level_start_index=level_start_index,
|
||||
valid_ratios=valid_ratios,
|
||||
reg_branches=reg_branches,
|
||||
**kwargs)
|
||||
|
||||
inter_references_out = inter_references
|
||||
|
||||
return inter_states, inter_references_out, topk_score, topk_anchor
|
||||
Reference in New Issue
Block a user