"git@developer.sourcefind.cn:OpenDAS/torch-cluster.git" did not exist on "7fc01df2a4cf646989f83742697aae4f6c3336f4"
Commit d660903c authored by Sam Tsai's avatar Sam Tsai Committed by Facebook GitHub Bot
Browse files

use nearest interpolation when applying segmentation warp for affine augmentation

Summary:
Pull Request resolved: https://github.com/facebookresearch/d2go/pull/413

Switch to using nearest pixel interpolation when warping and added unit test.

Reviewed By: wat3rBro

Differential Revision: D41042506

fbshipit-source-id: 92b817f21e862422428a0d0df969ec9e037f99fb
parent 69dac5b5
#!/usr/bin/env python3
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
import copy
import json
import random
from typing import List, Optional, Tuple
......@@ -37,18 +37,29 @@ class AffineTransform(Transform):
if border_mode is not None:
self.warp_kwargs["borderMode"] = border_mode
def apply_image(self, img: np.ndarray) -> np.ndarray:
def _warp_array(self, input_data: np.array, interp_flag: Optional[int] = None):
warp_kwargs = copy.deepcopy(self.warp_kwargs)
if interp_flag is not None:
flags = warp_kwargs.get("flags", 0)
# remove previous interp and add the new one
flags = (flags - (flags & cv2.INTER_MAX)) + interp_flag
warp_kwargs["flags"] = flags
M = self.M
if self.is_inversed_M:
M = M[:2]
img = cv2.warpAffine(
img,
input_data,
M,
(int(self.img_w), (self.img_h)),
**self.warp_kwargs,
**warp_kwargs,
)
return img
def apply_image(self, img: np.ndarray) -> np.ndarray:
return self._warp_array(img)
def apply_coords(self, coords: np.ndarray) -> np.ndarray:
# Add row of ones to enable matrix multiplication
coords = coords.T
......@@ -61,7 +72,7 @@ class AffineTransform(Transform):
return coords
def apply_segmentation(self, img: np.ndarray) -> np.ndarray:
raise NotImplementedError()
return self._warp_array(img, interp_flag=cv2.INTER_NEAREST)
class RandomPivotScaling(TransformGen):
......
......@@ -86,9 +86,25 @@ def generate_test_data(
borderMode=cv2.BORDER_REPLICATE,
)
# Create test boxes
M_inv = np.vstack([M_inv, [0.0, 0.0, 1.0]])
# Create annotations
test_bbox = [0.25 * img_w, 0.25 * img_h, 0.75 * img_h, 0.75 * img_h]
# Generate segmentation test data
segm_mask = np.zeros_like(source_img)
segm_mask[
int(test_bbox[0]) : int(test_bbox[2]), int(test_bbox[1]) : int(test_bbox[3])
] = 255
exp_out_segm = cv2.warpAffine(
segm_mask,
M_inv,
(out_w, out_h),
flags=cv2.WARP_INVERSE_MAP + cv2.INTER_NEAREST,
borderMode=cv2.BORDER_REPLICATE,
)
# Generate bounding box test data
M_inv = np.vstack([M_inv, [0.0, 0.0, 1.0]])
points = np.array(
[
[test_bbox[0], test_bbox[0], test_bbox[2], test_bbox[2]],
......@@ -97,7 +113,12 @@ def generate_test_data(
).T
_xp = warp_points(points, M_inv)
out_bbox = [min(_xp[:, 0]), min(_xp[:, 1]), max(_xp[:, 0]), max(_xp[:, 1])]
return aug_str, AugInput(source_img, boxes=[test_bbox]), (exp_out_img, [out_bbox])
return (
aug_str,
AugInput(source_img, boxes=[test_bbox], sem_seg=segm_mask),
(exp_out_img, [out_bbox], exp_out_segm),
)
def warp_points(coords: np.array, xfm_M: np.array):
......@@ -110,17 +131,25 @@ def warp_points(coords: np.array, xfm_M: np.array):
class TestDataTransformsAffine(unittest.TestCase):
def _check_array_close(self, aug_output, exp_img, exp_bboxes):
def _validate_results(self, aug_output, exp_outputs):
exp_img = exp_outputs[0]
self.assertTrue(
np.allclose(exp_img, aug_output.image),
f"Augmented image not the same, expecting\n{exp_img[:,:,0]} \n got\n{aug_output.image[:,:,0]} ",
)
exp_bboxes = exp_outputs[1]
self.assertTrue(
np.allclose(exp_bboxes, aug_output.boxes, atol=0.000001),
f"Augmented bbox not the same, expecting\n{exp_img[:,:,0]} \n got\n{aug_output.image[:,:,0]} ",
)
exp_segm = exp_outputs[2]
self.assertTrue(
np.allclose(exp_segm, aug_output.sem_seg),
f"Augmented segm not the same, expecting\n{exp_segm} \n got\n{aug_output.sem_seg[:,:]} ",
)
def test_affine_transforms_angle(self):
default_cfg = Detectron2GoRunner.get_default_cfg()
......@@ -129,15 +158,13 @@ class TestDataTransformsAffine(unittest.TestCase):
img[((img_sz + 1) // 2) - 1, :, :] = 255
for angle in [45, 90]:
aug_str, aug_input, (exp_out_img, exp_out_bboxes) = generate_test_data(
img, angle=angle
)
aug_str, aug_input, exp_outputs = generate_test_data(img, angle=angle)
default_cfg.D2GO_DATA.AUG_OPS.TRAIN = [aug_str]
tfm = build_transform_gen(default_cfg, is_train=True)
# Test augmentation
aug_output, _ = apply_augmentations(tfm, aug_input)
self._check_array_close(aug_output, exp_out_img, exp_out_bboxes)
self._validate_results(aug_output, exp_outputs)
def test_affine_transforms_translation(self):
default_cfg = Detectron2GoRunner.get_default_cfg()
......@@ -148,7 +175,7 @@ class TestDataTransformsAffine(unittest.TestCase):
for translation in [0, 1, 2]:
# Test image
aug_str, aug_input, (exp_out_img, exp_out_bboxes) = generate_test_data(
aug_str, aug_input, exp_outputs = generate_test_data(
img, translation=translation
)
default_cfg.D2GO_DATA.AUG_OPS.TRAIN = [aug_str]
......@@ -156,7 +183,7 @@ class TestDataTransformsAffine(unittest.TestCase):
# Test augmentation
aug_output, _ = apply_augmentations(tfm, aug_input)
self._check_array_close(aug_output, exp_out_img, exp_out_bboxes)
self._validate_results(aug_output, exp_outputs)
def test_affine_transforms_shear(self):
default_cfg = Detectron2GoRunner.get_default_cfg()
......@@ -166,15 +193,13 @@ class TestDataTransformsAffine(unittest.TestCase):
img[((img_sz + 1) // 2) - 1, :, :] = 255
for shear in [0, 1, 2]:
aug_str, aug_input, (exp_out_img, exp_out_bboxes) = generate_test_data(
img, shear=shear
)
aug_str, aug_input, exp_outputs = generate_test_data(img, shear=shear)
default_cfg.D2GO_DATA.AUG_OPS.TRAIN = [aug_str]
tfm = build_transform_gen(default_cfg, is_train=True)
# Test augmentation
aug_output, _ = apply_augmentations(tfm, aug_input)
self._check_array_close(aug_output, exp_out_img, exp_out_bboxes)
self._validate_results(aug_output, exp_outputs)
def test_affine_transforms_scale(self):
default_cfg = Detectron2GoRunner.get_default_cfg()
......@@ -184,15 +209,13 @@ class TestDataTransformsAffine(unittest.TestCase):
img[((img_sz + 1) // 2) - 1, :, :] = 255
for scale in [0.9, 1, 1.1]:
aug_str, aug_input, (exp_out_img, exp_out_bboxes) = generate_test_data(
img, scale=scale
)
aug_str, aug_input, exp_outputs = generate_test_data(img, scale=scale)
default_cfg.D2GO_DATA.AUG_OPS.TRAIN = [aug_str]
tfm = build_transform_gen(default_cfg, is_train=True)
# Test augmentation
aug_output, _ = apply_augmentations(tfm, aug_input)
self._check_array_close(aug_output, exp_out_img, exp_out_bboxes)
self._validate_results(aug_output, exp_outputs)
def test_affine_transforms_angle_non_square(self):
default_cfg = Detectron2GoRunner.get_default_cfg()
......@@ -202,7 +225,7 @@ class TestDataTransformsAffine(unittest.TestCase):
img[((img_sz + 1) // 2) - 1, :, :] = 255
for keep_aspect_ratio in [False, True]:
aug_str, aug_input, (exp_out_img, exp_out_bboxes) = generate_test_data(
aug_str, aug_input, exp_outputs = generate_test_data(
img, angle=45, keep_aspect_ratio=keep_aspect_ratio
)
default_cfg.D2GO_DATA.AUG_OPS.TRAIN = [aug_str]
......@@ -210,7 +233,7 @@ class TestDataTransformsAffine(unittest.TestCase):
# Test augmentation
aug_output, _ = apply_augmentations(tfm, aug_input)
self._check_array_close(aug_output, exp_out_img, exp_out_bboxes)
self._validate_results(aug_output, exp_outputs)
def test_affine_transforms_angle_no_fit_to_frame(self):
default_cfg = Detectron2GoRunner.get_default_cfg()
......@@ -219,7 +242,7 @@ class TestDataTransformsAffine(unittest.TestCase):
img = np.zeros((img_sz, img_sz, 3)).astype(np.uint8)
img[((img_sz + 1) // 2) - 1, :, :] = 255
aug_str, aug_input, (exp_out_img, exp_out_bboxes) = generate_test_data(
aug_str, aug_input, exp_outputs = generate_test_data(
img, angle=45, fit_in_frame=False
)
default_cfg.D2GO_DATA.AUG_OPS.TRAIN = [aug_str]
......@@ -227,4 +250,4 @@ class TestDataTransformsAffine(unittest.TestCase):
# Test augmentation
aug_output, _ = apply_augmentations(tfm, aug_input)
self._check_array_close(aug_output, exp_out_img, exp_out_bboxes)
self._validate_results(aug_output, exp_outputs)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment