├── PIT_tensor.py ├── README.md ├── pit_annotations.py ├── pit_images_in_root_folder.py ├── test_annotations └── aachen_000000_000019_leftImg8bit.xml └── test_images ├── RGB.png └── gray.png /PIT_tensor.py: -------------------------------------------------------------------------------- 1 | #position-invariant transform 2 | 3 | import math 4 | import os 5 | import sys 6 | import time 7 | import numpy as np 8 | from PIL import Image 9 | import torch 10 | from itertools import product 11 | 12 | 13 | class PIT_module: 14 | def __init__(self, w, h, fovx = 0, fovy = 0, isPITedSize = False): 15 | ''' 16 | w and h: the width and height of input image 17 | fovx, fovy: intrinsic parameter of camera. One is enough, the other would be calculated through the aspect ratio. Provided in Radian system. 18 | isPITedSize = False: the size(w and h) comes from an original image. 19 | isPITedSize = Ture: the size(w and h) comes from a PITed image. (Used when need to reverse PIT a PITed image, and don't know the original size) 20 | 21 | If you need to transform a image circularly (original->PITed->original), don't need to create two PIT_module. 22 | You can do this by setting the "reverse" parameter in the "pit" function. 23 | ''' 24 | self.device = 'cuda' if torch.cuda.is_available() else 'cpu' 25 | self.plain_width, self.plain_height, self.arc_width, self.arc_height = 0, 0, 0, 0 26 | 27 | if isPITedSize: 28 | self.arc_width, self.arc_height = w, h 29 | self.cal_fov_with_PITed_image_size(fovx, fovy) 30 | self.cal_plain_size() 31 | else: 32 | self.plain_width, self.plain_height = w, h 33 | self.aspect_ratio = self.plain_width / self.plain_height 34 | self.cal_fov_with_original_image_size(fovx, fovy) 35 | self.cal_arc_size() 36 | 37 | self.arc_pos_list = 0 38 | self.plain_pos_list = 0 39 | 40 | def cal_fov_with_PITed_image_size(self, fovx, fovy): 41 | '''Calculate the focal length (and fovx or fovy if not provided)''' 42 | self.fovx = fovx 43 | self.fovy = fovy 44 | if not fovx == 0 and not fovy == 0: 45 | self.fx = self.arc_width / fovx 46 | self.fy = self.arc_height / fovy 47 | elif not fovx == 0: 48 | self.fx = self.arc_width / fovx 49 | self.fy = self.fx 50 | self.fovy = self.arc_height / self.fy 51 | elif not fovy == 0: 52 | self.fy = self.arc_height / fovy 53 | self.fx = self.fy 54 | self.fovx = self.arc_width/ self.fx 55 | 56 | def cal_fov_with_original_image_size(self, fovx, fovy): 57 | self.fovx = fovx 58 | self.fovy = fovy 59 | if not fovx == 0 and not fovy == 0: 60 | pass 61 | elif not fovx == 0: 62 | self.fovy = 2 * math.atan(1 / self.aspect_ratio * math.tan(fovx / 2)) 63 | else: # not fovy == 0: 64 | self.fovx = 2 * math.atan(self.aspect_ratio * math.tan(fovy / 2)) 65 | 66 | self.fx = self.plain_width / (2 * math.tan(self.fovx / 2)) #focal length x 67 | self.fy = self.plain_height / (2 * math.tan(self.fovy / 2)) #focal length y 68 | 69 | def cal_arc_size(self): 70 | '''known the size of original image, calculate the size of PITed image''' 71 | self.arc_width = int(2 * math.atan(self.plain_width / self.fx / 2) * self.fx) 72 | self.arc_height = int(2 * math.atan(self.plain_height / self.fy / 2) * self.fy) 73 | 74 | def cal_plain_size(self): 75 | '''known the size of PITed image, calculate the size of original image''' 76 | self.plain_width = int(2 * math.tan(self.arc_width / self.fx / 2) * self.fx) 77 | self.plain_height = int(2 * math.tan(self.arc_height / self.fy / 2) * self.fy) 78 | 79 | def coord_plain_to_arc(self, pos_list): 80 | x = pos_list[:,0] + 0.5 81 | y = pos_list[:,1] + 0.5 82 | u = self.fx * (self.fovx / 2 - torch.atan((self.plain_width / 2 - x) / self.fx)) 83 | v = self.fy * (self.fovy / 2 - torch.atan((self.plain_height / 2 - y) / self.fy)) 84 | u -= 0.5 85 | v -= 0.5 86 | new_pos_list = torch.stack([u, v], dim=1) 87 | return new_pos_list 88 | 89 | def coord_arc_to_plain(self, pos_list): 90 | u = pos_list[:,0] + 0.5 91 | v = pos_list[:,1] + 0.5 92 | x = self.plain_width / 2 - self.fx * torch.tan(self.fovx / 2 - u / self.fx) 93 | y = self.plain_height / 2 - self.fy * torch.tan(self.fovy / 2 - v / self.fy) 94 | x -= 0.5 95 | y -= 0.5 96 | new_pos_list = torch.stack([x,y], dim = 1) 97 | return new_pos_list 98 | 99 | def coord_plain_to_arc_scalar(self, x, y): 100 | '''used for pit annotations''' 101 | x += 0.5 102 | y += 0.5 103 | u = self.fx * (self.fovx / 2 - math.atan((self.plain_width / 2 - x) / self.fx)) 104 | v = self.fy * (self.fovy / 2 - math.atan((self.plain_height / 2 - y) / self.fy)) 105 | u -= 0.5 106 | v -= 0.5 107 | return u,v 108 | 109 | def coord_arc_to_plain_scalar(self, u, v): 110 | '''used for pit annotations''' 111 | u += 0.5 112 | v += 0.5 113 | x = self.plain_width / 2 - self.fx * math.tan(self.fovx / 2 - u / self.fx) 114 | y = self.plain_height / 2 - self.fy * math.tan(self.fovy / 2 - v / self.fy) 115 | x -= 0.5 116 | y -= 0.5 117 | return x,y 118 | 119 | def create_pos_list(self, w, h): 120 | a = list(range(w)) # x 121 | b = list(range(h)) # y 122 | pos = [i for i in product(a, b)] 123 | pos = torch.Tensor(pos).to(self.device) 124 | return pos 125 | 126 | def limit_range(self, t, min_value, max_value): 127 | '''set the value in t less than min_value to min_value, more than max_value to max_value''' 128 | t[t < min_value] = min_value 129 | t[t > max_value] = max_value 130 | return t 131 | 132 | def pit_cal_pos_list(self): 133 | '''used for pit''' 134 | self.arc_pos_list = self.create_pos_list(self.arc_width, self.arc_height) 135 | self.pos_in_plain = self.coord_arc_to_plain(self.arc_pos_list) 136 | self.pos_in_plain_nearest = self.change_2d_pos_into_1d_index(self.limit_range(torch.round(self.pos_in_plain), 0, self.plain_width - 1), self.plain_width) 137 | self.pos_in_plain_4_vtx = self.cal_4_vertex(self.pos_in_plain, self.plain_width, self.plain_height) 138 | self.pos_in_plain_16_vtx = self.cal_16_vertex(self.pos_in_plain, self.plain_width, self.plain_height) 139 | 140 | def rpit_cal_pos_list(self): 141 | '''used for reversed pit''' 142 | self.plain_pos_list = self.create_pos_list(self.plain_width, self.plain_height) 143 | self.pos_in_arc = self.coord_plain_to_arc(self.plain_pos_list) 144 | self.pos_in_arc_nearest = self.change_2d_pos_into_1d_index(self.limit_range(torch.round(self.pos_in_arc), 0, self.arc_width - 1), self.arc_width) 145 | self.pos_in_arc_4_vtx = self.cal_4_vertex(self.pos_in_arc, self.arc_width, self.arc_height) 146 | self.pos_in_arc_16_vtx = self.cal_16_vertex(self.pos_in_arc, self.arc_width, self.arc_height) 147 | 148 | def change_2d_pos_into_1d_index(self, pos, w): 149 | '''the function torch.take() needs 1d index''' 150 | x = pos[:, 0] 151 | y = pos[:, 1] 152 | pos_1dim = (y * w + x).long() 153 | return pos_1dim 154 | 155 | def cal_4_vertex(self, pos_in_float, w, h): 156 | '''used for bilinear interpolation''' 157 | x = self.limit_range(pos_in_float[:, 0], 0, w - 1) # 158 | y = self.limit_range(pos_in_float[:, 1], 0, h - 1) 159 | 160 | pos_4_vtx = [[0 for i in range(2)] for i in range(2)] 161 | 162 | x_list = [torch.floor(x), torch.ceil(x)] # x_list 163 | y_list = [torch.floor(y), torch.ceil(y)] # y_list 164 | 165 | for i in range(2): 166 | for j in range(2): 167 | pos = torch.stack([x_list[i], y_list[j]], dim=1) 168 | pos_4_vtx[i][j] = self.change_2d_pos_into_1d_index(pos, w) 169 | 170 | return pos_4_vtx 171 | 172 | def cal_16_vertex(self, pos_in_float, w, h): 173 | '''used for bicubic interpolation''' 174 | x = self.limit_range(pos_in_float[:, 0], 0, w - 1) # 175 | y = self.limit_range(pos_in_float[:, 1], 0, h - 1) 176 | x_list = [self.limit_range(torch.floor(x), 0, w - 1), torch.floor(x), torch.ceil(x), 177 | self.limit_range((torch.ceil(x) + 1), 0, w - 1)] 178 | y_list = [self.limit_range(torch.floor(y), 0, h - 1), torch.floor(y), torch.ceil(y), 179 | self.limit_range((torch.ceil(y) + 1), 0, h - 1)] 180 | 181 | pos_16_vtx = [[0 for i in range(4)] for i in range(4)] 182 | for i in range(4): 183 | for j in range(4): 184 | pos = torch.stack([x_list[i], y_list[j]], dim=1) 185 | pos_16_vtx[i][j] = self.change_2d_pos_into_1d_index(pos, w) 186 | return pos_16_vtx 187 | 188 | def nearest_interpolation(self, this_channel, pos): 189 | res = torch.take(this_channel, pos) 190 | return res 191 | 192 | def bilinear_interpolation(self, this_channel, real_pos, pos_4_vtx): 193 | pix_lt = torch.take(this_channel, pos_4_vtx[0][0]) # left top 194 | pix_lb = torch.take(this_channel, pos_4_vtx[0][1]) # left bottom 195 | pix_rt = torch.take(this_channel, pos_4_vtx[1][0]) # right top 196 | pix_rb = torch.take(this_channel, pos_4_vtx[1][1]) # right bottom 197 | 198 | x = real_pos[:, 0] 199 | y = real_pos[:, 1] 200 | xfrac, yfrac = torch.frac(x), torch.frac(y) 201 | 202 | t = pix_lt + (pix_rt - pix_lt) * xfrac 203 | b = pix_lb + (pix_rb - pix_lb) * xfrac 204 | res = t + (b - t) * yfrac 205 | return res 206 | 207 | def W(self, x): 208 | '''used for bicubic interpolation''' 209 | x = torch.abs(x) 210 | a = 1 - 2 * x.pow(2) + x.pow(3) 211 | b = 4 - 8 * x + 5 * x.pow(2) - x.pow(3) 212 | c = torch.zeros_like(x) 213 | res1 = torch.where(x <= 1, a, c) 214 | res2 = torch.where(x < 2, b, c) 215 | res = torch.where(res1 == c, res2, res1) 216 | return res 217 | 218 | def bicubic_interpolation(self, this_channel, real_pos, pos_16_vtx): 219 | ''' 220 | The caculation method comes from: 221 | https://blog.csdn.net/yycocl/article/details/102588362 222 | ''' 223 | x = real_pos[:, 0] 224 | y = real_pos[:, 1] 225 | u,v = torch.frac(x), torch.frac(y) #u,v 226 | 227 | pix = [[0 for i in range(4)] for i in range(4)] 228 | for i in range(4): 229 | for j in range(4): 230 | pix[i][j] = torch.take(this_channel, pos_16_vtx[i][j]) 231 | 232 | res = 0 233 | 234 | for i in range(4): 235 | for j in range(4): 236 | res += pix[i][j] * self.W((i - 1 - u)) * self.W((j - 1 - v)) 237 | res = self.limit_range(res, 0, 255) 238 | return res 239 | 240 | def pit(self, im, interpolation = 'bilinear', reverse = False, ori_w = 0, ori_h = 0): 241 | ''' 242 | im: image in torch tensor format. [N,C,H,W] 243 | reverse: False = PIT, True = rPIT 244 | interpolation: 'nearest' or 'bilinear' or 'bicubic' 245 | ori_w/ori_h: the width and height of original image. 246 | if reverse == True, the better to provide them to avoid error. 247 | ''' 248 | pix = im #n,c,h,w torch.Size([2, 19, 180, 360]) 249 | #print(im.shape) 250 | start_dim = len(pix.shape) 251 | assert start_dim == 3 or start_dim == 4 252 | if start_dim == 3: #n,h,w 253 | pix = pix[:,None,:,:] 254 | 255 | self.n, self.c = pix.shape[0], pix.shape[1] 256 | 257 | pos = 0 258 | pos_4_vtx = 0 259 | pos_nearest = 0 260 | new_w, new_h = 0, 0 261 | 262 | if not reverse: 263 | new_w, new_h = self.arc_width, self.arc_height 264 | if type(self.arc_pos_list) == type(0): 265 | self.pit_cal_pos_list() 266 | pos = self.pos_in_plain 267 | pos_nearest = self.pos_in_plain_nearest 268 | pos_4_vtx = self.pos_in_plain_4_vtx 269 | pos_16_vtx = self.pos_in_plain_16_vtx 270 | if reverse: 271 | if ori_w: 272 | self.plain_width = ori_w 273 | if ori_h: 274 | self.plain_height = ori_h 275 | new_w, new_h = self.plain_width, self.plain_height 276 | if type(self.plain_pos_list) == type(0): 277 | self.rpit_cal_pos_list() 278 | pos = self.pos_in_arc 279 | pos_nearest = self.pos_in_arc_nearest 280 | pos_4_vtx = self.pos_in_arc_4_vtx 281 | pos_16_vtx = self.pos_in_arc_16_vtx 282 | 283 | batch_new = [] 284 | for i in range(self.n): 285 | pix_new = [] 286 | for j in range(self.c): 287 | res = 0 288 | this_channel = pix[i,j,:,:].float().squeeze() # dtype:torch.float32 289 | if interpolation == 'nearest' or interpolation == 1: 290 | res = self.nearest_interpolation(this_channel, pos_nearest) 291 | elif interpolation == 'bilinear' or interpolation == 2: 292 | res = self.bilinear_interpolation(this_channel, pos, pos_4_vtx) 293 | elif interpolation == 'bicubic' or interpolation == 3: 294 | res = self.bicubic_interpolation(this_channel, pos, pos_16_vtx) 295 | else: 296 | print('"' + interpolation + '" is not a interpolation mode!') 297 | 298 | res = torch.round(res).reshape(new_w, new_h).int() # [h,w] 299 | res = torch.transpose(res, 0, 1) # [h,w] 300 | pix_new.append(res) 301 | pix_new = torch.stack(pix_new, dim=0) 302 | batch_new.append(pix_new) 303 | batch_new = torch.stack(batch_new, dim = 0) 304 | if start_dim == 3: 305 | batch_new = batch_new.flatten(0,1) 306 | return batch_new 307 | 308 | '''convert tensor and image''' 309 | def tensor_to_image(t): 310 | if t.shape[1] == 1: #gray image 311 | im = t[0, 0, ...] 312 | else: #rgb image 313 | im = t[0,...].permute(1,2,0) # n c h w -> h w c 314 | if not t.device == 'cpu': 315 | im = im.cpu() 316 | im = im.numpy().astype('uint8') 317 | im = Image.fromarray(im) 318 | return im 319 | 320 | def image_to_tensor(im): 321 | t = torch.from_numpy(np.array(im)) #[h,w,channel] 322 | if len(t.shape) == 2: #gray images 323 | t = t[None, None, ...] # n c h w 324 | else: #RGB images 325 | t = t.permute(2, 0, 1)[None, ...] #n c h w 326 | return t 327 | 328 | 329 | if __name__ == "__main__": 330 | '''Usage of PIT_module''' 331 | 332 | interpolation = 2 333 | 334 | '''testing for gray images (1 channel) ''' 335 | im_path = 'test_images/gray.png' 336 | im = Image.open(im_path) 337 | t = image_to_tensor(im).cuda() #create input 338 | 339 | width, height = t.shape[3], t.shape[2] 340 | proj = PIT_module(width, height, fovx=math.pi / 2) 341 | 342 | t_new = proj.pit(t, interpolation = interpolation) 343 | im_new = tensor_to_image(t_new) 344 | im_new.save('test_images/gray_pit_tensor.png') 345 | 346 | t_cycle = proj.pit(t_new, interpolation = interpolation, reverse = True, ori_w = width, ori_h = height) 347 | im_cycle = tensor_to_image(t_cycle) 348 | im_cycle.save('test_images/gray_cycle_tensor.png') 349 | 350 | '''testing for RGB images (3 channel)''' 351 | im_path = 'test_images/RGB.png' 352 | im = Image.open(im_path) 353 | t = image_to_tensor(im).cuda() #create input 354 | 355 | width, height = t.shape[3], t.shape[2] 356 | proj = PIT_module(width, height, fovx=math.pi / 2) 357 | 358 | t_new = proj.pit(t, interpolation = interpolation) 359 | im_new = tensor_to_image(t_new) 360 | im_new.save('test_images/RGB_pit_tensor.png') 361 | 362 | t_cycle = proj.pit(t_new, interpolation = interpolation, reverse = True, ori_w = width, ori_h = height) 363 | im_cycle = tensor_to_image(t_cycle) 364 | im_cycle.save('test_images/RGB_cycle_tensor.png') 365 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## [PIT: Position-Invariant Transform for Cross-FoV Domain Adaptation (ICCV 2021)](https://arxiv.org/abs/2108.07142) 2 | 3 | ### Getting started 4 | *** 5 | ##### Clone the repo: 6 | 7 | ``` 8 | git clone https://github.com/sheepooo/pit-Position-Invariant-Transform/ 9 | cd pit-Position-Invariant-Transform 10 | ``` 11 | 12 | ##### Requirements: 13 | 14 | * Python >= 3.6 (numpy, itertools, argparse) 15 | * pytorch >= 0.4.1 16 | 17 | ##### To test the PIT function, you can run: 18 | ``` 19 | python PIT_tensor.py 20 | ``` 21 | The images in "test_images" folder will be PITed and RPITed, and be saved in the same folder. 22 | 23 | 24 | ##### To PIT all images in a folder, you can run: 25 | ``` 26 | python pit_images_in_root_folder.py --fovx 'YourFovx' --root_path 'YourImageFolderName' 27 | ``` 28 | either fovx or fovy is enough (both is ok, too). 29 | **NOTICE:** this code would change the images in the root folder directly, so you may need to back up the original images. 30 | 31 | ##### To PIT annotations for object detection (XML file in "Pascal VOC" format, as shown in the "test_annotations" folder), you can run: 32 | ``` 33 | python pit_annotations.py --fovx 'YourFovx' --root_path 'YourAnnotationFolderName' 34 | ``` 35 | either fovx or fovy is enough (both is ok, too). 36 | **NOTICE:** this code would change the annotations in the root folder directly, so you may need to back up the original annotations. 37 | -------------------------------------------------------------------------------- /pit_annotations.py: -------------------------------------------------------------------------------- 1 | import os 2 | import math 3 | import numpy as np 4 | import argparse 5 | import xml.etree.ElementTree as ET 6 | from PIT_tensor import * 7 | 8 | def parse_args(): 9 | """ 10 | Parse input arguments 11 | """ 12 | parser = argparse.ArgumentParser(description='FOV') 13 | parser.add_argument('--dataset', dest='dataset', 14 | help='cityscapes, foggy_cityscapes, kitti, vkitti', 15 | default='kitti', type=str) 16 | parser.add_argument('--fovx', dest='fovx', 17 | help='fovx, the unit is degree', 18 | default='0', type=int) 19 | parser.add_argument('--fovy', dest='fovy', 20 | help='fovy, the unit is degree', 21 | default='0', type=int) 22 | parser.add_argument('--root_path', dest='root_path', 23 | help='the path to place the image folder', 24 | default='./', type=str) 25 | args = parser.parse_args() 26 | return args 27 | 28 | if __name__ == "__main__": 29 | args = parse_args() 30 | print(args) 31 | 32 | for root_folder, dirs, files in os.walk(args.root_path, topdown=True): 33 | print(root_folder) 34 | for file_name in files: 35 | if not file_name.endswith('.xml'): 36 | continue 37 | print(file_name) 38 | file_path = os.path.join(root_folder, file_name) 39 | tree = ET.parse(file_path) 40 | root = tree.getroot() 41 | 42 | size = root.find('size') 43 | width, height = int(size[0].text), int(size[1].text) 44 | pit = PIT_module(width, height, args.fovx / 180 * math.pi, args.fovy / 180 * math.pi) 45 | 46 | new_width, new_height = pit.coord_plain_to_arc_scalar(width, height) 47 | new_width, new_height = int(new_width), int(new_height) 48 | # print(new_width) 49 | size[0].text, size[1].text = str(new_width), str(new_height) 50 | 51 | for child in root: 52 | if child.tag == 'object': 53 | bbox = child.find('bndbox') 54 | xmin, ymin = int(bbox[0].text), int(bbox[1].text) 55 | xmax, ymax = int(bbox[2].text), int(bbox[3].text) 56 | #print('--',xmin, ymin, xmax, ymax, new_width, new_height) 57 | xmin, ymin = pit.coord_plain_to_arc_scalar(xmin, ymin) 58 | xmax, ymax = pit.coord_plain_to_arc_scalar(xmax, ymax) 59 | xmin, ymin = int(np.around(xmin)), int(np.around(ymin)) 60 | xmax, ymax = int(np.around(xmax)), int(np.around(ymax)) 61 | #print(xmin, ymin, xmax, ymax, new_width, new_height) 62 | assert xmin >= 0 and ymin >= 0 and xmax - 1 <= new_width and ymax - 1 <= new_height 63 | if xmax > new_width: 64 | xmax = new_width 65 | if ymax > new_height: 66 | ymax = new_height 67 | bbox[0].text, bbox[1].text = str(xmin), str(ymin) 68 | bbox[2].text, bbox[3].text = str(xmax), str(ymax) 69 | # break 70 | tree.write(file_path) 71 | 72 | print('All annotation projected') 73 | -------------------------------------------------------------------------------- /pit_images_in_root_folder.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from PIT_tensor import * 3 | 4 | def parse_args(): 5 | """ 6 | Parse input arguments 7 | """ 8 | parser = argparse.ArgumentParser(description='position invariant transform') 9 | parser.add_argument('--fovx', dest='fovx', 10 | help='fovx, the unit is degree', 11 | default='0', type=int) 12 | parser.add_argument('--fovy', dest='fovy', 13 | help='fovy, the unit is degree', 14 | default='0', type=int) 15 | parser.add_argument('--root_path', dest='root_path', 16 | help='the path to place the image folder', 17 | default='./', type=str) 18 | parser.add_argument('--itp', dest='itp', 19 | help='interpolation mode, 1 as nearest, 2 as biliner, 3 as bicubic', 20 | default='2', type=int) 21 | args = parser.parse_args() 22 | return args 23 | 24 | if __name__ == "__main__": 25 | args = parse_args() 26 | print(args) 27 | 28 | for root, dirs, files in os.walk(args.root_path, topdown=True): 29 | for name in files: 30 | if not name.endswith('.png') and not name.endswith('.jpg'): 31 | continue 32 | im_path = os.path.join(root, name) 33 | 34 | img = Image.open(im_path) 35 | im = image_to_tensor(img).cuda() # create input 36 | width, height = im.shape[3], im.shape[2] 37 | proj = PIT_module(width, height, fovx = args.fovx / 180 * math.pi, fovy = args.fovy * math.pi / 180) 38 | 39 | im_new = proj.pit(im, interpolation=args.itp, reverse=False) 40 | im_new = tensor_to_image(im_new) 41 | im_new.save(im_path) 42 | img.close() 43 | 44 | print('Image done:', im_path) 45 | 46 | 47 | -------------------------------------------------------------------------------- /test_annotations/aachen_000000_000019_leftImg8bit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | VOC2007 4 | source_aachen_000000_000019_leftImg8bit.jpg 5 | 6 | The VOC2007 Database 7 | PASCAL VOC2007 8 | flickr 9 | 0 10 | 11 | 12 | rtz 13 | ? 14 | 15 | 16 | 2048 17 | 1024 18 | 3 19 | 20 | 0 21 | 22 | car 23 | Frontal 24 | 0 25 | 0 26 | 27 | 609 28 | 420 29 | 807 30 | 532 31 | 32 | 33 | 34 | car 35 | Frontal 36 | 0 37 | 0 38 | 39 | 145 40 | 429 41 | 304 42 | 502 43 | 44 | 45 | 46 | car 47 | Frontal 48 | 0 49 | 0 50 | 51 | 145 52 | 429 53 | 304 54 | 502 55 | 56 | 57 | 58 | car 59 | Frontal 60 | 0 61 | 0 62 | 63 | 1962 64 | 488 65 | 2047 66 | 526 67 | 68 | 69 | 70 | car 71 | Frontal 72 | 0 73 | 0 74 | 75 | 1512 76 | 446 77 | 1660 78 | 499 79 | 80 | 81 | 82 | car 83 | Frontal 84 | 0 85 | 0 86 | 87 | 1479 88 | 456 89 | 1634 90 | 514 91 | 92 | 93 | 94 | car 95 | Frontal 96 | 0 97 | 0 98 | 99 | 1862 100 | 481 101 | 1937 102 | 500 103 | 104 | 105 | 106 | car 107 | Frontal 108 | 0 109 | 0 110 | 111 | 1872 112 | 486 113 | 1956 114 | 513 115 | 116 | 117 | 118 | car 119 | Frontal 120 | 0 121 | 0 122 | 123 | 1721 124 | 450 125 | 1864 126 | 509 127 | 128 | 129 | 130 | car 131 | Frontal 132 | 0 133 | 0 134 | 135 | 145 136 | 429 137 | 304 138 | 502 139 | 140 | 141 | 142 | bicycle 143 | Frontal 144 | 0 145 | 0 146 | 147 | 787 148 | 446 149 | 828 150 | 490 151 | 152 | 153 | 154 | person 155 | Frontal 156 | 0 157 | 0 158 | 159 | 887 160 | 446 161 | 913 162 | 498 163 | 164 | 165 | 166 | person 167 | Frontal 168 | 0 169 | 0 170 | 171 | 901 172 | 444 173 | 934 174 | 498 175 | 176 | 177 | 178 | car 179 | Frontal 180 | 0 181 | 0 182 | 183 | 609 184 | 420 185 | 807 186 | 532 187 | 188 | 189 | 190 | rider 191 | Frontal 192 | 0 193 | 0 194 | 195 | 1831 196 | 436 197 | 1887 198 | 548 199 | 200 | 201 | 202 | bicycle 203 | Frontal 204 | 0 205 | 0 206 | 207 | 1839 208 | 468 209 | 1877 210 | 547 211 | 212 | 213 | -------------------------------------------------------------------------------- /test_images/RGB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sheepooo/PIT-Position-Invariant-Transform/990e71d3f1971299328a31c81268641809530d1e/test_images/RGB.png -------------------------------------------------------------------------------- /test_images/gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sheepooo/PIT-Position-Invariant-Transform/990e71d3f1971299328a31c81268641809530d1e/test_images/gray.png --------------------------------------------------------------------------------