Documentation

Documentation

Framework structure

VLBenchmarks is organised into four parts, corresponding to an equal number of MATLAB packages (namespaces):

Options of functions and of class objects are passed as optional function/constructor arguments in the following format:

  function(obligatoryParams,'OptionName',OptionValue,...)

Available options are listed in help string of functions or classes. It is not possible to change options of already constructed object.

Feature objects

All classes from package localFeatures are subclasses of GenericLocalFeaturesExtractor. In VLBenchmarks those feature extractors wrappers are already implemented:

Class name Extracts Supported platforms Note
Feat. Descr. WIN LNX OS X
VlFeatSift Y Y Y Y Y Built-in [1], DoG detector, SIFT descriptor
VlFeatCovdet Y Y Y Y Y Built-in, DoG, Hessian and Harris detectors and their variants.
VlFeatMser Y N Y Y Y Built-in [1]
VggAffine Y N N Y N Affine feature frame detection[2]
VggDescriptor N Y N Y N Descriptor calculation from [2]
Ebr Y N N Y N Edge based region detector [2]
Ibr Y N N Y N Intensity based region detector [2]
CmpBinHessian Y N N Y N Hessian affine [3]

Benchmarking your own feature extractor

The VLBenchmarks framework is easily extensible with your own image feature extraction algorithm. Your feature extractor wrapper has to be a subclass of GenericLocalFeaturesExtractor class and has to implement method extractFeatures(imgPath) and/or extractDescriptors(imgPath, frames) depending on your algorithm abilities.

You can use existing infrastructure of the benchmark. For example, by inheriting from GenericLocalFeatureExtractor you also inherit helpers.Logger class which implements simple logger. See Logging for details.

Another helper class used with the most of the built in detectors is helpers.GenericInstaller. This class handles installation process and supports to define class dependencies on web-located archives, mex files and other classes.

This example shows a feature extractor which supports both feature frame detection and descriptor calculation. This class supports downloading and compiling mex source code of the detector.

classdef ExtractorY < localFeatures.GenericLocalFeatureExtractor & ...
  helpers.GenericInstaller
% localFeatures.DetectorY Y feature frames detector.

%   localFeatures.DetectorY('OptionName', optionValue) Construct new wrapper
%   of Y image features extractor. This algorithm is able to both detect image
%   features and compute their descriptors.
%
%   This class support caching and automatic installation. The sources are
%   downloaded from:
%
%   http://supercoolstuff.com/dreamdetector.zip
%
% Options:
%   DetOption:: 0
%     Serious configuration stuff...
%
%   AutoInstall:: true
%     Install dependencies during object construction.
%
%   This class accepts helpers.Logger options.
%
% See also: localFeatures.DetectorX helpers.Logger
  properties (SetAccess=private, GetAccess=public)
    Opts = struct('detOption',0); % Default option value
  end
  properties (Constant)
    % Location of your code
    Dir = fullfile('data','software','exy');
    % URL with the source codes
    Url = 'http://supercoolstuff.com/dreamdetector.zip';
    % MEX files needed to be compiled for the detector
    SrcFiles = {fullfile(localFeatures.ExtractorY.Dir,'y_algorithm.c')};
    % MEX files needed to be compiled for the detector
    MexFiles = ...
      {fullfile(localFeatures.ExtractorY.Dir,['y_algorithm.',mexext])};
  end
  methods
    function obj = ExampleLocalFeatureExtractor(varargin)
      obj.Name = 'Detector Y'; % Name of the wrapper
      varargin = obj.configureLogger(obj.Name,varargin); % Configure logger
      varargin = obj.checkInstall(varargin); % Check whether installed
      obj.Opts = vl_argparse(obj.Opts,varargin); % Parse object options
      obj.setup(); % Setup the paths
    end

    function setup(obj)
      % setup Setup extractorY paths.
      addpath(obj.Dir);
    end
    
    function [frames descriptors] = extractFeatures(obj, imagePath)
      % extractFeatures Extract features from an image
      %   FRAMES = extractFeatures(IMG_PATH) Detect features in image IMG_PATH
      %   using the Y algorithm.
      %
      %   [FRAMES DESCRIPTORS] = extractFeatures(IMG_PATH) Detect frames and
      %   compute their descriptors using the Y algorithm.
      frames = obj.loadFeatures(imagePath,nargout > 1); % Check cache
      if numel(frames) > 0; return; end;
      descriptors = [];
      obj.info('Computing frames of image %s.',getFileName(imagePath));
      if nargout == 1
        frames = y_algorithm(imagePath, obj.Opts{:});
        obj.debug('Frames computed in %gs',toc(startTime));
      elseif nargout == 2
        [frames descriptors] = y_algorithm(imagePath, obj.Opts{:});
        obj.debug('Frames and descriptors computed in %gs',toc(startTime));
      end
      obj.storeFeatures(imagePath, frames, descriptors); % Cache the results
    end

    function [frames descriptors] = extractDescriptors(obj, imagePath, frames)
      % extractDescriptors Extract descriptors of given frames
      startTime = tic;
      obj.info('Computing descriptors of image %s.',getFileName(imagePath));
      [frames, descriptors] = y_algorithm(imagePath,frames,obj.Opts{:});
      obj.debug('Descriptors computed in %gs',toc(startTime));
    end

    function signature = getSignature(obj)
      signature = [helpers.struct2str(obj.Opts),';',...
        helpers.fileSignature(obj.MexFiles)]; % Detector unique signature
    end
  end
  methods (Access=protected)
    % Define which archives should be downloaded and where extracted
    function [urls dstPaths] = getTarballsList(obj)
      import localFeatures.*;
      urls = {DetectorY.Url};
      dstPaths = {DetectorY.Dir};
    end
    % Define which mex files should be compiled
    function [srclist flags] = getMexSources(obj)
      import helpers.*;
      srclist = obj.SrcFiles;
      % Flags passed to mex command
      flags = {};
    end
  end
