My Steps
- Convert the video into Frames and then into Images
- using SIFT get pairs of corresponding points taken from the two images
- Recover homographies of each Image
- Warp the images using the homography
- Blend images into a mosaic using weighted average
- View the Results
function [mos_img]=mosaic(filename) % creates a mosaic image from a movie % assuming translation only between frames warning off MATLAB:mir_warning_variable_used_as_function; % using global names to cache frames in memory % performance boosting global Nframes; global AviName; AviName = filename; [h, w, Nframes] = avi_size(AviName); % relative displacements d = zeros(2,Nframes); RefFrame = GetImage(1); % align every two consequent frames for ii=2:Nframes % read a frame Frame = GetImage(ii); d(:,ii) = align(RefFrame, Frame, d(:,ii-1)); % current frame is next one's reference RefFrame = Frame; end % cumulative displacement relative to first frame D = cumsum(d,2); % minimal Minimum = min(D,[],2); %maximal Maximum = max(D,[],2); % minimal displacement is one D = D - Minimum*ones(1,Nframes)+1; % create an empty mosaic (compute the bounding box) mos_img = zeros([w, h]+ceil(Maximum'-Minimum'))'; [bm bn] = size(mos_img); % creates weights weights = zeros(bm,bn); % shfoch everything into the mosaic for ii=1:Nframes % read a frame Frame = GetImage(ii); tmpF(1:(h+1),1:(w+1)) = 0; tmpF(2:h+1,2:w+1) = Frame; intD = floor(D(:,ii)); wF = warp(tmpF, (D(:,ii)-intD)); wF(find(isnan(wF))) = 0; % mask m = zeros(size(tmpF)); m(2:h+1,2:w+1) = 1; wm = warp(m, (D(:,ii)-intD)); wm(find(isnan(wm))) = 0; [A B] = GetPosArray(bm, h, bn, w, intD); mos_img(A, B) = mos_img(A, B) + wF; weights(A, B) = weights(A, B) + wm; end weights = weights + (weights == 0); % in order to avoid zero division mos_img = mos_img./weights; function [A, B] = GetPosArray(bm, h, bn, w, intD) % Gets an array, where to put the image. % bm, bn - the total image's size % h, w - single frame's size % intD - the integer displacement. A = bm - h + 1 - intD(2): bm - intD(2) + 1; B = bn - w + 1- intD(1): bn - intD(1) + 1; function image = GetImage(index) % Gets the image data in the correct form (Double, greyscale). % using chached frames to boost performance % caching BUFFER_SIZE images at each access to the avi file BUFFER_SIZE = 40; persistent CurrentBlock M; global AviName Nframes; if (isempty(CurrentBlock)) CurrentBlock = -1; end; % zindex to start from zero rather than one. zindex = index - 1; if (floor(zindex / BUFFER_SIZE) ~= CurrentBlock) CurrentBlock = zindex / BUFFER_SIZE; ReadTo = min((CurrentBlock+1)*BUFFER_SIZE, Nframes); M = aviread(AviName, (CurrentBlock*BUFFER_SIZE + 1):ReadTo); end localIndex = mod(zindex, BUFFER_SIZE) + 1; %returning the right image in the correct format. image = im2double(rgb2gray(M(localIndex).cdata )); function [shift] = align(im1, im2, initial_shift) % This function aligns im2 to im1 assuming an homography consist of shift only % Input parameters % im1, im2 - images in double representation grey level range [0,1] % % The output parameters: % shift - a vector [dx, dy]' of displacement % initial guess for displacement shift = [0, 0]'; if (nargin == 3) shift = initial_shift; end; % global parameters ITER_EPS = 0.001; PYRAMID_DEPTH = 5; ITERATIONS_PER_LEVEL = 5; FILTER_PARAM = 0.4; % parameter for gaussian pyramid % Creating pyramids for both images. F = gaussian_pyramid(im1, PYRAMID_DEPTH, FILTER_PARAM); G = gaussian_pyramid(im2, PYRAMID_DEPTH, FILTER_PARAM); % Allowing initial shift - scale it to fit pyramid level shift = shift ./ (2^(PYRAMID_DEPTH+1)); % work coarse to fine in pyramids for ii = PYRAMID_DEPTH : -1 : 1 %PYRAMID_DEPTH clear warped_im2; % we are about to change its dimensionality shift = 2 * shift; % compensate the scale factor between pyramid levels. % compute the gradient once per pyramid level [Fx Fy] = gradient(F{ii}); % although we can re-calculate it according to overlapping zone % it is negligible thus we save computation by doing it once per level tmp = sum(Fx(:).*Fy(:)); A = inv( [ sum(Fx(:).^2) tmp ; tmp sum(Fy(:).^2) ] ); % iterate per pyramid level for jj = 1 : ITERATIONS_PER_LEVEL % always warp original image by accumulated shift, avoid over % blurring warped_im2 = warp(G{ii}, shift); % mask to rule out boundary (NaN values) mask = isnan(warped_im2); warped_im2(find(mask)) = 0; mask = 1-mask; % difference image - ignore parts that are not overlapping diff = ( F{ii}(:)-warped_im2(:) ).*mask(:); % Solve and update shift; tmpshift = A*[sum(diff.*Fx(:)) ; sum(diff.*Fy(:))] ; shift = shift + tmpshift; if (abs(tmpshift) < ITER_EPS) break; end; end; end; % wrapping an image with shift using bilinear interpolation function [warped] = warp(image, shift) [x y] = size(image); [X Y] = meshgrid(1:y,1:x); % Behold!!! warped = interp2(image, X+shift(1), Y+shift(2), 'linear'); function [G] = gaussian_pyramid(image, n, a) % compute n gaussian pyramid of an image % a - filter parameter G = cell(n,1); G{1} = image; for ii=1:n-1 % use tmp variable to comply with varying size of image through down sampling G{ii+1}=gaussian_pyramid_one_stage(G{ii}, a); end function [G]=gaussian_pyramid_one_stage(im, a) % compute one stage of a gaussian pyramid of an image % a is the filter parameter [m n]=size(im); % construct the filter filterKernel = [0.25 - (a/2), 0.25, a, 0.25, 0.25-(a/2)]; h = filterKernel'*filterKernel; % filter the image, i.e. blur it with the gaussian, replicate to overcome % boundary conditions filt = imfilter(im, h, 'replicate'); % down sample G = filt(1:2:m,1:2:n);