function phow_caltech101()
% PHOW_CALTECH101 Image classification in the Caltech-101 dataset
% This program demonstrates how to use VLFeat to construct an image
% classifier on the Caltech-101 data. The classifier uses PHOW
% features (dense SIFT), spatial histograms of visual words, and a
% Chi2 SVM. To speedup computation it uses VLFeat fast dense SIFT,
% kd-trees, and homogeneous kernel map. The program also
% demonstrates VLFeat PEGASOS SVM solver, although for this small
% dataset other solvers such as LIBLINEAR can be more efficient.
%
% By default 15 training images are used, which should result in
% about 64% performance (a good performance considering that only a
% single feature type is being used).
%
% Call PHOW_CALTECH101 to train and test a classifier on a small
% subset of the Caltech-101 data. Note that the program
% automatically downloads a copy of the Caltech-101 data from the
% Internet if it cannot find a local copy.
%
% Edit the PHOW_CALTECH101 file to change the program configuration.
%
% To run on the entire dataset change CONF.TINYPROBLEM to FALSE.
%
% The Caltech-101 data is saved into CONF.CALDIR, which defaults to
% 'data/caltech-101'. Change this path to the desired location, for
% instance to point to an existing copy of the Caltech-101 data.
%
% The program can also be used to train a model on custom data by
% pointing CONF.CALDIR to it. Just create a subdirectory for each
% class and put the training images there. Make sure to adjust
% CONF.NUMTRAIN accordingly.
%
% Intermediate files are stored in the directory CONF.DATADIR. All
% such files begin with the prefix CONF.PREFIX, which can be changed
% to test different parameter settings without overriding previous
% results.
%
% The program saves the trained model in
% <CONF.DATADIR>/<CONF.PREFIX>-model.mat. This model can be used to
% test novel images independently of the Caltech data.
%
% load('data/baseline-model.mat') ; # change to the model path
% label = model.classify(model, im) ;
%
% Author: Andrea Vedaldi
% Copyright (C) 2011-2013 Andrea Vedaldi
% All rights reserved.
%
% This file is part of the VLFeat library and is made available under
% the terms of the BSD license (see the COPYING file).
conf.calDir = 'data/caltech-101' ;
conf.dataDir = 'data/' ;
conf.autoDownloadData = true ;
conf.numTrain = 15 ;
conf.numTest = 15 ;
conf.numClasses = 102 ;
conf.numWords = 600 ;
conf.numSpatialX = [2 4] ;
conf.numSpatialY = [2 4] ;
conf.quantizer = 'kdtree' ;
conf.svm.C = 10 ;
conf.svm.solver = 'sdca' ;
%conf.svm.solver = 'sgd' ;
%conf.svm.solver = 'liblinear' ;
conf.svm.biasMultiplier = 1 ;
conf.phowOpts = {'Step', 3} ;
conf.clobber = false ;
conf.tinyProblem = true ;
conf.prefix = 'baseline' ;
conf.randSeed = 1 ;
if conf.tinyProblem
conf.prefix = 'tiny' ;
conf.numClasses = 5 ;
conf.numSpatialX = 2 ;
conf.numSpatialY = 2 ;
conf.numWords = 300 ;
conf.phowOpts = {'Verbose', 2, 'Sizes', 7, 'Step', 5} ;
end
conf.vocabPath = fullfile(conf.dataDir, [conf.prefix '-vocab.mat']) ;
conf.histPath = fullfile(conf.dataDir, [conf.prefix '-hists.mat']) ;
conf.modelPath = fullfile(conf.dataDir, [conf.prefix '-model.mat']) ;
conf.resultPath = fullfile(conf.dataDir, [conf.prefix '-result']) ;
randn('state',conf.randSeed) ;
rand('state',conf.randSeed) ;
vl_twister('state',conf.randSeed) ;
% --------------------------------------------------------------------
% Download Caltech-101 data
% --------------------------------------------------------------------
if ~exist(conf.calDir, 'dir') || ...
(~exist(fullfile(conf.calDir, 'airplanes'),'dir') && ...
~exist(fullfile(conf.calDir, '101_ObjectCategories', 'airplanes')))
if ~conf.autoDownloadData
error(...
['Caltech-101 data not found. ' ...
'Set conf.autoDownloadData=true to download the required data.']) ;
end
vl_xmkdir(conf.calDir) ;
calUrl = ['http://www.vision.caltech.edu/Image_Datasets/' ...
'Caltech101/101_ObjectCategories.tar.gz'] ;
fprintf('Downloading Caltech-101 data to ''%s''. This will take a while.', conf.calDir) ;
untar(calUrl, conf.calDir) ;
end
if ~exist(fullfile(conf.calDir, 'airplanes'),'dir')
conf.calDir = fullfile(conf.calDir, '101_ObjectCategories') ;
end
% --------------------------------------------------------------------
% Setup data
% --------------------------------------------------------------------
classes = dir(conf.calDir) ;
classes = classes([classes.isdir]) ;
classes = {classes(3:conf.numClasses+2).name} ;
images = {} ;
imageClass = {} ;
for ci = 1:length(classes)
ims = dir(fullfile(conf.calDir, classes{ci}, '*.jpg'))' ;
ims = vl_colsubset(ims, conf.numTrain + conf.numTest) ;
ims = cellfun(@(x)fullfile(classes{ci},x),{ims.name},'UniformOutput',false) ;
images = {images{:}, ims{:}} ;
imageClass{end+1} = ci * ones(1,length(ims)) ;
end
selTrain = find(mod(0:length(images)-1, conf.numTrain+conf.numTest) < conf.numTrain) ;
selTest = setdiff(1:length(images), selTrain) ;
imageClass = cat(2, imageClass{:}) ;
model.classes = classes ;
model.phowOpts = conf.phowOpts ;
model.numSpatialX = conf.numSpatialX ;
model.numSpatialY = conf.numSpatialY ;
model.quantizer = conf.quantizer ;
model.vocab = [] ;
model.w = [] ;
model.b = [] ;
model.classify = @classify ;
% --------------------------------------------------------------------
% Train vocabulary
% --------------------------------------------------------------------
if ~exist(conf.vocabPath) || conf.clobber
% Get some PHOW descriptors to train the dictionary
selTrainFeats = vl_colsubset(selTrain, 30) ;
descrs = {} ;
%for ii = 1:length(selTrainFeats)
parfor ii = 1:length(selTrainFeats)
im = imread(fullfile(conf.calDir, images{selTrainFeats(ii)})) ;
im = standarizeImage(im) ;
[drop, descrs{ii}] = vl_phow(im, model.phowOpts{:}) ;
end
descrs = vl_colsubset(cat(2, descrs{:}), 10e4) ;
descrs = single(descrs) ;
% Quantize the descriptors to get the visual words
vocab = vl_kmeans(descrs, conf.numWords, 'verbose', 'algorithm', 'elkan', 'MaxNumIterations', 50) ;
save(conf.vocabPath, 'vocab') ;
else
load(conf.vocabPath) ;
end
model.vocab = vocab ;
if strcmp(model.quantizer, 'kdtree')
model.kdtree = vl_kdtreebuild(vocab) ;
end
% --------------------------------------------------------------------
% Compute spatial histograms
% --------------------------------------------------------------------
if ~exist(conf.histPath) || conf.clobber
hists = {} ;
parfor ii = 1:length(images)
% for ii = 1:length(images)
fprintf('Processing %s (%.2f %%)\n', images{ii}, 100 * ii / length(images)) ;
im = imread(fullfile(conf.calDir, images{ii})) ;
hists{ii} = getImageDescriptor(model, im);
end
hists = cat(2, hists{:}) ;
save(conf.histPath, 'hists') ;
else
load(conf.histPath) ;
end
% --------------------------------------------------------------------
% Compute feature map
% --------------------------------------------------------------------
psix = vl_homkermap(hists, 1, 'kchi2', 'gamma', .5) ;
% --------------------------------------------------------------------
% Train SVM
% --------------------------------------------------------------------
if ~exist(conf.modelPath) || conf.clobber
switch conf.svm.solver
case {'sgd', 'sdca'}
lambda = 1 / (conf.svm.C * length(selTrain)) ;
w = [] ;
parfor ci = 1:length(classes)
perm = randperm(length(selTrain)) ;
fprintf('Training model for class %s\n', classes{ci}) ;
y = 2 * (imageClass(selTrain) == ci) - 1 ;
[w(:,ci) b(ci) info] = vl_svmtrain(psix(:, selTrain(perm)), y(perm), lambda, ...