Affine invariant feature-based image matching
This sample is similar to feature_homography_demo.m, but uses the affine transformation space sampling technique, called ASIFT. 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.
Sources:
function asift_demo() % load input grayscale images files = { fullfile(mexopencv.root(),'test','aero1.jpg') fullfile(mexopencv.root(),'test','aero3.jpg') }; for i=1:numel(files) if exist(files{i}, 'file') ~= 2 disp('Downloading...') url = 'https://cdn.rawgit.com/opencv/opencv/3.2.0/samples/data/'; [~,fname,ext] = fileparts(files{i}); urlwrite([url fname ext], files{i}); end end img1 = cv.imread(files{1}, 'Grayscale',true); img2 = cv.imread(files{2}, 'Grayscale',true); % ASIFT [vis, H] = run_asift(img1, img2, 'BRISK', true); imshow(vis), title('ASIFT') % interactive exploration if ~mexopencv.isOctave() && mexopencv.require('images') % interactively select region in img1 pos1 = select_poly(gca) - 1; if isempty(pos1), return; end % apply homography pos2 = cv.perspectiveTransform(pos1, H); pos2(:,1) = pos2(:,1) + size(img1,2); % draw region in img1 and transformed region in img2 vis = cv.polylines(vis, pos1, 'Closed',true, ... 'Color',[0 255 0], 'Thickness',2, 'LineType','AA'); vis = cv.polylines(vis, pos2, 'Closed',true, ... 'Color',[0 255 0], 'Thickness',2, 'LineType','AA'); imshow(vis), title('ASIFT') end end function [vis, H] = run_asift(img1, img2, feature_name, use_flann) %RUN_ASIFT Run ASIFT algorithm, estimate homography and visualize matches % create detector and matcher objects [detector, matcher] = init_feature(feature_name, use_flann); % detect features using ASIFT method disp('Detecting...') tic, [kp1, desc1] = affine_detect(detector, img1); toc tic, [kp2, desc2] = affine_detect(detector, img2); toc fprintf('img1: %d features, img2: %d features\n', numel(kp1), numel(kp2)); % match ASIFT features disp('Matching...') tic, matches = matcher.knnMatch(desc1, desc2, 2); toc fprintf('%d matches\n', numel(matches)); % filter matches [matches, p1, p2] = filter_matches(kp1, kp2, matches); fprintf('%d good matches\n', numel(matches)); assert(numel(matches) >= 4, 'not enough matches for homography estimation'); % estimate homography with RANSAC method [H,inliers] = cv.findHomography(p1, p2, 'Method','Ransac'); assert(~isempty(H), 'homography estimation failed'); inliers = logical(inliers); fprintf('%d inliers\n', nnz(inliers)); % visualize good and inlier matches vis = cv.drawMatches(img1, kp1, img2, kp2, matches, ... 'NotDrawSinglePoints',true, 'MatchesMask',inliers); end function [detector, matcher] = init_feature(feature_name, use_flann) %INIT_FEATURE Create detector and matcher objects % detector switch upper(feature_name) case 'SURF' detector = cv.SURF('HessianThreshold',400); case 'SIFT' detector = cv.SIFT(); case 'ORB' detector = cv.ORB(); case 'BRISK' detector = cv.BRISK(); case 'AKAZE' detector = cv.AKAZE(); case 'KAZE' detector = cv.KAZE(); otherwise error('unrecognized feature: %s', feature_name) end % matcher if use_flann if ~isempty(strfind(detector.defaultNorm(), 'Hamming')) opts = {'LSH', 'TableNumber',6, 'KeySize',12, 'MultiProbeLevel',1}; else opts = {'KDTree', 'Trees',5}; end matcher = cv.DescriptorMatcher('FlannBasedMatcher', 'Index',opts); else matcher = cv.DescriptorMatcher('BFMatcher', ... 'NormType',detector.defaultNorm()); end end function [kpts, descs] = affine_detect(detector, img, mask) %AFFINE_DETECT Affine-SIFT (ASIFT) algorithm % % [kpts, descs] = affine_detect(detector, img) % [kpts, descs] = affine_detect(detector, img, mask) % % ## Input % * __detector__ detector object % * __img__ input image % * __mask__ optional mask, default all image included % % ## Output % * __kpts__ concatenated keypoints % * __descs__ corresponding concatenated descriptors % % Applies 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. % % default mask if not specified if nargin < 3 mask = 255 * ones(size(img,1), size(img,2), 'uint8'); end % parameter sampling: Tilt and Phi % (simulates all possible affine distortions caused by the change of % camera optical axis orientation from a frontal position) params = [1 0]; % no tilt and no rotation, thus original image for t = sqrt(2).^(1:5) % geometric series 1, a, a^2, .., a^n; a=sqrt(2), n=5 for p = 0:72/t:180-72/t % arithmetic series 0, b/t, .., k*b/t < 180; b=72 params(end+1,:) = [t, p]; end end % for each pair of distortion parameters % (this loop is candidate for parallelization, i.e parfor) N = size(params,1); kpts = cell(N, 1); descs = cell(N, 1); for i=1:N % transform image [timg, tmask, Ai] = affine_skew(params(i,1), params(i,2), img, mask); % detect features using a similarity invariant matching method (SIFT) [kp, feat] = detector.detectAndCompute(timg, 'Mask',tmask); %TODO: Remove keypoints close to the boundary of the transformed image % project keypoints from the coordinates of the rotated and tilted % image back to the original image corrdinates for j=1:numel(kp) kp(j).pt = [kp(j).pt, 1] * Ai.'; end kpts{i} = kp(:); descs{i} = feat; end % concatenate all features from all simulated images kpts = cat(1, kpts{:}); descs = cat(1, descs{:}); end function [img, mask, Ai] = affine_skew(tilt, phi, img, mask) %AFFINE_SKEW Transform image/mask by an affine distortion % % [img, mask, Ai] = affine_skew(tilt, phi, img, mask) % % ## Input % * __tilt__ tilt % * __phi__ rotation angle in degrees % * __img__ image % * __mask__ mask % % ## Output % * __img__ transformed image % * __mask__ transformed mask % * __Ai__ affine transform matrix from output/skewed `img` to input `img` % % initialize affine transformation matrix (identity) A = eye(2,3); [h,w,~] = size(img); % in the case of (tilt=1, phi=0), then no transformation (identity) if phi ~= 0 % rotate image A = [cosd(phi) -sind(phi); sind(phi) cosd(phi)]; corners = [0 0; w 0; w h; 0 h]; corners = round(corners * A.'); rect = cv.boundingRect(corners); A(:,3) = -rect(1:2); img = cv.warpAffine(img, A, 'DSize',rect(3:4), ... 'Interpolation','Linear', 'BorderType','Replicate'); end if tilt ~= 1 % anti-aliasing filtering in the vertical direction, then tilt image % (subsample in vertical direction by a factor of tilt) s = 0.8 * sqrt(tilt^2 - 1); img = cv.GaussianBlur(img, 'KSize',[0 0], 'SigmaX',s, 'SigmaY',0.01); img = cv.resize(img, 1/tilt, 1, 'Interpolation','Nearest'); A(1) = A(1) / tilt; end if phi ~= 0 || tilt ~= 1 % apply same transformation on mask [h,w,~] = size(img); mask = cv.warpAffine(mask, A, 'DSize',[w h], 'Interpolation','Nearest'); end % inverse affine transformation Ai = cv.invertAffineTransform(A); end function [matches, p1, p2] = filter_matches(kp1, kp2, matches, ratio) %FILTER_MATCHES Filter out 2-NN matches using Lowe's ratio test if nargin < 4, ratio = 0.75; end % good matches idx = cellfun(@(m) (numel(m) == 2) && ... (m(1).distance < ratio * m(2).distance), matches); matches = cellfun(@(m) m(1), matches(idx)); % corresponding points p1 = cat(1, kp1([matches.queryIdx]+1).pt); p2 = cat(1, kp2([matches.trainIdx]+1).pt); end function pos = select_poly(ax) %SELECT_POLY Select polygon area using the mouse % % pos = select_poly() % pos = select_poly(ax) % % ## Input % * __ax__ axes handle, default gca % % ## Output % * __pos__ Nx2 matrix of points % if nargin < 1, ax = gca; end hRoi = impoly(ax); api = iptgetapi(hRoi); try api.setColor('green'); api.setPositionConstraintFcn(makeConstrainToRectFcn('impoly', ... get(ax,'XLim').*[1 0.5], get(ax,'YLim'))); pos = wait(hRoi); catch pos = zeros(0,2); end delete(hRoi); end
Detecting... Elapsed time is 2.437649 seconds. Elapsed time is 1.939506 seconds. img1: 25629 features, img2: 17387 features Matching... Elapsed time is 3.822513 seconds. 25629 matches 89 good matches 16 inliers