CoolFace
Apppublic

naver/PUMP

sourceHugging Faceupdated 4y agoView on Hugging Face
1likes
post_filter.py236 linesDownload Raw Back to root
1# Copyright 2022-present NAVER Corp.2# CC BY-NC-SA 4.03# Available only for non-commercial use4 5import pdb, sys, os6import argparse7import numpy as np8from scipy.sparse import coo_matrix, csr_matrix, triu, csgraph9 10import core.functional as myF11from tools.common import image, image_with_trf12from tools.viz import dbgfig, show_correspondences13 14 15def arg_parser():16    parser = argparse.ArgumentParser("Post-filtering of Deep matching correspondences")17 18    parser.add_argument("--img1", required=True, help="path to first image")19    parser.add_argument("--img2", required=True, help="path to second image")20    parser.add_argument("--resize", default=0, type=int, help="prior image downsize (0 if recursive)")21    parser.add_argument("--corres", required=True, help="input path")22    parser.add_argument("--output", default="", help="filtered corres output")23 24    parser.add_argument("--locality", type=float, default=2, help="tolerance to deformation")25    parser.add_argument("--min-cc-size", type=int, default=50, help="min connex-component size")26    parser.add_argument("--densify", default='no', choices=['no','full','cc','convex'], help="output pixel-dense corres field")27    parser.add_argument("--dense-side", default='left', choices=['left','right'], help="img to densify")28 29    parser.add_argument("--verbose", "-v", type=int, default=0, help="verbosity level")30    parser.add_argument("--dbg", type=str, nargs='+', default=(), help="debug options")31    return parser32 33 34def main(args):35    import test_singlescale as pump36    corres = np.load(args.corres)['corres']37    imgs = tuple(map(image, pump.Main.load_images(args)))38 39    if dbgfig('raw',args.dbg):40        show_correspondences(*imgs, corres)41 42    corres = filter_corres( *imgs, corres, 43        locality=args.locality, min_cc_size=args.min_cc_size, 44        densify=args.densify, dense_side=args.dense_side,45        verbose=args.verbose, dbg=args.dbg)46 47    if dbgfig('viz',args.dbg):48        show_correspondences(*imgs, corres)49 50    return pump.save_output( args, corres )51 52 53def filter_corres( img0, img1, corres, 54            locality = None, # graph edge locality55            min_cc_size = None, # min CC size56            densify = None, 57            dense_side = None,58            verbose = 0, dbg=()):59 60    if None in (locality, min_cc_size, densify, dense_side):61        default_params = arg_parser()62        locality = locality or default_params.get_default('locality')63        min_cc_size = min_cc_size or default_params.get_default('min_cc_size')64        densify = densify or default_params.get_default('densify')65        dense_side = dense_side or default_params.get_default('dense_side')66 67    img0, trf0 = img0 if isinstance(img0,tuple) else (img0, np.eye(3))68    img1, trf1 = img1 if isinstance(img1,tuple) else (img1, np.eye(3))69    assert isinstance(img0, np.ndarray) and isinstance(img1, np.ndarray)70 71    corres = myF.affmul((np.linalg.inv(trf0),np.linalg.inv(trf1)), corres)72    n_corres = len(corres)73    if verbose: print(f'>> input: {len(corres)} correspondences')74 75    graph = compute_graph(corres, max_dis=locality*4)76    if verbose: print(f'>> {locality=}: {graph.nnz} nodes in graph')77 78    cc_sizes = measure_connected_components(graph)79    corres[:,4] += np.log2(cc_sizes)80    corres = corres[cc_sizes > min_cc_size]81    if verbose: print(f'>> {min_cc_size=}: remaining {len(corres)} correspondences')82 83    final = myF.affmul((trf0,trf1), corres)84 85    if densify != 'no':86        # densify correspondences87        if dense_side == 'right': # temporary swap88            final = final[:,[2,3,0,1]]89            H = round(img1.shape[0] / trf1[1,1])90            W = round(img1.shape[1] / trf1[0,0])91        else:92            H = round(img0.shape[0] / trf0[1,1])93            W = round(img0.shape[1] / trf0[0,0])94        95        if densify == 'cc':96            assert False, 'todo'97        elif densify in (True, 'full', 'convex'):98            # recover true image0's shape99            final = densify_corres( final, (H, W), full=(densify!='convex') )100        else:101            raise ValueError(f'Bad mode for {densify=}')102 103        if dense_side == 'right': # undo temporary swap104            final = final[:,[2,3,0,1]]105 106    return final107 108 109def compute_graph(corres, max_dis=10, min_ang=90):110    """ 4D distances (corres can only be connected to same scale)111        using sparse matrices for efficiency112 113    step1: build horizontal and vertical binning, binsize = max_dis114           add in each bin all neighbor bins115    step2: for each corres, we can intersect 2 bins to get a short list of candidates116    step3: verify euclidean distance < maxdis (optional?)117    """118    def bin_positions(pos):119        # every corres goes into a single bin120        bin_indices = np.int32(pos.clip(min=0) // max_dis) + 1121        cols = np.arange(len(pos))122        123        # add the cell before and the cell after, to handle border effects124        res = csr_matrix((np.ones(len(bin_indices)*3,dtype=np.float32), 125            (np.r_[bin_indices-1, bin_indices, bin_indices+1], np.r_[cols,cols,cols])),126            shape=(bin_indices.max()+2 if bin_indices.size else 1, len(pos)))127 128        return res, bin_indices129 130    # 1-hot matrices of shape = nbins x n_corres131    x1_bins = bin_positions(corres[:,0])132    y1_bins = bin_positions(corres[:,1])133    x2_bins = bin_positions(corres[:,2])134    y2_bins = bin_positions(corres[:,3])    135 136    def row_indices(ngh):137        res = np.bincount(ngh.indptr[1:-1], minlength=ngh.indptr[-1])[:-1]138        return res.cumsum()139 140    def compute_dist( ngh, pts, scale=None ):141        # pos from the second point142        x_pos = pts[ngh.indices,0]143        y_pos = pts[ngh.indices,1]144 145        # subtract pos from the 1st point146        rows = row_indices(ngh)147        x_pos -= pts[rows, 0]148        y_pos -= pts[rows, 1]149        dis = np.sqrt(np.square(x_pos) + np.square(y_pos))150        if scale is not None: 151            # there is a scale for each of the 2 pts, we encline to choose the worst one152            dis *= (scale[rows] + scale[ngh.indices]) / 2 # so we use arithmetic instead of geometric mean153 154        return normed(np.c_[x_pos, y_pos]), dis155 156    def Rot( ngh, degrees ):157        rows = row_indices(ngh)158        rad = degrees * np.pi / 180159        rad = (rad[rows] + rad[ngh.indices]) / 2 # average angle between 2 corres160        cos, sin = np.cos(rad), np.sin(rad)161        return np.float32(((cos, -sin), (sin,cos))).transpose(2,0,1)162 163    def match(xbins, ybins, pt1, pt2, way):164        xb, ixb = xbins165        yb, iyb = ybins166        167        # gets for each corres a list of potential matches168        ngh = xb[ixb].multiply( yb[iyb] ) # shape = n_corres x n_corres    169        ngh = triu(ngh, k=1).tocsr() # remove mirrored matches170        # ngh = matches of matches, shape = n_corres x n_corres171 172        # verify locality and flow173        vec1, d1 = compute_dist(ngh, pt1) # for each match, distance and orientation in img1174        # assert d1.max()**0.5 < 2*max_dis*1.415, 'cannot be larger than 2 cells in diagonals, or there is a bug'+bb()175        scale, rot = myF.decode_scale_rot(corres[:,5])176        vec2, d2 = compute_dist(ngh, pt2, scale=scale**(-way))177        ang = np.einsum('ik,ik->i', (vec1[:,None] @ Rot(ngh,way*rot))[:,0], vec2)178 179        valid = (d1 <= max_dis) & (d2 <= max_dis) & (ang >= np.cos(min_ang*np.pi/180))180        res = csr_matrix((valid, ngh.indices, ngh.indptr), shape=ngh.shape)181        res.eliminate_zeros()182        return res183 184    # find all neihbors within each xy bin185    ngh1 = match(x1_bins, y1_bins, corres[:,0:2], corres[:,2:4], way=+1)186    ngh2 = match(x2_bins, y2_bins, corres[:,2:4], corres[:,0:2], way=-1).T187 188    return ngh1 + ngh2 # union189    190 191def measure_connected_components(graph, dbg=()):192    # compute connected components193    nc, labels = csgraph.connected_components(graph, directed=False)194 195    # filter and remove all small components196    count = np.bincount(labels)197    198    return count[labels]199 200def normed( mat ):201    return mat / np.linalg.norm(mat, axis=-1, keepdims=True).clip(min=1e-16)202 203 204def densify_corres( corres, shape, full=True ):205    from scipy.interpolate import LinearNDInterpolator206    from scipy.spatial import cKDTree as KDTree207 208    assert len(corres) > 3, 'Not enough corres for densification'209    H, W = shape210 211    interp = LinearNDInterpolator(corres[:,0:2], corres[:,2:4])212    X, Y = np.mgrid[0:H, 0:W][::-1] # H x W, H x W213    p1 = np.c_[X.ravel(), Y.ravel()]214    p2 = interp(X, Y) # H x W x 2215 216    p2 = p2.reshape(-1,2)217    invalid = np.isnan(p2).any(axis=1)218 219    if full:220        # interpolate pixels outside of the convex hull221        badp = p1[invalid]222        tree = KDTree(corres[:,0:2])223        _, nn = tree.query(badp, 3) # find 3 closest neighbors224        corflow = corres[:,2:4] - corres[:,0:2]225        p2.reshape(-1,2)[invalid] = corflow[nn].mean(axis=1) + p1[invalid]226    else:227        # remove nans, i.e. remove points outside of convex hull228        p1, p2 = p1[~invalid], p2[~invalid]229 230    # return correspondence field231    return np.c_[p1, p2]232 233 234if __name__ == '__main__':235    main(arg_parser().parse_args())236