end

Method extractFeatures(imgPath) can be called with one output argument when only feature frames need to be detected. When called with two output arguments, it extracts both feature frames and descriptors. This may seem to be dual to the extractDescriptors() method however some detectors does not support computation of descriptors of given frames.

To cache your detected features, you can use loadFeatures() or obj.storeFeatures() methods. However these methods need to implement method obj.getSignature() which generates unique string signature of the detector parameters. It is also useful to include signature of the detector binary file (helpers.fileSignature). Caching can be enabled/disables with methods obj.enableCaching(), obj.disableCaching()

To see details about logging, class options and installation framework, see the localFeatures.ExampleLocalFeatureExtractor class.

Datasets

All datasets are subclasses of abstract GenericDataset which defines method getImagePath(imageNumber) to access dataset images. Number of images in the dataset can be obtained from object property obj.NumImages.

Repeatability benchmark needs datasets which inherits from class GenericTransfDataset. This benchmark needs method getTransformation(imageNumber) which returns homography between a dataset image and a reference image (first image).

VggAffineDataset

Wrapper of the datasets from [2] is implemented in class VggAffineDataset and is a subclass of GenericTransfDataset. Datasets data are available and downloaded from VGG website.

Available categories (accessible through option 'Category') are:

Category Name 'Category' option value Image transformation
Wall 'wall' Viewpoint angle
Boat 'boat' Scale changes
Bark 'bark' Scale changes
Bikes 'bikes' Increasing blur
Trees 'trees' Increasing blur
Leuven 'leuven' Decreasing light
UBC 'ubc' JPEG compression

VggRetrievalDataset

For the retrieval benchmark VggRetrievalDataset wraps around datasets introduced in [4], [5] These datasets provide both a set of images and a set of queries. Each query is specified by the query image, a bounding box and three subset of images:

A query is accessible through method getQuery(queryNum) which returns a structure with the following format:

>> dst = datasets.VggRetrievalDataset();
(INFO)  VggRetrievalDataset:  Loading dataset VggRetrievalDataset.
(DEBUG) VggRetrievalDataset:  Size of the images subset: 945
>> query = dst.getQuery(1)

query = 

         name: 'all_souls_1'        % Query name
    imageName: 'all_souls_000013'   % Name of the query image
      imageId: 8                    % Query image id
          box: [4x1 double]         % Query bounding box [xmin ymin xmax ymax]
         good: [1x24 double]
           ok: [1x54 double]
         junk: [1x33 double]

The image subsets are numeric arrays with image IDs. To obtain a path of a certain image from these subsets, you can simply call:

>> dst.getImagePath(query.junk(1))

ans =

data/datasets/vggRetrievalDataset/oxbuild/all_souls_000220.jpg

Number of all images in the dataset is stored in property obj.NumQueries.

Currently, two dataset categories are available:

Category Name 'Category' option value Number of images Number of queries Source
The Oxford Buildings Dataset [4] 'oxbuild' 5062 55 link
The Paris Dataset[5] 'paris' 6412 55 link

