qitaoz's picture
Upload 57 files
4562a06 verified
import cv2
import ipdb
import numpy as np
from PIL import Image
import torch
# https://gist.github.com/davegreenwood/820d51ac5ec88a2aeda28d3079e7d9eb
def apply_distortion(pts, k1, k2):
"""
Arguments:
pts (N x 2): numpy array in NDC coordinates
k1, k2 distortion coefficients
Return:
pts (N x 2): distorted points in NDC coordinates
"""
r2 = np.square(pts).sum(-1)
f = 1 + k1 * r2 + k2 * r2**2
return f[..., None] * pts
# https://gist.github.com/davegreenwood/820d51ac5ec88a2aeda28d3079e7d9eb
def apply_distortion_tensor(pts, k1, k2):
"""
Arguments:
pts (N x 2): numpy array in NDC coordinates
k1, k2 distortion coefficients
Return:
pts (N x 2): distorted points in NDC coordinates
"""
r2 = torch.square(pts).sum(-1)
f = 1 + k1 * r2 + k2 * r2**2
return f[..., None] * pts
# https://gist.github.com/davegreenwood/820d51ac5ec88a2aeda28d3079e7d9eb
def remove_distortion_iter(points, k1, k2):
"""
Arguments:
pts (N x 2): numpy array in NDC coordinates
k1, k2 distortion coefficients
Return:
pts (N x 2): distorted points in NDC coordinates
"""
pts = ptsd = points
for _ in range(5):
r2 = np.square(pts).sum(-1)
f = 1 + k1 * r2 + k2 * r2**2
pts = ptsd / f[..., None]
return pts
def make_square(im, fill_color=(0, 0, 0)):
x, y = im.size
size = max(x, y)
new_im = Image.new("RGB", (size, size), fill_color)
corner = (int((size - x) / 2), int((size - y) / 2))
new_im.paste(im, corner)
return new_im, corner
def pixel_to_ndc(coords, image_size):
"""
Converts pixel coordinates to normalized device coordinates (Pytorch3D convention
with upper left = (1, 1)) for a square image.
Args:
coords: Pixel coordinates UL=(0, 0), LR=(image_size, image_size).
image_size (int): Image size.
Returns:
NDC coordinates UL=(1, 1) LR=(-1, -1).
"""
coords = np.array(coords)
return 1 - coords / image_size * 2
def ndc_to_pixel(coords, image_size):
"""
Converts normalized device coordinates to pixel coordinates for a square image.
"""
num_points = coords.shape[0]
sizes = np.tile(np.array(image_size, dtype=np.float32)[None, ...], (num_points, 1))
coords = np.array(coords, dtype=np.float32)
return (1 - coords) * sizes / 2
def distort_image(image, bbox, k1, k2, modify_bbox=False):
# We want to operate in -1 to 1 space using the padded square of the original image
image, corner = make_square(image)
bbox[:2] += np.array(corner)
bbox[2:] += np.array(corner)
# Construct grid points
x = np.linspace(1, -1, image.width, dtype=np.float32)
y = np.linspace(1, -1, image.height, dtype=np.float32)
x, y = np.meshgrid(x, y, indexing="xy")
xy_grid = np.stack((x, y), axis=-1)
points = xy_grid.reshape((image.height * image.width, 2))
new_points = ndc_to_pixel(apply_distortion(points, k1, k2), image.size)
# Distort image by remapping
map_x = new_points[:, 0].reshape((image.height, image.width))
map_y = new_points[:, 1].reshape((image.height, image.width))
distorted = cv2.remap(
np.asarray(image),
map_x,
map_y,
cv2.INTER_LINEAR,
)
distorted = Image.fromarray(distorted)
# Find distorted crop bounds - inverse process of above
if modify_bbox:
center = (bbox[:2] + bbox[2:]) / 2
top, bottom = (bbox[0], center[1]), (bbox[2], center[1])
left, right = (center[0], bbox[1]), (center[0], bbox[3])
bbox_points = np.array(
[
pixel_to_ndc(top, image.size),
pixel_to_ndc(left, image.size),
pixel_to_ndc(bottom, image.size),
pixel_to_ndc(right, image.size),
],
dtype=np.float32,
)
else:
bbox_points = np.array(
[pixel_to_ndc(bbox[:2], image.size), pixel_to_ndc(bbox[2:], image.size)],
dtype=np.float32,
)
# Inverse mapping
distorted_bbox = remove_distortion_iter(bbox_points, k1, k2)
if modify_bbox:
p = ndc_to_pixel(distorted_bbox, image.size)
distorted_bbox = np.array([p[0][0], p[1][1], p[2][0], p[3][1]])
else:
distorted_bbox = ndc_to_pixel(distorted_bbox, image.size).reshape(4)
return distorted, distorted_bbox