|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| """COCO data loader for Pix2Seq."""
|
|
|
| from typing import Tuple
|
| import tensorflow as tf, tf_keras
|
|
|
| from official.projects.pix2seq import utils
|
| from official.projects.pix2seq.configs import pix2seq as pix2seq_cfg
|
| from official.projects.simclr.dataloaders import preprocess_ops as simclr_preprocess_ops
|
| from official.vision.dataloaders import parser
|
| from official.vision.ops import box_ops
|
| from official.vision.ops import preprocess_ops
|
|
|
| RESIZE_SCALES = (480, 512, 544, 576, 608, 640)
|
|
|
|
|
| class Parser(parser.Parser):
|
| """Parse an image and its annotations into a dictionary of tensors."""
|
|
|
| def __init__(
|
| self,
|
| eos_token_weight: float = 0.1,
|
| output_size: Tuple[int, int] = (1333, 1333),
|
| max_num_boxes: int = 100,
|
| aug_rand_hflip=True,
|
| aug_scale_min=0.3,
|
| aug_scale_max=2.0,
|
| aug_color_jitter_strength: float = 0.5,
|
| aug_color_jitter_impl='simclrv2',
|
| coord_vocab_shift=1000,
|
| quantization_bins=1000,
|
| skip_crowd_during_training=True,
|
| label_shift: int = 0,
|
| ):
|
| self._eos_token_weight = eos_token_weight
|
| self._output_size = output_size
|
| self._max_num_boxes = max_num_boxes
|
| self._aug_rand_hflip = aug_rand_hflip
|
| self._aug_scale_min = aug_scale_min
|
| self._aug_scale_max = aug_scale_max
|
| self._aug_color_jitter_strength = aug_color_jitter_strength
|
| self._aug_color_jitter_impl = aug_color_jitter_impl
|
| self._coord_vocab_shift = coord_vocab_shift
|
| self._quantization_bins = quantization_bins
|
| self._skip_crowd_during_training = skip_crowd_during_training
|
| self._label_shift = label_shift
|
|
|
| def _parse_train_data(self, data):
|
| """Parses data for training and evaluation."""
|
| classes = data['groundtruth_classes'] + self._label_shift
|
| boxes = data['groundtruth_boxes']
|
|
|
| is_crowds = data['groundtruth_is_crowd']
|
|
|
| if self._skip_crowd_during_training:
|
| num_groundtruths = tf.shape(classes)[0]
|
| with tf.control_dependencies([num_groundtruths, is_crowds]):
|
| indices = tf.cond(
|
| tf.greater(tf.size(is_crowds), 0),
|
| lambda: tf.where(tf.logical_not(is_crowds))[:, 0],
|
| lambda: tf.cast(tf.range(num_groundtruths), tf.int64),
|
| )
|
| classes = tf.gather(classes, indices)
|
| boxes = tf.gather(boxes, indices)
|
|
|
|
|
| image = data['image']
|
|
|
|
|
| image = tf.image.convert_image_dtype(image, dtype=tf.float32)
|
|
|
| image = simclr_preprocess_ops.random_color_jitter(
|
| image=image,
|
| color_jitter_strength=self._aug_color_jitter_strength,
|
| impl=self._aug_color_jitter_impl,
|
| )
|
| image = tf.clip_by_value(image, 0.0, 1.0)
|
| image, boxes, _ = preprocess_ops.random_horizontal_flip(image, boxes)
|
|
|
| image_shape = tf.shape(image)[:2]
|
| boxes = box_ops.denormalize_boxes(boxes, image_shape)
|
|
|
| image, image_info = preprocess_ops.resize_and_crop_image(
|
| image,
|
| self._output_size,
|
| padded_size=self._output_size,
|
| aug_scale_min=self._aug_scale_min,
|
| aug_scale_max=self._aug_scale_max)
|
|
|
| boxes = preprocess_ops.resize_and_crop_boxes(
|
| boxes, image_info[2, :], image_info[1, :], image_info[3, :]
|
| )
|
| boxes = box_ops.normalize_boxes(boxes, image_info[1, :])
|
|
|
|
|
| indices = box_ops.get_non_empty_box_indices(boxes)
|
| boxes = tf.gather(boxes, indices)
|
| classes = tf.gather(classes, indices)
|
|
|
| boxes, classes = utils.reorder_object_instances(boxes, classes, 'random')
|
| boxes, classes = utils.inject_noise_bbox(
|
| boxes, classes, self._max_num_boxes
|
| )
|
|
|
| boxes = utils.clip_or_pad_to_max_len(boxes, self._max_num_boxes, 0)
|
| classes = utils.clip_or_pad_to_max_len(classes, self._max_num_boxes, 0)
|
|
|
| outputs = self.build_response_seq_from_bbox(
|
| boxes, classes, self._coord_vocab_shift, self._quantization_bins
|
| )
|
| response_seq, response_seq_class_m, token_weights = outputs
|
| prompt_seq = utils.build_prompt_seq_from_task_id(
|
| pix2seq_cfg.OD_ID, response_seq
|
| )
|
| input_seq = tf.concat([prompt_seq, response_seq_class_m], -1)
|
| target_seq = tf.concat([prompt_seq, response_seq], -1)
|
|
|
| backgrnd_val = 0.3
|
| image = backgrnd_val + tf.image.pad_to_bounding_box(
|
| image - backgrnd_val, 0, 0, self._output_size[0], self._output_size[1]
|
| )
|
|
|
| input_seq = utils.clip_or_pad_to_max_len(
|
| input_seq, self._max_num_boxes * 5 + 1, -1)
|
| target_seq = utils.clip_or_pad_to_max_len(
|
| target_seq, self._max_num_boxes * 5 + 1, -1
|
| )
|
|
|
| input_seq, target_seq = input_seq[..., :-1], target_seq[..., 1:]
|
| token_weights = utils.clip_or_pad_to_max_len(
|
| token_weights, self._max_num_boxes * 5, -1
|
| )
|
|
|
|
|
| token_weights = tf.where(
|
| target_seq == pix2seq_cfg.PADDING_TOKEN,
|
| tf.zeros_like(token_weights) + self._eos_token_weight,
|
| token_weights,
|
| )
|
|
|
| labels = {
|
| 'targets': target_seq,
|
| 'weights': token_weights,
|
| 'inputs': input_seq,
|
| }
|
|
|
| return image, labels
|
|
|
| def build_response_seq_from_bbox(
|
| self,
|
| bbox,
|
| label,
|
| coord_vocab_shift,
|
| quantization_bins,
|
| noise_bbox_weight=1.0,
|
| class_label_corruption='rand_n_fake_cls',
|
| ):
|
| """Build target seq from bounding bboxes for object detection.
|
|
|
| Objects are serialized using the format of yxyxc.
|
|
|
| Args:
|
| bbox: `float` bounding box of shape (n, 4).
|
| label: `int` label of shape (n).
|
| coord_vocab_shift: `int`, shifting coordinates by a specified integer.
|
| quantization_bins: `int`.
|
| noise_bbox_weight: `float` on the token weights for noise bboxes.
|
| class_label_corruption: `string` specifying how labels are corrupted for
|
| the input_seq.
|
|
|
| Returns:
|
| discrete sequences with shape (seqlen).
|
| """
|
|
|
| is_padding = tf.expand_dims(tf.equal(label, 0), -1)
|
| quantized_bbox = utils.quantize(bbox, quantization_bins)
|
| quantized_bbox = quantized_bbox + coord_vocab_shift
|
| quantized_bbox = tf.where(
|
| is_padding, tf.zeros_like(quantized_bbox), quantized_bbox
|
| )
|
| new_label = tf.expand_dims(label + pix2seq_cfg.BASE_VOCAB_SHIFT, -1)
|
| new_label = tf.where(is_padding, tf.zeros_like(new_label), new_label)
|
| lb_shape = tf.shape(new_label)
|
|
|
|
|
| response_seq = tf.concat([quantized_bbox, new_label], axis=-1)
|
|
|
| response_seq = tf.reshape(response_seq, [-1])
|
| rand_cls = pix2seq_cfg.BASE_VOCAB_SHIFT + tf.random.uniform(
|
| lb_shape,
|
| 0,
|
| coord_vocab_shift - pix2seq_cfg.BASE_VOCAB_SHIFT,
|
| dtype=new_label.dtype,
|
| )
|
| fake_cls = pix2seq_cfg.FAKE_CLASS_TOKEN + tf.zeros_like(new_label)
|
| rand_n_fake_cls = tf.where(
|
| tf.random.uniform(lb_shape) > 0.5, rand_cls, fake_cls
|
| )
|
| real_n_fake_cls = tf.where(
|
| tf.random.uniform(lb_shape) > 0.5, new_label, fake_cls
|
| )
|
| real_n_rand_n_fake_cls = tf.where(
|
| tf.random.uniform(lb_shape) > 0.5, new_label, rand_n_fake_cls
|
| )
|
| label_mapping = {
|
| 'none': new_label,
|
| 'rand_cls': rand_cls,
|
| 'real_n_fake_cls': real_n_fake_cls,
|
| 'rand_n_fake_cls': rand_n_fake_cls,
|
| 'real_n_rand_n_fake_cls': real_n_rand_n_fake_cls,
|
| }
|
| new_label_m = label_mapping[class_label_corruption]
|
| new_label_m = tf.where(is_padding, tf.zeros_like(new_label_m), new_label_m)
|
|
|
| response_seq_class_m = tf.concat([quantized_bbox, new_label_m], axis=-1)
|
| response_seq_class_m = tf.reshape(response_seq_class_m, [-1])
|
|
|
|
|
| is_real = tf.cast(
|
| tf.not_equal(new_label, pix2seq_cfg.FAKE_CLASS_TOKEN), tf.float32
|
| )
|
| bbox_weight = tf.tile(is_real, [1, 4])
|
| label_weight = is_real + (1.0 - is_real) * noise_bbox_weight
|
| token_weights = tf.concat([bbox_weight, label_weight], -1)
|
| token_weights = tf.reshape(token_weights, [-1])
|
|
|
| return response_seq, response_seq_class_m, token_weights
|
|
|
| def _parse_eval_data(self, data):
|
| """Parses data for training and evaluation."""
|
| classes = data['groundtruth_classes'] + self._label_shift
|
| boxes = data['groundtruth_boxes']
|
| is_crowd = data['groundtruth_is_crowd']
|
|
|
|
|
| image = data['image']
|
| image = tf.image.convert_image_dtype(image, dtype=tf.float32)
|
|
|
| image_shape = tf.shape(image)[:2]
|
| boxes = box_ops.denormalize_boxes(boxes, image_shape)
|
| gt_boxes = boxes
|
| image, image_info = preprocess_ops.resize_image(
|
| image, min(self._output_size), max(self._output_size)
|
| )
|
| boxes = preprocess_ops.resize_and_crop_boxes(
|
| boxes, image_info[2, :], image_info[1, :], image_info[3, :]
|
| )
|
| scale = tf.cast(
|
| tf.concat([self._output_size, self._output_size], -1), boxes.dtype
|
| )
|
| boxes = boxes / scale
|
|
|
|
|
| indices = box_ops.get_non_empty_box_indices(boxes)
|
| boxes = tf.gather(boxes, indices)
|
| classes = tf.gather(classes, indices)
|
| is_crowd = tf.gather(is_crowd, indices)
|
|
|
| prompt_seq = tf.constant([pix2seq_cfg.OD_ID], dtype=tf.int64)
|
| backgrnd_val = 0.3
|
| image = backgrnd_val + tf.image.pad_to_bounding_box(
|
| image - backgrnd_val, 0, 0, self._output_size[0], self._output_size[1]
|
| )
|
|
|
| labels = {
|
| 'prompt': prompt_seq,
|
| 'classes': preprocess_ops.clip_or_pad_to_fixed_size(
|
| classes, self._max_num_boxes
|
| ),
|
| 'boxes': preprocess_ops.clip_or_pad_to_fixed_size(
|
| boxes, self._max_num_boxes
|
| ),
|
| }
|
| labels.update({
|
| 'id': int(data['source_id']),
|
| 'image_info': image_info,
|
| 'is_crowd': preprocess_ops.clip_or_pad_to_fixed_size(
|
| is_crowd, self._max_num_boxes
|
| ),
|
| 'gt_boxes': preprocess_ops.clip_or_pad_to_fixed_size(
|
| gt_boxes, self._max_num_boxes
|
| ),
|
| })
|
|
|
| return image, labels
|
|
|