├── examples ├── ex_shape_from_shading.m ├── ex_gen_rnd_surface_curve.m ├── ex_seam_carving.m ├── ex_retinex_filter.m ├── ex_optical_flow.m └── ex_bilateral_filter.m ├── src ├── genRandCurve.m ├── shapeFromShading.m ├── regBilateralFilter.m ├── genRandSurface.m ├── retinexFilter.m ├── opticalFlow.m ├── reinit.m ├── fastBilateralFilter.m └── seamCarving.m ├── README.md └── license.txt /examples/ex_shape_from_shading.m: -------------------------------------------------------------------------------- 1 | % (C) Copyright Kirill Lykov 2013. 2 | % 3 | % Distributed under the FreeBSD Software License (See accompanying file license.txt) 4 | 5 | clearvars all; 6 | close all; 7 | 8 | image = double(rgb2gray(imread('sheef.jpg'))); 9 | depth = shapeFromShading(image, 1000, 1/32); 10 | 11 | figure; 12 | mesh(depth); 13 | title('Surface reconstruction'); 14 | -------------------------------------------------------------------------------- /examples/ex_gen_rnd_surface_curve.m: -------------------------------------------------------------------------------- 1 | % (C) Copyright Kirill Lykov 2013. 2 | % 3 | % Distributed under the FreeBSD Software License (See accompanying file license.txt) 4 | 5 | countPointInCurve = 1000; 6 | clearvars all; 7 | close all; 8 | figure 9 | subplot(1,2,1) 10 | [x,y] = genRandCurve(0.4188, 2, countPointInCurve, 1, 1); 11 | 12 | dx = 0.2; 13 | dy = 0.2; 14 | subplot(1,2,2); 15 | genRandSurface(dx, dy, [x;y], countPointInCurve, 1, 1); 16 | -------------------------------------------------------------------------------- /examples/ex_seam_carving.m: -------------------------------------------------------------------------------- 1 | % (C) Copyright Kirill Lykov 2013. 2 | % 3 | % Distributed under the FreeBSD Software License (See accompanying file license.txt) 4 | 5 | clearvars all; 6 | close all; 7 | 8 | % use small pictures because implementation is slow 9 | initialData = imread('sasha.jpg'); 10 | image = double(initialData); 11 | 12 | figure(1); 13 | plot(1); 14 | imshow(initialData); 15 | title(['Original picture: ' int2str(size(image, 1)) 'x' int2str(size(image, 2))]); 16 | 17 | newSize = [size(image, 1) - 60, size(image, 2) - 80]; 18 | 19 | image = seamCarving(newSize, image); 20 | 21 | figure(2); 22 | plot(2); 23 | 24 | imshow(uint8(image)); 25 | title(['Modified picture: ' int2str(size(image, 1)) 'x' int2str(size(image, 2))]); 26 | 27 | imwrite(uint8(image), 'sasha-reduced.jpg', 'jpg'); 28 | 29 | -------------------------------------------------------------------------------- /src/genRandCurve.m: -------------------------------------------------------------------------------- 1 | % (C) Copyright Kirill Lykov 2013. 2 | % 3 | % Distributed under the FreeBSD Software License (See accompanying file license.txt) 4 | 5 | function [X,Y] = genRandCurve( dt, distrortionModule, outSize, scaleFactor, doPlot ) 6 | 7 | pointCount = 2 * pi/ dt; 8 | 9 | randGrid = linspace(0, 2*pi, pointCount); 10 | splineGrid = linspace(0, 2*pi, outSize); 11 | 12 | % if distrortionModule=0, than it is "smooth" i.e. circle 13 | % the bigger is distortion the bigger is curvature 14 | r = scaleFactor*(1 + distrortionModule * rand(size(randGrid))); 15 | r(end) = r(1); %closed curve 16 | rr = spline(randGrid, [0 r 0], splineGrid); 17 | 18 | X = rr.*cos(splineGrid); 19 | Y = rr.*sin(splineGrid); 20 | 21 | if (doPlot == 1) 22 | plot(X, Y); 23 | end 24 | end 25 | 26 | -------------------------------------------------------------------------------- /examples/ex_retinex_filter.m: -------------------------------------------------------------------------------- 1 | % (C) Copyright Kirill Lykov 2013. 2 | % 3 | % Distributed under the FreeBSD Software License (See accompanying file license.txt) 4 | 5 | clearvars all; 6 | close all; 7 | 8 | initialData = imread('yourpicture.jpg'); 9 | 10 | [h,s,v] = rgb2hsv(initialData); % Elad used HSV and applied retinex to V 11 | 12 | originalGrayImg = double(rgb2gray(initialData)); 13 | originalRGBImage = double(initialData); 14 | 15 | subplot(221); 16 | imagesc(initialData); 17 | title('original image'); 18 | %colormap gray; 19 | 20 | % parameters 21 | sigmaSpatial = 30; 22 | sigmaRange = 0.2; 23 | 24 | samplingSpatial = sigmaSpatial; 25 | samplingRange = sigmaRange; 26 | 27 | gamma = 1.5; 28 | 29 | subplot(224); 30 | v = retinexFilter(v, sigmaSpatial, sigmaRange, samplingSpatial, samplingRange, gamma, 0); 31 | 32 | imhsv(:,:,1) = h; 33 | imhsv(:,:,2) = s; 34 | imhsv(:,:,3) = v; 35 | outRGBImg = hsv2rgb(imhsv); 36 | 37 | imagesc(outRGBImg); 38 | title('after retinex'); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | cvision-algorithms 2 | ================== 3 | 4 | Collection of Computer Vision algorithms implemented in Matlab. 5 | 6 | Algorithms: 7 | - seam carving ("Seam carving for content-aware image processing" by S. Avidan & A. Shamir, 2007) 8 | Algorithm and code are described in details http://kirilllykov.github.io/blog/2013/06/06/seam-carving-algorithm/ 9 | - bilateral filter ("Bilateral filtering for gray and color images" by C. Tomasi & R. Manduchi, 1998) 10 | - fast bilateral filter ("A fast approximation of the bilateral filter using a signal processing approach" by S. Paris & F. Durand, 2006) 11 | - retinex ("Retinex by two bilateral filters" by M. Elad, 2005) 12 | - shape from shading algorithm by Horn and Ikeuchi ("The Variational Approach to Shape from Shading" 13 | by B. Horn and M. Brooks, 1985) 14 | - optical flow algorithm by Horn-Shunck for an array of input images ("Determining optical flow" by Horn and Schunck, 1980) 15 | - generator of random surface and closed curve, auxiliary code -------------------------------------------------------------------------------- /examples/ex_optical_flow.m: -------------------------------------------------------------------------------- 1 | % (C) Copyright Kirill Lykov 2013. 2 | % 3 | % Distributed under the FreeBSD Software License (See accompanying file license.txt) 4 | 5 | clearvars all; 6 | close all; 7 | 8 | img1 = imread('/Users/kirill/Desktop/smith1.png'); 9 | img1 = double(rgb2gray(img1)); 10 | 11 | img2 = imread('/Users/kirill/Desktop/smith2.png'); 12 | img2 = double(rgb2gray(img2)); 13 | 14 | images = zeros(size(img2, 1), size(img2, 2), 2); 15 | images(:, :, 1) = img1; 16 | images(:, :, 2) = img2; 17 | 18 | [Vx, Vy] = opticalFlow(images, 1, 10); 19 | 20 | % Show flow on the picture without showing all vectors (othervise it will 21 | % be all green) 22 | 23 | rSize = 5; 24 | for i=1:size(Vx, 1) 25 | for j=1:size(Vx, 2) 26 | if (floor(i/rSize) ~= i/rSize || floor(j/rSize) ~= j/rSize) 27 | Vx(i, j) = 0; 28 | Vy(i, j) = 0; 29 | end 30 | end 31 | end 32 | 33 | figure(); 34 | imshow(uint8(img2)); 35 | hold on; 36 | 37 | % To avoid shoing too many 0 vectos 38 | validIndex = (abs(Vx) ~= 0); 39 | 40 | [gridX, gridY] = meshgrid(1:size(img1, 2), 1:size(img1, 1)); 41 | % plot only valid flow vectors 42 | quiver(gridX(validIndex), gridY(validIndex), ... 43 | Vx(validIndex), Vy(validIndex), 3, 'color', 'g', 'linewidth', 1); 44 | 45 | 46 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright 2013, Kirill Lykov 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 1. Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | 12 | THIS SOFTWARE IS PROVIDED BY KIRILL LYKOV ''AS IS'' AND ANY 13 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 14 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 15 | DISCLAIMED. IN NO EVENT SHALL KIRILL LYKOV BE LIABLE FOR ANY 16 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 17 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 18 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 19 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 20 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22 | -------------------------------------------------------------------------------- /examples/ex_bilateral_filter.m: -------------------------------------------------------------------------------- 1 | % (C) Copyright Kirill Lykov 2013. 2 | % 3 | % Distributed under the FreeBSD Software License (See accompanying file license.txt) 4 | 5 | clearvars all; 6 | close all; 7 | 8 | initialData = imread('cat.png'); 9 | 10 | originalGrayImg = double(rgb2gray(initialData)); 11 | 12 | subplot(221); 13 | imagesc(originalGrayImg); 14 | title('original image'); 15 | colormap gray; 16 | 17 | subplot(222); 18 | % noise functions expects integers 19 | noisyGrayImg = imnoise(rgb2gray(initialData),'gaussian', 0, 0.01); 20 | noisyGrayImg = double(noisyGrayImg); 21 | imagesc(noisyGrayImg); 22 | title('noisy image'); 23 | 24 | % parameters 25 | sigmaSpatial = 10; 26 | sigmaRange = 100; 27 | 28 | samplingSpatial = sigmaSpatial; 29 | samplingRange = sigmaRange; 30 | 31 | % brute force Bilateral filter 32 | subplot(223); 33 | tStart = cputime; 34 | filteredImg = regBilateralFilter(noisyGrayImg, 0, sigmaSpatial, sigmaRange, samplingSpatial); 35 | tEnd = cputime; 36 | imagesc(filteredImg); 37 | title(['brute force BF. Time: ' int2str(tEnd - tStart) 'sec']); 38 | 39 | % fast bilateral filter - terms and notations are from the paper 40 | subplot(224); 41 | tStart = cputime; 42 | filteredImg = fastBilateralFilter(noisyGrayImg, sigmaSpatial, sigmaRange, samplingSpatial, samplingRange, 0, 15); 43 | tEnd = cputime; 44 | imagesc(filteredImg); 45 | title(['fast BF. Time: ' int2str(tEnd - tStart) 'sec']); 46 | -------------------------------------------------------------------------------- /src/shapeFromShading.m: -------------------------------------------------------------------------------- 1 | %(C) Copyright Kirill Lykov 2013. 2 | % 3 | % Distributed under the FreeBSD Software License (See accompanying file license.txt) 4 | 5 | function depth = shapeFromShading( image, niter, lambda ) 6 | % shape from shading using variational approach, 7 | % lack-of-smoothness regularization term by Horn & Ikeuchi 8 | 9 | sz = size(image); 10 | Z = image / max(max(image)); 11 | 12 | Zx = 0.5 * imfilter(Z, [-1 0 1]); 13 | Zy = 0.5 * imfilter(Z, [-1; 0; 1]); 14 | 15 | gt = zeros(sz); 16 | ft = zeros(sz); 17 | 18 | ft(Zx >= 0.5 * max(max(Zx))) = -2; 19 | ft(Zx < 0.5 * min(min(Zx))) = 2; 20 | gt(Zy >= 0.5 * max(max(Zy))) = 2; 21 | gt(Zy < 0.5 * min(min(Zy))) = -2; 22 | 23 | y = ft.^2 + gt.^2; 24 | 25 | fs = ft; 26 | gs = gt; 27 | 28 | fs(y~=4) = 0; 29 | gs(y~=4) = 0; 30 | 31 | kernel = (1/8) * [1 1 1; 1 0 1; 1 1 1]; 32 | 33 | for i = 1 : niter 34 | if (mod(i, 500) == 0) 35 | i 36 | end 37 | 38 | depth = (-fs.^2 - gs.^2 + 4) ./ (fs.^2 + gs.^2 + 4); 39 | rnorm = 1./( fs.^2 + gs.^2 + 4 ).^2; 40 | fmid = imfilter(fs, kernel); 41 | diff = Z - depth; 42 | f = (1/lambda) * fs.* rnorm .* diff; 43 | % fs = fsmooth + fdata; 44 | fs(ft==0) = fmid(ft==0) - f(ft==0); 45 | gmid = imfilter(gs, kernel); 46 | g = (1/lambda) * gs .* rnorm .* diff; 47 | % gs = gsmooth + gdata; 48 | gs(gt == 0) = gmid(gt == 0) - g(gt == 0); 49 | end 50 | 51 | end 52 | 53 | -------------------------------------------------------------------------------- /src/regBilateralFilter.m: -------------------------------------------------------------------------------- 1 | % (C) Copyright Kirill Lykov 2013. 2 | % 3 | % Distributed under the FreeBSD Software License (See accompanying file license.txt) 4 | 5 | function filteredImg = regBilateralFilter(inputImage, isRetinex, sigmaD, sigmaR, stencilSz) 6 | % Implementation of the Bilateral Filter by C. Tomasi and R. Manduchi 7 | % "Bilateral filtering for gray and color images" 8 | % isRetinex flag is used for purpose of retinex algorithm 9 | func = @(x) bilateralCore(isRetinex, sigmaD, sigmaR, x); 10 | filteredImg = nlfilter(inputImage, [stencilSz stencilSz], func); 11 | end 12 | 13 | function res = bilateralCore(isRetinex, sigmaD, sigmaR, pixelVicinity) 14 | 15 | sigmaD2 = sigmaD*sigmaD; 16 | sigmaR2 = sigmaR*sigmaR; 17 | 18 | dist2 = @(x,y) double((x(1) - y(1))^2 + (x(2) - y(2))^2); 19 | 20 | sz = size(pixelVicinity); 21 | center = int32(sz/2); 22 | f_center = pixelVicinity(center(1), center(2)); 23 | 24 | totalMass = 0.0; 25 | filteredVal = 0.0; 26 | for i=1:sz(1) 27 | for j = 1:sz(2) 28 | 29 | spatialDist = exp(-(f_center - pixelVicinity(i,j))^2/2/sigmaR2); 30 | if (f_center > pixelVicinity(i,j) && isRetinex == 1) 31 | spatialDist = 0; 32 | end; 33 | w = exp(-dist2(center, [i, j])/2/sigmaD2) * spatialDist; 34 | 35 | totalMass = totalMass + w; 36 | 37 | filteredVal = filteredVal + pixelVicinity(i, j) * w; 38 | end; 39 | end; 40 | 41 | res = filteredVal / totalMass; 42 | end -------------------------------------------------------------------------------- /src/genRandSurface.m: -------------------------------------------------------------------------------- 1 | % (C) Copyright Kirill Lykov 2013. 2 | % 3 | % Distributed under the FreeBSD Software License (See accompanying file license.txt) 4 | 5 | function [x, y, z, backwardMapping] = genRandSurface(dx, dy, coord, countPointInCurve, distrortionModule, doPlot) 6 | %if distrortionModule==0 than it is level set 7 | %as domain use 2 x bounding box 8 | xmin = 2 * floor(min(coord(1,:))); 9 | xmax = 2 * ceil(max(coord(1,:))); 10 | ymin = 2 * floor(min(coord(2,:))); 11 | ymax = 2 * ceil(max(coord(2,:))); 12 | 13 | %construct 0,1 map. for every voxel it indicates whether there is a curve 14 | %point inside or not. 15 | dim = [fix((xmax - xmin)/dx) + 1, fix((ymax - ymin)/dy) + 1]; 16 | %[X, Y] = meshgrid(xmin:dx:xmax, ymin:dy:ymax); 17 | curveMask = zeros(dim(1), dim(2)); 18 | backwardMapping = zeros(1, countPointInCurve); 19 | for i = 1:countPointInCurve 20 | indX = fix((coord(1, i) - xmin)/dx); 21 | indY = fix((coord(2, i) - ymin)/dy); 22 | 23 | backwardMapping(1, i) = indX; 24 | backwardMapping(2, i) = indY; 25 | 26 | curveMask(indX, indY) = 1; 27 | end 28 | 29 | signMask = imfill(int32(curveMask), 'holes'); 30 | 31 | signMask = -2 * signMask + 1; 32 | 33 | %subplot(2,2,2); 34 | %imagesc(curveMask); 35 | 36 | %subplot(2,2,3); 37 | %imagesc(signMask); 38 | 39 | [x, y] = meshgrid(xmin:dx:xmax, ymin:dy:ymax); 40 | z = double(bwdist(curveMask)); 41 | z = z.*double(signMask); 42 | 43 | if distrortionModule ~= 0 44 | negativeCurveMask = 1 - curveMask; 45 | noise = distrortionModule * rand(size(z)); 46 | noise = noise.*negativeCurveMask; 47 | z = z + noise; 48 | end; 49 | 50 | % it must be dx == dy, otherwise it is not convinient to use bwdist 51 | z = dx*z'; 52 | 53 | if doPlot == 1 54 | surf(x, y, z); 55 | end; 56 | end -------------------------------------------------------------------------------- /src/retinexFilter.m: -------------------------------------------------------------------------------- 1 | % (C) Copyright Kirill Lykov 2013. 2 | % 3 | % Distributed under the FreeBSD Software License (See accompanying file license.txt) 4 | 5 | function output = retinexFilter( image, sigmaSpatial, sigmaRange, ... 6 | samplingSpatial, samplingRange, gamma, showPlots ) 7 | % should be applied to the v channel of HSV picture 8 | % following notations by Elad. Based on paper Retinex by two bilateral 9 | % filters. 10 | % http://www.cs.technion.ac.il/~elad/talks/2005/Retinex-ScaleSpace-Short.pdf 11 | 12 | % 1. transform to ln 13 | image( image == 0 ) = image( image == 0 ) + 0.001; % to avoid inf values for log 14 | illumination = log(image); 15 | reflection = illumination; 16 | 17 | % 2. find illumination by filtering with envelope mode 18 | %illumination = fastBilateralFilter(illumination, sigmaSpatial, sigmaRange, samplingSpatial, samplingRange); 19 | illumination = regBilateralFilter(illumination, 1, sigmaSpatial, sigmaRange, 15); 20 | if (showPlots == 1) 21 | subplot(222); 22 | imagesc(illumination); 23 | end; 24 | 25 | % 3. find reflection by filtering with regular mode 26 | % at this point reflection stores original image 27 | reflection = (reflection - illumination); 28 | %reflection = fastBilateralFilter(reflection, sigmaSpatial, sigmaRange, samplingSpatial, samplingRange); 29 | reflection = regBilateralFilter(reflection, 0, sigmaSpatial, sigmaRange, 5); 30 | subplot(223); 31 | imagesc(exp(illumination)); 32 | 33 | % 4. apply gamma correction to illumination 34 | illumination = 1/gamma*illumination; %1.0/gamma*(illumination - log(255)) + log(255); % for [1,255] 35 | if (showPlots == 1) 36 | subplot(224); 37 | imagesc(exp(illumination)); 38 | end; 39 | 40 | % 5. S_res = exp(s_res) = exp( illumination_corrected + reflection ) 41 | output = exp( reflection + illumination); 42 | 43 | end 44 | -------------------------------------------------------------------------------- /src/opticalFlow.m: -------------------------------------------------------------------------------- 1 | % (C) Copyright Kirill Lykov 2013. 2 | % 3 | % Distributed under the FreeBSD Software License (See accompanying file license.txt) 4 | 5 | function [Vj,Vi] = opticalFlow(images, alpha, iterations) 6 | % implementation of Horn-Shunck flow for arrai of input images 7 | %http://dspace.mit.edu/bitstream/handle/1721.1/6337/AIM-572.pdf?sequence=2 8 | 9 | [height, width, frames] = size(images); 10 | 11 | Vj = zeros(height, width); 12 | Vi = zeros(height, width); 13 | 14 | for k = 1:frames-1 15 | 16 | Ej = zeros(height-1, width-1, frames-1); 17 | Ei = zeros(height-1, width-1, frames-1); 18 | Et = zeros(height-1, width-1, frames-1); 19 | 20 | % page 6, quantization and noise 21 | for j = 2:width-1 22 | for i = 2:height-1 23 | Ej(i, j, k) = (images(i + 1, j + 1, k) - images(i + 1, j, k) + images(i, j + 1, k)... 24 | - images(i, j, k) + images(i + 1, j + 1, k + 1) - images(i + 1, j, k + 1)... 25 | + images(i, j + 1,k+1)-images(i,j,k+1))/4; 26 | 27 | Ei(i, j, k) = (images(i, j, k) - images(i + 1, j, k) + images(i, j + 1, k)... 28 | - images(i + 1, j + 1, k) + images(i, j, k + 1)-images(i + 1, j, k + 1)... 29 | + images(i, j + 1, k + 1) - images(i + 1, j + 1, k + 1))/4; 30 | 31 | Et(i,j,k) = (images(i + 1, j, k + 1) - images(i + 1, j, k) + images(i, j, k + 1)... 32 | - images(i, j, k) + images(i + 1, j + 1, k + 1)- images(i + 1, j + 1, k)... 33 | + images(i, j + 1, k + 1) - images(i, j + 1, k))/4; 34 | end 35 | end 36 | 37 | for nn = 1:iterations 38 | for j = 2:width-1 39 | for i = 2:height-1 40 | 41 | % page 6, estimating the laplacian of the flow velocities 42 | Vjbar = (Vj(i - 1, j) + Vj(i, j + 1) + Vj(i + 1,j) + Vj(i, j - 1))/6 +... 43 | (Vj(i - 1,j - 1) + Vj(i - 1,j + 1) + Vj(i + 1,j + 1) + Vj(i + 1,j - 1)) / 12; 44 | 45 | Vibar = (Vi(i - 1,j) + Vi(i,j + 1) + Vi(i + 1,j) + Vi(i, j - 1))/6+... 46 | (Vi(i - 1,j - 1) + Vi(i - 1,j + 1) + Vi(i + 1, j + 1) + Vi(i + 1, j - 1)) / 12; 47 | 48 | %page 12, iterative solution 49 | coef = (Ej(i,j,k) * Vjbar + Ei(i,j,k) * Vibar + Et(i,j,k)) ... 50 | /(alpha^2 + Ej(i,j,k)^2 + Ei(i,j,k)^2); 51 | %// update u and v 52 | Vj(i,j) = Vjbar - Ej(i,j,k) * coef; 53 | Vi(i,j) = Vibar - Ei(i,j,k) * coef; 54 | end 55 | end 56 | end 57 | 58 | end 59 | 60 | -------------------------------------------------------------------------------- /src/reinit.m: -------------------------------------------------------------------------------- 1 | % (C) Copyright Kirill Lykov 2013. 2 | % 3 | % Distributed under the FreeBSD Software License (See accompanying file license.txt) 4 | 5 | function [res, eps] = reinit(phi, dt, h) 6 | % Solve reinitialization equation for the Signed Diatance Function 7 | % phi_t + sign(phi)(1 - |grad(phi)|)=0, which gives SDF with property |grad(phi)|=1 8 | % Fast marching method from http://persson.berkeley.edu/pub/persson05levelset.pdf, formula (16) 9 | % forward/backward differences 10 | phi_x_b = (phi - backward_x(phi)) / h(1); 11 | phi_x_f = (forward_x(phi) - phi) / h(1); 12 | phi_y_b = (phi - backward_y(phi)) / h(2); 13 | phi_y_f = (forward_y(phi) - phi) / h(2); 14 | phi_z_b = (phi - backward_z(phi)) / h(3); 15 | phi_z_f = (forward_z(phi) - phi) / h(3); 16 | 17 | x_b_p = phi_x_b; x_b_n = phi_x_b; 18 | x_f_p = phi_x_f; x_f_n = phi_x_f; 19 | y_b_p = phi_y_b; y_b_n = phi_y_b; 20 | y_f_p = phi_y_f; y_f_n = phi_y_f; 21 | z_b_p = phi_z_b; z_b_n = phi_z_b; 22 | z_f_p = phi_z_f; z_f_n = phi_z_f; 23 | 24 | x_b_p(phi_x_b < 0) = 0; 25 | x_b_n(phi_x_b > 0) = 0; 26 | x_f_p(phi_x_f < 0) = 0; 27 | x_f_n(phi_x_f > 0) = 0; 28 | 29 | y_b_p(phi_y_b < 0) = 0; 30 | y_b_n(phi_y_b > 0) = 0; 31 | y_f_p(phi_y_f < 0) = 0; 32 | y_f_n(phi_y_f > 0) = 0; 33 | 34 | z_b_p(phi_z_f < 0) = 0; 35 | z_b_n(phi_z_f > 0) = 0; 36 | z_f_p(phi_z_f < 0) = 0; 37 | z_f_n(phi_z_f > 0) = 0; 38 | 39 | df = zeros(size(phi)); 40 | phi_neg_ind = find(phi < 0); 41 | phi_pos_ind = find(phi > 0); 42 | df(phi_pos_ind) = 1 - sqrt(max(y_b_p(phi_pos_ind).^2, y_f_n(phi_pos_ind).^2) ... 43 | + max(x_b_p(phi_pos_ind).^2, x_f_n(phi_pos_ind).^2) ... 44 | + max(z_b_p(phi_pos_ind).^2, z_f_n(phi_pos_ind).^2)); 45 | 46 | df(phi_neg_ind) = 1 - sqrt(max(y_b_n(phi_neg_ind).^2, y_f_p(phi_neg_ind).^2) ... 47 | + max(x_b_n(phi_neg_ind).^2, x_f_p(phi_neg_ind).^2) ... 48 | + max(z_b_n(phi_neg_ind).^2, z_f_p(phi_neg_ind).^2)); 49 | 50 | delta = dt .* sign(phi) .* df; 51 | 52 | res = phi + delta; 53 | eps = min(min(min(abs(df)))); % should be |grad(phi)| = 1 54 | end 55 | 56 | function [shift] = forward_x(M) 57 | shift = cat(1, M(2:end, :, :), M(end, :, :)); 58 | end 59 | 60 | function [shift] = backward_x(M) 61 | shift = cat(1, M(1, :, :), M(1:end-1, :, :)); 62 | end 63 | 64 | function [shift] = forward_y(M) 65 | shift = cat(2, M(:, 2:end, :), M(:, end, :)); 66 | end 67 | 68 | function [shift] = backward_y(M) 69 | shift = cat(2, M(:, 1, :), M(:, 1:end-1, :)); 70 | end 71 | 72 | function [shift] = forward_z(M) 73 | shift = cat(3, M(:, :, 2:end), M(:, :, end)); 74 | end 75 | 76 | function [shift] = backward_z(M) 77 | shift = cat(3, M(:, :, 1), M(:, :, 1:end-1)); 78 | end 79 | 80 | function [sgn] = sign(phi) 81 | sgn = phi ./ sqrt(phi.^2 + 1); 82 | end 83 | -------------------------------------------------------------------------------- /src/fastBilateralFilter.m: -------------------------------------------------------------------------------- 1 | % (C) Copyright Kirill Lykov 2013. 2 | % 3 | % Distributed under the FreeBSD Software License (See accompanying file license.txt) 4 | 5 | function output = fastBilateralFilter( image, sigmaSpatial, sigmaRange, ... 6 | samplingSpatial, samplingRange, regularMode, stencilSz ) 7 | % Implementation of the approach described in the paper by S. Paris and 8 | % Dorsey (A fast approximation of the bilateral filter using a signal processing approach '07) 9 | % I also used their presentation slides: http://cs.brown.edu/courses/cs129/lectures/bf_course_Brown_Oct2012.pdf 10 | if ~exist( 'regularMode', 'var' ) 11 | regularMode = 0; 12 | end; 13 | 14 | autoStencilSize = 1; 15 | if exist( 'stencilSz', 'var' ) 16 | autoStencilSize = 0; 17 | end; 18 | 19 | sizeImg = size(image); 20 | 21 | minImg = min( image(:) ); 22 | maxImg = max( image(:) ); 23 | 24 | spanImg = maxImg - minImg; 25 | 26 | derivedSigmaSpatial = sigmaSpatial / samplingSpatial; 27 | derivedSigmaRange = sigmaRange / samplingRange; 28 | 29 | % to avoid checking points on borders in gausian convolution, create a 0 band 30 | % width and height 'padding' around the grid 31 | paddingXY = floor(2 * derivedSigmaSpatial) + 1; 32 | paddingZ = floor(2 * derivedSigmaRange) + 1; 33 | if (autoStencilSize == 0) 34 | paddingXY = floor((stencilSz - 1)/2); 35 | paddingZ = floor((stencilSz - 1)/2); 36 | end; 37 | 38 | % 1. create grid 39 | downsampledWidth = floor((sizeImg(2) - 1) / samplingSpatial) + 1 + 2 * paddingXY; 40 | downsampledHeight = floor((sizeImg(1) - 1) / samplingSpatial) + 1 + 2 * paddingXY; 41 | downsampledDepth = floor(spanImg / samplingRange) + 1 + 2 * paddingZ; 42 | 43 | grid_wi = zeros(downsampledHeight, downsampledWidth, downsampledDepth); 44 | grid_w = zeros(downsampledHeight, downsampledWidth, downsampledDepth); 45 | 46 | % 2. downsampling 47 | for i = 1 : sizeImg(1) 48 | for j = 1 : sizeImg(2) 49 | valImg = image(i, j); 50 | 51 | if ~isnan( valImg ) % skip point in the 0 band 52 | x = round(i / samplingSpatial) + paddingXY + 1; 53 | y = round(j / samplingSpatial) + paddingXY + 1; 54 | ksi = round((valImg - minImg ) / samplingRange) + paddingZ + 1; 55 | end; 56 | 57 | grid_wi(x, y, ksi) = grid_wi(x, y, ksi) + valImg; 58 | grid_w(x, y, ksi) = grid_w(x, y, ksi) + 1; 59 | end; 60 | end; 61 | 62 | % create gaussian kernel 63 | kernelWidth = 2 * derivedSigmaSpatial + 1; 64 | kernelHeight = kernelWidth; 65 | kernelDepth = 2 * derivedSigmaRange + 1; 66 | if (autoStencilSize == 0) 67 | kernelWidth = stencilSz; 68 | kernelHeight = stencilSz; 69 | kernelDepth = stencilSz; 70 | end; 71 | 72 | if (regularMode == 0) 73 | % convolution with normal gaussian kernel 74 | grid_wi_b = gausConv3D(kernelWidth, kernelHeight, kernelDepth, ... 75 | derivedSigmaSpatial, derivedSigmaRange, grid_wi); 76 | grid_w_b = gausConv3D(kernelWidth, kernelHeight, kernelDepth, ... 77 | derivedSigmaSpatial, derivedSigmaRange, grid_w); 78 | else 79 | % using notation from 80 | % http://www.cs.technion.ac.il/~elad/publications/conferences/2005/24_ScaleSpace_Retinex_Bilateral.pdf 81 | % formula 14 82 | % TODO Instead of regular convolution use a custom which takes only 83 | % pixels s_ij > s_center 84 | %nlfilter doesn't work for 3D ;-( 85 | %func = @(x) fastBilateralCore(kernel, x); 86 | %grid_wi_b = nlfilter(grid_wi, [stencilSz stencilSz stencilSz], func); 87 | %grid_w_b = nlfilter(grid_w, [stencilSz stencilSz stencilSz], func); 88 | end; 89 | 90 | % 3. divide wi_b / w_b 91 | normalizedBlurredGrid = zeros(downsampledHeight, downsampledWidth, downsampledDepth); 92 | for i = 1 : downsampledHeight 93 | for j = 1 : downsampledWidth 94 | for k = 1 : downsampledDepth 95 | if grid_w_b(i, j, k) ~= 0 96 | normalizedBlurredGrid(i, j, k) = grid_wi_b(i, j, k) / grid_w_b(i, j, k); 97 | else 98 | normalizedBlurredGrid(i, j, k) = 0; 99 | end 100 | end; 101 | end; 102 | end; 103 | 104 | % 4. upsample 105 | [ jj, ii ] = meshgrid( 0 : sizeImg(2) - 1, 0 : sizeImg(1) - 1 ); 106 | 107 | di = (ii / samplingSpatial) + paddingXY + 1; 108 | dj = (jj / samplingSpatial) + paddingXY + 1; 109 | dz = (image - minImg) / samplingRange + paddingZ + 1; 110 | 111 | output = interpn(normalizedBlurredGrid, di, dj, dz); 112 | end 113 | 114 | function res = gausConv3D(kernelWidth, kernelHeight, kernelDepth, ... 115 | derivedSigmaSpatial, derivedSigmaRange, grid) 116 | kernelX = fspecial('Gaussian', [kernelWidth 1], derivedSigmaSpatial); 117 | kernelY = fspecial('Gaussian', [1 kernelHeight], derivedSigmaSpatial); 118 | kernelZ = fspecial('Gaussian', [kernelDepth 1], derivedSigmaRange); 119 | 120 | k = zeros([1, 1, kernelDepth]); 121 | k(:) = kernelZ; 122 | 123 | data2 = grid; 124 | data2 = convn(data2, kernelX, 'same'); 125 | data2 = convn(data2, kernelY, 'same'); 126 | data2 = convn(data2, k, 'same'); 127 | res = data2; 128 | end 129 | -------------------------------------------------------------------------------- /src/seamCarving.m: -------------------------------------------------------------------------------- 1 | % (C) Copyright Kirill Lykov 2013. 2 | % 3 | % Distributed under the FreeBSD Software License (See accompanying file license.txt) 4 | 5 | function image = seamCarving(newSize, image) 6 | % apply seam carving to the image 7 | % following paper by Avidan and Shamir '07 8 | sizeReductionX = size(image, 1) - newSize(1); 9 | sizeReductionY = size(image, 2) - newSize(2); 10 | 11 | mmax = @(left, right) max([left right]); 12 | 13 | image = seamCarvingReduce([mmax(0, sizeReductionX), mmax(0, sizeReductionY)], image); 14 | 15 | image = seamCarvingEnlarge([mmax(0, -sizeReductionX), mmax(0, -sizeReductionY)], image); 16 | end 17 | 18 | function image = seamCarvingReduce(sizeReduction, image) 19 | if (sizeReduction == 0) 20 | return; 21 | end; 22 | [T, transBitMask] = findTransportMatrix(sizeReduction, image); 23 | image = addOrDeleteSeams(transBitMask, sizeReduction, image, @reduceImageByMask); 24 | end 25 | 26 | % TODO Bug: enlarge gives artifacts althout I chouse different seams as described 27 | % in 4.3 in the paper 28 | function image = seamCarvingEnlarge(sizeEnlarge, image) 29 | if (sizeEnlarge == 0) 30 | return; 31 | end; 32 | [T, transBitMask] = findTransportMatrix(sizeEnlarge, image); 33 | image = addOrDeleteSeams(transBitMask, sizeEnlarge, image, @enlargeImageByMask); 34 | end 35 | 36 | function [T, transBitMask] = findTransportMatrix(sizeReduction, image) 37 | % find optimal order of removing raws and columns 38 | 39 | T = zeros(sizeReduction(1) + 1, sizeReduction(2) + 1, 'double'); 40 | transBitMask = ones(size(T)) * -1; 41 | 42 | % fill in borders 43 | imageNoRow = image; 44 | for i = 2 : size(T, 1) 45 | energy = energyRGB(imageNoRow); 46 | [optSeamMask, seamEnergyRow] = findOptSeam(energy'); 47 | imageNoRow = reduceImageByMask(imageNoRow, optSeamMask, 0); 48 | transBitMask(i, 1) = 0; 49 | 50 | T(i, 1) = T(i - 1, 1) + seamEnergyRow; 51 | end; 52 | 53 | imageNoColumn = image; 54 | for j = 2 : size(T, 2) 55 | energy = energyRGB(imageNoColumn); 56 | [optSeamMask, seamEnergyColumn] = findOptSeam(energy); 57 | imageNoColumn = reduceImageByMask(imageNoColumn, optSeamMask, 1); 58 | transBitMask(1, j) = 1; 59 | 60 | T(1, j) = T(1, j - 1) + seamEnergyColumn; 61 | end; 62 | 63 | % on the borders, just remove one column and one row before proceeding 64 | energy = energyRGB(image); 65 | [optSeamMask, seamEnergyRow] = findOptSeam(energy'); 66 | image = reduceImageByMask(image, optSeamMask, 0); 67 | 68 | energy = energyRGB(image); 69 | [optSeamMask, seamEnergyColumn] = findOptSeam(energy); 70 | image = reduceImageByMask(image, optSeamMask, 1); 71 | 72 | % fill in internal part 73 | for i = 2 : size(T, 1) 74 | 75 | imageWithoutRow = image; % copy for deleting columns 76 | 77 | for j = 2 : size(T, 2) 78 | energy = energyRGB(imageWithoutRow); 79 | 80 | [optSeamMaskRow, seamEnergyRow] = findOptSeam(energy'); 81 | imageNoRow = reduceImageByMask(imageWithoutRow, optSeamMaskRow, 0); 82 | 83 | [optSeamMaskColumn, seamEnergyColumn] = findOptSeam(energy); 84 | imageNoColumn = reduceImageByMask(imageWithoutRow, optSeamMaskColumn, 1); 85 | 86 | neighbors = [(T(i - 1, j) + seamEnergyRow) (T(i, j - 1) + seamEnergyColumn)]; 87 | [val, ind] = min(neighbors); 88 | 89 | T(i, j) = val; 90 | transBitMask(i, j) = ind - 1; 91 | 92 | % move from left to right 93 | imageWithoutRow = imageNoColumn; 94 | end; 95 | 96 | energy = energyRGB(image); 97 | [optSeamMaskRow, seamEnergyRow] = findOptSeam(energy'); 98 | % move from top to bottom 99 | image = reduceImageByMask(image, optSeamMaskRow, 0); 100 | end; 101 | 102 | end 103 | 104 | function image = addOrDeleteSeams(transBitMask, sizeReduction, image, operation) 105 | % delete seams following optimal way 106 | i = size(transBitMask, 1); 107 | j = size(transBitMask, 2); 108 | 109 | for it = 1 : (sizeReduction(1) + sizeReduction(2)) 110 | 111 | energy = energyRGB(image); 112 | if (transBitMask(i, j) == 0) 113 | [optSeamMask, seamEnergyRaw] = findOptSeam(energy'); 114 | image = operation(image, optSeamMask, 0); 115 | i = i - 1; 116 | else 117 | [optSeamMask, seamEnergyColumn] = findOptSeam(energy); 118 | image = operation(image, optSeamMask, 1); 119 | j = j - 1; 120 | end; 121 | 122 | end; 123 | end 124 | 125 | function [optSeamMask, seamEnergy] = findOptSeam(energy) 126 | % following paper by Avidan and Shamir `07 127 | % finds optimal seam 128 | % returns mask with 0 mean a pixel is in the seam 129 | 130 | % find M for vertical seams 131 | % for vertical - use I` 132 | M = padarray(energy, [0 1], realmax('double')); % to avoid handling border elements 133 | 134 | sz = size(M); 135 | for i = 2 : sz(1) 136 | for j = 2 : (sz(2) - 1) 137 | neighbors = [M(i - 1, j - 1) M(i - 1, j) M(i - 1, j + 1)]; 138 | M(i, j) = M(i, j) + min(neighbors); 139 | end 140 | end 141 | 142 | % find the min element in the last raw 143 | [val, indJ] = min(M(sz(1), :)); 144 | seamEnergy = val; 145 | 146 | %optSeam = zeros(sz(1), 1, 'int32'); 147 | optSeamMask = zeros(size(energy), 'uint8'); 148 | 149 | %indJ = indJ - 1; % because of padding on 1 element from left 150 | 151 | %go backward and save (i, j) 152 | for i = sz(1) : -1 : 2 153 | %optSeam(i) = indJ - 1; 154 | optSeamMask(i, indJ - 1) = 1; % -1 because of padding on 1 element from left 155 | neighbors = [M(i - 1, indJ - 1) M(i - 1, indJ) M(i - 1, indJ + 1)]; 156 | [val, indIncr] = min(neighbors); 157 | 158 | seamEnergy = seamEnergy + val; 159 | 160 | indJ = indJ + (indIncr - 2); % (x - 2): [1,2]->[-1,1]] 161 | end 162 | %optSeam(1) = indJ; % to avoid if in loop becuase matlab is slow as hell 163 | 164 | optSeamMask(1, indJ - 1) = 1; % -1 because of padding on 1 element from left 165 | optSeamMask = ~optSeamMask; 166 | 167 | end 168 | 169 | function imageReduced = reduceImageByMask( image, seamMask, isVerical ) 170 | % removes pixels by input mask 171 | % removes vertical line if isVerical == 1, otherwise horizontal 172 | if (isVerical) 173 | imageReduced = reduceImageByMaskVertical(image, seamMask); 174 | else 175 | imageReduced = reduceImageByMaskHorizontal(image, seamMask'); 176 | end; 177 | end 178 | 179 | % could not find a more elegant way to do it 180 | function imageReduced = reduceImageByMaskVertical(image, seamMask) 181 | imageReduced = zeros(size(image, 1), size(image, 2) - 1, size(image, 3)); 182 | for i = 1 : size(seamMask, 1) 183 | imageReduced(i, :, 1) = image(i, seamMask(i, :), 1); 184 | imageReduced(i, :, 2) = image(i, seamMask(i, :), 2); 185 | imageReduced(i, :, 3) = image(i, seamMask(i, :), 3); 186 | end 187 | end 188 | 189 | function imageReduced = reduceImageByMaskHorizontal(image, seamMask) 190 | imageReduced = zeros(size(image, 1) - 1, size(image, 2), size(image, 3)); 191 | for j = 1 : size(seamMask, 2) 192 | imageReduced(:, j, 1) = image(seamMask(:, j), j, 1); 193 | imageReduced(:, j, 2) = image(seamMask(:, j), j, 2); 194 | imageReduced(:, j, 3) = image(seamMask(:, j), j, 3); 195 | end 196 | end 197 | 198 | function imageEnlarged = enlargeImageByMask(image, seamMask, isVerical) 199 | % removes pixels by input mask 200 | % removes vertical line if isVerical == 1, otherwise horizontal 201 | if (isVerical) 202 | imageEnlarged = enlargeImageByMaskVertical(image, seamMask); 203 | else 204 | imageEnlarged = enlargeImageByMaskHorizontal(image, seamMask'); 205 | end; 206 | end 207 | 208 | function imageEnlarged = enlargeImageByMaskVertical(image, seamMask) 209 | 210 | avg = @(image, i, j, k) (image(i, j-1, k) + image(i, j+1, k))/2; 211 | 212 | imageEnlarged = zeros(size(image, 1), size(image, 2) + 1, size(image, 3)); 213 | for i = 1 : size(seamMask, 1) 214 | j = find(seamMask(i, :) ~= 1); 215 | imageEnlarged(i, :, 1) = [image(i, 1:j, 1), avg(image, i, j, 1), image(i, j+1:end, 1)]; 216 | imageEnlarged(i, :, 2) = [image(i, 1:j, 2), avg(image, i, j, 2), image(i, j+1:end, 2)]; 217 | imageEnlarged(i, :, 3) = [image(i, 1:j, 3), avg(image, i, j, 3), image(i, j+1:end, 3)]; 218 | end; 219 | end 220 | 221 | function imageEnlarged = enlargeImageByMaskHorizontal(image, seamMask) 222 | 223 | avg = @(image, i, j, k) (image(i-1, j, k) + image(i+1, j, k))/2; 224 | 225 | imageEnlarged = zeros(size(image, 1) + 1, size(image, 2), size(image, 3)); 226 | for j = 1 : size(seamMask, 2) 227 | i = find(seamMask(:, j) ~= 1); 228 | imageEnlarged(:, j, 1) = [image(1:i, j, 1); avg(image, i, j, 1); image(i+1:end, j, 1)]; 229 | imageEnlarged(:, j, 2) = [image(1:i, j, 2); avg(image, i, j, 2); image(i+1:end, j, 2)]; 230 | imageEnlarged(:, j, 3) = [image(1:i, j, 3); avg(image, i, j, 3); image(i+1:end, j, 3)]; 231 | end; 232 | end 233 | 234 | function res = energyRGB(I) 235 | % returns energy of all pixelels 236 | % e = |dI/dx| + |dI/dy| 237 | res = energyGrey(I(:, :, 1)) + energyGrey(I(:, :, 2)) + energyGrey(I(:, :, 3)); 238 | end 239 | 240 | function res = energyGrey(I) 241 | % returns energy of all pixelels 242 | % e = |dI/dx| + |dI/dy| 243 | res = abs(imfilter(I, [-1,0,1], 'replicate')) + abs(imfilter(I, [-1;0;1], 'replicate')); 244 | end 245 | --------------------------------------------------------------------------------