CoolFace
Apppublic

naver/PUMP

sourceHugging Faceupdated 4y agoView on Hugging Face
1likes
pair_loader.py292 linesDownload Raw Back to datasets
1# Copyright 2022-present NAVER Corp.2# CC BY-NC-SA 4.03# Available only for non-commercial use4 5from pdb import set_trace as bb6from PIL import Image7import numpy as np8 9from core import functional as myF10from tools.common import todevice11from .transforms import instanciate_transforms12from .utils import *13 14 15class FastPairLoader (DatasetWithRng):16    """ On-the-fly generation of related image pairs17    crop:   random crop applied to both images18    scale:  random scaling applied to img219    distort: random ditorsion applied to img220    21    self[idx] returns: (img1, img2), dict(homography=)22        (homography: 3x3 array, can be nan)23    """24    def __init__(self, dataset, crop=256, transform='', p_flip=0, p_swap=0, scale_jitter=0, seed=None):25        super().__init__(seed)26        self.dataset = self.with_same_rng(dataset)27        self.transform = instanciate_transforms( transform, rng=self.rng )28        self.crop_size = crop29        self.p_swap = p_swap30        self.p_flip = p_flip31        self.scale_jitter = abs(np.log1p(scale_jitter))32 33    def __len__(self):34        return len(self.dataset)35 36    def __repr__(self):37        fmt_str = f'FastPairLoader({self.dataset},\n'38        short_repr = lambda s: repr(s).strip().replace('\n',', ')[14:-1].replace('    ',' ')39        fmt_str += '    Transform:\t%s\n' % short_repr(self.transform)40        fmt_str +=f'    Crop={self.crop_size}, scale_jitter=x{np.exp(self.scale_jitter):g}, p_swap={self.p_swap:g}'41        return fmt_str42 43    def init_worker(self, tid):44        super().init_worker(tid)45        self.dataset.init_worker(tid)46 47    def set_epoch(self, epoch):48        self.dataset.set_epoch(epoch)49 50    def __getitem__(self, idx):51        self.init_worker(idx) # preserve RNG for this pair52        (img1, img2), gt = self.dataset[idx]53 54        if self.rng.random() < self.p_swap:55            img1, img2 = img2, img156            if 'homography' in gt: gt['homography'] = invh(gt['homography'])57            if 'corres' in gt: gt['corres'] = swap_corres(gt['corres'])58 59        if self.rng.random() < self.p_flip:60            img1, img2, gt = flip_image_pair(img1, img2, gt)61 62        # apply transformations to the second image63        img2 = self.transform(dict(img=img2))64 65        homography, corres = spatial_relationship( img1, img2, gt )66 67        # find a good window68        img1, img2 = map(self._pad_rgb_numpy, (img1, img2['img']))69 70        if not 'debug':71            from tools.viz import show_correspondences72            print(np.median(corres[:,5]))73            show_correspondences(img1, img2, corres, bb=bb)74 75        def windows_from_corres( idx, scale_jitter=1 ):76            c = corres[idx]77            p1, p2, scale = c[0:2], c[2:4], c[6]78            scale *= scale_jitter79 80            # make windows based on scaling81            win1 = window(*p1, self.crop_size, max(1, 1/scale), img1.shape)82            win2 = window(*p2, self.crop_size, max(1, scale/1), img2.shape)83            return win1, win284 85        best = 0, None86        for idx in self.rng.choice(len(corres), size=min(len(corres),5), replace=False):87            # pick a correspondence at random88            win1, win2 = windows_from_corres( idx )89 90            # check how many matches are in the 2 windows91            score = score_windows(is_in(corres[:,0:2],win1), is_in(corres[:,2:4],win2))92            if score > best[0]: best = score, idx93 94        others = {}95        if None in best: # counldn't find a good window96            img1 = img2 = np.zeros((self.crop_size,self.crop_size,3), dtype=np.uint8)97            corres = np.empty((0, 6), dtype=np.float32)98        else:99            # jitter scales100            scale_jitter = np.exp(self.rng.uniform(-self.scale_jitter, self.scale_jitter))101            win1, win2 = windows_from_corres( best[1], scale_jitter )102            # print(win1, win2, img1.shape, img2.shape)103            img1, img2 = imresize(img1[win1], self.crop_size), imresize(img2[win2], self.crop_size)104            trf1, trf2 = wintrf(win1, img1), wintrf(win2, img2)105 106            # fix rotation if necessary107            angle_scores = np.bincount(corres[:,5].astype(int) % 8)108            rot90 = int((((angle_scores.argmax() + 4) % 8) - 4) / 2)109            if rot90: # rectify rotation110                img2, trf = myF.rotate_img_90((img2, np.eye(3)), 90*rot90)111                trf2 = invh(trf) @ trf2112 113            homography = trf2 @ homography @ invh(trf1)114            corres = myF.affmul((trf1,trf2), corres)115 116        f32c = lambda i,**kw: np.require(i, requirements='CWAE', **kw)117        return (f32c(img1), f32c(img2)), dict(homography = f32c(homography, dtype=np.float32), corres=corres, **others)118 119    def _pad_rgb_numpy(self, img):120        if img.mode != 'RGB': 121            img = img.convert('RGB')122        if min(img.size) < self.crop_size:123            w, h = img.size124            result = Image.new('RGB', (max(w,self.crop_size), max(h,self.crop_size)), 0)125            result.paste(img, (0, 0))126            img = result127        return np.asarray(img)128 129 130 131def swap_corres( corres ):  # swap img1 and img2132    res = corres.copy()133    res[:,[0,1,2,3]] = corres[:,[2,3,0,1]]134    if corres.shape[1] > 4: # invert rotation and scale135        scale, rot = myF.decode_scale_rot(corres[:,5])136        res[:,5] = myF.encode_scale_rot(1/scale, -rot)137    return res138 139def flip(img):140    w, h = img.size141    return img.transpose(Image.FLIP_LEFT_RIGHT), np.float32( [[-1,0,w-1],[0,1,0],[0,0,1]] )142 143def flip_image_pair(img1, img2, gt):144    img1, F1 = flip(img1)145    img2, F2 = flip(img2)146    res = {}147    for key, value in gt.items():148        if key == 'homography':149            res['homography'] = F2 @ value @ F1150        elif key == 'aflow':151            assert False, 'flip for aflow: todo'152        elif key == 'corres':153            new_corres = np.c_[applyh(F1,value[:,0:2]), applyh(F2,value[:,2:4])]154            if value.shape[1] == 4: pass155            elif value.shape[1] == 6:156                scale, rot = myF.decode_scale_rot(value[:,5])157                new_code = myF.encode_scale_rot(scale, -rot)158                new_corres = np.c_[new_corres,value[:,4],new_code]159            res['corres'] = new_corres160        else:161            raise ValueError(f"flip_image_pair: bad gt field '{key}'")162    return img1, img2, res163 164 165def spatial_relationship( img1, img2, gt ):166    if 'homography' in gt:167        homography = gt['homography']168        if 'homography' in img2: 169            homography = np.float32(img2['homography']) @ homography170        corres = corres_from_homography(homography, *img1.size)171 172    elif 'corres' in gt:173        homography = np.full((3,3), np.nan, dtype=np.float32)174        corres = gt['corres']175        if 'homography' in img2:176            corres[:,2:4] = applyh(img2['homography'], corres[:,2:4])177        else:178            img2['homography'] = np.eye(3)179        scales = np.sqrt(np.abs(np.linalg.det(jacobianh(img2['homography'], corres[:,0:2]).T)))180 181        if corres.shape[1] == 4:182            scales, rots = scale_rot_from_corres(corres)183            corres = np.c_[corres, np.ones_like(scales), myF.encode_scale_rot(scales,rots*180/np.pi), scales]184        elif corres.shape[1] == 6:185            corres = np.c_[corres, scales * myF.decode_scale_rot(corres[:,5])[0]]186        else:187            assert ValueError(f'bad shape for corres: {corres.shape}')188 189    return homography, corres190 191 192def scale_rot_from_corres( corres, sub=256, nn=16 ):193    # select a subset of relevant correspondences194    sub = np.random.choice(len(corres), size=min(len(corres),sub), replace=False)195    sub = corres[sub]196 197    # for each corres, find the scale change w.r.t. its NNs198    from scipy.spatial.distance import cdist199    nns = cdist(corres, sub, metric='sqeuclidean').argsort(axis=1)[:,:nn]200 201    # affine transform for this set of neighboring correspondences202    pts = sub[nns] # shape = npts x sub x 4203    # [P1,1] @ A = P2  with A = 3x2 matrix204    # A = [P1,1]^-1 @ P2205    P1, P2 = pts[:,:,0:2], pts[:,:,2:4] # each row = list of correspondences206    P1 = np.concatenate((P1,np.ones_like(P1[:,:,:1])),axis=-1)207    A = (np.linalg.pinv(P1) @ P2).transpose(0,2,1)208 209    scale, (angy,angx) = detect_scale_rotation(A.transpose(1,2,0)[:,1::-1])210    rot = np.arctan2(angy, angx)211    return scale.clip(min=0.2, max=5), rot212 213 214def window1(x, size, w):215    l = x - int(0.5 + size / 2)216    r = l + int(0.5 + size)217    if l < 0: l,r = (0, r - l)218    if r > w: l,r = (l + w - r, w)219    if l < 0: l,r = 0,w # larger than width220    return slice(l,r)221 222def window(cx, cy, win_size, scale, img_shape):223    return (window1(int(cy), win_size*scale, img_shape[0]), 224            window1(int(cx), win_size*scale, img_shape[1]))225 226def is_in( pts, window ):227    x, y = pts.T228    sly, slx = window229    return (slx.start <= x) & (x < slx.stop) & (sly.start <= y) & (y < sly.stop)230 231def score_windows( valid1, valid2 ):232    inter = (valid1 & valid2).sum()233    iou1 = inter / (valid1.sum() + 1e-8)234    iou2 = inter / (valid2.sum() + 1e-8)235    return inter * min(iou1, iou2)236 237def imresize( img, max_size, resample=Image.ANTIALIAS):238    if max(img.shape[:2]) > max_size:239        if img.shape[-1] == 2:240            img = np.stack([np.float32(Image.fromarray(img[...,i]).resize((max_size,max_size), resample=resample)) for i in range(2)], axis=-1)241        else:242            img = np.asarray(Image.fromarray(img).resize((max_size,max_size), resample=resample))243    assert img.shape[0] == img.shape[1] == max_size, bb()244    return img245 246def wintrf( window, final_img ):247    wy, wx = window248    H, W = final_img.shape[:2]249    T = np.float32((((wx.stop-wx.start)/W, 0, wx.start),250                    (0, (wy.stop-wy.start)/H, wy.start),251                    (0, 0, 1)) )252    return invh(T)253 254 255def collate_ordered(batch, _use_shared_memory=True):256    pairs, gt = zip(*batch)257    imgs1, imgs2 = zip(*pairs)258    assert len(imgs1) == len(imgs2) == len(gt) and isinstance(gt[0], dict)259    260    # reorder samples (supervised ones first, unsupervised ones last)261    supervised = [i for i,b in enumerate(gt) if np.isfinite(b['homography']).all()]262    unsupervsd = [i for i,b in enumerate(gt) if np.isnan(b['homography']).any()]263    order = supervised + unsupervsd264 265    def collate( tensors, key=None ):266        import torch267        batch = todevice([tensors[i] for i in order], 'cpu')268        if key == 'corres': return batch # cannot concat269        if _use_shared_memory: # shared memory tensor to avoid an extra copy270            numel = sum([x.numel() for x in batch])271            storage = batch[0].storage()._new_shared(numel)272            out = batch[0].new(storage)273        return torch.stack(batch, dim=0, out=out)274 275    return (collate(imgs1), collate(imgs2)), {k:collate([b[k] for b in gt],k) for k in gt[0]}276 277 278if __name__ == '__main__':279    from datasets import *280    from tools.viz import show_random_pairs281 282    db = BalancedCatImagePairs(283                3125, SyntheticImagePairs(RandomWebImages(0,52),distort='RandomTilting(0.5)'),284                4875, SyntheticImagePairs(SfM120k_Images(),distort='RandomTilting(0.5)'),285                8000, SfM120k_Pairs())286 287    db = FastPairLoader(db, 288            crop=256, transform='RandomRotation(20), RandomScale(256,1536,ar=1.3,can_upscale=True), PixelNoise()',289            p_swap=0.5, p_flip=0.5, scale_jitter=0, seed=777)290 291    show_random_pairs(db)292