├── .gitignore ├── LICENSE ├── README.md ├── demos ├── multiple.m ├── pair.m └── transform.m ├── helpers ├── Untitled.m ├── find_ordered_homography.m ├── fit_homography.m ├── homography_transform.m ├── ransac.m ├── select_putative_matches.m ├── stitch.m ├── stitch_pair.m └── vl_sift_wrapper.m ├── img ├── hill │ ├── 1.JPG │ ├── 2.JPG │ └── 3.JPG ├── ledge │ ├── 1.JPG │ ├── 2.JPG │ └── 3.JPG ├── pier │ ├── 1.JPG │ ├── 2.JPG │ └── 3.JPG └── uttower │ ├── uttower_left.jpg │ └── uttower_right.jpg ├── lib ├── dist2.m ├── find_sift.m └── harris.m ├── stitch_images.m └── tests └── helper_test.m /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *~ 3 | *.swp 4 | *.asv 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Daeyun Shin. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | * Redistributions of source code must retain the above copyright 6 | notice, this list of conditions and the following disclaimer. 7 | * Redistributions in binary form must reproduce the above copyright 8 | notice, this list of conditions and the following disclaimer in the 9 | documentation and/or other materials provided with the distribution. 10 | * Neither the name of the project nor the 11 | names of its contributors may be used to endorse or promote products 12 | derived from this software without specific prior written permission. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 18 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Image-Stitching 2 | =============== 3 | 4 | MATLAB code for panorama image stitching. 5 | 6 | STITCH_IMAGES - Given a set of images with overlapping regions, 7 | automatically compute a panorama image. 8 | 9 | Usage: [result_img, H, num_inliers, residual] ... 10 | = stitch_images (images, sift_r, harris_r, ... 11 | harris_thresh, harris_sigma, num_putative_matches, ransac_n) 12 | 13 | Usage example: 14 | stitch_images(images, 5, 5, 0.03, 1, 100, 4000) 15 | 16 | Arguments: 17 | images - 1 by n cell array of images. 18 | sift_r - radius of the SIFT descriptor. 19 | harris_r - radius of the Harris corner detector. 20 | harris_thresh - Harris corner detector threshold. 21 | harris_sigma - standard deviation of smoothing Gaussian 22 | num_putative_matches - number of putative matches to run 23 | RANSAC. 24 | ransac_n - number of RANSAC iterations. 25 | 26 | Returns: 27 | result_img - computed paranoma image. 28 | H - n by n cell array of homography matrices. 29 | H{i, j} is the homography matrix between images 30 | i and j. 31 | num_inliers - n by n array of number of inliers. num_inliers{i, 32 | j} is the number of inliers between images i and 33 | j. 34 | residual - n by n array of sum of squared disrances. 35 | residual{i, j} is the residual between images i 36 | and j. 37 | 38 | Dependencies: 39 | VLFeat - download at http://www.vlfeat.org/download.html 40 | -------------------------------------------------------------------------------- /demos/multiple.m: -------------------------------------------------------------------------------- 1 | images = cell(1, 3); 2 | images{1} = imread('image_stitching/img/ledge/1.JPG'); 3 | images{2} = imread('image_stitching/img/ledge/2.JPG'); 4 | images{3} = imread('image_stitching/img/ledge/3.JPG'); 5 | 6 | [result, H, num_inliers, residual] = ... 7 | stitch_images(images, 5, 5, 0.03, 1, 100, 4000); 8 | 9 | imshow(result); -------------------------------------------------------------------------------- /demos/pair.m: -------------------------------------------------------------------------------- 1 | target_image = imread('../img/uttower_left.jpg'); 2 | image = imread('../img/uttower_right.jpg'); 3 | 4 | [result_img, H, num_inliers, residual] = ... 5 | stitch_pair(image, target_image, 5, 5, 0.03, 1, 200, 4000); -------------------------------------------------------------------------------- /demos/transform.m: -------------------------------------------------------------------------------- 1 | % Testing the fit_homography function based on numbers from 2 | % http://www.leet.it/home/giusti/teaching/matlab_sessions/stitching/stitch.html 3 | 4 | x1 = 1.0e+03 * [0.8860; 5 | 1.5505; 6 | 1.5835; 7 | 1.3225]; 8 | y1 = [371.7500; 9 | 386.7500; 10 | 833.7500; 11 | 881.7500]; 12 | x2 = [41.5000; 13 | 697.0000; 14 | 724.0000; 15 | 482.5000]; 16 | y2 = 1.0e+03 * [0.5563; 17 | 0.5668; 18 | 0.9823; 19 | 1.0588]; 20 | 21 | T = maketform('projective', [x2 y2], [x1 y1]); 22 | H=(T.tdata.T)'; 23 | H=fit_homography(x2, y2, x1, y1); 24 | 25 | X = [x2 y2 ones(size(y1,1),1)]'; 26 | 27 | X_=H*X; 28 | 29 | X_(1,:)=X_(1,:)./X_(3,:); 30 | X_(2,:)=X_(2,:)./X_(3,:); 31 | X_(3,:)=X_(3,:)./X_(3,:); 32 | 33 | % Display results 34 | X 35 | X_ 36 | -------------------------------------------------------------------------------- /helpers/Untitled.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daeyun/Image-Stitching/189e3b69e6a8115b08c64dcac62aa00f93c0518a/helpers/Untitled.m -------------------------------------------------------------------------------- /helpers/find_ordered_homography.m: -------------------------------------------------------------------------------- 1 | function image_order = ... 2 | find_ordered_homography(images, sift_r, harris_r, harris_thresh, ... 3 | harris_sigma, num_putative_matches, ransac_n) 4 | 5 | n_img = length(images); 6 | num_inliers = zeros(n_img, n_img); 7 | H = cell(n_img, n_img); 8 | residual = zeros(n_img, n_img); 9 | for i = 1:n_img 10 | for j = i+1:n_img 11 | [~ , H{i,j}, num_inliers(i,j), residual(i,j)] = ... 12 | stitch_pair(images{i}, images{j}, sift_r, harris_r, harris_thresh, ... 13 | harris_sigma, num_putative_matches, ransac_n); 14 | 15 | H{j,i} = H{i,j}; 16 | num_inliers(j,i) = num_inliers(i,j); 17 | residual(j,i) = residual(i,j); 18 | end 19 | end 20 | 21 | link = cell(1, n_img); 22 | 23 | [~, sort_index] = sort(num_inliers(:), 'descend'); 24 | inlier_thresh = num_inliers(sort_index(n_img*2-2)); 25 | 26 | count_links = zeros(1, 2); 27 | for i = 1:n_img 28 | link{i} = find(num_inliers(i,:) >= inlier_thresh); 29 | num_links = length(link{i}); 30 | assert(0 < num_links && num_links <= 2); 31 | count_links(num_links) = count_links(num_links) + 1; 32 | end 33 | assert(count_links(1) == 2); 34 | 35 | selected = zeros(1, n_img); 36 | image_order = zeros(1, n_img); 37 | for i = 1:n_img 38 | num_links = length(find(num_inliers(i,:) >= inlier_thresh)); 39 | if num_links == 1 40 | selected(i) = 1; 41 | image_order(1) = i; 42 | break; 43 | end 44 | end 45 | 46 | for i = 1:n_img-1 47 | prev = image_order(i); 48 | for j = link{prev} 49 | if selected(j) == 0 50 | image_order(i+1) = j; 51 | break; 52 | end 53 | end 54 | end 55 | end -------------------------------------------------------------------------------- /helpers/fit_homography.m: -------------------------------------------------------------------------------- 1 | function H = fit_homography(XY, XY_) 2 | [h, w] = size(XY); 3 | [h_, w_] = size(XY_); 4 | assert(h == h_); 5 | assert(w == 2); 6 | assert(w_ == 2); 7 | 8 | A = []; 9 | for i = 1:h 10 | Xi = [XY(i,:)'; 1]; 11 | xi_ = XY_(i, 1); 12 | yi_ = XY_(i, 2); 13 | zs = zeros(3, 1); 14 | A = cat(1, A, cat(2, zs', Xi', -yi_*Xi')); 15 | A = cat(1, A, cat(2, Xi', zs', -xi_*Xi')); 16 | end 17 | 18 | [U, S, V] = svd(A); 19 | H = V(:,end); 20 | 21 | H = reshape(H, [3 3])'; 22 | end -------------------------------------------------------------------------------- /helpers/homography_transform.m: -------------------------------------------------------------------------------- 1 | function [XY_] = homography_transform(XY, H) 2 | Xi = cat(1, XY', ones(1, size(XY, 1))); 3 | lambdaXi_ = H*Xi; 4 | lambdaXi_ = lambdaXi_'; 5 | XY_ = lambdaXi_(:,1:2); 6 | XY_(:,1)=XY_(:,1)./lambdaXi_(:,3); 7 | XY_(:,2)=XY_(:,2)./lambdaXi_(:,3); 8 | end -------------------------------------------------------------------------------- /helpers/ransac.m: -------------------------------------------------------------------------------- 1 | % RANSAC - A simple RANSAC implementation. 2 | % Author: Daeyun Shin 3 | % March 2014 4 | 5 | function [result, best_num_inliers, residual] = ransac(XY, XY_, N, estimate_func, transform_func) 6 | best_H = []; 7 | best_num_inliers = 0; 8 | 9 | for i = 1:N 10 | ind = randperm(size(XY,1)); 11 | ind_s = ind(1:4); 12 | ind_r = ind(5:end); 13 | 14 | XYs = XY(ind_s,:); 15 | XYs_ = XY_(ind_s,:); 16 | 17 | XYr = XY(ind_r,:); 18 | XYr_ = XY_(ind_r,:); 19 | 20 | H = estimate_func(XYs, XYs_); 21 | [XYf_] = transform_func(XYr, H); 22 | 23 | dists = sum((XYr_ - XYf_).^2,2); 24 | 25 | ind_b = find(dists<0.3); 26 | num_inliers = length(ind_b); 27 | 28 | if best_num_inliers < num_inliers 29 | best_H = H; 30 | best_num_inliers = num_inliers; 31 | residual = mean(dists(ind_b)); 32 | end 33 | end 34 | 35 | result = best_H; 36 | end -------------------------------------------------------------------------------- /helpers/select_putative_matches.m: -------------------------------------------------------------------------------- 1 | function [as, bs] = select_putative_matches(d1, d2, num) 2 | d1 = zscore(d1')'; 3 | d2 = zscore(d2')'; 4 | 5 | dist = dist2(d1, d2); 6 | 7 | [h, w] = size(dist); 8 | dist = reshape(dist, 1, []); 9 | [~, I] = sort(dist); 10 | [rr, cc] = ind2sub([h, w], I(1:num)); 11 | 12 | as = rr'; 13 | bs = cc'; 14 | end -------------------------------------------------------------------------------- /helpers/stitch.m: -------------------------------------------------------------------------------- 1 | % Reference http://www.leet.it/home/giusti/teaching/matlab_sessions/stitching/stitch.html 2 | 3 | function result = stitch(image, H, target_image) 4 | [~, xdata, ydata] = ... 5 | imtransform(image,maketform('projective',H')); 6 | 7 | xdata_out=[min(1,xdata(1)) max(size(target_image,2), xdata(2))]; 8 | ydata_out=[min(1,ydata(1)) max(size(target_image,1), ydata(2))]; 9 | 10 | result1 = imtransform(image, maketform('projective',H'),... 11 | 'XData',xdata_out,'YData',ydata_out); 12 | result2 = imtransform(target_image, maketform('affine',eye(3)),... 13 | 'XData',xdata_out,'YData',ydata_out); 14 | result = result1 + result2; 15 | overlap = (result1 > 0.0) & (result2 > 0.0); 16 | result_avg = (result1/2 + result2/2); 17 | 18 | result(overlap) = result_avg(overlap); 19 | end -------------------------------------------------------------------------------- /helpers/stitch_pair.m: -------------------------------------------------------------------------------- 1 | function [result_img, H, num_inliers, residual] = ... 2 | stitch_pair (image, target_image, sift_r, ... 3 | harris_r, harris_thresh, harris_sigma, num_putative_matches, ransac_n) 4 | 5 | image_bw = im2single(rgb2gray(image)); 6 | target_image_bw = im2single(rgb2gray(target_image)); 7 | 8 | [cim, left_Y, left_X] = harris(image_bw, harris_sigma, ... 9 | harris_thresh, harris_r, 0); 10 | [cim2, right_Y, right_X] = harris(target_image_bw, harris_sigma, ... 11 | harris_thresh, harris_r, 0); 12 | 13 | image_keypoint = cat(2, left_X, left_Y, ... 14 | repmat(sift_r, length(left_X), 1)); 15 | target_image_keypoint = cat(2, right_X, right_Y, ... 16 | repmat(sift_r, length(right_X), 1)); 17 | 18 | [image_descriptor_loc, left_descriptors] = ... 19 | vl_sift_wrapper(image_bw, image_keypoint); 20 | [target_image_descriptor_loc, right_descriptors] = ... 21 | vl_sift_wrapper(target_image_bw, target_image_keypoint); 22 | 23 | [left_matches, right_matches] = ... 24 | select_putative_matches(left_descriptors, right_descriptors, num_putative_matches); 25 | 26 | XY = image_descriptor_loc(left_matches,:); 27 | XY_ = target_image_descriptor_loc(right_matches,:); 28 | 29 | [H, num_inliers, residual] = ransac(XY, XY_, ransac_n, ... 30 | @fit_homography, @homography_transform); 31 | 32 | result_img = stitch(image, H, target_image); 33 | 34 | end -------------------------------------------------------------------------------- /helpers/vl_sift_wrapper.m: -------------------------------------------------------------------------------- 1 | function [f, d] = vl_sift_wrapper(I, circles) 2 | fc = circles'; 3 | [h, w] = size(fc); 4 | fc = cat(1, fc, zeros(1, w)); 5 | 6 | [f, d] = vl_sift(I,'frames',fc, 'orientations') ; 7 | d = double(d'); 8 | f = double(f(1:2,:)'); 9 | end -------------------------------------------------------------------------------- /img/hill/1.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daeyun/Image-Stitching/189e3b69e6a8115b08c64dcac62aa00f93c0518a/img/hill/1.JPG -------------------------------------------------------------------------------- /img/hill/2.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daeyun/Image-Stitching/189e3b69e6a8115b08c64dcac62aa00f93c0518a/img/hill/2.JPG -------------------------------------------------------------------------------- /img/hill/3.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daeyun/Image-Stitching/189e3b69e6a8115b08c64dcac62aa00f93c0518a/img/hill/3.JPG -------------------------------------------------------------------------------- /img/ledge/1.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daeyun/Image-Stitching/189e3b69e6a8115b08c64dcac62aa00f93c0518a/img/ledge/1.JPG -------------------------------------------------------------------------------- /img/ledge/2.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daeyun/Image-Stitching/189e3b69e6a8115b08c64dcac62aa00f93c0518a/img/ledge/2.JPG -------------------------------------------------------------------------------- /img/ledge/3.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daeyun/Image-Stitching/189e3b69e6a8115b08c64dcac62aa00f93c0518a/img/ledge/3.JPG -------------------------------------------------------------------------------- /img/pier/1.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daeyun/Image-Stitching/189e3b69e6a8115b08c64dcac62aa00f93c0518a/img/pier/1.JPG -------------------------------------------------------------------------------- /img/pier/2.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daeyun/Image-Stitching/189e3b69e6a8115b08c64dcac62aa00f93c0518a/img/pier/2.JPG -------------------------------------------------------------------------------- /img/pier/3.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daeyun/Image-Stitching/189e3b69e6a8115b08c64dcac62aa00f93c0518a/img/pier/3.JPG -------------------------------------------------------------------------------- /img/uttower/uttower_left.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daeyun/Image-Stitching/189e3b69e6a8115b08c64dcac62aa00f93c0518a/img/uttower/uttower_left.jpg -------------------------------------------------------------------------------- /img/uttower/uttower_right.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daeyun/Image-Stitching/189e3b69e6a8115b08c64dcac62aa00f93c0518a/img/uttower/uttower_right.jpg -------------------------------------------------------------------------------- /lib/dist2.m: -------------------------------------------------------------------------------- 1 | function n2 = dist2(x, c) 2 | % DIST2 Calculates squared distance between two sets of points. 3 | % Adapted from Netlab neural network software: 4 | % http://www.ncrg.aston.ac.uk/netlab/index.php 5 | % 6 | % Description 7 | % D = DIST2(X, C) takes two matrices of vectors and calculates the 8 | % squared Euclidean distance between them. Both matrices must be of 9 | % the same column dimension. If X has M rows and N columns, and C has 10 | % L rows and N columns, then the result has M rows and L columns. The 11 | % I, Jth entry is the squared distance from the Ith row of X to the 12 | % Jth row of C. 13 | % 14 | % 15 | % Copyright (c) Ian T Nabney (1996-2001) 16 | 17 | [ndata, dimx] = size(x); 18 | [ncentres, dimc] = size(c); 19 | if dimx ~= dimc 20 | error('Data dimension does not match dimension of centres') 21 | end 22 | 23 | n2 = (ones(ncentres, 1) * sum((x.^2)', 1))' + ... 24 | ones(ndata, 1) * sum((c.^2)',1) - ... 25 | 2.*(x*(c')); 26 | 27 | % Rounding errors occasionally cause negative entries in n2 28 | if any(any(n2<0)) 29 | n2(n2<0) = 0; 30 | end -------------------------------------------------------------------------------- /lib/find_sift.m: -------------------------------------------------------------------------------- 1 | function sift_arr = find_sift(I, circles, enlarge_factor) 2 | %% 3 | %% Compute non-rotation-invariant SIFT descriptors of a set of circles 4 | %% I is the image 5 | %% circles is an Nx3 array where N is the number of circles, where the 6 | %% first column is the x-coordinate, the second column is the y-coordinate, 7 | %% and the third column is the radius 8 | %% enlarge_factor is by how much to enarge the radius of the circle before 9 | %% computing the descriptor (a factor of 1.5 or larger is usually necessary 10 | %% for best performance) 11 | %% The output is an Nx128 array of SIFT descriptors 12 | %% 13 | %% Note that this code is not rotation-invariant, i.e., it does not attempt 14 | %% to normalize the patches by rotating them so that the horizontal direction 15 | %% is aligned with the dominant gradient orientation of the patch. 16 | %% 17 | %% (c) Lana Lazebnik 18 | %% 19 | 20 | if ndims(I) == 3 21 | I = im2double(rgb2gray(I)); 22 | else 23 | I = im2double(I); 24 | end 25 | 26 | 27 | fprintf('Running find_sift\n'); 28 | 29 | % parameters (default SIFT size) 30 | num_angles = 8; 31 | num_bins = 4; 32 | num_samples = num_bins * num_bins; 33 | alpha = 9; % smoothing for orientation histogram 34 | 35 | if nargin < 3 36 | enlarge_factor = 1.5; 37 | end 38 | 39 | angle_step = 2 * pi / num_angles; 40 | angles = 0:angle_step:2*pi; 41 | angles(num_angles+1) = []; % bin centers 42 | 43 | [hgt wid] = size(I); 44 | num_pts = size(circles,1); 45 | 46 | sift_arr = zeros(num_pts, num_samples * num_angles); 47 | 48 | % edge image 49 | sigma_edge = 1; 50 | 51 | 52 | [G_X,G_Y]=gen_dgauss(sigma_edge); 53 | I_X = filter2(G_X, I, 'same'); % vertical edges 54 | I_Y = filter2(G_Y, I, 'same'); % horizontal edges 55 | I_mag = sqrt(I_X.^2 + I_Y.^2); % gradient magnitude 56 | I_theta = atan2(I_Y,I_X); 57 | I_theta(isnan(I_theta)) = 0; % necessary???? 58 | 59 | % make default grid of samples (centered at zero, width 2) 60 | interval = 2/num_bins:2/num_bins:2; 61 | interval = interval - (1/num_bins + 1); 62 | [grid_x grid_y] = meshgrid(interval, interval); 63 | grid_x = reshape(grid_x, [1 num_samples]); 64 | grid_y = reshape(grid_y, [1 num_samples]); 65 | 66 | % make orientation images 67 | I_orientation = zeros(hgt, wid, num_angles); 68 | % for each histogram angle 69 | for a=1:num_angles 70 | % compute each orientation channel 71 | tmp = cos(I_theta - angles(a)).^alpha; 72 | tmp = tmp .* (tmp > 0); 73 | 74 | % weight by magnitude 75 | I_orientation(:,:,a) = tmp .* I_mag; 76 | end 77 | 78 | % for all circles 79 | for i=1:num_pts 80 | cx = circles(i,1); 81 | cy = circles(i,2); 82 | r = circles(i,3) * enlarge_factor; 83 | 84 | % find coordinates of sample points (bin centers) 85 | grid_x_t = grid_x * r + cx; 86 | grid_y_t = grid_y * r + cy; 87 | grid_res = grid_y_t(2) - grid_y_t(1); 88 | 89 | % find window of pixels that contributes to this descriptor 90 | x_lo = floor(max(cx - r - grid_res/2, 1)); 91 | x_hi = ceil(min(cx + r + grid_res/2, wid)); 92 | y_lo = floor(max(cy - r - grid_res/2, 1)); 93 | y_hi = ceil(min(cy + r + grid_res/2, hgt)); 94 | 95 | % find coordinates of pixels 96 | [grid_px, grid_py] = meshgrid(x_lo:x_hi,y_lo:y_hi); 97 | num_pix = numel(grid_px); 98 | grid_px = reshape(grid_px, [num_pix 1]); 99 | grid_py = reshape(grid_py, [num_pix 1]); 100 | 101 | % find (horiz, vert) distance between each pixel and each grid sample 102 | dist_px = abs(repmat(grid_px, [1 num_samples]) - repmat(grid_x_t, [num_pix 1])); 103 | dist_py = abs(repmat(grid_py, [1 num_samples]) - repmat(grid_y_t, [num_pix 1])); 104 | 105 | % find weight of contribution of each pixel to each bin 106 | weights_x = dist_px/grid_res; 107 | weights_x = (1 - weights_x) .* (weights_x <= 1); 108 | weights_y = dist_py/grid_res; 109 | weights_y = (1 - weights_y) .* (weights_y <= 1); 110 | weights = weights_x .* weights_y; 111 | 112 | % make sift descriptor 113 | curr_sift = zeros(num_angles, num_samples); 114 | for a = 1:num_angles 115 | tmp = reshape(I_orientation(y_lo:y_hi,x_lo:x_hi,a),[num_pix 1]); 116 | tmp = repmat(tmp, [1 num_samples]); 117 | curr_sift(a,:) = sum(tmp .* weights); 118 | end 119 | sift_arr(i,:) = reshape(curr_sift, [1 num_samples * num_angles]); 120 | 121 | % % visualization 122 | % if sigma_edge >= 3 123 | % subplot(1,2,1); 124 | % rescale_and_imshow(I(y_lo:y_hi,x_lo:x_hi) .* reshape(sum(weights,2), [y_hi-y_lo+1,x_hi-x_lo+1])); 125 | % subplot(1,2,2); 126 | % rescale_and_imshow(curr_sift); 127 | % pause; 128 | % end 129 | end 130 | 131 | 132 | %% 133 | %% normalize the SIFT descriptors more or less as described in Lowe (2004) 134 | %% 135 | tmp = sqrt(sum(sift_arr.^2, 2)); 136 | normalize_ind = find(tmp > 1); 137 | 138 | sift_arr_norm = sift_arr(normalize_ind,:); 139 | sift_arr_norm = sift_arr_norm ./ repmat(tmp(normalize_ind,:), [1 size(sift_arr,2)]); 140 | 141 | % suppress large gradients 142 | sift_arr_norm(find(sift_arr_norm > 0.2)) = 0.2; 143 | 144 | % finally, renormalize to unit length 145 | tmp = sqrt(sum(sift_arr_norm.^2, 2)); 146 | sift_arr_norm = sift_arr_norm ./ repmat(tmp, [1 size(sift_arr,2)]); 147 | 148 | sift_arr(normalize_ind,:) = sift_arr_norm; 149 | 150 | 151 | 152 | 153 | function [GX,GY]=gen_dgauss(sigma) 154 | 155 | f_wid = 4 * floor(sigma); 156 | G = normpdf(-f_wid:f_wid,0,sigma); 157 | G = G' * G; 158 | [GX,GY] = gradient(G); 159 | 160 | GX = GX * 2 ./ sum(sum(abs(GX))); 161 | GY = GY * 2 ./ sum(sum(abs(GY))); 162 | -------------------------------------------------------------------------------- /lib/harris.m: -------------------------------------------------------------------------------- 1 | % HARRIS - Harris corner detector 2 | % 3 | % Usage: [cim, r, c] = harris(im, sigma, thresh, radius, disp) 4 | % 5 | % Arguments: 6 | % im - image to be processed. 7 | % sigma - standard deviation of smoothing Gaussian. Typical 8 | % values to use might be 1-3. 9 | % thresh - threshold (optional). Try a value ~1000. 10 | % radius - radius of region considered in non-maximal 11 | % suppression (optional). Typical values to use might 12 | % be 1-3. 13 | % disp - optional flag (0 or 1) indicating whether you want 14 | % to display corners overlayed on the original 15 | % image. This can be useful for parameter tuning. 16 | % 17 | % Returns: 18 | % cim - binary image marking corners. 19 | % r - row coordinates of corner points. 20 | % c - column coordinates of corner points. 21 | % 22 | % If thresh and radius are omitted from the argument list 'cim' is returned 23 | % as a raw corner strength image and r and c are returned empty. 24 | 25 | % Reference: 26 | % C.G. Harris and M.J. Stephens. "A combined corner and edge detector", 27 | % Proceedings Fourth Alvey Vision Conference, Manchester. 28 | % pp 147-151, 1988. 29 | % 30 | % Author: 31 | % Peter Kovesi 32 | % Department of Computer Science & Software Engineering 33 | % The University of Western Australia 34 | % pk@cs.uwa.edu.au www.cs.uwa.edu.au/~pk 35 | % 36 | % March 2002 37 | 38 | function [cim, r, c] = harris(im, sigma, thresh, radius, disp) 39 | 40 | error(nargchk(2,5,nargin)); 41 | 42 | dx = [-1 0 1; -1 0 1; -1 0 1]; % Derivative masks 43 | dy = dx'; 44 | 45 | Ix = conv2(im, dx, 'same'); % Image derivatives 46 | Iy = conv2(im, dy, 'same'); 47 | 48 | % Generate Gaussian filter of size 6*sigma (+/- 3sigma) and of 49 | % minimum size 1x1. 50 | g = fspecial('gaussian',max(1,fix(6*sigma)), sigma); 51 | 52 | Ix2 = conv2(Ix.^2, g, 'same'); % Smoothed squared image derivatives 53 | Iy2 = conv2(Iy.^2, g, 'same'); 54 | Ixy = conv2(Ix.*Iy, g, 'same'); 55 | 56 | cim = (Ix2.*Iy2 - Ixy.^2)./(Ix2 + Iy2 + eps); % Harris corner measure 57 | 58 | % Alternate Harris corner measure used by some. Suggested that 59 | % k=0.04 - I find this a bit arbitrary and unsatisfactory. 60 | % cim = (Ix2.*Iy2 - Ixy.^2) - k*(Ix2 + Iy2).^2; 61 | 62 | if nargin > 2 % We should perform nonmaximal suppression and threshold 63 | 64 | % Extract local maxima by performing a grey scale morphological 65 | % dilation and then finding points in the corner strength image that 66 | % match the dilated image and are also greater than the threshold. 67 | sze = 2*radius+1; % Size of mask. 68 | mx = ordfilt2(cim,sze^2,ones(sze)); % Grey-scale dilate. 69 | cim = (cim==mx)&(cim>thresh); % Find maxima. 70 | 71 | [r,c] = find(cim); % Find row,col coords. 72 | 73 | if nargin==5 & disp % overlay corners on original image 74 | figure, imagesc(im), axis image, colormap(gray), hold on 75 | plot(c,r,'ys'), title('corners detected'); 76 | end 77 | 78 | else % leave cim as a corner strength image and make r and c empty. 79 | r = []; c = []; 80 | end 81 | -------------------------------------------------------------------------------- /stitch_images.m: -------------------------------------------------------------------------------- 1 | % STITCH_IMAGES - Given a set of images with overlapping regions, 2 | % automatically compute a panorama image. 3 | % 4 | % Usage: [result_img, H, num_inliers, residual] ... 5 | % = stitch_images (images, sift_r, harris_r, ... 6 | % harris_thresh, harris_sigma, num_putative_matches, ransac_n) 7 | % 8 | % Usage example: 9 | % stitch_images(images, 5, 5, 0.03, 1, 100, 4000) 10 | % 11 | % Arguments: 12 | % images - 1 by n cell array of images. 13 | % sift_r - radius of the SIFT descriptor. 14 | % harris_r - radius of the Harris corner detector. 15 | % harris_thresh - Harris corner detector threshold. 16 | % harris_sigma - standard deviation of smoothing Gaussian 17 | % num_putative_matches - number of putative matches to run 18 | % RANSAC. 19 | % ransac_n - number of RANSAC iterations. 20 | % 21 | % Returns: 22 | % result_img - Computed paranoma image. 23 | % H - n by n cell array of homography matrices. 24 | % H{i, j} is the homography matrix between images 25 | % i and j. 26 | % num_inliers - n by n array of number of inliers. num_inliers{i, 27 | % j} is the number of inliers between images i and 28 | % j. 29 | % residual - n by n array of sum of squared disrances. 30 | % residual{i, j} is the residual between images i 31 | % and j. 32 | % 33 | % Dependencies: 34 | % VLFeat - Download at http://www.vlfeat.org/download.html 35 | % 36 | % Author: 37 | % Daeyun Shin 38 | % daeyun@daeyunshin.com daeyunshin.com 39 | % 40 | % March 2014 41 | 42 | function [result_img, H, num_inliers, residual] = stitch_images... 43 | (images, sift_r, harris_r, harris_thresh, harris_sigma, num_putative_matches, ransac_n) 44 | 45 | image_order = find_ordered_homography(images, sift_r, harris_r, harris_thresh, ... 46 | harris_sigma, num_putative_matches, ransac_n); 47 | 48 | n_img = length(images); 49 | num_inliers = zeros(n_img, n_img); 50 | H = cell(n_img, n_img); 51 | residual = zeros(n_img, n_img); 52 | 53 | result_img = images{image_order(1)}; 54 | for image_ind = 2:length(image_order) 55 | prev_ind = image_order(image_ind-1); 56 | ind = image_order(image_ind); 57 | 58 | [result_img, H{prev_ind, ind}, num_inliers(prev_ind, ind), residual(prev_ind, ind)] ... 59 | = stitch_pair(images{ind}, result_img, sift_r, harris_r, harris_thresh, ... 60 | harris_sigma, num_putative_matches, ransac_n); 61 | 62 | H{ind, prev_ind} = H{prev_ind, ind}; 63 | num_inliers(ind, prev_ind) = num_inliers(prev_ind, ind); 64 | residual(ind, prev_ind) = residual(prev_ind, ind); 65 | end 66 | end -------------------------------------------------------------------------------- /tests/helper_test.m: -------------------------------------------------------------------------------- 1 | %% Test select_putative_matches 2 | a = [1 2 3; 1 1 2; 4 1 4; 1 1 1]; 3 | b = [1 2 9; 1 1 2; 3 1 1; 1 4 1]; 4 | [q, w] = select_putative_matches(a, b, 0, 2); 5 | assert(isequal(q, [2; 2])); 6 | assert(isequal(w, [2; 1])); --------------------------------------------------------------------------------