asift.py 5.19 KB
Newer Older
1
#!/usr/bin/env python
2

3 4 5 6 7 8 9 10 11 12 13
'''
Affine invariant feature-based image matching sample.

This sample is similar to find_obj.py, but uses the affine transformation
space sampling technique, called ASIFT [1]. While the original implementation
is based on SIFT, you can try to use SURF or ORB detectors instead. Homography RANSAC
is used to reject outliers. Threading is used for faster affine sampling.

[1] http://www.ipol.im/pub/algo/my_affine_sift/

USAGE
14
  asift.py [--feature=<sift|surf|orb|brisk>[-flann]] [ <image1> <image2> ]
15

16 17
  --feature  - Feature to use. Can be sift, surf, orb or brisk. Append '-flann'
               to feature name to use Flann-based matcher instead bruteforce.
18

Martin Jul's avatar
Martin Jul committed
19
  Press left mouse button on a feature point to see its matching point.
20 21
'''

22 23 24
# Python 2/3 compatibility
from __future__ import print_function

25 26
import numpy as np
import cv2
27 28

# built-in modules
29 30 31
import itertools as it
from multiprocessing.pool import ThreadPool

32
# local modules
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
from common import Timer
from find_obj import init_feature, filter_matches, explore_match


def affine_skew(tilt, phi, img, mask=None):
    '''
    affine_skew(tilt, phi, img, mask=None) -> skew_img, skew_mask, Ai

    Ai - is an affine transform matrix from skew_img to img
    '''
    h, w = img.shape[:2]
    if mask is None:
        mask = np.zeros((h, w), np.uint8)
        mask[:] = 255
    A = np.float32([[1, 0, 0], [0, 1, 0]])
    if phi != 0.0:
        phi = np.deg2rad(phi)
        s, c = np.sin(phi), np.cos(phi)
        A = np.float32([[c,-s], [ s, c]])
        corners = [[0, 0], [w, 0], [w, h], [0, h]]
        tcorners = np.int32( np.dot(corners, A.T) )
        x, y, w, h = cv2.boundingRect(tcorners.reshape(1,-1,2))
        A = np.hstack([A, [[-x], [-y]]])
        img = cv2.warpAffine(img, A, (w, h), flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_REPLICATE)
    if tilt != 1.0:
        s = 0.8*np.sqrt(tilt*tilt-1)
        img = cv2.GaussianBlur(img, (0, 0), sigmaX=s, sigmaY=0.01)
        img = cv2.resize(img, (0, 0), fx=1.0/tilt, fy=1.0, interpolation=cv2.INTER_NEAREST)
        A[0] /= tilt
    if phi != 0.0 or tilt != 1.0:
        h, w = img.shape[:2]
        mask = cv2.warpAffine(mask, A, (w, h), flags=cv2.INTER_NEAREST)
    Ai = cv2.invertAffineTransform(A)
    return img, mask, Ai


def affine_detect(detector, img, mask=None, pool=None):
    '''
    affine_detect(detector, img, mask=None, pool=None) -> keypoints, descrs

    Apply a set of affine transormations to the image, detect keypoints and
    reproject them into initial image coordinates.
    See http://www.ipol.im/pub/algo/my_affine_sift/ for the details.

    ThreadPool object may be passed to speedup the computation.
    '''
    params = [(1.0, 0.0)]
    for t in 2**(0.5*np.arange(1,6)):
        for phi in np.arange(0, 180, 72.0 / t):
            params.append((t, phi))

    def f(p):
        t, phi = p
        timg, tmask, Ai = affine_skew(t, phi, img)
        keypoints, descrs = detector.detectAndCompute(timg, tmask)
        for kp in keypoints:
            x, y = kp.pt
            kp.pt = tuple( np.dot(Ai, (x, y, 1)) )
        if descrs is None:
            descrs = []
        return keypoints, descrs
94

95 96 97 98 99
    keypoints, descrs = [], []
    if pool is None:
        ires = it.imap(f, params)
    else:
        ires = pool.imap(f, params)
100

101
    for i, (k, d) in enumerate(ires):
102
        print('affine sampling: %d / %d\r' % (i+1, len(params)), end='')
103 104
        keypoints.extend(k)
        descrs.extend(d)
105

106
    print()
107 108 109
    return keypoints, np.array(descrs)

if __name__ == '__main__':
110
    print(__doc__)
111 112 113 114

    import sys, getopt
    opts, args = getopt.getopt(sys.argv[1:], '', ['feature='])
    opts = dict(opts)
115
    feature_name = opts.get('--feature', 'brisk-flann')
116 117
    try:
        fn1, fn2 = args
118
    except:
Dmitriy Anisimov's avatar
Dmitriy Anisimov committed
119 120
        fn1 = '../data/aero1.jpg'
        fn2 = '../data/aero3.jpg'
121 122 123 124

    img1 = cv2.imread(fn1, 0)
    img2 = cv2.imread(fn2, 0)
    detector, matcher = init_feature(feature_name)
125

126
    if img1 is None:
127
        print('Failed to load fn1:', fn1)
128
        sys.exit(1)
129

130
    if img2 is None:
131
        print('Failed to load fn2:', fn2)
132
        sys.exit(1)
133

134
    if detector is None:
135
        print('unknown feature:', feature_name)
136
        sys.exit(1)
137

138
    print('using', feature_name)
139 140 141 142

    pool=ThreadPool(processes = cv2.getNumberOfCPUs())
    kp1, desc1 = affine_detect(detector, img1, pool=pool)
    kp2, desc2 = affine_detect(detector, img2, pool=pool)
143
    print('img1 - %d features, img2 - %d features' % (len(kp1), len(kp2)))
144 145 146 147 148 149 150

    def match_and_draw(win):
        with Timer('matching'):
            raw_matches = matcher.knnMatch(desc1, trainDescriptors = desc2, k = 2) #2
        p1, p2, kp_pairs = filter_matches(kp1, kp2, raw_matches)
        if len(p1) >= 4:
            H, status = cv2.findHomography(p1, p2, cv2.RANSAC, 5.0)
151
            print('%d / %d  inliers/matched' % (np.sum(status), len(status)))
152 153 154 155
            # do not draw outliers (there will be a lot of them)
            kp_pairs = [kpp for kpp, flag in zip(kp_pairs, status) if flag]
        else:
            H, status = None, None
156
            print('%d matches found, not enough for homography estimation' % len(p1))
157 158 159 160 161 162 163

        vis = explore_match(win, img1, img2, kp_pairs, None, H)


    match_and_draw('affine find_obj')
    cv2.waitKey()
    cv2.destroyAllWindows()