These datasets usually contain thousands of images. It is possible to work only with its subset specified by these constructor parameters:

    GoodImagesNum
      Number of 'Good' images preserved in the database. When inf, all
      images preserved.
 
    OkImagesNum
      Number of 'ok' images preserved in the database. When inf, all
      images preserved.
 
    JunkImagesNum
      Number of 'junk' images preserved in the database. When inf, all
      images preserved.
 
    BadImagesNum
      Number of 'junk' images preserved in the database. When inf, all
      images preserved.
 
    SamplingSeed
      Seed of the random number generator used for sampling the image
      dataset.

Subsets are sampled used uniform random sampling. You can change the particular sampling by changing the seed of random number generator with parameter SamplingSeed.

Benchmarks

All benchmark classes are subclasses of abstract class GenericBenchmark. Currently three benchmarks are implemented.

RepeatabilityBenchmark is based on tests introduced in [2]. For details about this test see the help string of the RetrievalBenchmark class or see repeatability tutorial. Because this test is mostly reimplementing the original test, wrapper of the original benchmark IjcvOriginalBenchmark is also available. This class directly calls the repeatability.m script.

RetrievalBenchmark class implements simple retrieval benchmark of image features detectors. For details see help string of the class or see image retrieval benchmark tutorial.

Currently the image retrieval benchmark depends on the Yael library which is not available for Microsoft Windows platforms.

Parallelisation

The way how to parallelise is different for the repeatability and retrieval benchmark but both approaches use Matlab Parallel Computing Toolbox. For details about this toolbox, see its documentation.

Parallelisation of the repeatability benchmark can be done by interchanging for with parfor in any level, e.g.:

% Prevent cache to clear the last recently used items
helpers.DataCache.disableAutoclear();
for di = 1:numDetectors
  % Extract features from the first image to avoid race condition
  % as several processes may try to write the same file.
  % Features does not have to be stored in any variable as they are
  % cached.
  detectors{di}.extractFeatures(dataset.getImagePath(1));
  parfor imageIdx = 2:numImages
    % Run the repeatability benchmark
  end
end
helpers.DataCache.enableAutoclear();

This is possible thanks to the fact that the cache system depends only on the file system context and does not share any variables. However it does not implement any synchronisation mechanisms. If the loops are properly designed, each process works with different data. However cache autoclear has to be disabled as it can produce situations when two processes are trying to delete the same files.

The image retrieval benchmark is already parallelised and the class RetrievalBenchmark uses several parfor loops for both features extraction and KNN search.

In the case of KNN search you can advantage both from symmetric processing and distributed computing as the Yael KNN uses OpenMP to run its algorithms in multiple threads.

Helpers

Package helpers contains several classes and functions which are used in the rest of the project. Logger and DataCache classes are used almost in all classes therefore are described more in the following text.

Caching

This project extensively caches its results using key-value caching system. The key string is usually created based on the properties of input data and processing algorithm. Data itself can be any Matlab data object and are stored as *.mat files in directory ./data/cache. The target filename is created as MD5 sum of the key.

Binary data are usually distinguished by a signature of the source file. File signature contains the file name and last modification date of the file. This is also used for algorithm binaries. Among that, the signature usually contain algorithm parameters as their values affect the results.

DataCache methods

Caching is implemented in class helpers.DataCache and has only static methods, in order to be able to use it in parallel where no memory is shared among processes. This class implements the following methods:

DataCache properties

The caching properties can be changed only in the helpers/DataCache.m source code by changing the class constant properties:

Please note that the standard Matlab behaviour is to set these values only once when the class file is loaded for the first time. Therefore to apply the options you must call:

>> clear all;

Auto clear functionality

Caching system implements a way how to limit the overall size of the cached data, further denoted as an autoclear function. It checks the size of the files in the cache directory and removes the last recently used items (based on the file modification date) to limit the storage usage. To globally disable autoclear set the class property helpers.DataCache.autoClear to false. You can also disable it temporarily by calling method helpers.DataCache.disableAutoclear() which creates a lock file. It is recommended to call this method before running parallel code to prevent two processes to delete the same files.

Disable caching

Cache can be disabled globally by setting the constant property helpers.DataCache.autoClear to true. Also several framework classes implements methods disableCaching() and enableCaching() to control the caching behaviour of a single class.

Cache does not support data invalidation. However to invalidate cached data of a certain detector you can use a simple trick to change the detector binary modification date:

>> helpers.touch('./data/software/det_binary');

DataCache limits

