From 1339d2dc5655536aed9c5ec802d2d7480ea4ad3f Mon Sep 17 00:00:00 2001 From: anapt Date: Thu, 18 Jan 2018 20:40:16 +0200 Subject: [PATCH] MATLAB code --- code/demo_meanshift.m | 93 +++++ code/meanshift.m | 223 +++++++++++ code/meanshift.m~ | 225 +++++++++++ code/meanshift.prj | 874 ++++++++++++++++++++++++++++++++++++++++++ code/r15.mat | Bin 0 -> 4451 bytes 5 files changed, 1415 insertions(+) create mode 100755 code/demo_meanshift.m create mode 100755 code/meanshift.m create mode 100644 code/meanshift.m~ create mode 100644 code/meanshift.prj create mode 100755 code/r15.mat diff --git a/code/demo_meanshift.m b/code/demo_meanshift.m new file mode 100755 index 0000000..aa74509 --- /dev/null +++ b/code/demo_meanshift.m @@ -0,0 +1,93 @@ +% +% SCRIPT: DEMO_MEANSHIFT +% +% Sample script on usage of mean-shift function. +% +% DEPENDENCIES +% +% meanshift +% +% + + +%% CLEAN-UP + +clear; +close all; + + +%% PARAMETERS + +% dataset options +basepath = './code/'; +filename = 'r15'; +varX = 'X'; +varL = 'L'; + +% mean shift options +h = 1; +optMeanShift.epsilon = 1e-4*h; +optMeanShift.verbose = true; +optMeanShift.display = true; + + +%% (BEGIN) + +fprintf('\n *** begin %s ***\n\n',mfilename); + + +%% READ DATA + +fprintf('...reading data...\n') + +matFile = [basepath filesep filename '.mat']; + +fprintf(' - file: %s...\n', matFile) + +ioData = matfile( matFile ); + +x = ioData.(varX); +l = ioData.(varL); + +figure('name', 'original_data') +scatter(x(:,1),x(:,2), 8, l); + + +%% PERFORM MEAN SHIFT + +fprintf('...computing mean shift...') + +tic; +y = meanshift( x, h, optMeanShift ); +tElapsed = toc; + +fprintf('DONE in %.2f sec\n', tElapsed); + + +%% SHOW FINAL POSITIONS + +figure('name', 'final_local_maxima_points') +scatter(y(:,1),y(:,2), 8, l); + + +%% (END) + +fprintf('\n *** end %s ***\n\n',mfilename); + + +%%------------------------------------------------------------ +% +% AUTHORS +% +% Dimitris Floros fcdimitr@auth.gr +% +% VERSION +% +% 0.1 - December 29, 2017 +% +% CHANGELOG +% +% 0.1 (Dec 29, 2017) - Dimitris +% * initial implementation +% +% ------------------------------------------------------------ diff --git a/code/meanshift.m b/code/meanshift.m new file mode 100755 index 0000000..bfe71ed --- /dev/null +++ b/code/meanshift.m @@ -0,0 +1,223 @@ +function y = meanshift(x, h, varargin) +% MEANSHIFT - Mean shift implementation +% +% SYNTAX +% +% YOUT = MEANSHIFT( XIN, BAND ) +% YOUT = MEANSHIFT( ..., 'epsilon', EPSILON ) +% YOUT = MEANSHIFT( ..., 'verbose', VERBOSE ) +% YOUT = MEANSHIFT( ..., 'display', DISPLAY ) +% +% INPUT +% +% XIN Input data (for clustering) [n-by-d] +% BAND Bandwidth value [scalar] +% +% OPTIONAL +% +% EPSILON Threshold for convergence [scalar] +% {default: 1e-4*h} +% VERBOSE Print iteration number & error? [boolean] +% {default: false} +% DISPLAY Plot results of each iteration? [boolean] +% (only for 2D points) +% {default: false} +% +% OUTPUT +% +% YOUT Final points location after mean shift [n-by-d] +% +% DESCRIPTION +% +% YOUT = MEANSHIFT(XIN,BAND) implements mean shift algorithm on +% input points XIN, using Gaussian kernel with bandwidth BAND. +% The local maxima of each point is then recorded in the output +% array YOUT. +% +% DEPENDENCIES +% +% +% +% LOCAL-FUNCTIONS +% +% rangesearch2sparse +% parseOptArgs +% +% See also kmeans +% + + %% PARAMETERS + + % stoping threshold + opt.epsilon = 1e-4*h; + opt.verbose = false; + opt.display = false; + + + %% PARSE OPTIONAL INPUTS + + opt = parseOptArgs(opt, varargin{:}); + + + %% INITIALIZATION + + % number of points -- dimensionality + [n, d] = size( x ); + + % initialize output points to input points + y = x; + + % mean shift vectors (initialize to infinite) + m = inf; + + % iteration counter + iter = 0; + + if opt.display && d == 2 + fig = figure(1337); + set(fig, 'name', 'real_time_quiver') + end + + norm(x) + while norm(m) > opt.epsilon % --- iterate unitl convergence + + iter = iter + 1; + + % find pairwise distance matrix (inside radius) + [I, D] = rangesearch( x, y, h ); + D = cellfun( @(x) x.^2, D, 'UniformOutput', false ); + W = rangesearch2sparse( I, D ); + + + % compute kernel matrix + W = spfun( @(x) exp( -x / (2*h^2) ), W ); + + % make sure diagonal elements are 1 + W = W + spdiags( ones(n,1), 0, n, n ); + + % compute new y vector + y_new = W * x; + % normalize vector + + l = [sum(W, 2) sum(W, 2)]; + y_new = y_new ./ l; + + + % calculate mean-shift vector + m = y_new - y; + + if opt.display && d == 2 + + figure(1337) + clf + hold on + scatter( y(:,1), y(:,2) ); + quiver( y(:,1), y(:,2), m(:,1), m(:,2), 0 ); + pause(0.3) + + end + + % update y + y = y_new; + + if opt.verbose + fprintf( ' Iteration %d - error %.2g\n', iter, norm(m) ); + end + + end % while (m > epsilon) + + +end + + + +%% LOCAL FUNCTION: CREATE SPARSE MATRIX FROM RANGE SEARCH + +function mat = rangesearch2sparse(idxCol, dist) +% INPUT idxCol Index columns for matrix [n-cell] +% dist Distances of points [n-cell] +% OUTPUT mat Sparse matrix with distances [n-by-n sparse] + + % number of neighbors for each point + nNbr = cellfun( @(x) numel(x), idxCol ); + + % number of points + n = numel( idxCol ); + + % row indices (for sparse matrix formation convenience) + idxRow = arrayfun( @(n,i) i * ones( 1, n ), nNbr, (1:n)', ... + 'UniformOutput', false ); + + % sparse matrix formation + mat = sparse( [idxRow{:}], [idxCol{:}], [dist{:}], n, n ); + + +end + + + +%% LOCAL FUNCTION: PARSE OPTIONAL ARGUMENTS + +function opt = parseOptArgs (dflt, varargin) +% INPUT dflt Struct with default parameters [struct] +% [varargin] +% OUTPUT opt Updated parameters [struct] + + %% INITIALIZATION + + ip = inputParser; + + ip.CaseSensitive = false; + ip.KeepUnmatched = false; + ip.PartialMatching = true; + ip.StructExpand = true; + + + %% PARAMETERS + + argNames = fieldnames( dflt ); + for i = 1 : length(argNames) + addParameter( ip, argNames{i}, dflt.(argNames{i}) ); + end + + + %% PARSE AND RETURN + + parse( ip, varargin{:} ); + + opt = ip.Results; + + + %% SET EMPTY VALUES TO DEFAULTS + + for i = 1 : length(argNames) + if isempty( opt.(argNames{i}) ) + opt.(argNames{i}) = dflt.(argNames{i}); + end + end + +end + + + +%%------------------------------------------------------------ +% +% AUTHORS +% +% Dimitris Floros fcdimitr@auth.gr +% +% VERSION +% +% 0.2 - January 04, 2018 +% +% CHANGELOG +% +% 0.2 (Jan 04, 2018) - Dimitris +% * FIX: distance should be squared euclidean +% * FIX: range search radius should be bandwidth +% +% 0.1 (Dec 29, 2017) - Dimitris +% * initial implementation +% +% ------------------------------------------------------------ + diff --git a/code/meanshift.m~ b/code/meanshift.m~ new file mode 100644 index 0000000..771261e --- /dev/null +++ b/code/meanshift.m~ @@ -0,0 +1,225 @@ +function y = meanshift(x, h, varargin) +% MEANSHIFT - Mean shift implementation +% +% SYNTAX +% +% YOUT = MEANSHIFT( XIN, BAND ) +% YOUT = MEANSHIFT( ..., 'epsilon', EPSILON ) +% YOUT = MEANSHIFT( ..., 'verbose', VERBOSE ) +% YOUT = MEANSHIFT( ..., 'display', DISPLAY ) +% +% INPUT +% +% XIN Input data (for clustering) [n-by-d] +% BAND Bandwidth value [scalar] +% +% OPTIONAL +% +% EPSILON Threshold for convergence [scalar] +% {default: 1e-4*h} +% VERBOSE Print iteration number & error? [boolean] +% {default: false} +% DISPLAY Plot results of each iteration? [boolean] +% (only for 2D points) +% {default: false} +% +% OUTPUT +% +% YOUT Final points location after mean shift [n-by-d] +% +% DESCRIPTION +% +% YOUT = MEANSHIFT(XIN,BAND) implements mean shift algorithm on +% input points XIN, using Gaussian kernel with bandwidth BAND. +% The local maxima of each point is then recorded in the output +% array YOUT. +% +% DEPENDENCIES +% +% +% +% LOCAL-FUNCTIONS +% +% rangesearch2sparse +% parseOptArgs +% +% See also kmeans +% + + %% PARAMETERS + + % stoping threshold + opt.epsilon = 1e-4*h; + opt.verbose = false; + opt.display = false; + + + %% PARSE OPTIONAL INPUTS + + opt = parseOptArgs(opt, varargin{:}); + + + %% INITIALIZATION + + % number of points -- dimensionality + [n, d] = size( x ); + + % initialize output points to input points + y = x; + + % mean shift vectors (initialize to infinite) + m = inf; + + % iteration counter + iter = 0; + + if opt.display && d == 2 + fig = figure(1337); + set(fig, 'name', 'real_time_quiver') + end + + norm(x) + while norm(m) > opt.epsilon % --- iterate unitl convergence + + iter = iter + 1; + + % find pairwise distance matrix (inside radius) + [I, D] = rangesearch( x, y, h ); + D = cellfun( @(x) x.^2, D, 'UniformOutput', false ); + W = rangesearch2sparse( I, D ); + + + % compute kernel matrix + W = spfun( @(x) exp( -x / (2*h^2) ), W ); + + % make sure diagonal elements are 1 + W = W + spdiags( ones(n,1), 0, n, n ); + + % compute new y vector + y_new = W * x; + + % normalize vector + + l = [sum(W, 2) sum(W, 2)]; + + y_new = y_new ./ l; + + + % calculate mean-shift vector + m = y_new - y; + + if opt.display && d == 2 + + figure(1337) + clf + hold on + scatter( y(:,1), y(:,2) ); + quiver( y(:,1), y(:,2), m(:,1), m(:,2), 0 ); + pause(0.3) + + end + + % update y + y = y_new; + + if opt.verbose + fprintf( ' Iteration %d - error %.2g\n', iter, norm(m) ); + end + + end % while (m > epsilon) + + +end + + + +%% LOCAL FUNCTION: CREATE SPARSE MATRIX FROM RANGE SEARCH + +function mat = rangesearch2sparse(idxCol, dist) +% INPUT idxCol Index columns for matrix [n-cell] +% dist Distances of points [n-cell] +% OUTPUT mat Sparse matrix with distances [n-by-n sparse] + + % number of neighbors for each point + nNbr = cellfun( @(x) numel(x), idxCol ); + + % number of points + n = numel( idxCol ); + + % row indices (for sparse matrix formation convenience) + idxRow = arrayfun( @(n,i) i * ones( 1, n ), nNbr, (1:n)', ... + 'UniformOutput', false ); + + % sparse matrix formation + mat = sparse( [idxRow{:}], [idxCol{:}], [dist{:}], n, n ); + + +end + + + +%% LOCAL FUNCTION: PARSE OPTIONAL ARGUMENTS + +function opt = parseOptArgs (dflt, varargin) +% INPUT dflt Struct with default parameters [struct] +% [varargin] +% OUTPUT opt Updated parameters [struct] + + %% INITIALIZATION + + ip = inputParser; + + ip.CaseSensitive = false; + ip.KeepUnmatched = false; + ip.PartialMatching = true; + ip.StructExpand = true; + + + %% PARAMETERS + + argNames = fieldnames( dflt ); + for i = 1 : length(argNames) + addParameter( ip, argNames{i}, dflt.(argNames{i}) ); + end + + + %% PARSE AND RETURN + + parse( ip, varargin{:} ); + + opt = ip.Results; + + + %% SET EMPTY VALUES TO DEFAULTS + + for i = 1 : length(argNames) + if isempty( opt.(argNames{i}) ) + opt.(argNames{i}) = dflt.(argNames{i}); + end + end + +end + + + +%%------------------------------------------------------------ +% +% AUTHORS +% +% Dimitris Floros fcdimitr@auth.gr +% +% VERSION +% +% 0.2 - January 04, 2018 +% +% CHANGELOG +% +% 0.2 (Jan 04, 2018) - Dimitris +% * FIX: distance should be squared euclidean +% * FIX: range search radius should be bandwidth +% +% 0.1 (Dec 29, 2017) - Dimitris +% * initial implementation +% +% ------------------------------------------------------------ + diff --git a/code/meanshift.prj b/code/meanshift.prj new file mode 100644 index 0000000..fc5f99b --- /dev/null +++ b/code/meanshift.prj @@ -0,0 +1,874 @@ + + + + + + + + option.BuildFolder.Project + + + + + + + + + + + + + + + + + + + + + + + option.VerificationStatus.Inactive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + option.BuildFolder.Project + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + option.VerificationStatus.Inactive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + option.BuildFolder.Project + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + option.objective.c + reviewIssues + + + option.UseGlobals.No + ${PROJECT_ROOT}/codegen/lib/meanshift/meanshift.a + + + + + + + + + + + + + + false + false + + true + + meanshift_mex + meanshift + option.target.artifact.lib + ${PROJECT_ROOT}/codegen/lib/meanshift/meanshift.a + + false + + option.FixedPointMode.None + + + + + + + + + + + + + + + + + + + + + + + + + _fixpt + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /home/anapt/Documents/Parallel/mean-shift/code/codegen/lib/meanshift/meanshift.a + + + + /usr/local/MATLAB/MATLAB_Production_Server/R2015a + + + + + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + false + false + false + false + false + true + false + 4.13.0-26-generic + false + true + glnxa64 + true + + + \ No newline at end of file diff --git a/code/r15.mat b/code/r15.mat new file mode 100755 index 0000000000000000000000000000000000000000..7b69e9a96f2cb5d19a18dc8ba8be8fe7e22822d6 GIT binary patch literal 4451 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 literal 0 HcmV?d00001