As the cache depends heavily on a file system it is basically limited by a number of files in a single directory which depends on your operating system. Big directory structures can lead to slow file operations and therefore the cache operations can take longer. Thus it is recommended to clear the cache once a while (specially when using network file system).

Logger

Several classes in the framework uses logging implemented in class helper.Logger class. This class supports both sending the events to std output or writing them to a log file. Following events, inspired by the apache Log4j framework, are supported:

Event name 'VerboseLevel' value Usage
TRACE 3 finer-grained informational events than the DEBUG.
DEBUG 2 Information useful for debugging.
INFO 1 Informational messages about the application progress.
WARN 0 Logs potentially harmful situations. Calls warn command to show the backtrace.
ERROR -1 Several errors which does not allow application to continue. Calls Matlab error function.

The verbose level can be set both globally editing the constant properties of class helpers.Logger or locally for each object which inherits the Logger class by providing the 'OptionName',OptionValue parameters to the class constructor call. Supported options are:

   VerboseLevel:: [helpers.Logger.DEBUG]
    Maximal verbosity level of messages sent to stdout. If set to OFF all
    messages are ignored, even errors would not stop the program execution.

  FileVerboseLevel:: [helpers.Logger.OFF]
    Maximal verbosity level of messages which are written to a log file.

  LogFile :: [./data/log]
     Path to a log file.

If you want to change the format of the log output, just adjust the methods helpers.Logger.displayLog and helpers.Logger.logToFile according to your aesthetic preferences.

Using Logger in your own classes

You can also use logger on your class as well simply by specifying the helpers.Logger as a superclass. Here is a simple template how to do it and how to properly configure the logger:

  classdef MyClass < helpers.Logger
    methods
      function obj = RepeatabilityBenchmark(varargin)
        varargin = obj.configureLogger('MyClassName',varargin);
        % Now you can process varargin for your arguments
        obj.info('Constructed.');
      end
    end
  end

And constructing your object you will get:

>> MyClass();
(INFO)  MyClassName:  Constructed.

Logging to a file and distributed computing

When your computation are being computed with several processes, possibly on several computers, having a single logging file may lead to inconsistent data. That is why we would like to create separate files for each Matlab process. This is generally a difficult task as the parfor environment does not include any information about the process.

But simple trick can be used. Matlab creates default values for object properties only once when the file is loaded for the first time. And in classic matlabpool each lab has got its own MATLAB process, therefore a log file name in the helpers.Logger can be set for example to:

LogFile = fullfile('data',['log-',helpers.hostname(),...
  randsample(char(97:122),5)]);

To test we can call the following code:

>> matlabpool 5;
>> import helpers.Logger;
>> parfor i=1:3
  for j=1:3
    obj = Logger();
    fprintf('Proc. #%d Obj. #%d: %s\n',i,j,obj.LogFile);
  end;
end;

Proc. #1 Obj. #1: data/log-comp02xcdqv
Proc. #1 Obj. #2: data/log-comp02xcdqv
Proc. #1 Obj. #3: data/log-comp02xcdqv
Proc. #2 Obj. #1: data/log-comp02lwmxr
Proc. #3 Obj. #1: data/log-comp02scirh
Proc. #3 Obj. #2: data/log-comp02scirh
Proc. #2 Obj. #2: data/log-comp02lwmxr
Proc. #2 Obj. #3: data/log-comp02lwmxr
Proc. #3 Obj. #3: data/log-comp02scirh

You can see that each process generated the random string only once. If the parfor would be distributed on more computers, the hostname() value would change as well.

References

  1. A. Vedaldi and B. Fulkerson. VLFeat: An Open and Portable Library of Computer Vision Algorithms, vlfeat.org, 2008.
  2. K. Mikolajczyk, T. Tuytelaars, C. Schmid, A. Zisserman, J. Matas, F. Schaffalitzky, T. Kadir, and L. Van Gool. A comparison of affine region detectors. IJCV, 1(65):43–72, 2005.
  3. Perdoch, M. and Chum, O. and Matas, J. Efficient Representation of Local Geometry for Large Scale Object Retrieval. In proceedings of CVPR09, 2009.
  4. J. Philbin, O. Chum, M. Isard, J. Sivic and A. Zisserman. Object retrieval with large vocabularies and fast spatial matching CVPR, 2007
  5. J. Philbin, O. Chum, M. Isard, J. Sivic and A. Zisserman., Lost in Quantization: Improving Particular Object Retrieval in Large Scale Image Databases (2008)