@ -0,0 +1,2 @@ |
||||
set(the_description "Background Segmentation Algorithms") |
||||
ocv_define_module(bgsegm opencv_core opencv_imgproc opencv_video opencv_highgui) |
@ -0,0 +1,130 @@ |
||||
/*
|
||||
By downloading, copying, installing or using the software you agree to this |
||||
license. If you do not agree to this license, do not download, install, |
||||
copy or use the software. |
||||
|
||||
|
||||
License Agreement |
||||
For Open Source Computer Vision Library |
||||
(3-clause BSD License) |
||||
|
||||
Copyright (C) 2013, OpenCV Foundation, all rights reserved. |
||||
Third party copyrights are property of their respective owners. |
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, |
||||
are permitted provided that the following conditions are met: |
||||
|
||||
* Redistributions of source code must retain the above copyright notice, |
||||
this list of conditions and the following disclaimer. |
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice, |
||||
this list of conditions and the following disclaimer in the documentation |
||||
and/or other materials provided with the distribution. |
||||
|
||||
* Neither the names of the copyright holders nor the names of the contributors |
||||
may be used to endorse or promote products derived from this software |
||||
without specific prior written permission. |
||||
|
||||
This software is provided by the copyright holders and contributors "as is" and |
||||
any express or implied warranties, including, but not limited to, the implied |
||||
warranties of merchantability and fitness for a particular purpose are |
||||
disclaimed. In no event shall copyright holders or contributors be liable for |
||||
any direct, indirect, incidental, special, exemplary, or consequential damages |
||||
(including, but not limited to, procurement of substitute goods or services; |
||||
loss of use, data, or profits; or business interruption) however caused |
||||
and on any theory of liability, whether in contract, strict liability, |
||||
or tort (including negligence or otherwise) arising in any way out of |
||||
the use of this software, even if advised of the possibility of such damage. |
||||
*/ |
||||
|
||||
#ifndef __OPENCV_BGSEGM_HPP__ |
||||
#define __OPENCV_BGSEGM_HPP__ |
||||
|
||||
#include "opencv2/video.hpp" |
||||
|
||||
#ifdef __cplusplus |
||||
|
||||
namespace cv |
||||
{ |
||||
namespace bgsegm |
||||
{ |
||||
|
||||
/*!
|
||||
Gaussian Mixture-based Backbround/Foreground Segmentation Algorithm |
||||
|
||||
The class implements the following algorithm: |
||||
"An improved adaptive background mixture model for real-time tracking with shadow detection" |
||||
P. KadewTraKuPong and R. Bowden, |
||||
Proc. 2nd European Workshp on Advanced Video-Based Surveillance Systems, 2001." |
||||
http://personal.ee.surrey.ac.uk/Personal/R.Bowden/publications/avbs01/avbs01.pdf
|
||||
|
||||
*/ |
||||
class CV_EXPORTS_W BackgroundSubtractorMOG : public BackgroundSubtractor |
||||
{ |
||||
public: |
||||
CV_WRAP virtual int getHistory() const = 0; |
||||
CV_WRAP virtual void setHistory(int nframes) = 0; |
||||
|
||||
CV_WRAP virtual int getNMixtures() const = 0; |
||||
CV_WRAP virtual void setNMixtures(int nmix) = 0; |
||||
|
||||
CV_WRAP virtual double getBackgroundRatio() const = 0; |
||||
CV_WRAP virtual void setBackgroundRatio(double backgroundRatio) = 0; |
||||
|
||||
CV_WRAP virtual double getNoiseSigma() const = 0; |
||||
CV_WRAP virtual void setNoiseSigma(double noiseSigma) = 0; |
||||
}; |
||||
|
||||
CV_EXPORTS_W Ptr<BackgroundSubtractorMOG> |
||||
createBackgroundSubtractorMOG(int history=200, int nmixtures=5, |
||||
double backgroundRatio=0.7, double noiseSigma=0); |
||||
|
||||
/**
|
||||
* Background Subtractor module. Takes a series of images and returns a sequence of mask (8UC1) |
||||
* images of the same size, where 255 indicates Foreground and 0 represents Background. |
||||
* This class implements an algorithm described in "Visual Tracking of Human Visitors under |
||||
* Variable-Lighting Conditions for a Responsive Audio Art Installation," A. Godbehere, |
||||
* A. Matsukawa, K. Goldberg, American Control Conference, Montreal, June 2012. |
||||
*/ |
||||
class CV_EXPORTS_W BackgroundSubtractorGMG : public BackgroundSubtractor |
||||
{ |
||||
public: |
||||
CV_WRAP virtual int getMaxFeatures() const = 0; |
||||
CV_WRAP virtual void setMaxFeatures(int maxFeatures) = 0; |
||||
|
||||
CV_WRAP virtual double getDefaultLearningRate() const = 0; |
||||
CV_WRAP virtual void setDefaultLearningRate(double lr) = 0; |
||||
|
||||
CV_WRAP virtual int getNumFrames() const = 0; |
||||
CV_WRAP virtual void setNumFrames(int nframes) = 0; |
||||
|
||||
CV_WRAP virtual int getQuantizationLevels() const = 0; |
||||
CV_WRAP virtual void setQuantizationLevels(int nlevels) = 0; |
||||
|
||||
CV_WRAP virtual double getBackgroundPrior() const = 0; |
||||
CV_WRAP virtual void setBackgroundPrior(double bgprior) = 0; |
||||
|
||||
CV_WRAP virtual int getSmoothingRadius() const = 0; |
||||
CV_WRAP virtual void setSmoothingRadius(int radius) = 0; |
||||
|
||||
CV_WRAP virtual double getDecisionThreshold() const = 0; |
||||
CV_WRAP virtual void setDecisionThreshold(double thresh) = 0; |
||||
|
||||
CV_WRAP virtual bool getUpdateBackgroundModel() const = 0; |
||||
CV_WRAP virtual void setUpdateBackgroundModel(bool update) = 0; |
||||
|
||||
CV_WRAP virtual double getMinVal() const = 0; |
||||
CV_WRAP virtual void setMinVal(double val) = 0; |
||||
|
||||
CV_WRAP virtual double getMaxVal() const = 0; |
||||
CV_WRAP virtual void setMaxVal(double val) = 0; |
||||
}; |
||||
|
||||
CV_EXPORTS_W Ptr<BackgroundSubtractorGMG> createBackgroundSubtractorGMG(int initializationFrames=120, |
||||
double decisionThreshold=0.8);
|
||||
|
||||
} |
||||
} |
||||
|
||||
#endif |
||||
#endif |
@ -0,0 +1,81 @@ |
||||
/*
|
||||
* FGBGTest.cpp |
||||
* |
||||
* Created on: May 7, 2012 |
||||
* Author: Andrew B. Godbehere |
||||
*/ |
||||
|
||||
#include "opencv2/bgsegm.hpp" |
||||
#include "opencv2/videoio.hpp" |
||||
#include "opencv2/highgui.hpp" |
||||
#include <opencv2/core/utility.hpp> |
||||
#include <iostream> |
||||
|
||||
using namespace cv; |
||||
using namespace cv::bgsegm; |
||||
|
||||
static void help() |
||||
{ |
||||
std::cout << |
||||
"\nA program demonstrating the use and capabilities of a particular BackgroundSubtraction\n" |
||||
"algorithm described in A. Godbehere, A. Matsukawa, K. Goldberg, \n" |
||||
"\"Visual Tracking of Human Visitors under Variable-Lighting Conditions for a Responsive\n" |
||||
"Audio Art Installation\", American Control Conference, 2012, used in an interactive\n" |
||||
"installation at the Contemporary Jewish Museum in San Francisco, CA from March 31 through\n" |
||||
"July 31, 2011.\n" |
||||
"Call:\n" |
||||
"./BackgroundSubtractorGMG_sample\n" |
||||
"Using OpenCV version " << CV_VERSION << "\n"<<std::endl; |
||||
} |
||||
|
||||
int main(int argc, char** argv) |
||||
{ |
||||
help(); |
||||
|
||||
setUseOptimized(true); |
||||
setNumThreads(8); |
||||
|
||||
Ptr<BackgroundSubtractor> fgbg = createBackgroundSubtractorGMG(20, 0.7); |
||||
if (!fgbg) |
||||
{ |
||||
std::cerr << "Failed to create BackgroundSubtractor.GMG Algorithm." << std::endl; |
||||
return -1; |
||||
} |
||||
|
||||
VideoCapture cap; |
||||
if (argc > 1) |
||||
cap.open(argv[1]); |
||||
else |
||||
cap.open(0); |
||||
|
||||
if (!cap.isOpened()) |
||||
{ |
||||
std::cerr << "Cannot read video. Try moving video file to sample directory." << std::endl; |
||||
return -1; |
||||
} |
||||
|
||||
Mat frame, fgmask, segm; |
||||
|
||||
namedWindow("FG Segmentation", WINDOW_NORMAL); |
||||
|
||||
for (;;) |
||||
{ |
||||
cap >> frame; |
||||
|
||||
if (frame.empty()) |
||||
break; |
||||
|
||||
fgbg->apply(frame, fgmask); |
||||
|
||||
frame.convertTo(segm, CV_8U, 0.5); |
||||
add(frame, Scalar(100, 100, 0), segm, fgmask); |
||||
|
||||
imshow("FG Segmentation", segm); |
||||
|
||||
int c = waitKey(30); |
||||
if (c == 'q' || c == 'Q' || (c & 255) == 27) |
||||
break; |
||||
} |
||||
|
||||
return 0; |
||||
} |
@ -0,0 +1,475 @@ |
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2013, OpenCV Foundation, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "precomp.hpp" |
||||
#include <float.h> |
||||
|
||||
// to make sure we can use these short names
|
||||
#undef K |
||||
#undef L |
||||
#undef T |
||||
|
||||
// This is based on the "An Improved Adaptive Background Mixture Model for
|
||||
// Real-time Tracking with Shadow Detection" by P. KaewTraKulPong and R. Bowden
|
||||
// http://personal.ee.surrey.ac.uk/Personal/R.Bowden/publications/avbs01/avbs01.pdf
|
||||
//
|
||||
// The windowing method is used, but not the shadow detection. I make some of my
|
||||
// own modifications which make more sense. There are some errors in some of their
|
||||
// equations.
|
||||
//
|
||||
|
||||
namespace cv |
||||
{ |
||||
namespace bgsegm |
||||
{ |
||||
|
||||
static const int defaultNMixtures = 5; |
||||
static const int defaultHistory = 200; |
||||
static const double defaultBackgroundRatio = 0.7; |
||||
static const double defaultVarThreshold = 2.5*2.5; |
||||
static const double defaultNoiseSigma = 30*0.5; |
||||
static const double defaultInitialWeight = 0.05; |
||||
|
||||
class BackgroundSubtractorMOGImpl : public BackgroundSubtractorMOG |
||||
{ |
||||
public: |
||||
//! the default constructor
|
||||
BackgroundSubtractorMOGImpl() |
||||
{ |
||||
frameSize = Size(0,0); |
||||
frameType = 0; |
||||
|
||||
nframes = 0; |
||||
nmixtures = defaultNMixtures; |
||||
history = defaultHistory; |
||||
varThreshold = defaultVarThreshold; |
||||
backgroundRatio = defaultBackgroundRatio; |
||||
noiseSigma = defaultNoiseSigma; |
||||
name_ = "BackgroundSubtractor.MOG"; |
||||
} |
||||
// the full constructor that takes the length of the history,
|
||||
// the number of gaussian mixtures, the background ratio parameter and the noise strength
|
||||
BackgroundSubtractorMOGImpl(int _history, int _nmixtures, double _backgroundRatio, double _noiseSigma=0) |
||||
{ |
||||
frameSize = Size(0,0); |
||||
frameType = 0; |
||||
|
||||
nframes = 0; |
||||
nmixtures = std::min(_nmixtures > 0 ? _nmixtures : defaultNMixtures, 8); |
||||
history = _history > 0 ? _history : defaultHistory; |
||||
varThreshold = defaultVarThreshold; |
||||
backgroundRatio = std::min(_backgroundRatio > 0 ? _backgroundRatio : 0.95, 1.); |
||||
noiseSigma = _noiseSigma <= 0 ? defaultNoiseSigma : _noiseSigma; |
||||
} |
||||
|
||||
//! the update operator
|
||||
virtual void apply(InputArray image, OutputArray fgmask, double learningRate=0); |
||||
|
||||
//! re-initiaization method
|
||||
virtual void initialize(Size _frameSize, int _frameType) |
||||
{ |
||||
frameSize = _frameSize; |
||||
frameType = _frameType; |
||||
nframes = 0; |
||||
|
||||
int nchannels = CV_MAT_CN(frameType); |
||||
CV_Assert( CV_MAT_DEPTH(frameType) == CV_8U ); |
||||
|
||||
// for each gaussian mixture of each pixel bg model we store ...
|
||||
// the mixture sort key (w/sum_of_variances), the mixture weight (w),
|
||||
// the mean (nchannels values) and
|
||||
// the diagonal covariance matrix (another nchannels values)
|
||||
bgmodel.create( 1, frameSize.height*frameSize.width*nmixtures*(2 + 2*nchannels), CV_32F ); |
||||
bgmodel = Scalar::all(0); |
||||
} |
||||
|
||||
virtual AlgorithmInfo* info() const { return 0; } |
||||
|
||||
virtual void getBackgroundImage(OutputArray) const |
||||
{ |
||||
CV_Error( Error::StsNotImplemented, "" ); |
||||
} |
||||
|
||||
virtual int getHistory() const { return history; } |
||||
virtual void setHistory(int _nframes) { history = _nframes; } |
||||
|
||||
virtual int getNMixtures() const { return nmixtures; } |
||||
virtual void setNMixtures(int nmix) { nmixtures = nmix; } |
||||
|
||||
virtual double getBackgroundRatio() const { return backgroundRatio; } |
||||
virtual void setBackgroundRatio(double _backgroundRatio) { backgroundRatio = _backgroundRatio; } |
||||
|
||||
virtual double getNoiseSigma() const { return noiseSigma; } |
||||
virtual void setNoiseSigma(double _noiseSigma) { noiseSigma = _noiseSigma; } |
||||
|
||||
virtual void write(FileStorage& fs) const |
||||
{ |
||||
fs << "name" << name_ |
||||
<< "history" << history |
||||
<< "nmixtures" << nmixtures |
||||
<< "backgroundRatio" << backgroundRatio |
||||
<< "noiseSigma" << noiseSigma; |
||||
} |
||||
|
||||
virtual void read(const FileNode& fn) |
||||
{ |
||||
CV_Assert( (String)fn["name"] == name_ ); |
||||
history = (int)fn["history"]; |
||||
nmixtures = (int)fn["nmixtures"]; |
||||
backgroundRatio = (double)fn["backgroundRatio"]; |
||||
noiseSigma = (double)fn["noiseSigma"]; |
||||
} |
||||
|
||||
protected: |
||||
Size frameSize; |
||||
int frameType; |
||||
Mat bgmodel; |
||||
int nframes; |
||||
int history; |
||||
int nmixtures; |
||||
double varThreshold; |
||||
double backgroundRatio; |
||||
double noiseSigma; |
||||
String name_; |
||||
}; |
||||
|
||||
|
||||
template<typename VT> struct MixData |
||||
{ |
||||
float sortKey; |
||||
float weight; |
||||
VT mean; |
||||
VT var; |
||||
}; |
||||
|
||||
|
||||
static void process8uC1( const Mat& image, Mat& fgmask, double learningRate, |
||||
Mat& bgmodel, int nmixtures, double backgroundRatio, |
||||
double varThreshold, double noiseSigma ) |
||||
{ |
||||
int x, y, k, k1, rows = image.rows, cols = image.cols; |
||||
float alpha = (float)learningRate, T = (float)backgroundRatio, vT = (float)varThreshold; |
||||
int K = nmixtures; |
||||
MixData<float>* mptr = (MixData<float>*)bgmodel.data; |
||||
|
||||
const float w0 = (float)defaultInitialWeight; |
||||
const float sk0 = (float)(w0/(defaultNoiseSigma*2)); |
||||
const float var0 = (float)(defaultNoiseSigma*defaultNoiseSigma*4); |
||||
const float minVar = (float)(noiseSigma*noiseSigma); |
||||
|
||||
for( y = 0; y < rows; y++ ) |
||||
{ |
||||
const uchar* src = image.ptr<uchar>(y); |
||||
uchar* dst = fgmask.ptr<uchar>(y); |
||||
|
||||
if( alpha > 0 ) |
||||
{ |
||||
for( x = 0; x < cols; x++, mptr += K ) |
||||
{ |
||||
float wsum = 0; |
||||
float pix = src[x]; |
||||
int kHit = -1, kForeground = -1; |
||||
|
||||
for( k = 0; k < K; k++ ) |
||||
{ |
||||
float w = mptr[k].weight; |
||||
wsum += w; |
||||
if( w < FLT_EPSILON ) |
||||
break; |
||||
float mu = mptr[k].mean; |
||||
float var = mptr[k].var; |
||||
float diff = pix - mu; |
||||
float d2 = diff*diff; |
||||
if( d2 < vT*var ) |
||||
{ |
||||
wsum -= w; |
||||
float dw = alpha*(1.f - w); |
||||
mptr[k].weight = w + dw; |
||||
mptr[k].mean = mu + alpha*diff; |
||||
var = std::max(var + alpha*(d2 - var), minVar); |
||||
mptr[k].var = var; |
||||
mptr[k].sortKey = w/std::sqrt(var); |
||||
|
||||
for( k1 = k-1; k1 >= 0; k1-- ) |
||||
{ |
||||
if( mptr[k1].sortKey >= mptr[k1+1].sortKey ) |
||||
break; |
||||
std::swap( mptr[k1], mptr[k1+1] ); |
||||
} |
||||
|
||||
kHit = k1+1; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if( kHit < 0 ) // no appropriate gaussian mixture found at all, remove the weakest mixture and create a new one
|
||||
{ |
||||
kHit = k = std::min(k, K-1); |
||||
wsum += w0 - mptr[k].weight; |
||||
mptr[k].weight = w0; |
||||
mptr[k].mean = pix; |
||||
mptr[k].var = var0; |
||||
mptr[k].sortKey = sk0; |
||||
} |
||||
else |
||||
for( ; k < K; k++ ) |
||||
wsum += mptr[k].weight; |
||||
|
||||
float wscale = 1.f/wsum; |
||||
wsum = 0; |
||||
for( k = 0; k < K; k++ ) |
||||
{ |
||||
wsum += mptr[k].weight *= wscale; |
||||
mptr[k].sortKey *= wscale; |
||||
if( wsum > T && kForeground < 0 ) |
||||
kForeground = k+1; |
||||
} |
||||
|
||||
dst[x] = (uchar)(-(kHit >= kForeground)); |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
for( x = 0; x < cols; x++, mptr += K ) |
||||
{ |
||||
float pix = src[x]; |
||||
int kHit = -1, kForeground = -1; |
||||
|
||||
for( k = 0; k < K; k++ ) |
||||
{ |
||||
if( mptr[k].weight < FLT_EPSILON ) |
||||
break; |
||||
float mu = mptr[k].mean; |
||||
float var = mptr[k].var; |
||||
float diff = pix - mu; |
||||
float d2 = diff*diff; |
||||
if( d2 < vT*var ) |
||||
{ |
||||
kHit = k; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if( kHit >= 0 ) |
||||
{ |
||||
float wsum = 0; |
||||
for( k = 0; k < K; k++ ) |
||||
{ |
||||
wsum += mptr[k].weight; |
||||
if( wsum > T ) |
||||
{ |
||||
kForeground = k+1; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
dst[x] = (uchar)(kHit < 0 || kHit >= kForeground ? 255 : 0); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
static void process8uC3( const Mat& image, Mat& fgmask, double learningRate, |
||||
Mat& bgmodel, int nmixtures, double backgroundRatio, |
||||
double varThreshold, double noiseSigma ) |
||||
{ |
||||
int x, y, k, k1, rows = image.rows, cols = image.cols; |
||||
float alpha = (float)learningRate, T = (float)backgroundRatio, vT = (float)varThreshold; |
||||
int K = nmixtures; |
||||
|
||||
const float w0 = (float)defaultInitialWeight; |
||||
const float sk0 = (float)(w0/(defaultNoiseSigma*2*std::sqrt(3.))); |
||||
const float var0 = (float)(defaultNoiseSigma*defaultNoiseSigma*4); |
||||
const float minVar = (float)(noiseSigma*noiseSigma); |
||||
MixData<Vec3f>* mptr = (MixData<Vec3f>*)bgmodel.data; |
||||
|
||||
for( y = 0; y < rows; y++ ) |
||||
{ |
||||
const uchar* src = image.ptr<uchar>(y); |
||||
uchar* dst = fgmask.ptr<uchar>(y); |
||||
|
||||
if( alpha > 0 ) |
||||
{ |
||||
for( x = 0; x < cols; x++, mptr += K ) |
||||
{ |
||||
float wsum = 0; |
||||
Vec3f pix(src[x*3], src[x*3+1], src[x*3+2]); |
||||
int kHit = -1, kForeground = -1; |
||||
|
||||
for( k = 0; k < K; k++ ) |
||||
{ |
||||
float w = mptr[k].weight; |
||||
wsum += w; |
||||
if( w < FLT_EPSILON ) |
||||
break; |
||||
Vec3f mu = mptr[k].mean; |
||||
Vec3f var = mptr[k].var; |
||||
Vec3f diff = pix - mu; |
||||
float d2 = diff.dot(diff); |
||||
if( d2 < vT*(var[0] + var[1] + var[2]) ) |
||||
{ |
||||
wsum -= w; |
||||
float dw = alpha*(1.f - w); |
||||
mptr[k].weight = w + dw; |
||||
mptr[k].mean = mu + alpha*diff; |
||||
var = Vec3f(std::max(var[0] + alpha*(diff[0]*diff[0] - var[0]), minVar), |
||||
std::max(var[1] + alpha*(diff[1]*diff[1] - var[1]), minVar), |
||||
std::max(var[2] + alpha*(diff[2]*diff[2] - var[2]), minVar)); |
||||
mptr[k].var = var; |
||||
mptr[k].sortKey = w/std::sqrt(var[0] + var[1] + var[2]); |
||||
|
||||
for( k1 = k-1; k1 >= 0; k1-- ) |
||||
{ |
||||
if( mptr[k1].sortKey >= mptr[k1+1].sortKey ) |
||||
break; |
||||
std::swap( mptr[k1], mptr[k1+1] ); |
||||
} |
||||
|
||||
kHit = k1+1; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if( kHit < 0 ) // no appropriate gaussian mixture found at all, remove the weakest mixture and create a new one
|
||||
{ |
||||
kHit = k = std::min(k, K-1); |
||||
wsum += w0 - mptr[k].weight; |
||||
mptr[k].weight = w0; |
||||
mptr[k].mean = pix; |
||||
mptr[k].var = Vec3f(var0, var0, var0); |
||||
mptr[k].sortKey = sk0; |
||||
} |
||||
else |
||||
for( ; k < K; k++ ) |
||||
wsum += mptr[k].weight; |
||||
|
||||
float wscale = 1.f/wsum; |
||||
wsum = 0; |
||||
for( k = 0; k < K; k++ ) |
||||
{ |
||||
wsum += mptr[k].weight *= wscale; |
||||
mptr[k].sortKey *= wscale; |
||||
if( wsum > T && kForeground < 0 ) |
||||
kForeground = k+1; |
||||
} |
||||
|
||||
dst[x] = (uchar)(-(kHit >= kForeground)); |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
for( x = 0; x < cols; x++, mptr += K ) |
||||
{ |
||||
Vec3f pix(src[x*3], src[x*3+1], src[x*3+2]); |
||||
int kHit = -1, kForeground = -1; |
||||
|
||||
for( k = 0; k < K; k++ ) |
||||
{ |
||||
if( mptr[k].weight < FLT_EPSILON ) |
||||
break; |
||||
Vec3f mu = mptr[k].mean; |
||||
Vec3f var = mptr[k].var; |
||||
Vec3f diff = pix - mu; |
||||
float d2 = diff.dot(diff); |
||||
if( d2 < vT*(var[0] + var[1] + var[2]) ) |
||||
{ |
||||
kHit = k; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if( kHit >= 0 ) |
||||
{ |
||||
float wsum = 0; |
||||
for( k = 0; k < K; k++ ) |
||||
{ |
||||
wsum += mptr[k].weight; |
||||
if( wsum > T ) |
||||
{ |
||||
kForeground = k+1; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
dst[x] = (uchar)(kHit < 0 || kHit >= kForeground ? 255 : 0); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
void BackgroundSubtractorMOGImpl::apply(InputArray _image, OutputArray _fgmask, double learningRate) |
||||
{ |
||||
Mat image = _image.getMat(); |
||||
bool needToInitialize = nframes == 0 || learningRate >= 1 || image.size() != frameSize || image.type() != frameType; |
||||
|
||||
if( needToInitialize ) |
||||
initialize(image.size(), image.type()); |
||||
|
||||
CV_Assert( image.depth() == CV_8U ); |
||||
_fgmask.create( image.size(), CV_8U ); |
||||
Mat fgmask = _fgmask.getMat(); |
||||
|
||||
++nframes; |
||||
learningRate = learningRate >= 0 && nframes > 1 ? learningRate : 1./std::min( nframes, history ); |
||||
CV_Assert(learningRate >= 0); |
||||
|
||||
if( image.type() == CV_8UC1 ) |
||||
process8uC1( image, fgmask, learningRate, bgmodel, nmixtures, backgroundRatio, varThreshold, noiseSigma ); |
||||
else if( image.type() == CV_8UC3 ) |
||||
process8uC3( image, fgmask, learningRate, bgmodel, nmixtures, backgroundRatio, varThreshold, noiseSigma ); |
||||
else |
||||
CV_Error( Error::StsUnsupportedFormat, "Only 1- and 3-channel 8-bit images are supported in BackgroundSubtractorMOG" ); |
||||
} |
||||
|
||||
Ptr<BackgroundSubtractorMOG> createBackgroundSubtractorMOG(int history, int nmixtures, |
||||
double backgroundRatio, double noiseSigma) |
||||
{ |
||||
return makePtr<BackgroundSubtractorMOGImpl>(history, nmixtures, backgroundRatio, noiseSigma); |
||||
} |
||||
|
||||
} |
||||
} |
||||
|
||||
/* End of file. */ |
@ -0,0 +1,521 @@ |
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2013, OpenCV Foundation, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
/*
|
||||
* This class implements an algorithm described in "Visual Tracking of Human Visitors under |
||||
* Variable-Lighting Conditions for a Responsive Audio Art Installation," A. Godbehere, |
||||
* A. Matsukawa, K. Goldberg, American Control Conference, Montreal, June 2012. |
||||
* |
||||
* Prepared and integrated by Andrew B. Godbehere. |
||||
*/ |
||||
|
||||
#include "precomp.hpp" |
||||
#include "opencv2/core/utility.hpp" |
||||
#include <limits> |
||||
|
||||
namespace cv |
||||
{ |
||||
namespace bgsegm |
||||
{ |
||||
|
||||
class BackgroundSubtractorGMGImpl : public BackgroundSubtractorGMG |
||||
{ |
||||
public: |
||||
BackgroundSubtractorGMGImpl() |
||||
{ |
||||
/*
|
||||
* Default Parameter Values. Override with algorithm "set" method. |
||||
*/ |
||||
maxFeatures = 64; |
||||
learningRate = 0.025; |
||||
numInitializationFrames = 120; |
||||
quantizationLevels = 16; |
||||
backgroundPrior = 0.8; |
||||
decisionThreshold = 0.8; |
||||
smoothingRadius = 7; |
||||
updateBackgroundModel = true; |
||||
minVal_ = maxVal_ = 0; |
||||
name_ = "BackgroundSubtractor.GMG"; |
||||
} |
||||
|
||||
~BackgroundSubtractorGMGImpl() |
||||
{ |
||||
} |
||||
|
||||
virtual AlgorithmInfo* info() const { return 0; } |
||||
|
||||
/**
|
||||
* Validate parameters and set up data structures for appropriate image size. |
||||
* Must call before running on data. |
||||
* @param frameSize input frame size |
||||
* @param min minimum value taken on by pixels in image sequence. Usually 0 |
||||
* @param max maximum value taken on by pixels in image sequence. e.g. 1.0 or 255 |
||||
*/ |
||||
void initialize(Size frameSize, double minVal, double maxVal); |
||||
|
||||
/**
|
||||
* Performs single-frame background subtraction and builds up a statistical background image |
||||
* model. |
||||
* @param image Input image |
||||
* @param fgmask Output mask image representing foreground and background pixels |
||||
*/ |
||||
virtual void apply(InputArray image, OutputArray fgmask, double learningRate=-1.0); |
||||
|
||||
/**
|
||||
* Releases all inner buffers. |
||||
*/ |
||||
void release(); |
||||
|
||||
virtual int getMaxFeatures() const { return maxFeatures; } |
||||
virtual void setMaxFeatures(int _maxFeatures) { maxFeatures = _maxFeatures; } |
||||
|
||||
virtual double getDefaultLearningRate() const { return learningRate; } |
||||
virtual void setDefaultLearningRate(double lr) { learningRate = lr; } |
||||
|
||||
virtual int getNumFrames() const { return numInitializationFrames; } |
||||
virtual void setNumFrames(int nframes) { numInitializationFrames = nframes; } |
||||
|
||||
virtual int getQuantizationLevels() const { return quantizationLevels; } |
||||
virtual void setQuantizationLevels(int nlevels) { quantizationLevels = nlevels; } |
||||
|
||||
virtual double getBackgroundPrior() const { return backgroundPrior; } |
||||
virtual void setBackgroundPrior(double bgprior) { backgroundPrior = bgprior; } |
||||
|
||||
virtual int getSmoothingRadius() const { return smoothingRadius; } |
||||
virtual void setSmoothingRadius(int radius) { smoothingRadius = radius; } |
||||
|
||||
virtual double getDecisionThreshold() const { return decisionThreshold; } |
||||
virtual void setDecisionThreshold(double thresh) { decisionThreshold = thresh; } |
||||
|
||||
virtual bool getUpdateBackgroundModel() const { return updateBackgroundModel; } |
||||
virtual void setUpdateBackgroundModel(bool update) { updateBackgroundModel = update; } |
||||
|
||||
virtual double getMinVal() const { return minVal_; } |
||||
virtual void setMinVal(double val) { minVal_ = val; } |
||||
|
||||
virtual double getMaxVal() const { return maxVal_; } |
||||
virtual void setMaxVal(double val) { maxVal_ = val; } |
||||
|
||||
virtual void getBackgroundImage(OutputArray backgroundImage) const |
||||
{ |
||||
backgroundImage.release(); |
||||
} |
||||
|
||||
virtual void write(FileStorage& fs) const |
||||
{ |
||||
fs << "name" << name_ |
||||
<< "maxFeatures" << maxFeatures |
||||
<< "defaultLearningRate" << learningRate |
||||
<< "numFrames" << numInitializationFrames |
||||
<< "quantizationLevels" << quantizationLevels |
||||
<< "backgroundPrior" << backgroundPrior |
||||
<< "decisionThreshold" << decisionThreshold |
||||
<< "smoothingRadius" << smoothingRadius |
||||
<< "updateBackgroundModel" << (int)updateBackgroundModel; |
||||
// we do not save minVal_ & maxVal_, since they depend on the image type.
|
||||
} |
||||
|
||||
virtual void read(const FileNode& fn) |
||||
{ |
||||
CV_Assert( (String)fn["name"] == name_ ); |
||||
maxFeatures = (int)fn["maxFeatures"]; |
||||
learningRate = (double)fn["defaultLearningRate"]; |
||||
numInitializationFrames = (int)fn["numFrames"]; |
||||
quantizationLevels = (int)fn["quantizationLevels"]; |
||||
backgroundPrior = (double)fn["backgroundPrior"]; |
||||
smoothingRadius = (int)fn["smoothingRadius"]; |
||||
decisionThreshold = (double)fn["decisionThreshold"]; |
||||
updateBackgroundModel = (int)fn["updateBackgroundModel"] != 0; |
||||
minVal_ = maxVal_ = 0; |
||||
frameSize_ = Size(); |
||||
} |
||||
|
||||
//! Total number of distinct colors to maintain in histogram.
|
||||
int maxFeatures; |
||||
//! Set between 0.0 and 1.0, determines how quickly features are "forgotten" from histograms.
|
||||
double learningRate; |
||||
//! Number of frames of video to use to initialize histograms.
|
||||
int numInitializationFrames; |
||||
//! Number of discrete levels in each channel to be used in histograms.
|
||||
int quantizationLevels; |
||||
//! Prior probability that any given pixel is a background pixel. A sensitivity parameter.
|
||||
double backgroundPrior; |
||||
//! Value above which pixel is determined to be FG.
|
||||
double decisionThreshold; |
||||
//! Smoothing radius, in pixels, for cleaning up FG image.
|
||||
int smoothingRadius; |
||||
//! Perform background model update
|
||||
bool updateBackgroundModel; |
||||
|
||||
private: |
||||
double maxVal_; |
||||
double minVal_; |
||||
|
||||
Size frameSize_; |
||||
int frameNum_; |
||||
|
||||
String name_; |
||||
|
||||
Mat_<int> nfeatures_; |
||||
Mat_<unsigned int> colors_; |
||||
Mat_<float> weights_; |
||||
|
||||
Mat buf_; |
||||
}; |
||||
|
||||
|
||||
void BackgroundSubtractorGMGImpl::initialize(Size frameSize, double minVal, double maxVal) |
||||
{ |
||||
CV_Assert(minVal < maxVal); |
||||
CV_Assert(maxFeatures > 0); |
||||
CV_Assert(learningRate >= 0.0 && learningRate <= 1.0); |
||||
CV_Assert(numInitializationFrames >= 1); |
||||
CV_Assert(quantizationLevels >= 1 && quantizationLevels <= 255); |
||||
CV_Assert(backgroundPrior >= 0.0 && backgroundPrior <= 1.0); |
||||
|
||||
minVal_ = minVal; |
||||
maxVal_ = maxVal; |
||||
|
||||
frameSize_ = frameSize; |
||||
frameNum_ = 0; |
||||
|
||||
nfeatures_.create(frameSize_); |
||||
colors_.create(frameSize_.area(), maxFeatures); |
||||
weights_.create(frameSize_.area(), maxFeatures); |
||||
|
||||
nfeatures_.setTo(Scalar::all(0)); |
||||
} |
||||
|
||||
static float findFeature(unsigned int color, const unsigned int* colors, const float* weights, int nfeatures) |
||||
{ |
||||
for (int i = 0; i < nfeatures; ++i) |
||||
{ |
||||
if (color == colors[i]) |
||||
return weights[i]; |
||||
} |
||||
|
||||
// not in histogram, so return 0.
|
||||
return 0.0f; |
||||
} |
||||
|
||||
static void normalizeHistogram(float* weights, int nfeatures) |
||||
{ |
||||
float total = 0.0f; |
||||
for (int i = 0; i < nfeatures; ++i) |
||||
total += weights[i]; |
||||
|
||||
if (total != 0.0f) |
||||
{ |
||||
for (int i = 0; i < nfeatures; ++i) |
||||
weights[i] /= total; |
||||
} |
||||
} |
||||
|
||||
static bool insertFeature(unsigned int color, float weight, unsigned int* colors, float* weights, int& nfeatures, int maxFeatures) |
||||
{ |
||||
int idx = -1; |
||||
for (int i = 0; i < nfeatures; ++i) |
||||
{ |
||||
if (color == colors[i]) |
||||
{ |
||||
// feature in histogram
|
||||
weight += weights[i]; |
||||
idx = i; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if (idx >= 0) |
||||
{ |
||||
// move feature to beginning of list
|
||||
|
||||
::memmove(colors + 1, colors, idx * sizeof(unsigned int)); |
||||
::memmove(weights + 1, weights, idx * sizeof(float)); |
||||
|
||||
colors[0] = color; |
||||
weights[0] = weight; |
||||
} |
||||
else if (nfeatures == maxFeatures) |
||||
{ |
||||
// discard oldest feature
|
||||
|
||||
::memmove(colors + 1, colors, (nfeatures - 1) * sizeof(unsigned int)); |
||||
::memmove(weights + 1, weights, (nfeatures - 1) * sizeof(float)); |
||||
|
||||
colors[0] = color; |
||||
weights[0] = weight; |
||||
} |
||||
else |
||||
{ |
||||
colors[nfeatures] = color; |
||||
weights[nfeatures] = weight; |
||||
|
||||
++nfeatures; |
||||
|
||||
return true; |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
template <typename T> struct Quantization |
||||
{ |
||||
static unsigned int apply(const void* src_, int x, int cn, double minVal, double maxVal, int quantizationLevels) |
||||
{ |
||||
const T* src = static_cast<const T*>(src_); |
||||
src += x * cn; |
||||
|
||||
unsigned int res = 0; |
||||
for (int i = 0, shift = 0; i < cn; ++i, ++src, shift += 8) |
||||
res |= static_cast<int>((*src - minVal) * quantizationLevels / (maxVal - minVal)) << shift; |
||||
|
||||
return res; |
||||
} |
||||
}; |
||||
|
||||
class GMG_LoopBody : public ParallelLoopBody |
||||
{ |
||||
public: |
||||
GMG_LoopBody(const Mat& frame, const Mat& fgmask, const Mat_<int>& nfeatures, const Mat_<unsigned int>& colors, const Mat_<float>& weights, |
||||
int maxFeatures, double learningRate, int numInitializationFrames, int quantizationLevels, double backgroundPrior, double decisionThreshold, |
||||
double maxVal, double minVal, int frameNum, bool updateBackgroundModel) : |
||||
frame_(frame), fgmask_(fgmask), nfeatures_(nfeatures), colors_(colors), weights_(weights), |
||||
maxFeatures_(maxFeatures), learningRate_(learningRate), numInitializationFrames_(numInitializationFrames), quantizationLevels_(quantizationLevels), |
||||
backgroundPrior_(backgroundPrior), decisionThreshold_(decisionThreshold), updateBackgroundModel_(updateBackgroundModel), |
||||
maxVal_(maxVal), minVal_(minVal), frameNum_(frameNum) |
||||
{ |
||||
} |
||||
|
||||
void operator() (const Range& range) const; |
||||
|
||||
private: |
||||
Mat frame_; |
||||
|
||||
mutable Mat_<uchar> fgmask_; |
||||
|
||||
mutable Mat_<int> nfeatures_; |
||||
mutable Mat_<unsigned int> colors_; |
||||
mutable Mat_<float> weights_; |
||||
|
||||
int maxFeatures_; |
||||
double learningRate_; |
||||
int numInitializationFrames_; |
||||
int quantizationLevels_; |
||||
double backgroundPrior_; |
||||
double decisionThreshold_; |
||||
bool updateBackgroundModel_; |
||||
|
||||
double maxVal_; |
||||
double minVal_; |
||||
int frameNum_; |
||||
}; |
||||
|
||||
void GMG_LoopBody::operator() (const Range& range) const |
||||
{ |
||||
typedef unsigned int (*func_t)(const void* src_, int x, int cn, double minVal, double maxVal, int quantizationLevels); |
||||
static const func_t funcs[] = |
||||
{ |
||||
Quantization<uchar>::apply, |
||||
Quantization<schar>::apply, |
||||
Quantization<ushort>::apply, |
||||
Quantization<short>::apply, |
||||
Quantization<int>::apply, |
||||
Quantization<float>::apply, |
||||
Quantization<double>::apply |
||||
}; |
||||
|
||||
const func_t func = funcs[frame_.depth()]; |
||||
CV_Assert(func != 0); |
||||
|
||||
const int cn = frame_.channels(); |
||||
|
||||
for (int y = range.start, featureIdx = y * frame_.cols; y < range.end; ++y) |
||||
{ |
||||
const uchar* frame_row = frame_.ptr(y); |
||||
int* nfeatures_row = nfeatures_[y]; |
||||
uchar* fgmask_row = fgmask_[y]; |
||||
|
||||
for (int x = 0; x < frame_.cols; ++x, ++featureIdx) |
||||
{ |
||||
int nfeatures = nfeatures_row[x]; |
||||
unsigned int* colors = colors_[featureIdx]; |
||||
float* weights = weights_[featureIdx]; |
||||
|
||||
unsigned int newFeatureColor = func(frame_row, x, cn, minVal_, maxVal_, quantizationLevels_); |
||||
|
||||
bool isForeground = false; |
||||
|
||||
if (frameNum_ >= numInitializationFrames_) |
||||
{ |
||||
// typical operation
|
||||
|
||||
const double weight = findFeature(newFeatureColor, colors, weights, nfeatures); |
||||
|
||||
// see Godbehere, Matsukawa, Goldberg (2012) for reasoning behind this implementation of Bayes rule
|
||||
const double posterior = (weight * backgroundPrior_) / (weight * backgroundPrior_ + (1.0 - weight) * (1.0 - backgroundPrior_)); |
||||
|
||||
isForeground = ((1.0 - posterior) > decisionThreshold_); |
||||
|
||||
// update histogram.
|
||||
|
||||
if (updateBackgroundModel_) |
||||
{ |
||||
for (int i = 0; i < nfeatures; ++i) |
||||
weights[i] *= (float)(1.0f - learningRate_); |
||||
|
||||
bool inserted = insertFeature(newFeatureColor, (float)learningRate_, colors, weights, nfeatures, maxFeatures_); |
||||
|
||||
if (inserted) |
||||
{ |
||||
normalizeHistogram(weights, nfeatures); |
||||
nfeatures_row[x] = nfeatures; |
||||
} |
||||
} |
||||
} |
||||
else if (updateBackgroundModel_) |
||||
{ |
||||
// training-mode update
|
||||
|
||||
insertFeature(newFeatureColor, 1.0f, colors, weights, nfeatures, maxFeatures_); |
||||
|
||||
if (frameNum_ == numInitializationFrames_ - 1) |
||||
normalizeHistogram(weights, nfeatures); |
||||
} |
||||
|
||||
fgmask_row[x] = (uchar)(-(schar)isForeground); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void BackgroundSubtractorGMGImpl::apply(InputArray _frame, OutputArray _fgmask, double newLearningRate) |
||||
{ |
||||
Mat frame = _frame.getMat(); |
||||
|
||||
CV_Assert(frame.depth() == CV_8U || frame.depth() == CV_16U || frame.depth() == CV_32F); |
||||
CV_Assert(frame.channels() == 1 || frame.channels() == 3 || frame.channels() == 4); |
||||
|
||||
if (newLearningRate != -1.0) |
||||
{ |
||||
CV_Assert(newLearningRate >= 0.0 && newLearningRate <= 1.0); |
||||
learningRate = newLearningRate; |
||||
} |
||||
|
||||
if (frame.size() != frameSize_) |
||||
{ |
||||
double minval = minVal_; |
||||
double maxval = maxVal_; |
||||
if( minVal_ == 0 && maxVal_ == 0 ) |
||||
{ |
||||
minval = 0; |
||||
maxval = frame.depth() == CV_8U ? 255.0 : frame.depth() == CV_16U ? std::numeric_limits<ushort>::max() : 1.0; |
||||
} |
||||
initialize(frame.size(), minval, maxval); |
||||
} |
||||
|
||||
_fgmask.create(frameSize_, CV_8UC1); |
||||
Mat fgmask = _fgmask.getMat(); |
||||
|
||||
GMG_LoopBody body(frame, fgmask, nfeatures_, colors_, weights_, |
||||
maxFeatures, learningRate, numInitializationFrames, quantizationLevels, backgroundPrior, decisionThreshold, |
||||
maxVal_, minVal_, frameNum_, updateBackgroundModel); |
||||
parallel_for_(Range(0, frame.rows), body, frame.total()/(double)(1<<16)); |
||||
|
||||
if (smoothingRadius > 0) |
||||
{ |
||||
medianBlur(fgmask, buf_, smoothingRadius); |
||||
swap(fgmask, buf_); |
||||
} |
||||
|
||||
// keep track of how many frames we have processed
|
||||
++frameNum_; |
||||
} |
||||
|
||||
void BackgroundSubtractorGMGImpl::release() |
||||
{ |
||||
frameSize_ = Size(); |
||||
|
||||
nfeatures_.release(); |
||||
colors_.release(); |
||||
weights_.release(); |
||||
buf_.release(); |
||||
} |
||||
|
||||
|
||||
Ptr<BackgroundSubtractorGMG> createBackgroundSubtractorGMG(int initializationFrames, double decisionThreshold) |
||||
{ |
||||
Ptr<BackgroundSubtractorGMG> bgfg = makePtr<BackgroundSubtractorGMGImpl>(); |
||||
bgfg->setNumFrames(initializationFrames); |
||||
bgfg->setDecisionThreshold(decisionThreshold); |
||||
|
||||
return bgfg; |
||||
} |
||||
|
||||
/*
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
CV_INIT_ALGORITHM(BackgroundSubtractorGMG, "BackgroundSubtractor.GMG", |
||||
obj.info()->addParam(obj, "maxFeatures", obj.maxFeatures,false,0,0, |
||||
"Maximum number of features to store in histogram. Harsh enforcement of sparsity constraint."); |
||||
obj.info()->addParam(obj, "learningRate", obj.learningRate,false,0,0, |
||||
"Adaptation rate of histogram. Close to 1, slow adaptation. Close to 0, fast adaptation, features forgotten quickly."); |
||||
obj.info()->addParam(obj, "initializationFrames", obj.numInitializationFrames,false,0,0, |
||||
"Number of frames to use to initialize histograms of pixels."); |
||||
obj.info()->addParam(obj, "quantizationLevels", obj.quantizationLevels,false,0,0, |
||||
"Number of discrete colors to be used in histograms. Up-front quantization."); |
||||
obj.info()->addParam(obj, "backgroundPrior", obj.backgroundPrior,false,0,0, |
||||
"Prior probability that each individual pixel is a background pixel."); |
||||
obj.info()->addParam(obj, "smoothingRadius", obj.smoothingRadius,false,0,0, |
||||
"Radius of smoothing kernel to filter noise from FG mask image."); |
||||
obj.info()->addParam(obj, "decisionThreshold", obj.decisionThreshold,false,0,0, |
||||
"Threshold for FG decision rule. Pixel is FG if posterior probability exceeds threshold."); |
||||
obj.info()->addParam(obj, "updateBackgroundModel", obj.updateBackgroundModel,false,0,0, |
||||
"Perform background model update."); |
||||
obj.info()->addParam(obj, "minVal", obj.minVal_,false,0,0, |
||||
"Minimum of the value range (mostly for regression testing)"); |
||||
obj.info()->addParam(obj, "maxVal", obj.maxVal_,false,0,0, |
||||
"Maximum of the value range (mostly for regression testing)"); |
||||
); |
||||
*/ |
||||
|
||||
} |
||||
} |
||||
|
@ -0,0 +1,52 @@ |
||||
/*
|
||||
|
||||
By downloading, copying, installing or using the software you agree to this |
||||
license. If you do not agree to this license, do not download, install, |
||||
copy or use the software. |
||||
|
||||
|
||||
License Agreement |
||||
For Open Source Computer Vision Library |
||||
(3-clause BSD License) |
||||
|
||||
Copyright (C) 2013, OpenCV Foundation, all rights reserved. |
||||
Third party copyrights are property of their respective owners. |
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, |
||||
are permitted provided that the following conditions are met: |
||||
|
||||
* Redistributions of source code must retain the above copyright notice, |
||||
this list of conditions and the following disclaimer. |
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice, |
||||
this list of conditions and the following disclaimer in the documentation |
||||
and/or other materials provided with the distribution. |
||||
|
||||
* Neither the names of the copyright holders nor the names of the contributors |
||||
may be used to endorse or promote products derived from this software |
||||
without specific prior written permission. |
||||
|
||||
This software is provided by the copyright holders and contributors "as is" and |
||||
any express or implied warranties, including, but not limited to, the implied |
||||
warranties of merchantability and fitness for a particular purpose are |
||||
disclaimed. In no event shall copyright holders or contributors be liable for |
||||
any direct, indirect, incidental, special, exemplary, or consequential damages |
||||
(including, but not limited to, procurement of substitute goods or services; |
||||
loss of use, data, or profits; or business interruption) however caused |
||||
and on any theory of liability, whether in contract, strict liability, |
||||
or tort (including negligence or otherwise) arising in any way out of |
||||
the use of this software, even if advised of the possibility of such damage. |
||||
|
||||
*/ |
||||
|
||||
#ifndef __OPENCV_BGSEGM_PRECOMP_HPP__ |
||||
#define __OPENCV_BGSEGM_PRECOMP_HPP__ |
||||
|
||||
#include <opencv2/bgsegm.hpp> |
||||
#include <opencv2/video.hpp> |
||||
#include <opencv2/imgproc.hpp> |
||||
|
||||
#include <algorithm> |
||||
#include <cmath> |
||||
|
||||
#endif |
@ -0,0 +1,138 @@ |
||||
/*
|
||||
* BackgroundSubtractorGBH_test.cpp |
||||
* |
||||
* Created on: Jun 14, 2012 |
||||
* Author: andrewgodbehere |
||||
*/ |
||||
|
||||
#include "test_precomp.hpp" |
||||
|
||||
using namespace cv; |
||||
using namespace cv::bgsegm; |
||||
|
||||
class CV_BackgroundSubtractorTest : public cvtest::BaseTest |
||||
{ |
||||
public: |
||||
CV_BackgroundSubtractorTest(); |
||||
protected: |
||||
void run(int); |
||||
}; |
||||
|
||||
CV_BackgroundSubtractorTest::CV_BackgroundSubtractorTest() |
||||
{ |
||||
} |
||||
|
||||
/**
|
||||
* This test checks the following: |
||||
* (i) BackgroundSubtractorGMG can operate with matrices of various types and sizes |
||||
* (ii) Training mode returns empty fgmask |
||||
* (iii) End of training mode, and anomalous frame yields every pixel detected as FG |
||||
*/ |
||||
void CV_BackgroundSubtractorTest::run(int) |
||||
{ |
||||
int code = cvtest::TS::OK; |
||||
RNG& rng = ts->get_rng(); |
||||
int type = ((unsigned int)rng)%7; //!< pick a random type, 0 - 6, defined in types_c.h
|
||||
int channels = 1 + ((unsigned int)rng)%4; //!< random number of channels from 1 to 4.
|
||||
int channelsAndType = CV_MAKETYPE(type,channels); |
||||
int width = 2 + ((unsigned int)rng)%98; //!< Mat will be 2 to 100 in width and height
|
||||
int height = 2 + ((unsigned int)rng)%98; |
||||
|
||||
Ptr<BackgroundSubtractorGMG> fgbg = createBackgroundSubtractorGMG(); |
||||
Mat fgmask; |
||||
|
||||
if (!fgbg) |
||||
CV_Error(Error::StsError,"Failed to create Algorithm\n"); |
||||
|
||||
/**
|
||||
* Set a few parameters |
||||
*/ |
||||
fgbg->setSmoothingRadius(7); |
||||
fgbg->setDecisionThreshold(0.7); |
||||
fgbg->setNumFrames(120); |
||||
|
||||
/**
|
||||
* Generate bounds for the values in the matrix for each type |
||||
*/ |
||||
double maxd = 0, mind = 0; |
||||
|
||||
/**
|
||||
* Max value for simulated images picked randomly in upper half of type range |
||||
* Min value for simulated images picked randomly in lower half of type range |
||||
*/ |
||||
if (type == CV_8U) |
||||
{ |
||||
uchar half = UCHAR_MAX/2; |
||||
maxd = (unsigned char)rng.uniform(half+32, UCHAR_MAX); |
||||
mind = (unsigned char)rng.uniform(0, half-32); |
||||
} |
||||
else if (type == CV_8S) |
||||
{ |
||||
maxd = (char)rng.uniform(32, CHAR_MAX); |
||||
mind = (char)rng.uniform(CHAR_MIN, -32); |
||||
} |
||||
else if (type == CV_16U) |
||||
{ |
||||
ushort half = USHRT_MAX/2; |
||||
maxd = (unsigned int)rng.uniform(half+32, USHRT_MAX); |
||||
mind = (unsigned int)rng.uniform(0, half-32); |
||||
} |
||||
else if (type == CV_16S) |
||||
{ |
||||
maxd = rng.uniform(32, SHRT_MAX); |
||||
mind = rng.uniform(SHRT_MIN, -32); |
||||
} |
||||
else if (type == CV_32S) |
||||
{ |
||||
maxd = rng.uniform(32, INT_MAX); |
||||
mind = rng.uniform(INT_MIN, -32); |
||||
} |
||||
else if (type == CV_32F) |
||||
{ |
||||
maxd = rng.uniform(32.0f, FLT_MAX); |
||||
mind = rng.uniform(-FLT_MAX, -32.0f); |
||||
} |
||||
else if (type == CV_64F) |
||||
{ |
||||
maxd = rng.uniform(32.0, DBL_MAX); |
||||
mind = rng.uniform(-DBL_MAX, -32.0); |
||||
} |
||||
|
||||
fgbg->setMinVal(mind); |
||||
fgbg->setMaxVal(maxd); |
||||
|
||||
Mat simImage = Mat::zeros(height, width, channelsAndType); |
||||
int numLearningFrames = 120; |
||||
for (int i = 0; i < numLearningFrames; ++i) |
||||
{ |
||||
/**
|
||||
* Genrate simulated "image" for any type. Values always confined to upper half of range. |
||||
*/ |
||||
rng.fill(simImage, RNG::UNIFORM, (mind + maxd)*0.5, maxd); |
||||
|
||||
/**
|
||||
* Feed simulated images into background subtractor |
||||
*/ |
||||
fgbg->apply(simImage,fgmask); |
||||
Mat fullbg = Mat::zeros(simImage.rows, simImage.cols, CV_8U); |
||||
|
||||
//! fgmask should be entirely background during training
|
||||
code = cvtest::cmpEps2( ts, fgmask, fullbg, 0, false, "The training foreground mask" ); |
||||
if (code < 0) |
||||
ts->set_failed_test_info( code ); |
||||
} |
||||
//! generate last image, distinct from training images
|
||||
rng.fill(simImage, RNG::UNIFORM, mind, maxd); |
||||
|
||||
fgbg->apply(simImage,fgmask); |
||||
//! now fgmask should be entirely foreground
|
||||
Mat fullfg = 255*Mat::ones(simImage.rows, simImage.cols, CV_8U); |
||||
code = cvtest::cmpEps2( ts, fgmask, fullfg, 255, false, "The final foreground mask" ); |
||||
if (code < 0) |
||||
{ |
||||
ts->set_failed_test_info( code ); |
||||
} |
||||
|
||||
} |
||||
|
||||
TEST(VIDEO_BGSUBGMG, accuracy) { CV_BackgroundSubtractorTest test; test.safe_run(); } |
@ -0,0 +1,3 @@ |
||||
#include "test_precomp.hpp" |
||||
|
||||
CV_TEST_MAIN("cv") |
@ -0,0 +1,18 @@ |
||||
#ifdef __GNUC__ |
||||
# pragma GCC diagnostic ignored "-Wmissing-declarations" |
||||
# if defined __clang__ || defined __APPLE__ |
||||
# pragma GCC diagnostic ignored "-Wmissing-prototypes" |
||||
# pragma GCC diagnostic ignored "-Wextra" |
||||
# endif |
||||
#endif |
||||
|
||||
#ifndef __OPENCV_TEST_PRECOMP_HPP__ |
||||
#define __OPENCV_TEST_PRECOMP_HPP__ |
||||
|
||||
#include <iostream> |
||||
#include "opencv2/ts.hpp" |
||||
#include "opencv2/imgproc.hpp" |
||||
#include "opencv2/bgsegm.hpp" |
||||
#include "opencv2/imgcodecs.hpp" |
||||
|
||||
#endif |
@ -0,0 +1,2 @@ |
||||
set(the_description "Face recognition etc") |
||||
ocv_define_module(face opencv_core opencv_imgproc) |
@ -0,0 +1,10 @@ |
||||
*************************************** |
||||
contrib. Contributed/Experimental Stuff |
||||
*************************************** |
||||
|
||||
The module contains some recently added functionality that has not been stabilized, or functionality that is considered optional. |
||||
|
||||
.. toctree:: |
||||
:maxdepth: 2 |
||||
|
||||
FaceRecognizer Documentation <facerec/index> |
@ -0,0 +1,400 @@ |
||||
/home/philipp/facerec/data/at/s13/2.pgm;12 |
||||
/home/philipp/facerec/data/at/s13/7.pgm;12 |
||||
/home/philipp/facerec/data/at/s13/6.pgm;12 |
||||
/home/philipp/facerec/data/at/s13/9.pgm;12 |
||||
/home/philipp/facerec/data/at/s13/5.pgm;12 |
||||
/home/philipp/facerec/data/at/s13/3.pgm;12 |
||||
/home/philipp/facerec/data/at/s13/4.pgm;12 |
||||
/home/philipp/facerec/data/at/s13/10.pgm;12 |
||||
/home/philipp/facerec/data/at/s13/8.pgm;12 |
||||
/home/philipp/facerec/data/at/s13/1.pgm;12 |
||||
/home/philipp/facerec/data/at/s17/2.pgm;16 |
||||
/home/philipp/facerec/data/at/s17/7.pgm;16 |
||||
/home/philipp/facerec/data/at/s17/6.pgm;16 |
||||
/home/philipp/facerec/data/at/s17/9.pgm;16 |
||||
/home/philipp/facerec/data/at/s17/5.pgm;16 |
||||
/home/philipp/facerec/data/at/s17/3.pgm;16 |
||||
/home/philipp/facerec/data/at/s17/4.pgm;16 |
||||
/home/philipp/facerec/data/at/s17/10.pgm;16 |
||||
/home/philipp/facerec/data/at/s17/8.pgm;16 |
||||
/home/philipp/facerec/data/at/s17/1.pgm;16 |
||||
/home/philipp/facerec/data/at/s32/2.pgm;31 |
||||
/home/philipp/facerec/data/at/s32/7.pgm;31 |
||||
/home/philipp/facerec/data/at/s32/6.pgm;31 |
||||
/home/philipp/facerec/data/at/s32/9.pgm;31 |
||||
/home/philipp/facerec/data/at/s32/5.pgm;31 |
||||
/home/philipp/facerec/data/at/s32/3.pgm;31 |
||||
/home/philipp/facerec/data/at/s32/4.pgm;31 |
||||
/home/philipp/facerec/data/at/s32/10.pgm;31 |
||||
/home/philipp/facerec/data/at/s32/8.pgm;31 |
||||
/home/philipp/facerec/data/at/s32/1.pgm;31 |
||||
/home/philipp/facerec/data/at/s10/2.pgm;9 |
||||
/home/philipp/facerec/data/at/s10/7.pgm;9 |
||||
/home/philipp/facerec/data/at/s10/6.pgm;9 |
||||
/home/philipp/facerec/data/at/s10/9.pgm;9 |
||||
/home/philipp/facerec/data/at/s10/5.pgm;9 |
||||
/home/philipp/facerec/data/at/s10/3.pgm;9 |
||||
/home/philipp/facerec/data/at/s10/4.pgm;9 |
||||
/home/philipp/facerec/data/at/s10/10.pgm;9 |
||||
/home/philipp/facerec/data/at/s10/8.pgm;9 |
||||
/home/philipp/facerec/data/at/s10/1.pgm;9 |
||||
/home/philipp/facerec/data/at/s27/2.pgm;26 |
||||
/home/philipp/facerec/data/at/s27/7.pgm;26 |
||||
/home/philipp/facerec/data/at/s27/6.pgm;26 |
||||
/home/philipp/facerec/data/at/s27/9.pgm;26 |
||||
/home/philipp/facerec/data/at/s27/5.pgm;26 |
||||
/home/philipp/facerec/data/at/s27/3.pgm;26 |
||||
/home/philipp/facerec/data/at/s27/4.pgm;26 |
||||
/home/philipp/facerec/data/at/s27/10.pgm;26 |
||||
/home/philipp/facerec/data/at/s27/8.pgm;26 |
||||
/home/philipp/facerec/data/at/s27/1.pgm;26 |
||||
/home/philipp/facerec/data/at/s5/2.pgm;4 |
||||
/home/philipp/facerec/data/at/s5/7.pgm;4 |
||||
/home/philipp/facerec/data/at/s5/6.pgm;4 |
||||
/home/philipp/facerec/data/at/s5/9.pgm;4 |
||||
/home/philipp/facerec/data/at/s5/5.pgm;4 |
||||
/home/philipp/facerec/data/at/s5/3.pgm;4 |
||||
/home/philipp/facerec/data/at/s5/4.pgm;4 |
||||
/home/philipp/facerec/data/at/s5/10.pgm;4 |
||||
/home/philipp/facerec/data/at/s5/8.pgm;4 |
||||
/home/philipp/facerec/data/at/s5/1.pgm;4 |
||||
/home/philipp/facerec/data/at/s20/2.pgm;19 |
||||
/home/philipp/facerec/data/at/s20/7.pgm;19 |
||||
/home/philipp/facerec/data/at/s20/6.pgm;19 |
||||
/home/philipp/facerec/data/at/s20/9.pgm;19 |
||||
/home/philipp/facerec/data/at/s20/5.pgm;19 |
||||
/home/philipp/facerec/data/at/s20/3.pgm;19 |
||||
/home/philipp/facerec/data/at/s20/4.pgm;19 |
||||
/home/philipp/facerec/data/at/s20/10.pgm;19 |
||||
/home/philipp/facerec/data/at/s20/8.pgm;19 |
||||
/home/philipp/facerec/data/at/s20/1.pgm;19 |
||||
/home/philipp/facerec/data/at/s30/2.pgm;29 |
||||
/home/philipp/facerec/data/at/s30/7.pgm;29 |
||||
/home/philipp/facerec/data/at/s30/6.pgm;29 |
||||
/home/philipp/facerec/data/at/s30/9.pgm;29 |
||||
/home/philipp/facerec/data/at/s30/5.pgm;29 |
||||
/home/philipp/facerec/data/at/s30/3.pgm;29 |
||||
/home/philipp/facerec/data/at/s30/4.pgm;29 |
||||
/home/philipp/facerec/data/at/s30/10.pgm;29 |
||||
/home/philipp/facerec/data/at/s30/8.pgm;29 |
||||
/home/philipp/facerec/data/at/s30/1.pgm;29 |
||||
/home/philipp/facerec/data/at/s39/2.pgm;38 |
||||
/home/philipp/facerec/data/at/s39/7.pgm;38 |
||||
/home/philipp/facerec/data/at/s39/6.pgm;38 |
||||
/home/philipp/facerec/data/at/s39/9.pgm;38 |
||||
/home/philipp/facerec/data/at/s39/5.pgm;38 |
||||
/home/philipp/facerec/data/at/s39/3.pgm;38 |
||||
/home/philipp/facerec/data/at/s39/4.pgm;38 |
||||
/home/philipp/facerec/data/at/s39/10.pgm;38 |
||||
/home/philipp/facerec/data/at/s39/8.pgm;38 |
||||
/home/philipp/facerec/data/at/s39/1.pgm;38 |
||||
/home/philipp/facerec/data/at/s35/2.pgm;34 |
||||
/home/philipp/facerec/data/at/s35/7.pgm;34 |
||||
/home/philipp/facerec/data/at/s35/6.pgm;34 |
||||
/home/philipp/facerec/data/at/s35/9.pgm;34 |
||||
/home/philipp/facerec/data/at/s35/5.pgm;34 |
||||
/home/philipp/facerec/data/at/s35/3.pgm;34 |
||||
/home/philipp/facerec/data/at/s35/4.pgm;34 |
||||
/home/philipp/facerec/data/at/s35/10.pgm;34 |
||||
/home/philipp/facerec/data/at/s35/8.pgm;34 |
||||
/home/philipp/facerec/data/at/s35/1.pgm;34 |
||||
/home/philipp/facerec/data/at/s23/2.pgm;22 |
||||
/home/philipp/facerec/data/at/s23/7.pgm;22 |
||||
/home/philipp/facerec/data/at/s23/6.pgm;22 |
||||
/home/philipp/facerec/data/at/s23/9.pgm;22 |
||||
/home/philipp/facerec/data/at/s23/5.pgm;22 |
||||
/home/philipp/facerec/data/at/s23/3.pgm;22 |
||||
/home/philipp/facerec/data/at/s23/4.pgm;22 |
||||
/home/philipp/facerec/data/at/s23/10.pgm;22 |
||||
/home/philipp/facerec/data/at/s23/8.pgm;22 |
||||
/home/philipp/facerec/data/at/s23/1.pgm;22 |
||||
/home/philipp/facerec/data/at/s4/2.pgm;3 |
||||
/home/philipp/facerec/data/at/s4/7.pgm;3 |
||||
/home/philipp/facerec/data/at/s4/6.pgm;3 |
||||
/home/philipp/facerec/data/at/s4/9.pgm;3 |
||||
/home/philipp/facerec/data/at/s4/5.pgm;3 |
||||
/home/philipp/facerec/data/at/s4/3.pgm;3 |
||||
/home/philipp/facerec/data/at/s4/4.pgm;3 |
||||
/home/philipp/facerec/data/at/s4/10.pgm;3 |
||||
/home/philipp/facerec/data/at/s4/8.pgm;3 |
||||
/home/philipp/facerec/data/at/s4/1.pgm;3 |
||||
/home/philipp/facerec/data/at/s9/2.pgm;8 |
||||
/home/philipp/facerec/data/at/s9/7.pgm;8 |
||||
/home/philipp/facerec/data/at/s9/6.pgm;8 |
||||
/home/philipp/facerec/data/at/s9/9.pgm;8 |
||||
/home/philipp/facerec/data/at/s9/5.pgm;8 |
||||
/home/philipp/facerec/data/at/s9/3.pgm;8 |
||||
/home/philipp/facerec/data/at/s9/4.pgm;8 |
||||
/home/philipp/facerec/data/at/s9/10.pgm;8 |
||||
/home/philipp/facerec/data/at/s9/8.pgm;8 |
||||
/home/philipp/facerec/data/at/s9/1.pgm;8 |
||||
/home/philipp/facerec/data/at/s37/2.pgm;36 |
||||
/home/philipp/facerec/data/at/s37/7.pgm;36 |
||||
/home/philipp/facerec/data/at/s37/6.pgm;36 |
||||
/home/philipp/facerec/data/at/s37/9.pgm;36 |
||||
/home/philipp/facerec/data/at/s37/5.pgm;36 |
||||
/home/philipp/facerec/data/at/s37/3.pgm;36 |
||||
/home/philipp/facerec/data/at/s37/4.pgm;36 |
||||
/home/philipp/facerec/data/at/s37/10.pgm;36 |
||||
/home/philipp/facerec/data/at/s37/8.pgm;36 |
||||
/home/philipp/facerec/data/at/s37/1.pgm;36 |
||||
/home/philipp/facerec/data/at/s24/2.pgm;23 |
||||
/home/philipp/facerec/data/at/s24/7.pgm;23 |
||||
/home/philipp/facerec/data/at/s24/6.pgm;23 |
||||
/home/philipp/facerec/data/at/s24/9.pgm;23 |
||||
/home/philipp/facerec/data/at/s24/5.pgm;23 |
||||
/home/philipp/facerec/data/at/s24/3.pgm;23 |
||||
/home/philipp/facerec/data/at/s24/4.pgm;23 |
||||
/home/philipp/facerec/data/at/s24/10.pgm;23 |
||||
/home/philipp/facerec/data/at/s24/8.pgm;23 |
||||
/home/philipp/facerec/data/at/s24/1.pgm;23 |
||||
/home/philipp/facerec/data/at/s19/2.pgm;18 |
||||
/home/philipp/facerec/data/at/s19/7.pgm;18 |
||||
/home/philipp/facerec/data/at/s19/6.pgm;18 |
||||
/home/philipp/facerec/data/at/s19/9.pgm;18 |
||||
/home/philipp/facerec/data/at/s19/5.pgm;18 |
||||
/home/philipp/facerec/data/at/s19/3.pgm;18 |
||||
/home/philipp/facerec/data/at/s19/4.pgm;18 |
||||
/home/philipp/facerec/data/at/s19/10.pgm;18 |
||||
/home/philipp/facerec/data/at/s19/8.pgm;18 |
||||
/home/philipp/facerec/data/at/s19/1.pgm;18 |
||||
/home/philipp/facerec/data/at/s8/2.pgm;7 |
||||
/home/philipp/facerec/data/at/s8/7.pgm;7 |
||||
/home/philipp/facerec/data/at/s8/6.pgm;7 |
||||
/home/philipp/facerec/data/at/s8/9.pgm;7 |
||||
/home/philipp/facerec/data/at/s8/5.pgm;7 |
||||
/home/philipp/facerec/data/at/s8/3.pgm;7 |
||||
/home/philipp/facerec/data/at/s8/4.pgm;7 |
||||
/home/philipp/facerec/data/at/s8/10.pgm;7 |
||||
/home/philipp/facerec/data/at/s8/8.pgm;7 |
||||
/home/philipp/facerec/data/at/s8/1.pgm;7 |
||||
/home/philipp/facerec/data/at/s21/2.pgm;20 |
||||
/home/philipp/facerec/data/at/s21/7.pgm;20 |
||||
/home/philipp/facerec/data/at/s21/6.pgm;20 |
||||
/home/philipp/facerec/data/at/s21/9.pgm;20 |
||||
/home/philipp/facerec/data/at/s21/5.pgm;20 |
||||
/home/philipp/facerec/data/at/s21/3.pgm;20 |
||||
/home/philipp/facerec/data/at/s21/4.pgm;20 |
||||
/home/philipp/facerec/data/at/s21/10.pgm;20 |
||||
/home/philipp/facerec/data/at/s21/8.pgm;20 |
||||
/home/philipp/facerec/data/at/s21/1.pgm;20 |
||||
/home/philipp/facerec/data/at/s1/2.pgm;0 |
||||
/home/philipp/facerec/data/at/s1/7.pgm;0 |
||||
/home/philipp/facerec/data/at/s1/6.pgm;0 |
||||
/home/philipp/facerec/data/at/s1/9.pgm;0 |
||||
/home/philipp/facerec/data/at/s1/5.pgm;0 |
||||
/home/philipp/facerec/data/at/s1/3.pgm;0 |
||||
/home/philipp/facerec/data/at/s1/4.pgm;0 |
||||
/home/philipp/facerec/data/at/s1/10.pgm;0 |
||||
/home/philipp/facerec/data/at/s1/8.pgm;0 |
||||
/home/philipp/facerec/data/at/s1/1.pgm;0 |
||||
/home/philipp/facerec/data/at/s7/2.pgm;6 |
||||
/home/philipp/facerec/data/at/s7/7.pgm;6 |
||||
/home/philipp/facerec/data/at/s7/6.pgm;6 |
||||
/home/philipp/facerec/data/at/s7/9.pgm;6 |
||||
/home/philipp/facerec/data/at/s7/5.pgm;6 |
||||
/home/philipp/facerec/data/at/s7/3.pgm;6 |
||||
/home/philipp/facerec/data/at/s7/4.pgm;6 |
||||
/home/philipp/facerec/data/at/s7/10.pgm;6 |
||||
/home/philipp/facerec/data/at/s7/8.pgm;6 |
||||
/home/philipp/facerec/data/at/s7/1.pgm;6 |
||||
/home/philipp/facerec/data/at/s16/2.pgm;15 |
||||
/home/philipp/facerec/data/at/s16/7.pgm;15 |
||||
/home/philipp/facerec/data/at/s16/6.pgm;15 |
||||
/home/philipp/facerec/data/at/s16/9.pgm;15 |
||||
/home/philipp/facerec/data/at/s16/5.pgm;15 |
||||
/home/philipp/facerec/data/at/s16/3.pgm;15 |
||||
/home/philipp/facerec/data/at/s16/4.pgm;15 |
||||
/home/philipp/facerec/data/at/s16/10.pgm;15 |
||||
/home/philipp/facerec/data/at/s16/8.pgm;15 |
||||
/home/philipp/facerec/data/at/s16/1.pgm;15 |
||||
/home/philipp/facerec/data/at/s36/2.pgm;35 |
||||
/home/philipp/facerec/data/at/s36/7.pgm;35 |
||||
/home/philipp/facerec/data/at/s36/6.pgm;35 |
||||
/home/philipp/facerec/data/at/s36/9.pgm;35 |
||||
/home/philipp/facerec/data/at/s36/5.pgm;35 |
||||
/home/philipp/facerec/data/at/s36/3.pgm;35 |
||||
/home/philipp/facerec/data/at/s36/4.pgm;35 |
||||
/home/philipp/facerec/data/at/s36/10.pgm;35 |
||||
/home/philipp/facerec/data/at/s36/8.pgm;35 |
||||
/home/philipp/facerec/data/at/s36/1.pgm;35 |
||||
/home/philipp/facerec/data/at/s25/2.pgm;24 |
||||
/home/philipp/facerec/data/at/s25/7.pgm;24 |
||||
/home/philipp/facerec/data/at/s25/6.pgm;24 |
||||
/home/philipp/facerec/data/at/s25/9.pgm;24 |
||||
/home/philipp/facerec/data/at/s25/5.pgm;24 |
||||
/home/philipp/facerec/data/at/s25/3.pgm;24 |
||||
/home/philipp/facerec/data/at/s25/4.pgm;24 |
||||
/home/philipp/facerec/data/at/s25/10.pgm;24 |
||||
/home/philipp/facerec/data/at/s25/8.pgm;24 |
||||
/home/philipp/facerec/data/at/s25/1.pgm;24 |
||||
/home/philipp/facerec/data/at/s14/2.pgm;13 |
||||
/home/philipp/facerec/data/at/s14/7.pgm;13 |
||||
/home/philipp/facerec/data/at/s14/6.pgm;13 |
||||
/home/philipp/facerec/data/at/s14/9.pgm;13 |
||||
/home/philipp/facerec/data/at/s14/5.pgm;13 |
||||
/home/philipp/facerec/data/at/s14/3.pgm;13 |
||||
/home/philipp/facerec/data/at/s14/4.pgm;13 |
||||
/home/philipp/facerec/data/at/s14/10.pgm;13 |
||||
/home/philipp/facerec/data/at/s14/8.pgm;13 |
||||
/home/philipp/facerec/data/at/s14/1.pgm;13 |
||||
/home/philipp/facerec/data/at/s34/2.pgm;33 |
||||
/home/philipp/facerec/data/at/s34/7.pgm;33 |
||||
/home/philipp/facerec/data/at/s34/6.pgm;33 |
||||
/home/philipp/facerec/data/at/s34/9.pgm;33 |
||||
/home/philipp/facerec/data/at/s34/5.pgm;33 |
||||
/home/philipp/facerec/data/at/s34/3.pgm;33 |
||||
/home/philipp/facerec/data/at/s34/4.pgm;33 |
||||
/home/philipp/facerec/data/at/s34/10.pgm;33 |
||||
/home/philipp/facerec/data/at/s34/8.pgm;33 |
||||
/home/philipp/facerec/data/at/s34/1.pgm;33 |
||||
/home/philipp/facerec/data/at/s11/2.pgm;10 |
||||
/home/philipp/facerec/data/at/s11/7.pgm;10 |
||||
/home/philipp/facerec/data/at/s11/6.pgm;10 |
||||
/home/philipp/facerec/data/at/s11/9.pgm;10 |
||||
/home/philipp/facerec/data/at/s11/5.pgm;10 |
||||
/home/philipp/facerec/data/at/s11/3.pgm;10 |
||||
/home/philipp/facerec/data/at/s11/4.pgm;10 |
||||
/home/philipp/facerec/data/at/s11/10.pgm;10 |
||||
/home/philipp/facerec/data/at/s11/8.pgm;10 |
||||
/home/philipp/facerec/data/at/s11/1.pgm;10 |
||||
/home/philipp/facerec/data/at/s26/2.pgm;25 |
||||
/home/philipp/facerec/data/at/s26/7.pgm;25 |
||||
/home/philipp/facerec/data/at/s26/6.pgm;25 |
||||
/home/philipp/facerec/data/at/s26/9.pgm;25 |
||||
/home/philipp/facerec/data/at/s26/5.pgm;25 |
||||
/home/philipp/facerec/data/at/s26/3.pgm;25 |
||||
/home/philipp/facerec/data/at/s26/4.pgm;25 |
||||
/home/philipp/facerec/data/at/s26/10.pgm;25 |
||||
/home/philipp/facerec/data/at/s26/8.pgm;25 |
||||
/home/philipp/facerec/data/at/s26/1.pgm;25 |
||||
/home/philipp/facerec/data/at/s18/2.pgm;17 |
||||
/home/philipp/facerec/data/at/s18/7.pgm;17 |
||||
/home/philipp/facerec/data/at/s18/6.pgm;17 |
||||
/home/philipp/facerec/data/at/s18/9.pgm;17 |
||||
/home/philipp/facerec/data/at/s18/5.pgm;17 |
||||
/home/philipp/facerec/data/at/s18/3.pgm;17 |
||||
/home/philipp/facerec/data/at/s18/4.pgm;17 |
||||
/home/philipp/facerec/data/at/s18/10.pgm;17 |
||||
/home/philipp/facerec/data/at/s18/8.pgm;17 |
||||
/home/philipp/facerec/data/at/s18/1.pgm;17 |
||||
/home/philipp/facerec/data/at/s29/2.pgm;28 |
||||
/home/philipp/facerec/data/at/s29/7.pgm;28 |
||||
/home/philipp/facerec/data/at/s29/6.pgm;28 |
||||
/home/philipp/facerec/data/at/s29/9.pgm;28 |
||||
/home/philipp/facerec/data/at/s29/5.pgm;28 |
||||
/home/philipp/facerec/data/at/s29/3.pgm;28 |
||||
/home/philipp/facerec/data/at/s29/4.pgm;28 |
||||
/home/philipp/facerec/data/at/s29/10.pgm;28 |
||||
/home/philipp/facerec/data/at/s29/8.pgm;28 |
||||
/home/philipp/facerec/data/at/s29/1.pgm;28 |
||||
/home/philipp/facerec/data/at/s33/2.pgm;32 |
||||
/home/philipp/facerec/data/at/s33/7.pgm;32 |
||||
/home/philipp/facerec/data/at/s33/6.pgm;32 |
||||
/home/philipp/facerec/data/at/s33/9.pgm;32 |
||||
/home/philipp/facerec/data/at/s33/5.pgm;32 |
||||
/home/philipp/facerec/data/at/s33/3.pgm;32 |
||||
/home/philipp/facerec/data/at/s33/4.pgm;32 |
||||
/home/philipp/facerec/data/at/s33/10.pgm;32 |
||||
/home/philipp/facerec/data/at/s33/8.pgm;32 |
||||
/home/philipp/facerec/data/at/s33/1.pgm;32 |
||||
/home/philipp/facerec/data/at/s12/2.pgm;11 |
||||
/home/philipp/facerec/data/at/s12/7.pgm;11 |
||||
/home/philipp/facerec/data/at/s12/6.pgm;11 |
||||
/home/philipp/facerec/data/at/s12/9.pgm;11 |
||||
/home/philipp/facerec/data/at/s12/5.pgm;11 |
||||
/home/philipp/facerec/data/at/s12/3.pgm;11 |
||||
/home/philipp/facerec/data/at/s12/4.pgm;11 |
||||
/home/philipp/facerec/data/at/s12/10.pgm;11 |
||||
/home/philipp/facerec/data/at/s12/8.pgm;11 |
||||
/home/philipp/facerec/data/at/s12/1.pgm;11 |
||||
/home/philipp/facerec/data/at/s6/2.pgm;5 |
||||
/home/philipp/facerec/data/at/s6/7.pgm;5 |
||||
/home/philipp/facerec/data/at/s6/6.pgm;5 |
||||
/home/philipp/facerec/data/at/s6/9.pgm;5 |
||||
/home/philipp/facerec/data/at/s6/5.pgm;5 |
||||
/home/philipp/facerec/data/at/s6/3.pgm;5 |
||||
/home/philipp/facerec/data/at/s6/4.pgm;5 |
||||
/home/philipp/facerec/data/at/s6/10.pgm;5 |
||||
/home/philipp/facerec/data/at/s6/8.pgm;5 |
||||
/home/philipp/facerec/data/at/s6/1.pgm;5 |
||||
/home/philipp/facerec/data/at/s22/2.pgm;21 |
||||
/home/philipp/facerec/data/at/s22/7.pgm;21 |
||||
/home/philipp/facerec/data/at/s22/6.pgm;21 |
||||
/home/philipp/facerec/data/at/s22/9.pgm;21 |
||||
/home/philipp/facerec/data/at/s22/5.pgm;21 |
||||
/home/philipp/facerec/data/at/s22/3.pgm;21 |
||||
/home/philipp/facerec/data/at/s22/4.pgm;21 |
||||
/home/philipp/facerec/data/at/s22/10.pgm;21 |
||||
/home/philipp/facerec/data/at/s22/8.pgm;21 |
||||
/home/philipp/facerec/data/at/s22/1.pgm;21 |
||||
/home/philipp/facerec/data/at/s15/2.pgm;14 |
||||
/home/philipp/facerec/data/at/s15/7.pgm;14 |
||||
/home/philipp/facerec/data/at/s15/6.pgm;14 |
||||
/home/philipp/facerec/data/at/s15/9.pgm;14 |
||||
/home/philipp/facerec/data/at/s15/5.pgm;14 |
||||
/home/philipp/facerec/data/at/s15/3.pgm;14 |
||||
/home/philipp/facerec/data/at/s15/4.pgm;14 |
||||
/home/philipp/facerec/data/at/s15/10.pgm;14 |
||||
/home/philipp/facerec/data/at/s15/8.pgm;14 |
||||
/home/philipp/facerec/data/at/s15/1.pgm;14 |
||||
/home/philipp/facerec/data/at/s2/2.pgm;1 |
||||
/home/philipp/facerec/data/at/s2/7.pgm;1 |
||||
/home/philipp/facerec/data/at/s2/6.pgm;1 |
||||
/home/philipp/facerec/data/at/s2/9.pgm;1 |
||||
/home/philipp/facerec/data/at/s2/5.pgm;1 |
||||
/home/philipp/facerec/data/at/s2/3.pgm;1 |
||||
/home/philipp/facerec/data/at/s2/4.pgm;1 |
||||
/home/philipp/facerec/data/at/s2/10.pgm;1 |
||||
/home/philipp/facerec/data/at/s2/8.pgm;1 |
||||
/home/philipp/facerec/data/at/s2/1.pgm;1 |
||||
/home/philipp/facerec/data/at/s31/2.pgm;30 |
||||
/home/philipp/facerec/data/at/s31/7.pgm;30 |
||||
/home/philipp/facerec/data/at/s31/6.pgm;30 |
||||
/home/philipp/facerec/data/at/s31/9.pgm;30 |
||||
/home/philipp/facerec/data/at/s31/5.pgm;30 |
||||
/home/philipp/facerec/data/at/s31/3.pgm;30 |
||||
/home/philipp/facerec/data/at/s31/4.pgm;30 |
||||
/home/philipp/facerec/data/at/s31/10.pgm;30 |
||||
/home/philipp/facerec/data/at/s31/8.pgm;30 |
||||
/home/philipp/facerec/data/at/s31/1.pgm;30 |
||||
/home/philipp/facerec/data/at/s28/2.pgm;27 |
||||
/home/philipp/facerec/data/at/s28/7.pgm;27 |
||||
/home/philipp/facerec/data/at/s28/6.pgm;27 |
||||
/home/philipp/facerec/data/at/s28/9.pgm;27 |
||||
/home/philipp/facerec/data/at/s28/5.pgm;27 |
||||
/home/philipp/facerec/data/at/s28/3.pgm;27 |
||||
/home/philipp/facerec/data/at/s28/4.pgm;27 |
||||
/home/philipp/facerec/data/at/s28/10.pgm;27 |
||||
/home/philipp/facerec/data/at/s28/8.pgm;27 |
||||
/home/philipp/facerec/data/at/s28/1.pgm;27 |
||||
/home/philipp/facerec/data/at/s40/2.pgm;39 |
||||
/home/philipp/facerec/data/at/s40/7.pgm;39 |
||||
/home/philipp/facerec/data/at/s40/6.pgm;39 |
||||
/home/philipp/facerec/data/at/s40/9.pgm;39 |
||||
/home/philipp/facerec/data/at/s40/5.pgm;39 |
||||
/home/philipp/facerec/data/at/s40/3.pgm;39 |
||||
/home/philipp/facerec/data/at/s40/4.pgm;39 |
||||
/home/philipp/facerec/data/at/s40/10.pgm;39 |
||||
/home/philipp/facerec/data/at/s40/8.pgm;39 |
||||
/home/philipp/facerec/data/at/s40/1.pgm;39 |
||||
/home/philipp/facerec/data/at/s3/2.pgm;2 |
||||
/home/philipp/facerec/data/at/s3/7.pgm;2 |
||||
/home/philipp/facerec/data/at/s3/6.pgm;2 |
||||
/home/philipp/facerec/data/at/s3/9.pgm;2 |
||||
/home/philipp/facerec/data/at/s3/5.pgm;2 |
||||
/home/philipp/facerec/data/at/s3/3.pgm;2 |
||||
/home/philipp/facerec/data/at/s3/4.pgm;2 |
||||
/home/philipp/facerec/data/at/s3/10.pgm;2 |
||||
/home/philipp/facerec/data/at/s3/8.pgm;2 |
||||
/home/philipp/facerec/data/at/s3/1.pgm;2 |
||||
/home/philipp/facerec/data/at/s38/2.pgm;37 |
||||
/home/philipp/facerec/data/at/s38/7.pgm;37 |
||||
/home/philipp/facerec/data/at/s38/6.pgm;37 |
||||
/home/philipp/facerec/data/at/s38/9.pgm;37 |
||||
/home/philipp/facerec/data/at/s38/5.pgm;37 |
||||
/home/philipp/facerec/data/at/s38/3.pgm;37 |
||||
/home/philipp/facerec/data/at/s38/4.pgm;37 |
||||
/home/philipp/facerec/data/at/s38/10.pgm;37 |
||||
/home/philipp/facerec/data/at/s38/8.pgm;37 |
||||
/home/philipp/facerec/data/at/s38/1.pgm;37 |
@ -0,0 +1,412 @@ |
||||
FaceRecognizer |
||||
============== |
||||
|
||||
.. highlight:: cpp |
||||
|
||||
.. Sample code:: |
||||
|
||||
* An example using the FaceRecognizer class can be found at opencv_source_code/samples/cpp/facerec_demo.cpp |
||||
|
||||
* (Python) An example using the FaceRecognizer class can be found at opencv_source_code/samples/python2/facerec_demo.py |
||||
|
||||
FaceRecognizer |
||||
-------------- |
||||
|
||||
.. ocv:class:: FaceRecognizer : public Algorithm |
||||
|
||||
All face recognition models in OpenCV are derived from the abstract base class :ocv:class:`FaceRecognizer`, which provides |
||||
a unified access to all face recongition algorithms in OpenCV. :: |
||||
|
||||
class FaceRecognizer : public Algorithm |
||||
{ |
||||
public: |
||||
//! virtual destructor |
||||
virtual ~FaceRecognizer() {} |
||||
|
||||
// Trains a FaceRecognizer. |
||||
virtual void train(InputArray src, InputArray labels) = 0; |
||||
|
||||
// Updates a FaceRecognizer. |
||||
virtual void update(InputArrayOfArrays src, InputArray labels); |
||||
|
||||
// Gets a prediction from a FaceRecognizer. |
||||
virtual int predict(InputArray src) const = 0; |
||||
|
||||
// Predicts the label and confidence for a given sample. |
||||
virtual void predict(InputArray src, int &label, double &confidence) const = 0; |
||||
|
||||
// Serializes this object to a given filename. |
||||
virtual void save(const String& filename) const; |
||||
|
||||
// Deserializes this object from a given filename. |
||||
virtual void load(const String& filename); |
||||
|
||||
// Serializes this object to a given cv::FileStorage. |
||||
virtual void save(FileStorage& fs) const = 0; |
||||
|
||||
// Deserializes this object from a given cv::FileStorage. |
||||
virtual void load(const FileStorage& fs) = 0; |
||||
|
||||
// Sets additional string info for the label |
||||
virtual void setLabelInfo(int label, const String& strInfo); |
||||
|
||||
// Gets string info by label |
||||
virtual String getLabelInfo(int label); |
||||
|
||||
// Gets labels by string info |
||||
virtual vector<int> getLabelsByString(const String& str); |
||||
}; |
||||
|
||||
|
||||
Description |
||||
+++++++++++ |
||||
|
||||
I'll go a bit more into detail explaining :ocv:class:`FaceRecognizer`, because it doesn't look like a powerful interface at first sight. But: Every :ocv:class:`FaceRecognizer` is an :ocv:class:`Algorithm`, so you can easily get/set all model internals (if allowed by the implementation). :ocv:class:`Algorithm` is a relatively new OpenCV concept, which is available since the 2.4 release. I suggest you take a look at its description. |
||||
|
||||
:ocv:class:`Algorithm` provides the following features for all derived classes: |
||||
|
||||
* So called “virtual constructor”. That is, each Algorithm derivative is registered at program start and you can get the list of registered algorithms and create instance of a particular algorithm by its name (see :ocv:func:`Algorithm::create`). If you plan to add your own algorithms, it is good practice to add a unique prefix to your algorithms to distinguish them from other algorithms. |
||||
|
||||
* Setting/Retrieving algorithm parameters by name. If you used video capturing functionality from OpenCV highgui module, you are probably familar with :ocv:cfunc:`cvSetCaptureProperty`, :ocv:cfunc:`cvGetCaptureProperty`, :ocv:func:`VideoCapture::set` and :ocv:func:`VideoCapture::get`. :ocv:class:`Algorithm` provides similar method where instead of integer id's you specify the parameter names as text Strings. See :ocv:func:`Algorithm::set` and :ocv:func:`Algorithm::get` for details. |
||||
|
||||
* Reading and writing parameters from/to XML or YAML files. Every Algorithm derivative can store all its parameters and then read them back. There is no need to re-implement it each time. |
||||
|
||||
Moreover every :ocv:class:`FaceRecognizer` supports the: |
||||
|
||||
* **Training** of a :ocv:class:`FaceRecognizer` with :ocv:func:`FaceRecognizer::train` on a given set of images (your face database!). |
||||
|
||||
* **Prediction** of a given sample image, that means a face. The image is given as a :ocv:class:`Mat`. |
||||
|
||||
* **Loading/Saving** the model state from/to a given XML or YAML. |
||||
|
||||
* **Setting/Getting labels info**, that is stored as a string. String labels info is useful for keeping names of the recognized people. |
||||
|
||||
.. note:: When using the FaceRecognizer interface in combination with Python, please stick to Python 2. Some underlying scripts like create_csv will not work in other versions, like Python 3. |
||||
|
||||
Setting the Thresholds |
||||
+++++++++++++++++++++++ |
||||
|
||||
Sometimes you run into the situation, when you want to apply a threshold on the prediction. A common scenario in face recognition is to tell, whether a face belongs to the training dataset or if it is unknown. You might wonder, why there's no public API in :ocv:class:`FaceRecognizer` to set the threshold for the prediction, but rest assured: It's supported. It just means there's no generic way in an abstract class to provide an interface for setting/getting the thresholds of *every possible* :ocv:class:`FaceRecognizer` algorithm. The appropriate place to set the thresholds is in the constructor of the specific :ocv:class:`FaceRecognizer` and since every :ocv:class:`FaceRecognizer` is a :ocv:class:`Algorithm` (see above), you can get/set the thresholds at runtime! |
||||
|
||||
Here is an example of setting a threshold for the Eigenfaces method, when creating the model: |
||||
|
||||
.. code-block:: cpp |
||||
|
||||
// Let's say we want to keep 10 Eigenfaces and have a threshold value of 10.0 |
||||
int num_components = 10; |
||||
double threshold = 10.0; |
||||
// Then if you want to have a cv::FaceRecognizer with a confidence threshold, |
||||
// create the concrete implementation with the appropiate parameters: |
||||
Ptr<FaceRecognizer> model = createEigenFaceRecognizer(num_components, threshold); |
||||
|
||||
Sometimes it's impossible to train the model, just to experiment with threshold values. Thanks to :ocv:class:`Algorithm` it's possible to set internal model thresholds during runtime. Let's see how we would set/get the prediction for the Eigenface model, we've created above: |
||||
|
||||
.. code-block:: cpp |
||||
|
||||
// The following line reads the threshold from the Eigenfaces model: |
||||
double current_threshold = model->getDouble("threshold"); |
||||
// And this line sets the threshold to 0.0: |
||||
model->set("threshold", 0.0); |
||||
|
||||
If you've set the threshold to ``0.0`` as we did above, then: |
||||
|
||||
.. code-block:: cpp |
||||
|
||||
// |
||||
Mat img = imread("person1/3.jpg", CV_LOAD_IMAGE_GRAYSCALE); |
||||
// Get a prediction from the model. Note: We've set a threshold of 0.0 above, |
||||
// since the distance is almost always larger than 0.0, you'll get -1 as |
||||
// label, which indicates, this face is unknown |
||||
int predicted_label = model->predict(img); |
||||
// ... |
||||
|
||||
is going to yield ``-1`` as predicted label, which states this face is unknown. |
||||
|
||||
Getting the name of a FaceRecognizer |
||||
+++++++++++++++++++++++++++++++++++++ |
||||
|
||||
Since every :ocv:class:`FaceRecognizer` is a :ocv:class:`Algorithm`, you can use :ocv:func:`Algorithm::name` to get the name of a :ocv:class:`FaceRecognizer`: |
||||
|
||||
.. code-block:: cpp |
||||
|
||||
// Create a FaceRecognizer: |
||||
Ptr<FaceRecognizer> model = createEigenFaceRecognizer(); |
||||
// And here's how to get its name: |
||||
String name = model->name(); |
||||
|
||||
|
||||
FaceRecognizer::train |
||||
--------------------- |
||||
|
||||
Trains a FaceRecognizer with given data and associated labels. |
||||
|
||||
.. ocv:function:: void FaceRecognizer::train( InputArrayOfArrays src, InputArray labels ) = 0 |
||||
|
||||
:param src: The training images, that means the faces you want to learn. The data has to be given as a ``vector<Mat>``. |
||||
|
||||
:param labels: The labels corresponding to the images have to be given either as a ``vector<int>`` or a |
||||
|
||||
The following source code snippet shows you how to learn a Fisherfaces model on a given set of images. The images are read with :ocv:func:`imread` and pushed into a ``std::vector<Mat>``. The labels of each image are stored within a ``std::vector<int>`` (you could also use a :ocv:class:`Mat` of type `CV_32SC1`). Think of the label as the subject (the person) this image belongs to, so same subjects (persons) should have the same label. For the available :ocv:class:`FaceRecognizer` you don't have to pay any attention to the order of the labels, just make sure same persons have the same label: |
||||
|
||||
.. code-block:: cpp |
||||
|
||||
// holds images and labels |
||||
vector<Mat> images; |
||||
vector<int> labels; |
||||
// images for first person |
||||
images.push_back(imread("person0/0.jpg", CV_LOAD_IMAGE_GRAYSCALE)); labels.push_back(0); |
||||
images.push_back(imread("person0/1.jpg", CV_LOAD_IMAGE_GRAYSCALE)); labels.push_back(0); |
||||
images.push_back(imread("person0/2.jpg", CV_LOAD_IMAGE_GRAYSCALE)); labels.push_back(0); |
||||
// images for second person |
||||
images.push_back(imread("person1/0.jpg", CV_LOAD_IMAGE_GRAYSCALE)); labels.push_back(1); |
||||
images.push_back(imread("person1/1.jpg", CV_LOAD_IMAGE_GRAYSCALE)); labels.push_back(1); |
||||
images.push_back(imread("person1/2.jpg", CV_LOAD_IMAGE_GRAYSCALE)); labels.push_back(1); |
||||
|
||||
Now that you have read some images, we can create a new :ocv:class:`FaceRecognizer`. In this example I'll create a Fisherfaces model and decide to keep all of the possible Fisherfaces: |
||||
|
||||
.. code-block:: cpp |
||||
|
||||
// Create a new Fisherfaces model and retain all available Fisherfaces, |
||||
// this is the most common usage of this specific FaceRecognizer: |
||||
// |
||||
Ptr<FaceRecognizer> model = createFisherFaceRecognizer(); |
||||
|
||||
And finally train it on the given dataset (the face images and labels): |
||||
|
||||
.. code-block:: cpp |
||||
|
||||
// This is the common interface to train all of the available cv::FaceRecognizer |
||||
// implementations: |
||||
// |
||||
model->train(images, labels); |
||||
|
||||
FaceRecognizer::update |
||||
---------------------- |
||||
|
||||
Updates a FaceRecognizer with given data and associated labels. |
||||
|
||||
.. ocv:function:: void FaceRecognizer::update( InputArrayOfArrays src, InputArray labels ) |
||||
|
||||
:param src: The training images, that means the faces you want to learn. The data has to be given as a ``vector<Mat>``. |
||||
|
||||
:param labels: The labels corresponding to the images have to be given either as a ``vector<int>`` or a |
||||
|
||||
This method updates a (probably trained) :ocv:class:`FaceRecognizer`, but only if the algorithm supports it. The Local Binary Patterns Histograms (LBPH) recognizer (see :ocv:func:`createLBPHFaceRecognizer`) can be updated. For the Eigenfaces and Fisherfaces method, this is algorithmically not possible and you have to re-estimate the model with :ocv:func:`FaceRecognizer::train`. In any case, a call to train empties the existing model and learns a new model, while update does not delete any model data. |
||||
|
||||
.. code-block:: cpp |
||||
|
||||
// Create a new LBPH model (it can be updated) and use the default parameters, |
||||
// this is the most common usage of this specific FaceRecognizer: |
||||
// |
||||
Ptr<FaceRecognizer> model = createLBPHFaceRecognizer(); |
||||
// This is the common interface to train all of the available cv::FaceRecognizer |
||||
// implementations: |
||||
// |
||||
model->train(images, labels); |
||||
// Some containers to hold new image: |
||||
vector<Mat> newImages; |
||||
vector<int> newLabels; |
||||
// You should add some images to the containers: |
||||
// |
||||
// ... |
||||
// |
||||
// Now updating the model is as easy as calling: |
||||
model->update(newImages,newLabels); |
||||
// This will preserve the old model data and extend the existing model |
||||
// with the new features extracted from newImages! |
||||
|
||||
Calling update on an Eigenfaces model (see :ocv:func:`createEigenFaceRecognizer`), which doesn't support updating, will throw an error similar to: |
||||
|
||||
.. code-block:: none |
||||
|
||||
OpenCV Error: The function/feature is not implemented (This FaceRecognizer (FaceRecognizer.Eigenfaces) does not support updating, you have to use FaceRecognizer::train to update it.) in update, file /home/philipp/git/opencv/modules/contrib/src/facerec.cpp, line 305 |
||||
terminate called after throwing an instance of 'cv::Exception' |
||||
|
||||
Please note: The :ocv:class:`FaceRecognizer` does not store your training images, because this would be very memory intense and it's not the responsibility of te :ocv:class:`FaceRecognizer` to do so. The caller is responsible for maintaining the dataset, he want to work with. |
||||
|
||||
FaceRecognizer::predict |
||||
----------------------- |
||||
|
||||
.. ocv:function:: int FaceRecognizer::predict( InputArray src ) const = 0 |
||||
.. ocv:function:: void FaceRecognizer::predict( InputArray src, int & label, double & confidence ) const = 0 |
||||
|
||||
Predicts a label and associated confidence (e.g. distance) for a given input image. |
||||
|
||||
:param src: Sample image to get a prediction from. |
||||
:param label: The predicted label for the given image. |
||||
:param confidence: Associated confidence (e.g. distance) for the predicted label. |
||||
|
||||
The suffix ``const`` means that prediction does not affect the internal model |
||||
state, so the method can be safely called from within different threads. |
||||
|
||||
The following example shows how to get a prediction from a trained model: |
||||
|
||||
.. code-block:: cpp |
||||
|
||||
using namespace cv; |
||||
// Do your initialization here (create the cv::FaceRecognizer model) ... |
||||
// ... |
||||
// Read in a sample image: |
||||
Mat img = imread("person1/3.jpg", CV_LOAD_IMAGE_GRAYSCALE); |
||||
// And get a prediction from the cv::FaceRecognizer: |
||||
int predicted = model->predict(img); |
||||
|
||||
Or to get a prediction and the associated confidence (e.g. distance): |
||||
|
||||
.. code-block:: cpp |
||||
|
||||
using namespace cv; |
||||
// Do your initialization here (create the cv::FaceRecognizer model) ... |
||||
// ... |
||||
Mat img = imread("person1/3.jpg", CV_LOAD_IMAGE_GRAYSCALE); |
||||
// Some variables for the predicted label and associated confidence (e.g. distance): |
||||
int predicted_label = -1; |
||||
double predicted_confidence = 0.0; |
||||
// Get the prediction and associated confidence from the model |
||||
model->predict(img, predicted_label, predicted_confidence); |
||||
|
||||
FaceRecognizer::save |
||||
-------------------- |
||||
|
||||
Saves a :ocv:class:`FaceRecognizer` and its model state. |
||||
|
||||
.. ocv:function:: void FaceRecognizer::save(const String& filename) const |
||||
|
||||
Saves this model to a given filename, either as XML or YAML. |
||||
|
||||
:param filename: The filename to store this :ocv:class:`FaceRecognizer` to (either XML/YAML). |
||||
|
||||
.. ocv:function:: void FaceRecognizer::save(FileStorage& fs) const |
||||
|
||||
Saves this model to a given :ocv:class:`FileStorage`. |
||||
|
||||
:param fs: The :ocv:class:`FileStorage` to store this :ocv:class:`FaceRecognizer` to. |
||||
|
||||
|
||||
Every :ocv:class:`FaceRecognizer` overwrites ``FaceRecognizer::save(FileStorage& fs)`` |
||||
to save the internal model state. ``FaceRecognizer::save(const String& filename)`` saves |
||||
the state of a model to the given filename. |
||||
|
||||
The suffix ``const`` means that prediction does not affect the internal model |
||||
state, so the method can be safely called from within different threads. |
||||
|
||||
FaceRecognizer::load |
||||
-------------------- |
||||
|
||||
Loads a :ocv:class:`FaceRecognizer` and its model state. |
||||
|
||||
.. ocv:function:: void FaceRecognizer::load( const String& filename ) |
||||
.. ocv:function:: void FaceRecognizer::load( const FileStorage& fs ) = 0 |
||||
|
||||
Loads a persisted model and state from a given XML or YAML file . Every |
||||
:ocv:class:`FaceRecognizer` has to overwrite ``FaceRecognizer::load(FileStorage& fs)`` |
||||
to enable loading the model state. ``FaceRecognizer::load(FileStorage& fs)`` in |
||||
turn gets called by ``FaceRecognizer::load(const String& filename)``, to ease |
||||
saving a model. |
||||
|
||||
FaceRecognizer::setLabelInfo |
||||
----------------------------- |
||||
|
||||
Sets string info for the specified model's label. |
||||
.. ocv:function:: void FaceRecognizer::setLabelInfo(int label, const String& strInfo) |
||||
|
||||
The string info is replaced by the provided value if it was set before for the specified label. |
||||
|
||||
FaceRecognizer::getLabelInfo |
||||
---------------------------- |
||||
|
||||
Gets string information by label. |
||||
.. ocv:function:: String FaceRecognizer::getLabelInfo(int label) |
||||
|
||||
If an unknown label id is provided or there is no label information associated with the specified label id the method returns an empty string. |
||||
|
||||
FaceRecognizer::getLabelsByString |
||||
--------------------------------- |
||||
Gets vector of labels by string. |
||||
|
||||
.. ocv:function:: vector<int> FaceRecognizer::getLabelsByString(const String& str) |
||||
|
||||
The function searches for the labels containing the specified sub-string in the associated string info. |
||||
|
||||
createEigenFaceRecognizer |
||||
------------------------- |
||||
|
||||
.. ocv:function:: Ptr<FaceRecognizer> createEigenFaceRecognizer(int num_components = 0, double threshold = DBL_MAX) |
||||
|
||||
:param num_components: The number of components (read: Eigenfaces) kept for this Principal Component Analysis. As a hint: There's no rule how many components (read: Eigenfaces) should be kept for good reconstruction capabilities. It is based on your input data, so experiment with the number. Keeping 80 components should almost always be sufficient. |
||||
|
||||
:param threshold: The threshold applied in the prediction. |
||||
|
||||
Notes: |
||||
++++++ |
||||
|
||||
* Training and prediction must be done on grayscale images, use :ocv:func:`cvtColor` to convert between the color spaces. |
||||
* **THE EIGENFACES METHOD MAKES THE ASSUMPTION, THAT THE TRAINING AND TEST IMAGES ARE OF EQUAL SIZE.** (caps-lock, because I got so many mails asking for this). You have to make sure your input data has the correct shape, else a meaningful exception is thrown. Use :ocv:func:`resize` to resize the images. |
||||
* This model does not support updating. |
||||
|
||||
Model internal data: |
||||
++++++++++++++++++++ |
||||
|
||||
* ``num_components`` see :ocv:func:`createEigenFaceRecognizer`. |
||||
* ``threshold`` see :ocv:func:`createEigenFaceRecognizer`. |
||||
* ``eigenvalues`` The eigenvalues for this Principal Component Analysis (ordered descending). |
||||
* ``eigenvectors`` The eigenvectors for this Principal Component Analysis (ordered by their eigenvalue). |
||||
* ``mean`` The sample mean calculated from the training data. |
||||
* ``projections`` The projections of the training data. |
||||
* ``labels`` The threshold applied in the prediction. If the distance to the nearest neighbor is larger than the threshold, this method returns -1. |
||||
|
||||
createFisherFaceRecognizer |
||||
-------------------------- |
||||
|
||||
.. ocv:function:: Ptr<FaceRecognizer> createFisherFaceRecognizer(int num_components = 0, double threshold = DBL_MAX) |
||||
|
||||
:param num_components: The number of components (read: Fisherfaces) kept for this Linear Discriminant Analysis with the Fisherfaces criterion. It's useful to keep all components, that means the number of your classes ``c`` (read: subjects, persons you want to recognize). If you leave this at the default (``0``) or set it to a value less-equal ``0`` or greater ``(c-1)``, it will be set to the correct number ``(c-1)`` automatically. |
||||
|
||||
:param threshold: The threshold applied in the prediction. If the distance to the nearest neighbor is larger than the threshold, this method returns -1. |
||||
|
||||
Notes: |
||||
++++++ |
||||
|
||||
* Training and prediction must be done on grayscale images, use :ocv:func:`cvtColor` to convert between the color spaces. |
||||
* **THE FISHERFACES METHOD MAKES THE ASSUMPTION, THAT THE TRAINING AND TEST IMAGES ARE OF EQUAL SIZE.** (caps-lock, because I got so many mails asking for this). You have to make sure your input data has the correct shape, else a meaningful exception is thrown. Use :ocv:func:`resize` to resize the images. |
||||
* This model does not support updating. |
||||
|
||||
Model internal data: |
||||
++++++++++++++++++++ |
||||
|
||||
* ``num_components`` see :ocv:func:`createFisherFaceRecognizer`. |
||||
* ``threshold`` see :ocv:func:`createFisherFaceRecognizer`. |
||||
* ``eigenvalues`` The eigenvalues for this Linear Discriminant Analysis (ordered descending). |
||||
* ``eigenvectors`` The eigenvectors for this Linear Discriminant Analysis (ordered by their eigenvalue). |
||||
* ``mean`` The sample mean calculated from the training data. |
||||
* ``projections`` The projections of the training data. |
||||
* ``labels`` The labels corresponding to the projections. |
||||
|
||||
|
||||
createLBPHFaceRecognizer |
||||
------------------------- |
||||
|
||||
.. ocv:function:: Ptr<FaceRecognizer> createLBPHFaceRecognizer(int radius=1, int neighbors=8, int grid_x=8, int grid_y=8, double threshold = DBL_MAX) |
||||
|
||||
:param radius: The radius used for building the Circular Local Binary Pattern. The greater the radius, the |
||||
:param neighbors: The number of sample points to build a Circular Local Binary Pattern from. An appropriate value is to use `` 8`` sample points. Keep in mind: the more sample points you include, the higher the computational cost. |
||||
:param grid_x: The number of cells in the horizontal direction, ``8`` is a common value used in publications. The more cells, the finer the grid, the higher the dimensionality of the resulting feature vector. |
||||
:param grid_y: The number of cells in the vertical direction, ``8`` is a common value used in publications. The more cells, the finer the grid, the higher the dimensionality of the resulting feature vector. |
||||
:param threshold: The threshold applied in the prediction. If the distance to the nearest neighbor is larger than the threshold, this method returns -1. |
||||
|
||||
Notes: |
||||
++++++ |
||||
|
||||
* The Circular Local Binary Patterns (used in training and prediction) expect the data given as grayscale images, use :ocv:func:`cvtColor` to convert between the color spaces. |
||||
* This model supports updating. |
||||
|
||||
Model internal data: |
||||
++++++++++++++++++++ |
||||
|
||||
* ``radius`` see :ocv:func:`createLBPHFaceRecognizer`. |
||||
* ``neighbors`` see :ocv:func:`createLBPHFaceRecognizer`. |
||||
* ``grid_x`` see :ocv:func:`createLBPHFaceRecognizer`. |
||||
* ``grid_y`` see :ocv:func:`createLBPHFaceRecognizer`. |
||||
* ``threshold`` see :ocv:func:`createLBPHFaceRecognizer`. |
||||
* ``histograms`` Local Binary Patterns Histograms calculated from the given training data (empty if none was given). |
||||
* ``labels`` Labels corresponding to the calculated Local Binary Patterns Histograms. |
@ -0,0 +1,86 @@ |
||||
Changelog |
||||
========= |
||||
|
||||
Release 0.05 |
||||
------------ |
||||
|
||||
This library is now included in the official OpenCV distribution (from 2.4 on). |
||||
The :ocv:class`FaceRecognizer` is now an :ocv:class:`Algorithm`, which better fits into the overall |
||||
OpenCV API. |
||||
|
||||
To reduce the confusion on user side and minimize my work, libfacerec and OpenCV |
||||
have been synchronized and are now based on the same interfaces and implementation. |
||||
|
||||
The library now has an extensive documentation: |
||||
|
||||
* The API is explained in detail and with a lot of code examples. |
||||
* The face recognition guide I had written for Python and GNU Octave/MATLAB has been adapted to the new OpenCV C++ ``cv::FaceRecognizer``. |
||||
* A tutorial for gender classification with Fisherfaces. |
||||
* A tutorial for face recognition in videos (e.g. webcam). |
||||
|
||||
|
||||
Release highlights |
||||
++++++++++++++++++ |
||||
|
||||
* There are no single highlights to pick from, this release is a highlight itself. |
||||
|
||||
Release 0.04 |
||||
------------ |
||||
|
||||
This version is fully Windows-compatible and works with OpenCV 2.3.1. Several |
||||
bugfixes, but none influenced the recognition rate. |
||||
|
||||
Release highlights |
||||
++++++++++++++++++ |
||||
|
||||
* A whole lot of exceptions with meaningful error messages. |
||||
* A tutorial for Windows users: `http://bytefish.de/blog/opencv_visual_studio_and_libfacerec <http://bytefish.de/blog/opencv_visual_studio_and_libfacerec>`_ |
||||
|
||||
|
||||
Release 0.03 |
||||
------------ |
||||
|
||||
Reworked the library to provide separate implementations in cpp files, because |
||||
it's the preferred way of contributing OpenCV libraries. This means the library |
||||
is not header-only anymore. Slight API changes were done, please see the |
||||
documentation for details. |
||||
|
||||
Release highlights |
||||
++++++++++++++++++ |
||||
|
||||
* New Unit Tests (for LBP Histograms) make the library more robust. |
||||
* Added more documentation. |
||||
|
||||
|
||||
Release 0.02 |
||||
------------ |
||||
|
||||
Reworked the library to provide separate implementations in cpp files, because |
||||
it's the preferred way of contributing OpenCV libraries. This means the library |
||||
is not header-only anymore. Slight API changes were done, please see the |
||||
documentation for details. |
||||
|
||||
Release highlights |
||||
++++++++++++++++++ |
||||
|
||||
* New Unit Tests (for LBP Histograms) make the library more robust. |
||||
* Added a documentation and changelog in reStructuredText. |
||||
|
||||
Release 0.01 |
||||
------------ |
||||
|
||||
Initial release as header-only library. |
||||
|
||||
Release highlights |
||||
++++++++++++++++++ |
||||
|
||||
* Colormaps for OpenCV to enhance the visualization. |
||||
* Face Recognition algorithms implemented: |
||||
|
||||
* Eigenfaces [TP91]_ |
||||
* Fisherfaces [BHK97]_ |
||||
* Local Binary Patterns Histograms [AHP04]_ |
||||
|
||||
* Added persistence facilities to store the models with a common API. |
||||
* Unit Tests (using `gtest <http://code.google.com/p/googletest/>`_). |
||||
* Providing a CMakeLists.txt to enable easy cross-platform building. |
@ -0,0 +1,628 @@ |
||||
Face Recognition with OpenCV |
||||
############################ |
||||
|
||||
.. contents:: Table of Contents |
||||
:depth: 3 |
||||
|
||||
Introduction |
||||
============ |
||||
|
||||
`OpenCV (Open Source Computer Vision) <http://opencv.org>`_ is a popular computer vision library started by `Intel <http://www.intel.com>`_ in 1999. The cross-platform library sets its focus on real-time image processing and includes patent-free implementations of the latest computer vision algorithms. In 2008 `Willow Garage <http://www.willowgarage.com>`_ took over support and OpenCV 2.3.1 now comes with a programming interface to C, C++, `Python <http://www.python.org>`_ and `Android <http://www.android.com>`_. OpenCV is released under a BSD license so it is used in academic projects and commercial products alike. |
||||
|
||||
OpenCV 2.4 now comes with the very new :ocv:class:`FaceRecognizer` class for face recognition, so you can start experimenting with face recognition right away. This document is the guide I've wished for, when I was working myself into face recognition. It shows you how to perform face recognition with :ocv:class:`FaceRecognizer` in OpenCV (with full source code listings) and gives you an introduction into the algorithms behind. I'll also show how to create the visualizations you can find in many publications, because a lot of people asked for. |
||||
|
||||
The currently available algorithms are: |
||||
|
||||
* Eigenfaces (see :ocv:func:`createEigenFaceRecognizer`) |
||||
* Fisherfaces (see :ocv:func:`createFisherFaceRecognizer`) |
||||
* Local Binary Patterns Histograms (see :ocv:func:`createLBPHFaceRecognizer`) |
||||
|
||||
You don't need to copy and paste the source code examples from this page, because they are available in the ``src`` folder coming with this documentation. If you have built OpenCV with the samples turned on, chances are good you have them compiled already! Although it might be interesting for very advanced users, I've decided to leave the implementation details out as I am afraid they confuse new users. |
||||
|
||||
All code in this document is released under the `BSD license <http://www.opensource.org/licenses/bsd-license>`_, so feel free to use it for your projects. |
||||
|
||||
Face Recognition |
||||
================ |
||||
|
||||
Face recognition is an easy task for humans. Experiments in [Tu06]_ have shown, that even one to three day old babies are able to distinguish between known faces. So how hard could it be for a computer? It turns out we know little about human recognition to date. Are inner features (eyes, nose, mouth) or outer features (head shape, hairline) used for a successful face recognition? How do we analyze an image and how does the brain encode it? It was shown by `David Hubel <http://en.wikipedia.org/wiki/David_H._Hubel>`_ and `Torsten Wiesel <http://en.wikipedia.org/wiki/Torsten_Wiesel>`_, that our brain has specialized nerve cells responding to specific local features of a scene, such as lines, edges, angles or movement. Since we don't see the world as scattered pieces, our visual cortex must somehow combine the different sources of information into useful patterns. Automatic face recognition is all about extracting those meaningful features from an image, putting them into a useful representation and performing some kind of classification on them. |
||||
|
||||
Face recognition based on the geometric features of a face is probably the most intuitive approach to face recognition. One of the first automated face recognition systems was described in [Kanade73]_: marker points (position of eyes, ears, nose, ...) were used to build a feature vector (distance between the points, angle between them, ...). The recognition was performed by calculating the euclidean distance between feature vectors of a probe and reference image. Such a method is robust against changes in illumination by its nature, but has a huge drawback: the accurate registration of the marker points is complicated, even with state of the art algorithms. Some of the latest work on geometric face recognition was carried out in [Bru92]_. A 22-dimensional feature vector was used and experiments on large datasets have shown, that geometrical features alone my not carry enough information for face recognition. |
||||
|
||||
The Eigenfaces method described in [TP91]_ took a holistic approach to face recognition: A facial image is a point from a high-dimensional image space and a lower-dimensional representation is found, where classification becomes easy. The lower-dimensional subspace is found with Principal Component Analysis, which identifies the axes with maximum variance. While this kind of transformation is optimal from a reconstruction standpoint, it doesn't take any class labels into account. Imagine a situation where the variance is generated from external sources, let it be light. The axes with maximum variance do not necessarily contain any discriminative information at all, hence a classification becomes impossible. So a class-specific projection with a Linear Discriminant Analysis was applied to face recognition in [BHK97]_. The basic idea is to minimize the variance within a class, while maximizing the variance between the classes at the same time. |
||||
|
||||
Recently various methods for a local feature extraction emerged. To avoid the high-dimensionality of the input data only local regions of an image are described, the extracted features are (hopefully) more robust against partial occlusion, illumation and small sample size. Algorithms used for a local feature extraction are Gabor Wavelets ([Wiskott97]_), Discrete Cosinus Transform ([Messer06]_) and Local Binary Patterns ([AHP04]_). It's still an open research question what's the best way to preserve spatial information when applying a local feature extraction, because spatial information is potentially useful information. |
||||
|
||||
Face Database |
||||
============== |
||||
|
||||
Let's get some data to experiment with first. I don't want to do a toy example here. We are doing face recognition, so you'll need some face images! You can either create your own dataset or start with one of the available face databases, `http://face-rec.org/databases/ <http://face-rec.org/databases>`_ gives you an up-to-date overview. Three interesting databases are (parts of the description are quoted from `http://face-rec.org <http://face-rec.org>`_): |
||||
|
||||
* `AT&T Facedatabase <http://www.cl.cam.ac.uk/research/dtg/attarchive/facedatabase.html>`_ The AT&T Facedatabase, sometimes also referred to as *ORL Database of Faces*, contains ten different images of each of 40 distinct subjects. For some subjects, the images were taken at different times, varying the lighting, facial expressions (open / closed eyes, smiling / not smiling) and facial details (glasses / no glasses). All the images were taken against a dark homogeneous background with the subjects in an upright, frontal position (with tolerance for some side movement). |
||||
|
||||
* `Yale Facedatabase A <http://vision.ucsd.edu/content/yale-face-database>`_, also known as Yalefaces. The AT&T Facedatabase is good for initial tests, but it's a fairly easy database. The Eigenfaces method already has a 97% recognition rate on it, so you won't see any great improvements with other algorithms. The Yale Facedatabase A (also known as Yalefaces) is a more appropriate dataset for initial experiments, because the recognition problem is harder. The database consists of 15 people (14 male, 1 female) each with 11 grayscale images sized :math:`320 \times 243` pixel. There are changes in the light conditions (center light, left light, right light), facial expressions (happy, normal, sad, sleepy, surprised, wink) and glasses (glasses, no-glasses). |
||||
|
||||
The original images are not cropped and aligned. Please look into the :ref:`appendixft` for a Python script, that does the job for you. |
||||
|
||||
* `Extended Yale Facedatabase B <http://vision.ucsd.edu/~leekc/ExtYaleDatabase/ExtYaleB.html>`_ The Extended Yale Facedatabase B contains 2414 images of 38 different people in its cropped version. The focus of this database is set on extracting features that are robust to illumination, the images have almost no variation in emotion/occlusion/... . I personally think, that this dataset is too large for the experiments I perform in this document. You better use the `AT&T Facedatabase <http://www.cl.cam.ac.uk/research/dtg/attarchive/facedatabase.html>`_ for intial testing. A first version of the Yale Facedatabase B was used in [BHK97]_ to see how the Eigenfaces and Fisherfaces method perform under heavy illumination changes. [Lee05]_ used the same setup to take 16128 images of 28 people. The Extended Yale Facedatabase B is the merge of the two databases, which is now known as Extended Yalefacedatabase B. |
||||
|
||||
Preparing the data |
||||
------------------- |
||||
|
||||
Once we have acquired some data, we'll need to read it in our program. In the demo applications I have decided to read the images from a very simple CSV file. Why? Because it's the simplest platform-independent approach I can think of. However, if you know a simpler solution please ping me about it. Basically all the CSV file needs to contain are lines composed of a ``filename`` followed by a ``;`` followed by the ``label`` (as *integer number*), making up a line like this: |
||||
|
||||
.. code-block:: none |
||||
|
||||
/path/to/image.ext;0 |
||||
|
||||
Let's dissect the line. ``/path/to/image.ext`` is the path to an image, probably something like this if you are in Windows: ``C:/faces/person0/image0.jpg``. Then there is the separator ``;`` and finally we assign the label ``0`` to the image. Think of the label as the subject (the person) this image belongs to, so same subjects (persons) should have the same label. |
||||
|
||||
Download the AT&T Facedatabase from AT&T Facedatabase and the corresponding CSV file from at.txt, which looks like this (file is without ... of course): |
||||
|
||||
.. code-block:: none |
||||
|
||||
./at/s1/1.pgm;0 |
||||
./at/s1/2.pgm;0 |
||||
... |
||||
./at/s2/1.pgm;1 |
||||
./at/s2/2.pgm;1 |
||||
... |
||||
./at/s40/1.pgm;39 |
||||
./at/s40/2.pgm;39 |
||||
|
||||
Imagine I have extracted the files to ``D:/data/at`` and have downloaded the CSV file to ``D:/data/at.txt``. Then you would simply need to Search & Replace ``./`` with ``D:/data/``. You can do that in an editor of your choice, every sufficiently advanced editor can do this. Once you have a CSV file with valid filenames and labels, you can run any of the demos by passing the path to the CSV file as parameter: |
||||
|
||||
.. code-block:: none |
||||
|
||||
facerec_demo.exe D:/data/at.txt |
||||
|
||||
Creating the CSV File |
||||
+++++++++++++++++++++ |
||||
|
||||
You don't really want to create the CSV file by hand. I have prepared you a little Python script ``create_csv.py`` (you find it at ``src/create_csv.py`` coming with this tutorial) that automatically creates you a CSV file. If you have your images in hierarchie like this (``/basepath/<subject>/<image.ext>``): |
||||
|
||||
.. code-block:: none |
||||
|
||||
philipp@mango:~/facerec/data/at$ tree |
||||
. |
||||
|-- s1 |
||||
| |-- 1.pgm |
||||
| |-- ... |
||||
| |-- 10.pgm |
||||
|-- s2 |
||||
| |-- 1.pgm |
||||
| |-- ... |
||||
| |-- 10.pgm |
||||
... |
||||
|-- s40 |
||||
| |-- 1.pgm |
||||
| |-- ... |
||||
| |-- 10.pgm |
||||
|
||||
|
||||
Then simply call create_csv.py with the path to the folder, just like this and you could save the output: |
||||
|
||||
.. code-block:: none |
||||
|
||||
philipp@mango:~/facerec/data$ python create_csv.py |
||||
at/s13/2.pgm;0 |
||||
at/s13/7.pgm;0 |
||||
at/s13/6.pgm;0 |
||||
at/s13/9.pgm;0 |
||||
at/s13/5.pgm;0 |
||||
at/s13/3.pgm;0 |
||||
at/s13/4.pgm;0 |
||||
at/s13/10.pgm;0 |
||||
at/s13/8.pgm;0 |
||||
at/s13/1.pgm;0 |
||||
at/s17/2.pgm;1 |
||||
at/s17/7.pgm;1 |
||||
at/s17/6.pgm;1 |
||||
at/s17/9.pgm;1 |
||||
at/s17/5.pgm;1 |
||||
at/s17/3.pgm;1 |
||||
[...] |
||||
|
||||
Please see the :ref:`appendixft` for additional informations. |
||||
|
||||
Eigenfaces |
||||
========== |
||||
|
||||
The problem with the image representation we are given is its high dimensionality. Two-dimensional :math:`p \times q` grayscale images span a :math:`m = pq`-dimensional vector space, so an image with :math:`100 \times 100` pixels lies in a :math:`10,000`-dimensional image space already. The question is: Are all dimensions equally useful for us? We can only make a decision if there's any variance in data, so what we are looking for are the components that account for most of the information. The Principal Component Analysis (PCA) was independently proposed by `Karl Pearson <http://en.wikipedia.org/wiki/Karl_Pearson>`_ (1901) and `Harold Hotelling <http://en.wikipedia.org/wiki/Harold_Hotelling>`_ (1933) to turn a set of possibly correlated variables into a smaller set of uncorrelated variables. The idea is, that a high-dimensional dataset is often described by correlated variables and therefore only a few meaningful dimensions account for most of the information. The PCA method finds the directions with the greatest variance in the data, called principal components. |
||||
|
||||
Algorithmic Description |
||||
----------------------- |
||||
|
||||
Let :math:`X = \{ x_{1}, x_{2}, \ldots, x_{n} \}` be a random vector with observations :math:`x_i \in R^{d}`. |
||||
|
||||
1. Compute the mean :math:`\mu` |
||||
|
||||
.. math:: |
||||
|
||||
\mu = \frac{1}{n} \sum_{i=1}^{n} x_{i} |
||||
|
||||
2. Compute the the Covariance Matrix `S` |
||||
|
||||
.. math:: |
||||
|
||||
S = \frac{1}{n} \sum_{i=1}^{n} (x_{i} - \mu) (x_{i} - \mu)^{T}` |
||||
|
||||
3. Compute the eigenvalues :math:`\lambda_{i}` and eigenvectors :math:`v_{i}` of :math:`S` |
||||
|
||||
.. math:: |
||||
|
||||
S v_{i} = \lambda_{i} v_{i}, i=1,2,\ldots,n |
||||
|
||||
4. Order the eigenvectors descending by their eigenvalue. The :math:`k` principal components are the eigenvectors corresponding to the :math:`k` largest eigenvalues. |
||||
|
||||
The :math:`k` principal components of the observed vector :math:`x` are then given by: |
||||
|
||||
.. math:: |
||||
|
||||
y = W^{T} (x - \mu) |
||||
|
||||
|
||||
where :math:`W = (v_{1}, v_{2}, \ldots, v_{k})`. |
||||
|
||||
The reconstruction from the PCA basis is given by: |
||||
|
||||
.. math:: |
||||
|
||||
x = W y + \mu |
||||
|
||||
where :math:`W = (v_{1}, v_{2}, \ldots, v_{k})`. |
||||
|
||||
|
||||
The Eigenfaces method then performs face recognition by: |
||||
|
||||
* Projecting all training samples into the PCA subspace. |
||||
* Projecting the query image into the PCA subspace. |
||||
* Finding the nearest neighbor between the projected training images and the projected query image. |
||||
|
||||
Still there's one problem left to solve. Imagine we are given :math:`400` images sized :math:`100 \times 100` pixel. The Principal Component Analysis solves the covariance matrix :math:`S = X X^{T}`, where :math:`{size}(X) = 10000 \times 400` in our example. You would end up with a :math:`10000 \times 10000` matrix, roughly :math:`0.8 GB`. Solving this problem isn't feasible, so we'll need to apply a trick. From your linear algebra lessons you know that a :math:`M \times N` matrix with :math:`M > N` can only have :math:`N - 1` non-zero eigenvalues. So it's possible to take the eigenvalue decomposition :math:`S = X^{T} X` of size :math:`N \times N` instead: |
||||
|
||||
.. math:: |
||||
|
||||
X^{T} X v_{i} = \lambda_{i} v{i} |
||||
|
||||
|
||||
and get the original eigenvectors of :math:`S = X X^{T}` with a left multiplication of the data matrix: |
||||
|
||||
.. math:: |
||||
|
||||
X X^{T} (X v_{i}) = \lambda_{i} (X v_{i}) |
||||
|
||||
The resulting eigenvectors are orthogonal, to get orthonormal eigenvectors they need to be normalized to unit length. I don't want to turn this into a publication, so please look into [Duda01]_ for the derivation and proof of the equations. |
||||
|
||||
Eigenfaces in OpenCV |
||||
-------------------- |
||||
|
||||
For the first source code example, I'll go through it with you. I am first giving you the whole source code listing, and after this we'll look at the most important lines in detail. Please note: every source code listing is commented in detail, so you should have no problems following it. |
||||
|
||||
.. literalinclude:: src/facerec_eigenfaces.cpp |
||||
:language: cpp |
||||
:linenos: |
||||
|
||||
The source code for this demo application is also available in the ``src`` folder coming with this documentation: |
||||
|
||||
* :download:`src/facerec_eigenfaces.cpp <src/facerec_eigenfaces.cpp>` |
||||
|
||||
|
||||
I've used the jet colormap, so you can see how the grayscale values are distributed within the specific Eigenfaces. You can see, that the Eigenfaces do not only encode facial features, but also the illumination in the images (see the left light in Eigenface \#4, right light in Eigenfaces \#5): |
||||
|
||||
.. image:: img/eigenfaces_opencv.png |
||||
:align: center |
||||
|
||||
We've already seen, that we can reconstruct a face from its lower dimensional approximation. So let's see how many Eigenfaces are needed for a good reconstruction. I'll do a subplot with :math:`10,30,\ldots,310` Eigenfaces: |
||||
|
||||
.. code-block:: cpp |
||||
|
||||
// Display or save the image reconstruction at some predefined steps: |
||||
for(int num_components = 10; num_components < 300; num_components+=15) { |
||||
// slice the eigenvectors from the model |
||||
Mat evs = Mat(W, Range::all(), Range(0, num_components)); |
||||
Mat projection = subspaceProject(evs, mean, images[0].reshape(1,1)); |
||||
Mat reconstruction = subspaceReconstruct(evs, mean, projection); |
||||
// Normalize the result: |
||||
reconstruction = norm_0_255(reconstruction.reshape(1, images[0].rows)); |
||||
// Display or save: |
||||
if(argc == 2) { |
||||
imshow(format("eigenface_reconstruction_%d", num_components), reconstruction); |
||||
} else { |
||||
imwrite(format("%s/eigenface_reconstruction_%d.png", output_folder.c_str(), num_components), reconstruction); |
||||
} |
||||
} |
||||
|
||||
10 Eigenvectors are obviously not sufficient for a good image reconstruction, 50 Eigenvectors may already be sufficient to encode important facial features. You'll get a good reconstruction with approximately 300 Eigenvectors for the AT&T Facedatabase. There are rule of thumbs how many Eigenfaces you should choose for a successful face recognition, but it heavily depends on the input data. [Zhao03]_ is the perfect point to start researching for this: |
||||
|
||||
.. image:: img/eigenface_reconstruction_opencv.png |
||||
:align: center |
||||
|
||||
|
||||
Fisherfaces |
||||
============ |
||||
|
||||
The Principal Component Analysis (PCA), which is the core of the Eigenfaces method, finds a linear combination of features that maximizes the total variance in data. While this is clearly a powerful way to represent data, it doesn't consider any classes and so a lot of discriminative information *may* be lost when throwing components away. Imagine a situation where the variance in your data is generated by an external source, let it be the light. The components identified by a PCA do not necessarily contain any discriminative information at all, so the projected samples are smeared together and a classification becomes impossible (see `http://www.bytefish.de/wiki/pca_lda_with_gnu_octave <http://www.bytefish.de/wiki/pca_lda_with_gnu_octave>`_ for an example). |
||||
|
||||
The Linear Discriminant Analysis performs a class-specific dimensionality reduction and was invented by the great statistician `Sir R. A. Fisher <http://en.wikipedia.org/wiki/Ronald_Fisher>`_. He successfully used it for classifying flowers in his 1936 paper *The use of multiple measurements in taxonomic problems* [Fisher36]_. In order to find the combination of features that separates best between classes the Linear Discriminant Analysis maximizes the ratio of between-classes to within-classes scatter, instead of maximizing the overall scatter. The idea is simple: same classes should cluster tightly together, while different classes are as far away as possible from each other in the lower-dimensional representation. This was also recognized by `Belhumeur <http://www.cs.columbia.edu/~belhumeur/>`_, `Hespanha <http://www.ece.ucsb.edu/~hespanha/>`_ and `Kriegman <http://cseweb.ucsd.edu/~kriegman/>`_ and so they applied a Discriminant Analysis to face recognition in [BHK97]_. |
||||
|
||||
Algorithmic Description |
||||
----------------------- |
||||
|
||||
Let :math:`X` be a random vector with samples drawn from :math:`c` classes: |
||||
|
||||
|
||||
.. math:: |
||||
:nowrap: |
||||
|
||||
\begin{align*} |
||||
X & = & \{X_1,X_2,\ldots,X_c\} \\ |
||||
X_i & = & \{x_1, x_2, \ldots, x_n\} |
||||
\end{align*} |
||||
|
||||
|
||||
The scatter matrices :math:`S_{B}` and `S_{W}` are calculated as: |
||||
|
||||
.. math:: |
||||
:nowrap: |
||||
|
||||
\begin{align*} |
||||
S_{B} & = & \sum_{i=1}^{c} N_{i} (\mu_i - \mu)(\mu_i - \mu)^{T} \\ |
||||
S_{W} & = & \sum_{i=1}^{c} \sum_{x_{j} \in X_{i}} (x_j - \mu_i)(x_j - \mu_i)^{T} |
||||
\end{align*} |
||||
|
||||
, where :math:`\mu` is the total mean: |
||||
|
||||
.. math:: |
||||
|
||||
\mu = \frac{1}{N} \sum_{i=1}^{N} x_i |
||||
|
||||
And :math:`\mu_i` is the mean of class :math:`i \in \{1,\ldots,c\}`: |
||||
|
||||
.. math:: |
||||
|
||||
\mu_i = \frac{1}{|X_i|} \sum_{x_j \in X_i} x_j |
||||
|
||||
Fisher's classic algorithm now looks for a projection :math:`W`, that maximizes the class separability criterion: |
||||
|
||||
.. math:: |
||||
|
||||
W_{opt} = \operatorname{arg\,max}_{W} \frac{|W^T S_B W|}{|W^T S_W W|} |
||||
|
||||
|
||||
Following [BHK97]_, a solution for this optimization problem is given by solving the General Eigenvalue Problem: |
||||
|
||||
.. math:: |
||||
:nowrap: |
||||
|
||||
\begin{align*} |
||||
S_{B} v_{i} & = & \lambda_{i} S_w v_{i} \nonumber \\ |
||||
S_{W}^{-1} S_{B} v_{i} & = & \lambda_{i} v_{i} |
||||
\end{align*} |
||||
|
||||
There's one problem left to solve: The rank of :math:`S_{W}` is at most :math:`(N-c)`, with :math:`N` samples and :math:`c` classes. In pattern recognition problems the number of samples :math:`N` is almost always samller than the dimension of the input data (the number of pixels), so the scatter matrix :math:`S_{W}` becomes singular (see [RJ91]_). In [BHK97]_ this was solved by performing a Principal Component Analysis on the data and projecting the samples into the :math:`(N-c)`-dimensional space. A Linear Discriminant Analysis was then performed on the reduced data, because :math:`S_{W}` isn't singular anymore. |
||||
|
||||
The optimization problem can then be rewritten as: |
||||
|
||||
.. math:: |
||||
:nowrap: |
||||
|
||||
\begin{align*} |
||||
W_{pca} & = & \operatorname{arg\,max}_{W} |W^T S_T W| \\ |
||||
W_{fld} & = & \operatorname{arg\,max}_{W} \frac{|W^T W_{pca}^T S_{B} W_{pca} W|}{|W^T W_{pca}^T S_{W} W_{pca} W|} |
||||
\end{align*} |
||||
|
||||
The transformation matrix :math:`W`, that projects a sample into the :math:`(c-1)`-dimensional space is then given by: |
||||
|
||||
.. math:: |
||||
|
||||
W = W_{fld}^{T} W_{pca}^{T} |
||||
|
||||
Fisherfaces in OpenCV |
||||
--------------------- |
||||
|
||||
.. literalinclude:: src/facerec_fisherfaces.cpp |
||||
:language: cpp |
||||
:linenos: |
||||
|
||||
The source code for this demo application is also available in the ``src`` folder coming with this documentation: |
||||
|
||||
* :download:`src/facerec_fisherfaces.cpp <src/facerec_fisherfaces.cpp>` |
||||
|
||||
|
||||
For this example I am going to use the Yale Facedatabase A, just because the plots are nicer. Each Fisherface has the same length as an original image, thus it can be displayed as an image. The demo shows (or saves) the first, at most 16 Fisherfaces: |
||||
|
||||
.. image:: img/fisherfaces_opencv.png |
||||
:align: center |
||||
|
||||
The Fisherfaces method learns a class-specific transformation matrix, so the they do not capture illumination as obviously as the Eigenfaces method. The Discriminant Analysis instead finds the facial features to discriminate between the persons. It's important to mention, that the performance of the Fisherfaces heavily depends on the input data as well. Practically said: if you learn the Fisherfaces for well-illuminated pictures only and you try to recognize faces in bad-illuminated scenes, then method is likely to find the wrong components (just because those features may not be predominant on bad illuminated images). This is somewhat logical, since the method had no chance to learn the illumination. |
||||
|
||||
The Fisherfaces allow a reconstruction of the projected image, just like the Eigenfaces did. But since we only identified the features to distinguish between subjects, you can't expect a nice reconstruction of the original image. For the Fisherfaces method we'll project the sample image onto each of the Fisherfaces instead. So you'll have a nice visualization, which feature each of the Fisherfaces describes: |
||||
|
||||
.. code-block:: cpp |
||||
|
||||
// Display or save the image reconstruction at some predefined steps: |
||||
for(int num_component = 0; num_component < min(16, W.cols); num_component++) { |
||||
// Slice the Fisherface from the model: |
||||
Mat ev = W.col(num_component); |
||||
Mat projection = subspaceProject(ev, mean, images[0].reshape(1,1)); |
||||
Mat reconstruction = subspaceReconstruct(ev, mean, projection); |
||||
// Normalize the result: |
||||
reconstruction = norm_0_255(reconstruction.reshape(1, images[0].rows)); |
||||
// Display or save: |
||||
if(argc == 2) { |
||||
imshow(format("fisherface_reconstruction_%d", num_component), reconstruction); |
||||
} else { |
||||
imwrite(format("%s/fisherface_reconstruction_%d.png", output_folder.c_str(), num_component), reconstruction); |
||||
} |
||||
} |
||||
|
||||
The differences may be subtle for the human eyes, but you should be able to see some differences: |
||||
|
||||
.. image:: img/fisherface_reconstruction_opencv.png |
||||
:align: center |
||||
|
||||
|
||||
Local Binary Patterns Histograms |
||||
================================ |
||||
|
||||
Eigenfaces and Fisherfaces take a somewhat holistic approach to face recognition. You treat your data as a vector somewhere in a high-dimensional image space. We all know high-dimensionality is bad, so a lower-dimensional subspace is identified, where (probably) useful information is preserved. The Eigenfaces approach maximizes the total scatter, which can lead to problems if the variance is generated by an external source, because components with a maximum variance over all classes aren't necessarily useful for classification (see `http://www.bytefish.de/wiki/pca_lda_with_gnu_octave <http://www.bytefish.de/wiki/pca_lda_with_gnu_octave>`_). So to preserve some discriminative information we applied a Linear Discriminant Analysis and optimized as described in the Fisherfaces method. The Fisherfaces method worked great... at least for the constrained scenario we've assumed in our model. |
||||
|
||||
Now real life isn't perfect. You simply can't guarantee perfect light settings in your images or 10 different images of a person. So what if there's only one image for each person? Our covariance estimates for the subspace *may* be horribly wrong, so will the recognition. Remember the Eigenfaces method had a 96% recognition rate on the AT&T Facedatabase? How many images do we actually need to get such useful estimates? Here are the Rank-1 recognition rates of the Eigenfaces and Fisherfaces method on the AT&T Facedatabase, which is a fairly easy image database: |
||||
|
||||
.. image:: img/at_database_small_sample_size.png |
||||
:scale: 60% |
||||
:align: center |
||||
|
||||
So in order to get good recognition rates you'll need at least 8(+-1) images for each person and the Fisherfaces method doesn't really help here. The above experiment is a 10-fold cross validated result carried out with the facerec framework at: `https://github.com/bytefish/facerec <https://github.com/bytefish/facerec>`_. This is not a publication, so I won't back these figures with a deep mathematical analysis. Please have a look into [KM01]_ for a detailed analysis of both methods, when it comes to small training datasets. |
||||
|
||||
So some research concentrated on extracting local features from images. The idea is to not look at the whole image as a high-dimensional vector, but describe only local features of an object. The features you extract this way will have a low-dimensionality implicitly. A fine idea! But you'll soon observe the image representation we are given doesn't only suffer from illumination variations. Think of things like scale, translation or rotation in images - your local description has to be at least a bit robust against those things. Just like ``SIFT``, the Local Binary Patterns methodology has its roots in 2D texture analysis. The basic idea of Local Binary Patterns is to summarize the local structure in an image by comparing each pixel with its neighborhood. Take a pixel as center and threshold its neighbors against. If the intensity of the center pixel is greater-equal its neighbor, then denote it with 1 and 0 if not. You'll end up with a binary number for each pixel, just like 11001111. So with 8 surrounding pixels you'll end up with 2^8 possible combinations, called *Local Binary Patterns* or sometimes referred to as *LBP codes*. The first LBP operator described in literature actually used a fixed 3 x 3 neighborhood just like this: |
||||
|
||||
.. image:: img/lbp/lbp.png |
||||
:scale: 80% |
||||
:align: center |
||||
|
||||
Algorithmic Description |
||||
----------------------- |
||||
|
||||
A more formal description of the LBP operator can be given as: |
||||
|
||||
.. math:: |
||||
|
||||
LBP(x_c, y_c) = \sum_{p=0}^{P-1} 2^p s(i_p - i_c) |
||||
|
||||
, with :math:`(x_c, y_c)` as central pixel with intensity :math:`i_c`; and :math:`i_n` being the intensity of the the neighbor pixel. :math:`s` is the sign function defined as: |
||||
|
||||
.. math:: |
||||
:nowrap: |
||||
|
||||
\begin{equation} |
||||
s(x) = |
||||
\begin{cases} |
||||
1 & \text{if $x \geq 0$}\\ |
||||
0 & \text{else} |
||||
\end{cases} |
||||
\end{equation} |
||||
|
||||
This description enables you to capture very fine grained details in images. In fact the authors were able to compete with state of the art results for texture classification. Soon after the operator was published it was noted, that a fixed neighborhood fails to encode details differing in scale. So the operator was extended to use a variable neighborhood in [AHP04]_. The idea is to align an abritrary number of neighbors on a circle with a variable radius, which enables to capture the following neighborhoods: |
||||
|
||||
.. image:: img/lbp/patterns.png |
||||
:scale: 80% |
||||
:align: center |
||||
|
||||
For a given Point :math:`(x_c,y_c)` the position of the neighbor :math:`(x_p,y_p), p \in P` can be calculated by: |
||||
|
||||
.. math:: |
||||
:nowrap: |
||||
|
||||
\begin{align*} |
||||
x_{p} & = & x_c + R \cos({\frac{2\pi p}{P}})\\ |
||||
y_{p} & = & y_c - R \sin({\frac{2\pi p}{P}}) |
||||
\end{align*} |
||||
|
||||
Where :math:`R` is the radius of the circle and :math:`P` is the number of sample points. |
||||
|
||||
The operator is an extension to the original LBP codes, so it's sometimes called *Extended LBP* (also referred to as *Circular LBP*) . If a points coordinate on the circle doesn't correspond to image coordinates, the point get's interpolated. Computer science has a bunch of clever interpolation schemes, the OpenCV implementation does a bilinear interpolation: |
||||
|
||||
.. math:: |
||||
:nowrap: |
||||
|
||||
\begin{align*} |
||||
f(x,y) \approx \begin{bmatrix} |
||||
1-x & x \end{bmatrix} \begin{bmatrix} |
||||
f(0,0) & f(0,1) \\ |
||||
f(1,0) & f(1,1) \end{bmatrix} \begin{bmatrix} |
||||
1-y \\ |
||||
y \end{bmatrix}. |
||||
\end{align*} |
||||
|
||||
By definition the LBP operator is robust against monotonic gray scale transformations. We can easily verify this by looking at the LBP image of an artificially modified image (so you see what an LBP image looks like!): |
||||
|
||||
.. image:: img/lbp/lbp_yale.jpg |
||||
:scale: 60% |
||||
:align: center |
||||
|
||||
So what's left to do is how to incorporate the spatial information in the face recognition model. The representation proposed by Ahonen et. al [AHP04]_ is to divide the LBP image into :math:`m` local regions and extract a histogram from each. The spatially enhanced feature vector is then obtained by concatenating the local histograms (**not merging them**). These histograms are called *Local Binary Patterns Histograms*. |
||||
|
||||
Local Binary Patterns Histograms in OpenCV |
||||
------------------------------------------ |
||||
|
||||
.. literalinclude:: src/facerec_lbph.cpp |
||||
:language: cpp |
||||
:linenos: |
||||
|
||||
The source code for this demo application is also available in the ``src`` folder coming with this documentation: |
||||
|
||||
* :download:`src/facerec_lbph.cpp <src/facerec_lbph.cpp>` |
||||
|
||||
Conclusion |
||||
========== |
||||
|
||||
You've learned how to use the new :ocv:class:`FaceRecognizer` in real applications. After reading the document you also know how the algorithms work, so now it's time for you to experiment with the available algorithms. Use them, improve them and let the OpenCV community participate! |
||||
|
||||
Credits |
||||
======= |
||||
|
||||
This document wouldn't be possible without the kind permission to use the face images of the *AT&T Database of Faces* and the *Yale Facedatabase A/B*. |
||||
|
||||
The Database of Faces |
||||
--------------------- |
||||
|
||||
** Important: when using these images, please give credit to "AT&T Laboratories, Cambridge." ** |
||||
|
||||
The Database of Faces, formerly *The ORL Database of Faces*, contains a set of face images taken between April 1992 and April 1994. The database was used in the context of a face recognition project carried out in collaboration with the Speech, Vision and Robotics Group of the Cambridge University Engineering Department. |
||||
|
||||
There are ten different images of each of 40 distinct subjects. For some subjects, the images were taken at different times, varying the lighting, facial expressions (open / closed eyes, smiling / not smiling) and facial details (glasses / no glasses). All the images were taken against a dark homogeneous background with the subjects in an upright, frontal position (with tolerance for some side movement). |
||||
|
||||
The files are in PGM format. The size of each image is 92x112 pixels, with 256 grey levels per pixel. The images are organised in 40 directories (one for each subject), which have names of the form sX, where X indicates the subject number (between 1 and 40). In each of these directories, there are ten different images of that subject, which have names of the form Y.pgm, where Y is the image number for that subject (between 1 and 10). |
||||
|
||||
A copy of the database can be retrieved from: `http://www.cl.cam.ac.uk/research/dtg/attarchive/pub/data/att_faces.zip <http://www.cl.cam.ac.uk/research/dtg/attarchive/pub/data/att_faces.zip>`_. |
||||
|
||||
Yale Facedatabase A |
||||
------------------- |
||||
|
||||
*With the permission of the authors I am allowed to show a small number of images (say subject 1 and all the variations) and all images such as Fisherfaces and Eigenfaces from either Yale Facedatabase A or the Yale Facedatabase B.* |
||||
|
||||
The Yale Face Database A (size 6.4MB) contains 165 grayscale images in GIF format of 15 individuals. There are 11 images per subject, one per different facial expression or configuration: center-light, w/glasses, happy, left-light, w/no glasses, normal, right-light, sad, sleepy, surprised, and wink. (Source: `http://cvc.yale.edu/projects/yalefaces/yalefaces.html <http://cvc.yale.edu/projects/yalefaces/yalefaces.html>`_) |
||||
|
||||
Yale Facedatabase B |
||||
-------------------- |
||||
|
||||
*With the permission of the authors I am allowed to show a small number of images (say subject 1 and all the variations) and all images such as Fisherfaces and Eigenfaces from either Yale Facedatabase A or the Yale Facedatabase B.* |
||||
|
||||
The extended Yale Face Database B contains 16128 images of 28 human subjects under 9 poses and 64 illumination conditions. The data format of this database is the same as the Yale Face Database B. Please refer to the homepage of the Yale Face Database B (or one copy of this page) for more detailed information of the data format. |
||||
|
||||
You are free to use the extended Yale Face Database B for research purposes. All publications which use this database should acknowledge the use of "the Exteded Yale Face Database B" and reference Athinodoros Georghiades, Peter Belhumeur, and David Kriegman's paper, "From Few to Many: Illumination Cone Models for Face Recognition under Variable Lighting and Pose", PAMI, 2001, `[bibtex] <http://vision.ucsd.edu/~leekc/ExtYaleDatabase/athosref.html>`_. |
||||
|
||||
The extended database as opposed to the original Yale Face Database B with 10 subjects was first reported by Kuang-Chih Lee, Jeffrey Ho, and David Kriegman in "Acquiring Linear Subspaces for Face Recognition under Variable Lighting, PAMI, May, 2005 `[pdf] <http://vision.ucsd.edu/~leekc/papers/9pltsIEEE.pdf>`_." All test image data used in the experiments are manually aligned, cropped, and then re-sized to 168x192 images. If you publish your experimental results with the cropped images, please reference the PAMI2005 paper as well. (Source: `http://vision.ucsd.edu/~leekc/ExtYaleDatabase/ExtYaleB.html <http://vision.ucsd.edu/~leekc/ExtYaleDatabase/ExtYaleB.html>`_) |
||||
|
||||
Literature |
||||
========== |
||||
|
||||
.. [AHP04] Ahonen, T., Hadid, A., and Pietikainen, M. *Face Recognition with Local Binary Patterns.* Computer Vision - ECCV 2004 (2004), 469–481. |
||||
|
||||
.. [BHK97] Belhumeur, P. N., Hespanha, J., and Kriegman, D. *Eigenfaces vs. Fisherfaces: Recognition Using Class Specific Linear Projection.* IEEE Transactions on Pattern Analysis and Machine Intelligence 19, 7 (1997), 711–720. |
||||
|
||||
.. [Bru92] Brunelli, R., Poggio, T. *Face Recognition through Geometrical Features.* European Conference on Computer Vision (ECCV) 1992, S. 792–800. |
||||
|
||||
.. [Duda01] Duda, Richard O. and Hart, Peter E. and Stork, David G., *Pattern Classification* (2nd Edition) 2001. |
||||
|
||||
.. [Fisher36] Fisher, R. A. *The use of multiple measurements in taxonomic problems.* Annals Eugen. 7 (1936), 179–188. |
||||
|
||||
.. [GBK01] Georghiades, A.S. and Belhumeur, P.N. and Kriegman, D.J., *From Few to Many: Illumination Cone Models for Face Recognition under Variable Lighting and Pose* IEEE Transactions on Pattern Analysis and Machine Intelligence 23, 6 (2001), 643-660. |
||||
|
||||
.. [Kanade73] Kanade, T. *Picture processing system by computer complex and recognition of human faces.* PhD thesis, Kyoto University, November 1973 |
||||
|
||||
.. [KM01] Martinez, A and Kak, A. *PCA versus LDA* IEEE Transactions on Pattern Analysis and Machine Intelligence, Vol. 23, No.2, pp. 228-233, 2001. |
||||
|
||||
.. [Lee05] Lee, K., Ho, J., Kriegman, D. *Acquiring Linear Subspaces for Face Recognition under Variable Lighting.* In: IEEE Transactions on Pattern Analysis and Machine Intelligence (PAMI) 27 (2005), Nr. 5 |
||||
|
||||
.. [Messer06] Messer, K. et al. *Performance Characterisation of Face Recognition Algorithms and Their Sensitivity to Severe Illumination Changes.* In: In: ICB, 2006, S. 1–11. |
||||
|
||||
.. [RJ91] S. Raudys and A.K. Jain. *Small sample size effects in statistical pattern recognition: Recommendations for practitioneers.* - IEEE Transactions on Pattern Analysis and Machine Intelligence 13, 3 (1991), 252-264. |
||||
|
||||
.. [Tan10] Tan, X., and Triggs, B. *Enhanced local texture feature sets for face recognition under difficult lighting conditions.* IEEE Transactions on Image Processing 19 (2010), 1635–650. |
||||
|
||||
.. [TP91] Turk, M., and Pentland, A. *Eigenfaces for recognition.* Journal of Cognitive Neuroscience 3 (1991), 71–86. |
||||
|
||||
.. [Tu06] Chiara Turati, Viola Macchi Cassia, F. S., and Leo, I. *Newborns face recognition: Role of inner and outer facial features. Child Development* 77, 2 (2006), 297–311. |
||||
|
||||
.. [Wiskott97] Wiskott, L., Fellous, J., Krüger, N., Malsburg, C. *Face Recognition By Elastic Bunch Graph Matching.* IEEE Transactions on Pattern Analysis and Machine Intelligence 19 (1997), S. 775–779 |
||||
|
||||
.. [Zhao03] Zhao, W., Chellappa, R., Phillips, P., and Rosenfeld, A. Face recognition: A literature survey. ACM Computing Surveys (CSUR) 35, 4 (2003), 399–458. |
||||
|
||||
.. _appendixft: |
||||
|
||||
Appendix |
||||
======== |
||||
|
||||
Creating the CSV File |
||||
--------------------- |
||||
|
||||
You don't really want to create the CSV file by hand. I have prepared you a little Python script ``create_csv.py`` (you find it at ``/src/create_csv.py`` coming with this tutorial) that automatically creates you a CSV file. If you have your images in hierarchie like this (``/basepath/<subject>/<image.ext>``): |
||||
|
||||
.. code-block:: none |
||||
|
||||
philipp@mango:~/facerec/data/at$ tree |
||||
. |
||||
|-- s1 |
||||
| |-- 1.pgm |
||||
| |-- ... |
||||
| |-- 10.pgm |
||||
|-- s2 |
||||
| |-- 1.pgm |
||||
| |-- ... |
||||
| |-- 10.pgm |
||||
... |
||||
|-- s40 |
||||
| |-- 1.pgm |
||||
| |-- ... |
||||
| |-- 10.pgm |
||||
|
||||
|
||||
Then simply call ``create_csv.py`` with the path to the folder, just like this and you could save the output: |
||||
|
||||
.. code-block:: none |
||||
|
||||
philipp@mango:~/facerec/data$ python create_csv.py |
||||
at/s13/2.pgm;0 |
||||
at/s13/7.pgm;0 |
||||
at/s13/6.pgm;0 |
||||
at/s13/9.pgm;0 |
||||
at/s13/5.pgm;0 |
||||
at/s13/3.pgm;0 |
||||
at/s13/4.pgm;0 |
||||
at/s13/10.pgm;0 |
||||
at/s13/8.pgm;0 |
||||
at/s13/1.pgm;0 |
||||
at/s17/2.pgm;1 |
||||
at/s17/7.pgm;1 |
||||
at/s17/6.pgm;1 |
||||
at/s17/9.pgm;1 |
||||
at/s17/5.pgm;1 |
||||
at/s17/3.pgm;1 |
||||
[...] |
||||
|
||||
Here is the script, if you can't find it: |
||||
|
||||
.. literalinclude:: ./src/create_csv.py |
||||
:language: python |
||||
:linenos: |
||||
|
||||
Aligning Face Images |
||||
--------------------- |
||||
|
||||
An accurate alignment of your image data is especially important in tasks like emotion detection, were you need as much detail as possible. Believe me... You don't want to do this by hand. So I've prepared you a tiny Python script. The code is really easy to use. To scale, rotate and crop the face image you just need to call *CropFace(image, eye_left, eye_right, offset_pct, dest_sz)*, where: |
||||
|
||||
* *eye_left* is the position of the left eye |
||||
* *eye_right* is the position of the right eye |
||||
* *offset_pct* is the percent of the image you want to keep next to the eyes (horizontal, vertical direction) |
||||
* *dest_sz* is the size of the output image |
||||
|
||||
If you are using the same *offset_pct* and *dest_sz* for your images, they are all aligned at the eyes. |
||||
|
||||
.. literalinclude:: ./src/crop_face.py |
||||
:language: python |
||||
:linenos: |
||||
|
||||
Imagine we are given `this photo of Arnold Schwarzenegger <http://en.wikipedia.org/wiki/File:Arnold_Schwarzenegger_edit%28ws%29.jpg>`_, which is under a Public Domain license. The (x,y)-position of the eyes is approximately *(252,364)* for the left and *(420,366)* for the right eye. Now you only need to define the horizontal offset, vertical offset and the size your scaled, rotated & cropped face should have. |
||||
|
||||
Here are some examples: |
||||
|
||||
+---------------------------------+----------------------------------------------------------------------------+ |
||||
| Configuration | Cropped, Scaled, Rotated Face | |
||||
+=================================+============================================================================+ |
||||
| 0.1 (10%), 0.1 (10%), (200,200) | .. image:: ./img/tutorial/gender_classification/arnie_10_10_200_200.jpg | |
||||
+---------------------------------+----------------------------------------------------------------------------+ |
||||
| 0.2 (20%), 0.2 (20%), (200,200) | .. image:: ./img/tutorial/gender_classification/arnie_20_20_200_200.jpg | |
||||
+---------------------------------+----------------------------------------------------------------------------+ |
||||
| 0.3 (30%), 0.3 (30%), (200,200) | .. image:: ./img/tutorial/gender_classification/arnie_30_30_200_200.jpg | |
||||
+---------------------------------+----------------------------------------------------------------------------+ |
||||
| 0.2 (20%), 0.2 (20%), (70,70) | .. image:: ./img/tutorial/gender_classification/arnie_20_20_70_70.jpg | |
||||
+---------------------------------+----------------------------------------------------------------------------+ |
||||
|
||||
CSV for the AT&T Facedatabase |
||||
------------------------------ |
||||
|
||||
.. literalinclude:: etc/at.txt |
||||
:language: none |
||||
:linenos: |
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 171 KiB |
After Width: | Height: | Size: 108 KiB |
After Width: | Height: | Size: 111 KiB |
After Width: | Height: | Size: 281 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 83 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 290 KiB |
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 6.2 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 7.1 KiB |
After Width: | Height: | Size: 92 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 9.9 KiB |
@ -0,0 +1,31 @@ |
||||
FaceRecognizer - Face Recognition with OpenCV |
||||
############################################## |
||||
|
||||
OpenCV 2.4 now comes with the very new :ocv:class:`FaceRecognizer` class for face recognition. This documentation is going to explain you :doc:`the API <facerec_api>` in detail and it will give you a lot of help to get started (full source code examples). :doc:`Face Recognition with OpenCV <facerec_tutorial>` is the definite guide to the new :ocv:class:`FaceRecognizer`. There's also a :doc:`tutorial on gender classification <tutorial/facerec_gender_classification>`, a :doc:`tutorial for face recognition in videos <tutorial/facerec_video_recognition>` and it's shown :doc:`how to load & save your results <tutorial/facerec_save_load>`. |
||||
|
||||
These documents are the help I have wished for, when I was working myself into face recognition. I hope you also think the new :ocv:class:`FaceRecognizer` is a useful addition to OpenCV. |
||||
|
||||
Please issue any feature requests and/or bugs on the official OpenCV bug tracker at: |
||||
|
||||
* http://code.opencv.org/projects/opencv/issues |
||||
|
||||
Contents |
||||
======== |
||||
|
||||
|
||||
.. toctree:: |
||||
:maxdepth: 1 |
||||
|
||||
FaceRecognizer API <facerec_api> |
||||
Guide to Face Recognition with OpenCV <facerec_tutorial> |
||||
Tutorial on Gender Classification <tutorial/facerec_gender_classification> |
||||
Tutorial on Face Recognition in Videos <tutorial/facerec_video_recognition> |
||||
Tutorial On Saving & Loading a FaceRecognizer <tutorial/facerec_save_load> |
||||
Changelog <facerec_changelog> |
||||
|
||||
Indices and tables |
||||
================== |
||||
|
||||
* :ref:`genindex` |
||||
* :ref:`modindex` |
||||
* :ref:`search` |
@ -0,0 +1,25 @@ |
||||
CMAKE_MINIMUM_REQUIRED(VERSION 2.6) |
||||
|
||||
set(name "facerec") |
||||
project(facerec_cpp_samples) |
||||
|
||||
#SET(OpenCV_DIR /path/to/your/opencv/installation) |
||||
|
||||
# packages |
||||
find_package(OpenCV REQUIRED) # http://opencv.org |
||||
|
||||
# probably you should loop through the sample files here |
||||
add_executable(facerec_demo facerec_demo.cpp) |
||||
target_link_libraries(facerec_demo opencv_core opencv_face opencv_imgproc opencv_highgui) |
||||
|
||||
add_executable(facerec_video facerec_video.cpp) |
||||
target_link_libraries(facerec_video opencv_face opencv_core opencv_imgproc opencv_highgui opencv_objdetect opencv_imgproc) |
||||
|
||||
add_executable(facerec_eigenfaces facerec_eigenfaces.cpp) |
||||
target_link_libraries(facerec_eigenfaces opencv_face opencv_core opencv_imgproc opencv_highgui) |
||||
|
||||
add_executable(facerec_fisherfaces facerec_fisherfaces.cpp) |
||||
target_link_libraries(facerec_fisherfaces opencv_face opencv_core opencv_imgproc opencv_highgui) |
||||
|
||||
add_executable(facerec_lbph facerec_lbph.cpp) |
||||
target_link_libraries(facerec_lbph opencv_face opencv_core opencv_imgproc opencv_highgui) |
@ -0,0 +1,43 @@ |
||||
#!/usr/bin/env python |
||||
|
||||
import sys |
||||
import os.path |
||||
|
||||
# This is a tiny script to help you creating a CSV file from a face |
||||
# database with a similar hierarchie: |
||||
# |
||||
# philipp@mango:~/facerec/data/at$ tree |
||||
# . |
||||
# |-- README |
||||
# |-- s1 |
||||
# | |-- 1.pgm |
||||
# | |-- ... |
||||
# | |-- 10.pgm |
||||
# |-- s2 |
||||
# | |-- 1.pgm |
||||
# | |-- ... |
||||
# | |-- 10.pgm |
||||
# ... |
||||
# |-- s40 |
||||
# | |-- 1.pgm |
||||
# | |-- ... |
||||
# | |-- 10.pgm |
||||
# |
||||
|
||||
if __name__ == "__main__": |
||||
|
||||
if len(sys.argv) != 2: |
||||
print "usage: create_csv <base_path>" |
||||
sys.exit(1) |
||||
|
||||
BASE_PATH=sys.argv[1] |
||||
SEPARATOR=";" |
||||
|
||||
label = 0 |
||||
for dirname, dirnames, filenames in os.walk(BASE_PATH): |
||||
for subdirname in dirnames: |
||||
subject_path = os.path.join(dirname, subdirname) |
||||
for filename in os.listdir(subject_path): |
||||
abs_path = "%s/%s" % (subject_path, filename) |
||||
print "%s%s%d" % (abs_path, SEPARATOR, label) |
||||
label = label + 1 |
@ -0,0 +1,112 @@ |
||||
#!/usr/bin/env python |
||||
# Software License Agreement (BSD License) |
||||
# |
||||
# Copyright (c) 2012, Philipp Wagner |
||||
# All rights reserved. |
||||
# |
||||
# Redistribution and use in source and binary forms, with or without |
||||
# modification, are permitted provided that the following conditions |
||||
# are met: |
||||
# |
||||
# * Redistributions of source code must retain the above copyright |
||||
# notice, this list of conditions and the following disclaimer. |
||||
# * Redistributions in binary form must reproduce the above |
||||
# copyright notice, this list of conditions and the following |
||||
# disclaimer in the documentation and/or other materials provided |
||||
# with the distribution. |
||||
# * Neither the name of the author nor the names of its |
||||
# contributors may be used to endorse or promote products derived |
||||
# from this software without specific prior written permission. |
||||
# |
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
||||
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
||||
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
||||
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||
# POSSIBILITY OF SUCH DAMAGE. |
||||
|
||||
import sys, math, Image |
||||
|
||||
def Distance(p1,p2): |
||||
dx = p2[0] - p1[0] |
||||
dy = p2[1] - p1[1] |
||||
return math.sqrt(dx*dx+dy*dy) |
||||
|
||||
def ScaleRotateTranslate(image, angle, center = None, new_center = None, scale = None, resample=Image.BICUBIC): |
||||
if (scale is None) and (center is None): |
||||
return image.rotate(angle=angle, resample=resample) |
||||
nx,ny = x,y = center |
||||
sx=sy=1.0 |
||||
if new_center: |
||||
(nx,ny) = new_center |
||||
if scale: |
||||
(sx,sy) = (scale, scale) |
||||
cosine = math.cos(angle) |
||||
sine = math.sin(angle) |
||||
a = cosine/sx |
||||
b = sine/sx |
||||
c = x-nx*a-ny*b |
||||
d = -sine/sy |
||||
e = cosine/sy |
||||
f = y-nx*d-ny*e |
||||
return image.transform(image.size, Image.AFFINE, (a,b,c,d,e,f), resample=resample) |
||||
|
||||
def CropFace(image, eye_left=(0,0), eye_right=(0,0), offset_pct=(0.2,0.2), dest_sz = (70,70)): |
||||
# calculate offsets in original image |
||||
offset_h = math.floor(float(offset_pct[0])*dest_sz[0]) |
||||
offset_v = math.floor(float(offset_pct[1])*dest_sz[1]) |
||||
# get the direction |
||||
eye_direction = (eye_right[0] - eye_left[0], eye_right[1] - eye_left[1]) |
||||
# calc rotation angle in radians |
||||
rotation = -math.atan2(float(eye_direction[1]),float(eye_direction[0])) |
||||
# distance between them |
||||
dist = Distance(eye_left, eye_right) |
||||
# calculate the reference eye-width |
||||
reference = dest_sz[0] - 2.0*offset_h |
||||
# scale factor |
||||
scale = float(dist)/float(reference) |
||||
# rotate original around the left eye |
||||
image = ScaleRotateTranslate(image, center=eye_left, angle=rotation) |
||||
# crop the rotated image |
||||
crop_xy = (eye_left[0] - scale*offset_h, eye_left[1] - scale*offset_v) |
||||
crop_size = (dest_sz[0]*scale, dest_sz[1]*scale) |
||||
image = image.crop((int(crop_xy[0]), int(crop_xy[1]), int(crop_xy[0]+crop_size[0]), int(crop_xy[1]+crop_size[1]))) |
||||
# resize it |
||||
image = image.resize(dest_sz, Image.ANTIALIAS) |
||||
return image |
||||
|
||||
def readFileNames(): |
||||
try: |
||||
inFile = open('path_to_created_csv_file.csv') |
||||
except: |
||||
raise IOError('There is no file named path_to_created_csv_file.csv in current directory.') |
||||
return False |
||||
|
||||
picPath = [] |
||||
picIndex = [] |
||||
|
||||
for line in inFile.readlines(): |
||||
if line != '': |
||||
fields = line.rstrip().split(';') |
||||
picPath.append(fields[0]) |
||||
picIndex.append(int(fields[1])) |
||||
|
||||
return (picPath, picIndex) |
||||
|
||||
|
||||
if __name__ == "__main__": |
||||
[images, indexes]=readFileNames() |
||||
if not os.path.exists("modified"): |
||||
os.makedirs("modified") |
||||
for img in images: |
||||
image = Image.open(img) |
||||
CropFace(image, eye_left=(252,364), eye_right=(420,366), offset_pct=(0.1,0.1), dest_sz=(200,200)).save("modified/"+img.rstrip().split('/')[1]+"_10_10_200_200.jpg") |
||||
CropFace(image, eye_left=(252,364), eye_right=(420,366), offset_pct=(0.2,0.2), dest_sz=(200,200)).save("modified/"+img.rstrip().split('/')[1]+"_20_20_200_200.jpg") |
||||
CropFace(image, eye_left=(252,364), eye_right=(420,366), offset_pct=(0.3,0.3), dest_sz=(200,200)).save("modified/"+img.rstrip().split('/')[1]+"_30_30_200_200.jpg") |
||||
CropFace(image, eye_left=(252,364), eye_right=(420,366), offset_pct=(0.2,0.2)).save("modified/"+img.rstrip().split('/')[1]+"_20_20_70_70.jpg") |
@ -0,0 +1,169 @@ |
||||
/*
|
||||
* Copyright (c) 2011. Philipp Wagner <bytefish[at]gmx[dot]de>. |
||||
* Released to public domain under terms of the BSD Simplified license. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are met: |
||||
* * Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* * Neither the name of the organization nor the names of its contributors |
||||
* may be used to endorse or promote products derived from this software |
||||
* without specific prior written permission. |
||||
* |
||||
* See <http://www.opensource.org/licenses/bsd-license>
|
||||
*/ |
||||
|
||||
#include "opencv2/core.hpp" |
||||
#include "opencv2/face.hpp" |
||||
#include "opencv2/highgui.hpp" |
||||
|
||||
#include <iostream> |
||||
#include <fstream> |
||||
#include <sstream> |
||||
|
||||
using namespace cv; |
||||
using namespace cv::face; |
||||
using namespace std; |
||||
|
||||
static Mat norm_0_255(InputArray _src) { |
||||
Mat src = _src.getMat(); |
||||
// Create and return normalized image:
|
||||
Mat dst; |
||||
switch(src.channels()) { |
||||
case 1: |
||||
cv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC1); |
||||
break; |
||||
case 3: |
||||
cv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC3); |
||||
break; |
||||
default: |
||||
src.copyTo(dst); |
||||
break; |
||||
} |
||||
return dst; |
||||
} |
||||
|
||||
static void read_csv(const string& filename, vector<Mat>& images, vector<int>& labels, char separator = ';') { |
||||
std::ifstream file(filename.c_str(), ifstream::in); |
||||
if (!file) { |
||||
string error_message = "No valid input file was given, please check the given filename."; |
||||
CV_Error(CV_StsBadArg, error_message); |
||||
} |
||||
string line, path, classlabel; |
||||
while (getline(file, line)) { |
||||
stringstream liness(line); |
||||
getline(liness, path, separator); |
||||
getline(liness, classlabel); |
||||
if(!path.empty() && !classlabel.empty()) { |
||||
images.push_back(imread(path, 0)); |
||||
labels.push_back(atoi(classlabel.c_str())); |
||||
} |
||||
} |
||||
} |
||||
|
||||
int main(int argc, const char *argv[]) { |
||||
// Check for valid command line arguments, print usage
|
||||
// if no arguments were given.
|
||||
if (argc != 2) { |
||||
cout << "usage: " << argv[0] << " <csv.ext>" << endl; |
||||
exit(1); |
||||
} |
||||
// Get the path to your CSV.
|
||||
string fn_csv = string(argv[1]); |
||||
// These vectors hold the images and corresponding labels.
|
||||
vector<Mat> images; |
||||
vector<int> labels; |
||||
// Read in the data. This can fail if no valid
|
||||
// input filename is given.
|
||||
try { |
||||
read_csv(fn_csv, images, labels); |
||||
} catch (cv::Exception& e) { |
||||
cerr << "Error opening file \"" << fn_csv << "\". Reason: " << e.msg << endl; |
||||
// nothing more we can do
|
||||
exit(1); |
||||
} |
||||
// Quit if there are not enough images for this demo.
|
||||
if(images.size() <= 1) { |
||||
string error_message = "This demo needs at least 2 images to work. Please add more images to your data set!"; |
||||
CV_Error(CV_StsError, error_message); |
||||
} |
||||
// Get the height from the first image. We'll need this
|
||||
// later in code to reshape the images to their original
|
||||
// size:
|
||||
int height = images[0].rows; |
||||
// The following lines simply get the last images from
|
||||
// your dataset and remove it from the vector. This is
|
||||
// done, so that the training data (which we learn the
|
||||
// cv::FaceRecognizer on) and the test data we test
|
||||
// the model with, do not overlap.
|
||||
Mat testSample = images[images.size() - 1]; |
||||
int testLabel = labels[labels.size() - 1]; |
||||
images.pop_back(); |
||||
labels.pop_back(); |
||||
// The following lines create an Eigenfaces model for
|
||||
// face recognition and train it with the images and
|
||||
// labels read from the given CSV file.
|
||||
// This here is a full PCA, if you just want to keep
|
||||
// 10 principal components (read Eigenfaces), then call
|
||||
// the factory method like this:
|
||||
//
|
||||
// cv::createEigenFaceRecognizer(10);
|
||||
//
|
||||
// If you want to create a FaceRecognizer with a
|
||||
// confidennce threshold, call it with:
|
||||
//
|
||||
// cv::createEigenFaceRecognizer(10, 123.0);
|
||||
//
|
||||
Ptr<FaceRecognizer> model = createFisherFaceRecognizer(); |
||||
model->train(images, labels); |
||||
// The following line predicts the label of a given
|
||||
// test image:
|
||||
int predictedLabel = model->predict(testSample); |
||||
//
|
||||
// To get the confidence of a prediction call the model with:
|
||||
//
|
||||
// int predictedLabel = -1;
|
||||
// double confidence = 0.0;
|
||||
// model->predict(testSample, predictedLabel, confidence);
|
||||
//
|
||||
string result_message = format("Predicted class = %d / Actual class = %d.", predictedLabel, testLabel); |
||||
cout << result_message << endl; |
||||
// Sometimes you'll need to get/set internal model data,
|
||||
// which isn't exposed by the public cv::FaceRecognizer.
|
||||
// Since each cv::FaceRecognizer is derived from a
|
||||
// cv::Algorithm, you can query the data.
|
||||
//
|
||||
// First we'll use it to set the threshold of the FaceRecognizer
|
||||
// to 0.0 without retraining the model. This can be useful if
|
||||
// you are evaluating the model:
|
||||
//
|
||||
model->set("threshold", 0.0); |
||||
// Now the threshold of this model is set to 0.0. A prediction
|
||||
// now returns -1, as it's impossible to have a distance below
|
||||
// it
|
||||
predictedLabel = model->predict(testSample); |
||||
cout << "Predicted class = " << predictedLabel << endl; |
||||
// Here is how to get the eigenvalues of this Eigenfaces model:
|
||||
Mat eigenvalues = model->getMat("eigenvalues"); |
||||
// And we can do the same to display the Eigenvectors (read Eigenfaces):
|
||||
Mat W = model->getMat("eigenvectors"); |
||||
// From this we will display the (at most) first 10 Eigenfaces:
|
||||
for (int i = 0; i < min(10, W.cols); i++) { |
||||
string msg = format("Eigenvalue #%d = %.5f", i, eigenvalues.at<double>(i)); |
||||
cout << msg << endl; |
||||
// get eigenvector #i
|
||||
Mat ev = W.col(i).clone(); |
||||
// Reshape to original size & normalize to [0...255] for imshow.
|
||||
Mat grayscale = norm_0_255(ev.reshape(1, height)); |
||||
// Show the image & apply a Jet colormap for better sensing.
|
||||
Mat cgrayscale; |
||||
applyColorMap(grayscale, cgrayscale, COLORMAP_JET); |
||||
imshow(format("%d", i), cgrayscale); |
||||
} |
||||
waitKey(0); |
||||
|
||||
return 0; |
||||
} |
@ -0,0 +1,194 @@ |
||||
/*
|
||||
* Copyright (c) 2011. Philipp Wagner <bytefish[at]gmx[dot]de>. |
||||
* Released to public domain under terms of the BSD Simplified license. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are met: |
||||
* * Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* * Neither the name of the organization nor the names of its contributors |
||||
* may be used to endorse or promote products derived from this software |
||||
* without specific prior written permission. |
||||
* |
||||
* See <http://www.opensource.org/licenses/bsd-license>
|
||||
*/ |
||||
|
||||
#include "opencv2/core.hpp" |
||||
#include "opencv2/face.hpp" |
||||
#include "opencv2/highgui.hpp" |
||||
|
||||
#include <iostream> |
||||
#include <fstream> |
||||
#include <sstream> |
||||
|
||||
using namespace cv; |
||||
using namespace cv::face; |
||||
using namespace std; |
||||
|
||||
static Mat norm_0_255(InputArray _src) { |
||||
Mat src = _src.getMat(); |
||||
// Create and return normalized image:
|
||||
Mat dst; |
||||
switch(src.channels()) { |
||||
case 1: |
||||
cv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC1); |
||||
break; |
||||
case 3: |
||||
cv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC3); |
||||
break; |
||||
default: |
||||
src.copyTo(dst); |
||||
break; |
||||
} |
||||
return dst; |
||||
} |
||||
|
||||
static void read_csv(const string& filename, vector<Mat>& images, vector<int>& labels, char separator = ';') { |
||||
std::ifstream file(filename.c_str(), ifstream::in); |
||||
if (!file) { |
||||
string error_message = "No valid input file was given, please check the given filename."; |
||||
CV_Error(CV_StsBadArg, error_message); |
||||
} |
||||
string line, path, classlabel; |
||||
while (getline(file, line)) { |
||||
stringstream liness(line); |
||||
getline(liness, path, separator); |
||||
getline(liness, classlabel); |
||||
if(!path.empty() && !classlabel.empty()) { |
||||
images.push_back(imread(path, 0)); |
||||
labels.push_back(atoi(classlabel.c_str())); |
||||
} |
||||
} |
||||
} |
||||
|
||||
int main(int argc, const char *argv[]) { |
||||
// Check for valid command line arguments, print usage
|
||||
// if no arguments were given.
|
||||
if (argc < 2) { |
||||
cout << "usage: " << argv[0] << " <csv.ext> <output_folder> " << endl; |
||||
exit(1); |
||||
} |
||||
string output_folder = "."; |
||||
if (argc == 3) { |
||||
output_folder = string(argv[2]); |
||||
} |
||||
// Get the path to your CSV.
|
||||
string fn_csv = string(argv[1]); |
||||
// These vectors hold the images and corresponding labels.
|
||||
vector<Mat> images; |
||||
vector<int> labels; |
||||
// Read in the data. This can fail if no valid
|
||||
// input filename is given.
|
||||
try { |
||||
read_csv(fn_csv, images, labels); |
||||
} catch (cv::Exception& e) { |
||||
cerr << "Error opening file \"" << fn_csv << "\". Reason: " << e.msg << endl; |
||||
// nothing more we can do
|
||||
exit(1); |
||||
} |
||||
// Quit if there are not enough images for this demo.
|
||||
if(images.size() <= 1) { |
||||
string error_message = "This demo needs at least 2 images to work. Please add more images to your data set!"; |
||||
CV_Error(CV_StsError, error_message); |
||||
} |
||||
// Get the height from the first image. We'll need this
|
||||
// later in code to reshape the images to their original
|
||||
// size:
|
||||
int height = images[0].rows; |
||||
// The following lines simply get the last images from
|
||||
// your dataset and remove it from the vector. This is
|
||||
// done, so that the training data (which we learn the
|
||||
// cv::FaceRecognizer on) and the test data we test
|
||||
// the model with, do not overlap.
|
||||
Mat testSample = images[images.size() - 1]; |
||||
int testLabel = labels[labels.size() - 1]; |
||||
images.pop_back(); |
||||
labels.pop_back(); |
||||
// The following lines create an Eigenfaces model for
|
||||
// face recognition and train it with the images and
|
||||
// labels read from the given CSV file.
|
||||
// This here is a full PCA, if you just want to keep
|
||||
// 10 principal components (read Eigenfaces), then call
|
||||
// the factory method like this:
|
||||
//
|
||||
// cv::createEigenFaceRecognizer(10);
|
||||
//
|
||||
// If you want to create a FaceRecognizer with a
|
||||
// confidence threshold (e.g. 123.0), call it with:
|
||||
//
|
||||
// cv::createEigenFaceRecognizer(10, 123.0);
|
||||
//
|
||||
// If you want to use _all_ Eigenfaces and have a threshold,
|
||||
// then call the method like this:
|
||||
//
|
||||
// cv::createEigenFaceRecognizer(0, 123.0);
|
||||
//
|
||||
Ptr<FaceRecognizer> model = createEigenFaceRecognizer(); |
||||
model->train(images, labels); |
||||
// The following line predicts the label of a given
|
||||
// test image:
|
||||
int predictedLabel = model->predict(testSample); |
||||
//
|
||||
// To get the confidence of a prediction call the model with:
|
||||
//
|
||||
// int predictedLabel = -1;
|
||||
// double confidence = 0.0;
|
||||
// model->predict(testSample, predictedLabel, confidence);
|
||||
//
|
||||
string result_message = format("Predicted class = %d / Actual class = %d.", predictedLabel, testLabel); |
||||
cout << result_message << endl; |
||||
// Here is how to get the eigenvalues of this Eigenfaces model:
|
||||
Mat eigenvalues = model->getMat("eigenvalues"); |
||||
// And we can do the same to display the Eigenvectors (read Eigenfaces):
|
||||
Mat W = model->getMat("eigenvectors"); |
||||
// Get the sample mean from the training data
|
||||
Mat mean = model->getMat("mean"); |
||||
// Display or save:
|
||||
if(argc == 2) { |
||||
imshow("mean", norm_0_255(mean.reshape(1, images[0].rows))); |
||||
} else { |
||||
imwrite(format("%s/mean.png", output_folder.c_str()), norm_0_255(mean.reshape(1, images[0].rows))); |
||||
} |
||||
// Display or save the Eigenfaces:
|
||||
for (int i = 0; i < min(10, W.cols); i++) { |
||||
string msg = format("Eigenvalue #%d = %.5f", i, eigenvalues.at<double>(i)); |
||||
cout << msg << endl; |
||||
// get eigenvector #i
|
||||
Mat ev = W.col(i).clone(); |
||||
// Reshape to original size & normalize to [0...255] for imshow.
|
||||
Mat grayscale = norm_0_255(ev.reshape(1, height)); |
||||
// Show the image & apply a Jet colormap for better sensing.
|
||||
Mat cgrayscale; |
||||
applyColorMap(grayscale, cgrayscale, COLORMAP_JET); |
||||
// Display or save:
|
||||
if(argc == 2) { |
||||
imshow(format("eigenface_%d", i), cgrayscale); |
||||
} else { |
||||
imwrite(format("%s/eigenface_%d.png", output_folder.c_str(), i), norm_0_255(cgrayscale)); |
||||
} |
||||
} |
||||
|
||||
// Display or save the image reconstruction at some predefined steps:
|
||||
for(int num_components = min(W.cols, 10); num_components < min(W.cols, 300); num_components+=15) { |
||||
// slice the eigenvectors from the model
|
||||
Mat evs = Mat(W, Range::all(), Range(0, num_components)); |
||||
Mat projection = subspaceProject(evs, mean, images[0].reshape(1,1)); |
||||
Mat reconstruction = subspaceReconstruct(evs, mean, projection); |
||||
// Normalize the result:
|
||||
reconstruction = norm_0_255(reconstruction.reshape(1, images[0].rows)); |
||||
// Display or save:
|
||||
if(argc == 2) { |
||||
imshow(format("eigenface_reconstruction_%d", num_components), reconstruction); |
||||
} else { |
||||
imwrite(format("%s/eigenface_reconstruction_%d.png", output_folder.c_str(), num_components), reconstruction); |
||||
} |
||||
} |
||||
// Display if we are not writing to an output folder:
|
||||
if(argc == 2) { |
||||
waitKey(0); |
||||
} |
||||
return 0; |
||||
} |
@ -0,0 +1,192 @@ |
||||
/*
|
||||
* Copyright (c) 2011. Philipp Wagner <bytefish[at]gmx[dot]de>. |
||||
* Released to public domain under terms of the BSD Simplified license. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are met: |
||||
* * Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* * Neither the name of the organization nor the names of its contributors |
||||
* may be used to endorse or promote products derived from this software |
||||
* without specific prior written permission. |
||||
* |
||||
* See <http://www.opensource.org/licenses/bsd-license>
|
||||
*/ |
||||
|
||||
#include "opencv2/core.hpp" |
||||
#include "opencv2/face.hpp" |
||||
#include "opencv2/highgui.hpp" |
||||
|
||||
#include <iostream> |
||||
#include <fstream> |
||||
#include <sstream> |
||||
|
||||
using namespace cv; |
||||
using namespace cv::face; |
||||
using namespace std; |
||||
|
||||
static Mat norm_0_255(InputArray _src) { |
||||
Mat src = _src.getMat(); |
||||
// Create and return normalized image:
|
||||
Mat dst; |
||||
switch(src.channels()) { |
||||
case 1: |
||||
cv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC1); |
||||
break; |
||||
case 3: |
||||
cv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC3); |
||||
break; |
||||
default: |
||||
src.copyTo(dst); |
||||
break; |
||||
} |
||||
return dst; |
||||
} |
||||
|
||||
static void read_csv(const string& filename, vector<Mat>& images, vector<int>& labels, char separator = ';') { |
||||
std::ifstream file(filename.c_str(), ifstream::in); |
||||
if (!file) { |
||||
string error_message = "No valid input file was given, please check the given filename."; |
||||
CV_Error(CV_StsBadArg, error_message); |
||||
} |
||||
string line, path, classlabel; |
||||
while (getline(file, line)) { |
||||
stringstream liness(line); |
||||
getline(liness, path, separator); |
||||
getline(liness, classlabel); |
||||
if(!path.empty() && !classlabel.empty()) { |
||||
images.push_back(imread(path, 0)); |
||||
labels.push_back(atoi(classlabel.c_str())); |
||||
} |
||||
} |
||||
} |
||||
|
||||
int main(int argc, const char *argv[]) { |
||||
// Check for valid command line arguments, print usage
|
||||
// if no arguments were given.
|
||||
if (argc < 2) { |
||||
cout << "usage: " << argv[0] << " <csv.ext> <output_folder> " << endl; |
||||
exit(1); |
||||
} |
||||
string output_folder = "."; |
||||
if (argc == 3) { |
||||
output_folder = string(argv[2]); |
||||
} |
||||
// Get the path to your CSV.
|
||||
string fn_csv = string(argv[1]); |
||||
// These vectors hold the images and corresponding labels.
|
||||
vector<Mat> images; |
||||
vector<int> labels; |
||||
// Read in the data. This can fail if no valid
|
||||
// input filename is given.
|
||||
try { |
||||
read_csv(fn_csv, images, labels); |
||||
} catch (cv::Exception& e) { |
||||
cerr << "Error opening file \"" << fn_csv << "\". Reason: " << e.msg << endl; |
||||
// nothing more we can do
|
||||
exit(1); |
||||
} |
||||
// Quit if there are not enough images for this demo.
|
||||
if(images.size() <= 1) { |
||||
string error_message = "This demo needs at least 2 images to work. Please add more images to your data set!"; |
||||
CV_Error(CV_StsError, error_message); |
||||
} |
||||
// Get the height from the first image. We'll need this
|
||||
// later in code to reshape the images to their original
|
||||
// size:
|
||||
int height = images[0].rows; |
||||
// The following lines simply get the last images from
|
||||
// your dataset and remove it from the vector. This is
|
||||
// done, so that the training data (which we learn the
|
||||
// cv::FaceRecognizer on) and the test data we test
|
||||
// the model with, do not overlap.
|
||||
Mat testSample = images[images.size() - 1]; |
||||
int testLabel = labels[labels.size() - 1]; |
||||
images.pop_back(); |
||||
labels.pop_back(); |
||||
// The following lines create an Fisherfaces model for
|
||||
// face recognition and train it with the images and
|
||||
// labels read from the given CSV file.
|
||||
// If you just want to keep 10 Fisherfaces, then call
|
||||
// the factory method like this:
|
||||
//
|
||||
// cv::createFisherFaceRecognizer(10);
|
||||
//
|
||||
// However it is not useful to discard Fisherfaces! Please
|
||||
// always try to use _all_ available Fisherfaces for
|
||||
// classification.
|
||||
//
|
||||
// If you want to create a FaceRecognizer with a
|
||||
// confidence threshold (e.g. 123.0) and use _all_
|
||||
// Fisherfaces, then call it with:
|
||||
//
|
||||
// cv::createFisherFaceRecognizer(0, 123.0);
|
||||
//
|
||||
Ptr<FaceRecognizer> model = createFisherFaceRecognizer(); |
||||
model->train(images, labels); |
||||
// The following line predicts the label of a given
|
||||
// test image:
|
||||
int predictedLabel = model->predict(testSample); |
||||
//
|
||||
// To get the confidence of a prediction call the model with:
|
||||
//
|
||||
// int predictedLabel = -1;
|
||||
// double confidence = 0.0;
|
||||
// model->predict(testSample, predictedLabel, confidence);
|
||||
//
|
||||
string result_message = format("Predicted class = %d / Actual class = %d.", predictedLabel, testLabel); |
||||
cout << result_message << endl; |
||||
// Here is how to get the eigenvalues of this Eigenfaces model:
|
||||
Mat eigenvalues = model->getMat("eigenvalues"); |
||||
// And we can do the same to display the Eigenvectors (read Eigenfaces):
|
||||
Mat W = model->getMat("eigenvectors"); |
||||
// Get the sample mean from the training data
|
||||
Mat mean = model->getMat("mean"); |
||||
// Display or save:
|
||||
if(argc == 2) { |
||||
imshow("mean", norm_0_255(mean.reshape(1, images[0].rows))); |
||||
} else { |
||||
imwrite(format("%s/mean.png", output_folder.c_str()), norm_0_255(mean.reshape(1, images[0].rows))); |
||||
} |
||||
// Display or save the first, at most 16 Fisherfaces:
|
||||
for (int i = 0; i < min(16, W.cols); i++) { |
||||
string msg = format("Eigenvalue #%d = %.5f", i, eigenvalues.at<double>(i)); |
||||
cout << msg << endl; |
||||
// get eigenvector #i
|
||||
Mat ev = W.col(i).clone(); |
||||
// Reshape to original size & normalize to [0...255] for imshow.
|
||||
Mat grayscale = norm_0_255(ev.reshape(1, height)); |
||||
// Show the image & apply a Bone colormap for better sensing.
|
||||
Mat cgrayscale; |
||||
applyColorMap(grayscale, cgrayscale, COLORMAP_BONE); |
||||
// Display or save:
|
||||
if(argc == 2) { |
||||
imshow(format("fisherface_%d", i), cgrayscale); |
||||
} else { |
||||
imwrite(format("%s/fisherface_%d.png", output_folder.c_str(), i), norm_0_255(cgrayscale)); |
||||
} |
||||
} |
||||
// Display or save the image reconstruction at some predefined steps:
|
||||
for(int num_component = 0; num_component < min(16, W.cols); num_component++) { |
||||
// Slice the Fisherface from the model:
|
||||
Mat ev = W.col(num_component); |
||||
Mat projection = subspaceProject(ev, mean, images[0].reshape(1,1)); |
||||
Mat reconstruction = subspaceReconstruct(ev, mean, projection); |
||||
// Normalize the result:
|
||||
reconstruction = norm_0_255(reconstruction.reshape(1, images[0].rows)); |
||||
// Display or save:
|
||||
if(argc == 2) { |
||||
imshow(format("fisherface_reconstruction_%d", num_component), reconstruction); |
||||
} else { |
||||
imwrite(format("%s/fisherface_reconstruction_%d.png", output_folder.c_str(), num_component), reconstruction); |
||||
} |
||||
} |
||||
// Display if we are not writing to an output folder:
|
||||
if(argc == 2) { |
||||
waitKey(0); |
||||
} |
||||
return 0; |
||||
} |
@ -0,0 +1,156 @@ |
||||
/*
|
||||
* Copyright (c) 2011. Philipp Wagner <bytefish[at]gmx[dot]de>. |
||||
* Released to public domain under terms of the BSD Simplified license. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are met: |
||||
* * Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* * Neither the name of the organization nor the names of its contributors |
||||
* may be used to endorse or promote products derived from this software |
||||
* without specific prior written permission. |
||||
* |
||||
* See <http://www.opensource.org/licenses/bsd-license>
|
||||
*/ |
||||
|
||||
#include "opencv2/core.hpp" |
||||
#include "opencv2/face.hpp" |
||||
#include "opencv2/highgui.hpp" |
||||
|
||||
#include <iostream> |
||||
#include <fstream> |
||||
#include <sstream> |
||||
|
||||
using namespace cv; |
||||
using namespace cv::face; |
||||
using namespace std; |
||||
|
||||
static void read_csv(const string& filename, vector<Mat>& images, vector<int>& labels, char separator = ';') { |
||||
std::ifstream file(filename.c_str(), ifstream::in); |
||||
if (!file) { |
||||
string error_message = "No valid input file was given, please check the given filename."; |
||||
CV_Error(CV_StsBadArg, error_message); |
||||
} |
||||
string line, path, classlabel; |
||||
while (getline(file, line)) { |
||||
stringstream liness(line); |
||||
getline(liness, path, separator); |
||||
getline(liness, classlabel); |
||||
if(!path.empty() && !classlabel.empty()) { |
||||
images.push_back(imread(path, 0)); |
||||
labels.push_back(atoi(classlabel.c_str())); |
||||
} |
||||
} |
||||
} |
||||
|
||||
int main(int argc, const char *argv[]) { |
||||
// Check for valid command line arguments, print usage
|
||||
// if no arguments were given.
|
||||
if (argc != 2) { |
||||
cout << "usage: " << argv[0] << " <csv.ext>" << endl; |
||||
exit(1); |
||||
} |
||||
// Get the path to your CSV.
|
||||
string fn_csv = string(argv[1]); |
||||
// These vectors hold the images and corresponding labels.
|
||||
vector<Mat> images; |
||||
vector<int> labels; |
||||
// Read in the data. This can fail if no valid
|
||||
// input filename is given.
|
||||
try { |
||||
read_csv(fn_csv, images, labels); |
||||
} catch (cv::Exception& e) { |
||||
cerr << "Error opening file \"" << fn_csv << "\". Reason: " << e.msg << endl; |
||||
// nothing more we can do
|
||||
exit(1); |
||||
} |
||||
// Quit if there are not enough images for this demo.
|
||||
if(images.size() <= 1) { |
||||
string error_message = "This demo needs at least 2 images to work. Please add more images to your data set!"; |
||||
CV_Error(CV_StsError, error_message); |
||||
} |
||||
// Get the height from the first image. We'll need this
|
||||
// later in code to reshape the images to their original
|
||||
// size:
|
||||
int height = images[0].rows; |
||||
// The following lines simply get the last images from
|
||||
// your dataset and remove it from the vector. This is
|
||||
// done, so that the training data (which we learn the
|
||||
// cv::FaceRecognizer on) and the test data we test
|
||||
// the model with, do not overlap.
|
||||
Mat testSample = images[images.size() - 1]; |
||||
int testLabel = labels[labels.size() - 1]; |
||||
images.pop_back(); |
||||
labels.pop_back(); |
||||
// The following lines create an LBPH model for
|
||||
// face recognition and train it with the images and
|
||||
// labels read from the given CSV file.
|
||||
//
|
||||
// The LBPHFaceRecognizer uses Extended Local Binary Patterns
|
||||
// (it's probably configurable with other operators at a later
|
||||
// point), and has the following default values
|
||||
//
|
||||
// radius = 1
|
||||
// neighbors = 8
|
||||
// grid_x = 8
|
||||
// grid_y = 8
|
||||
//
|
||||
// So if you want a LBPH FaceRecognizer using a radius of
|
||||
// 2 and 16 neighbors, call the factory method with:
|
||||
//
|
||||
// cv::createLBPHFaceRecognizer(2, 16);
|
||||
//
|
||||
// And if you want a threshold (e.g. 123.0) call it with its default values:
|
||||
//
|
||||
// cv::createLBPHFaceRecognizer(1,8,8,8,123.0)
|
||||
//
|
||||
Ptr<FaceRecognizer> model = createLBPHFaceRecognizer(); |
||||
model->train(images, labels); |
||||
// The following line predicts the label of a given
|
||||
// test image:
|
||||
int predictedLabel = model->predict(testSample); |
||||
//
|
||||
// To get the confidence of a prediction call the model with:
|
||||
//
|
||||
// int predictedLabel = -1;
|
||||
// double confidence = 0.0;
|
||||
// model->predict(testSample, predictedLabel, confidence);
|
||||
//
|
||||
string result_message = format("Predicted class = %d / Actual class = %d.", predictedLabel, testLabel); |
||||
cout << result_message << endl; |
||||
// Sometimes you'll need to get/set internal model data,
|
||||
// which isn't exposed by the public cv::FaceRecognizer.
|
||||
// Since each cv::FaceRecognizer is derived from a
|
||||
// cv::Algorithm, you can query the data.
|
||||
//
|
||||
// First we'll use it to set the threshold of the FaceRecognizer
|
||||
// to 0.0 without retraining the model. This can be useful if
|
||||
// you are evaluating the model:
|
||||
//
|
||||
model->set("threshold", 0.0); |
||||
// Now the threshold of this model is set to 0.0. A prediction
|
||||
// now returns -1, as it's impossible to have a distance below
|
||||
// it
|
||||
predictedLabel = model->predict(testSample); |
||||
cout << "Predicted class = " << predictedLabel << endl; |
||||
// Show some informations about the model, as there's no cool
|
||||
// Model data to display as in Eigenfaces/Fisherfaces.
|
||||
// Due to efficiency reasons the LBP images are not stored
|
||||
// within the model:
|
||||
cout << "Model Information:" << endl; |
||||
string model_info = format("\tLBPH(radius=%i, neighbors=%i, grid_x=%i, grid_y=%i, threshold=%.2f)", |
||||
model->getInt("radius"), |
||||
model->getInt("neighbors"), |
||||
model->getInt("grid_x"), |
||||
model->getInt("grid_y"), |
||||
model->getDouble("threshold")); |
||||
cout << model_info << endl; |
||||
// We could get the histograms for example:
|
||||
vector<Mat> histograms = model->getMatVector("histograms"); |
||||
// But should I really visualize it? Probably the length is interesting:
|
||||
cout << "Size of the histograms: " << histograms[0].total() << endl; |
||||
return 0; |
||||
} |
@ -0,0 +1,201 @@ |
||||
/*
|
||||
* Copyright (c) 2011. Philipp Wagner <bytefish[at]gmx[dot]de>. |
||||
* Released to public domain under terms of the BSD Simplified license. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are met: |
||||
* * Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* * Neither the name of the organization nor the names of its contributors |
||||
* may be used to endorse or promote products derived from this software |
||||
* without specific prior written permission. |
||||
* |
||||
* See <http://www.opensource.org/licenses/bsd-license>
|
||||
*/ |
||||
|
||||
#include "opencv2/core.hpp" |
||||
#include "opencv2/face.hpp" |
||||
#include "opencv2/highgui.hpp" |
||||
|
||||
#include <iostream> |
||||
#include <fstream> |
||||
#include <sstream> |
||||
|
||||
using namespace cv; |
||||
using namespace cv::face; |
||||
using namespace std; |
||||
|
||||
static Mat norm_0_255(InputArray _src) { |
||||
Mat src = _src.getMat(); |
||||
// Create and return normalized image:
|
||||
Mat dst; |
||||
switch(src.channels()) { |
||||
case 1: |
||||
cv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC1); |
||||
break; |
||||
case 3: |
||||
cv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC3); |
||||
break; |
||||
default: |
||||
src.copyTo(dst); |
||||
break; |
||||
} |
||||
return dst; |
||||
} |
||||
|
||||
static void read_csv(const string& filename, vector<Mat>& images, vector<int>& labels, char separator = ';') { |
||||
std::ifstream file(filename.c_str(), ifstream::in); |
||||
if (!file) { |
||||
string error_message = "No valid input file was given, please check the given filename."; |
||||
CV_Error(CV_StsBadArg, error_message); |
||||
} |
||||
string line, path, classlabel; |
||||
while (getline(file, line)) { |
||||
stringstream liness(line); |
||||
getline(liness, path, separator); |
||||
getline(liness, classlabel); |
||||
if(!path.empty() && !classlabel.empty()) { |
||||
images.push_back(imread(path, 0)); |
||||
labels.push_back(atoi(classlabel.c_str())); |
||||
} |
||||
} |
||||
} |
||||
|
||||
int main(int argc, const char *argv[]) { |
||||
// Check for valid command line arguments, print usage
|
||||
// if no arguments were given.
|
||||
if (argc < 2) { |
||||
cout << "usage: " << argv[0] << " <csv.ext> <output_folder> " << endl; |
||||
exit(1); |
||||
} |
||||
string output_folder = "."; |
||||
if (argc == 3) { |
||||
output_folder = string(argv[2]); |
||||
} |
||||
// Get the path to your CSV.
|
||||
string fn_csv = string(argv[1]); |
||||
// These vectors hold the images and corresponding labels.
|
||||
vector<Mat> images; |
||||
vector<int> labels; |
||||
// Read in the data. This can fail if no valid
|
||||
// input filename is given.
|
||||
try { |
||||
read_csv(fn_csv, images, labels); |
||||
} catch (cv::Exception& e) { |
||||
cerr << "Error opening file \"" << fn_csv << "\". Reason: " << e.msg << endl; |
||||
// nothing more we can do
|
||||
exit(1); |
||||
} |
||||
// Quit if there are not enough images for this demo.
|
||||
if(images.size() <= 1) { |
||||
string error_message = "This demo needs at least 2 images to work. Please add more images to your data set!"; |
||||
CV_Error(CV_StsError, error_message); |
||||
} |
||||
// Get the height from the first image. We'll need this
|
||||
// later in code to reshape the images to their original
|
||||
// size:
|
||||
int height = images[0].rows; |
||||
// The following lines simply get the last images from
|
||||
// your dataset and remove it from the vector. This is
|
||||
// done, so that the training data (which we learn the
|
||||
// cv::FaceRecognizer on) and the test data we test
|
||||
// the model with, do not overlap.
|
||||
Mat testSample = images[images.size() - 1]; |
||||
int testLabel = labels[labels.size() - 1]; |
||||
images.pop_back(); |
||||
labels.pop_back(); |
||||
// The following lines create an Eigenfaces model for
|
||||
// face recognition and train it with the images and
|
||||
// labels read from the given CSV file.
|
||||
// This here is a full PCA, if you just want to keep
|
||||
// 10 principal components (read Eigenfaces), then call
|
||||
// the factory method like this:
|
||||
//
|
||||
// cv::createEigenFaceRecognizer(10);
|
||||
//
|
||||
// If you want to create a FaceRecognizer with a
|
||||
// confidence threshold (e.g. 123.0), call it with:
|
||||
//
|
||||
// cv::createEigenFaceRecognizer(10, 123.0);
|
||||
//
|
||||
// If you want to use _all_ Eigenfaces and have a threshold,
|
||||
// then call the method like this:
|
||||
//
|
||||
// cv::createEigenFaceRecognizer(0, 123.0);
|
||||
//
|
||||
Ptr<FaceRecognizer> model0 = createEigenFaceRecognizer(); |
||||
model0->train(images, labels); |
||||
// save the model to eigenfaces_at.yaml
|
||||
model0->save("eigenfaces_at.yml"); |
||||
//
|
||||
//
|
||||
// Now create a new Eigenfaces Recognizer
|
||||
//
|
||||
Ptr<FaceRecognizer> model1 = createEigenFaceRecognizer(); |
||||
model1->load("eigenfaces_at.yml"); |
||||
// The following line predicts the label of a given
|
||||
// test image:
|
||||
int predictedLabel = model1->predict(testSample); |
||||
//
|
||||
// To get the confidence of a prediction call the model with:
|
||||
//
|
||||
// int predictedLabel = -1;
|
||||
// double confidence = 0.0;
|
||||
// model->predict(testSample, predictedLabel, confidence);
|
||||
//
|
||||
string result_message = format("Predicted class = %d / Actual class = %d.", predictedLabel, testLabel); |
||||
cout << result_message << endl; |
||||
// Here is how to get the eigenvalues of this Eigenfaces model:
|
||||
Mat eigenvalues = model1->getMat("eigenvalues"); |
||||
// And we can do the same to display the Eigenvectors (read Eigenfaces):
|
||||
Mat W = model1->getMat("eigenvectors"); |
||||
// Get the sample mean from the training data
|
||||
Mat mean = model1->getMat("mean"); |
||||
// Display or save:
|
||||
if(argc == 2) { |
||||
imshow("mean", norm_0_255(mean.reshape(1, images[0].rows))); |
||||
} else { |
||||
imwrite(format("%s/mean.png", output_folder.c_str()), norm_0_255(mean.reshape(1, images[0].rows))); |
||||
} |
||||
// Display or save the Eigenfaces:
|
||||
for (int i = 0; i < min(10, W.cols); i++) { |
||||
string msg = format("Eigenvalue #%d = %.5f", i, eigenvalues.at<double>(i)); |
||||
cout << msg << endl; |
||||
// get eigenvector #i
|
||||
Mat ev = W.col(i).clone(); |
||||
// Reshape to original size & normalize to [0...255] for imshow.
|
||||
Mat grayscale = norm_0_255(ev.reshape(1, height)); |
||||
// Show the image & apply a Jet colormap for better sensing.
|
||||
Mat cgrayscale; |
||||
applyColorMap(grayscale, cgrayscale, COLORMAP_JET); |
||||
// Display or save:
|
||||
if(argc == 2) { |
||||
imshow(format("eigenface_%d", i), cgrayscale); |
||||
} else { |
||||
imwrite(format("%s/eigenface_%d.png", output_folder.c_str(), i), norm_0_255(cgrayscale)); |
||||
} |
||||
} |
||||
// Display or save the image reconstruction at some predefined steps:
|
||||
for(int num_components = 10; num_components < 300; num_components+=15) { |
||||
// slice the eigenvectors from the model
|
||||
Mat evs = Mat(W, Range::all(), Range(0, num_components)); |
||||
Mat projection = subspaceProject(evs, mean, images[0].reshape(1,1)); |
||||
Mat reconstruction = subspaceReconstruct(evs, mean, projection); |
||||
// Normalize the result:
|
||||
reconstruction = norm_0_255(reconstruction.reshape(1, images[0].rows)); |
||||
// Display or save:
|
||||
if(argc == 2) { |
||||
imshow(format("eigenface_reconstruction_%d", num_components), reconstruction); |
||||
} else { |
||||
imwrite(format("%s/eigenface_reconstruction_%d.png", output_folder.c_str(), num_components), reconstruction); |
||||
} |
||||
} |
||||
// Display if we are not writing to an output folder:
|
||||
if(argc == 2) { |
||||
waitKey(0); |
||||
} |
||||
return 0; |
||||
} |
@ -0,0 +1,153 @@ |
||||
/*
|
||||
* Copyright (c) 2011. Philipp Wagner <bytefish[at]gmx[dot]de>. |
||||
* Released to public domain under terms of the BSD Simplified license. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are met: |
||||
* * Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* * Neither the name of the organization nor the names of its contributors |
||||
* may be used to endorse or promote products derived from this software |
||||
* without specific prior written permission. |
||||
* |
||||
* See <http://www.opensource.org/licenses/bsd-license>
|
||||
*/ |
||||
|
||||
#include "opencv2/core.hpp" |
||||
#include "opencv2/face.hpp" |
||||
#include "opencv2/highgui.hpp" |
||||
#include "opencv2/imgproc.hpp" |
||||
#include "opencv2/objdetect.hpp" |
||||
|
||||
#include <iostream> |
||||
#include <fstream> |
||||
#include <sstream> |
||||
|
||||
using namespace cv; |
||||
using namespace cv::face; |
||||
using namespace std; |
||||
|
||||
static void read_csv(const string& filename, vector<Mat>& images, vector<int>& labels, char separator = ';') { |
||||
std::ifstream file(filename.c_str(), ifstream::in); |
||||
if (!file) { |
||||
string error_message = "No valid input file was given, please check the given filename."; |
||||
CV_Error(CV_StsBadArg, error_message); |
||||
} |
||||
string line, path, classlabel; |
||||
while (getline(file, line)) { |
||||
stringstream liness(line); |
||||
getline(liness, path, separator); |
||||
getline(liness, classlabel); |
||||
if(!path.empty() && !classlabel.empty()) { |
||||
images.push_back(imread(path, 0)); |
||||
labels.push_back(atoi(classlabel.c_str())); |
||||
} |
||||
} |
||||
} |
||||
|
||||
int main(int argc, const char *argv[]) { |
||||
// Check for valid command line arguments, print usage
|
||||
// if no arguments were given.
|
||||
if (argc != 4) { |
||||
cout << "usage: " << argv[0] << " </path/to/haar_cascade> </path/to/csv.ext> </path/to/device id>" << endl; |
||||
cout << "\t </path/to/haar_cascade> -- Path to the Haar Cascade for face detection." << endl; |
||||
cout << "\t </path/to/csv.ext> -- Path to the CSV file with the face database." << endl; |
||||
cout << "\t <device id> -- The webcam device id to grab frames from." << endl; |
||||
exit(1); |
||||
} |
||||
// Get the path to your CSV:
|
||||
string fn_haar = string(argv[1]); |
||||
string fn_csv = string(argv[2]); |
||||
int deviceId = atoi(argv[3]); |
||||
// These vectors hold the images and corresponding labels:
|
||||
vector<Mat> images; |
||||
vector<int> labels; |
||||
// Read in the data (fails if no valid input filename is given, but you'll get an error message):
|
||||
try { |
||||
read_csv(fn_csv, images, labels); |
||||
} catch (cv::Exception& e) { |
||||
cerr << "Error opening file \"" << fn_csv << "\". Reason: " << e.msg << endl; |
||||
// nothing more we can do
|
||||
exit(1); |
||||
} |
||||
// Get the height from the first image. We'll need this
|
||||
// later in code to reshape the images to their original
|
||||
// size AND we need to reshape incoming faces to this size:
|
||||
int im_width = images[0].cols; |
||||
int im_height = images[0].rows; |
||||
// Create a FaceRecognizer and train it on the given images:
|
||||
Ptr<FaceRecognizer> model = createFisherFaceRecognizer(); |
||||
model->train(images, labels); |
||||
// That's it for learning the Face Recognition model. You now
|
||||
// need to create the classifier for the task of Face Detection.
|
||||
// We are going to use the haar cascade you have specified in the
|
||||
// command line arguments:
|
||||
//
|
||||
CascadeClassifier haar_cascade; |
||||
haar_cascade.load(fn_haar); |
||||
// Get a handle to the Video device:
|
||||
VideoCapture cap(deviceId); |
||||
// Check if we can use this device at all:
|
||||
if(!cap.isOpened()) { |
||||
cerr << "Capture Device ID " << deviceId << "cannot be opened." << endl; |
||||
return -1; |
||||
} |
||||
// Holds the current frame from the Video device:
|
||||
Mat frame; |
||||
for(;;) { |
||||
cap >> frame; |
||||
// Clone the current frame:
|
||||
Mat original = frame.clone(); |
||||
// Convert the current frame to grayscale:
|
||||
Mat gray; |
||||
cvtColor(original, gray, CV_BGR2GRAY); |
||||
// Find the faces in the frame:
|
||||
vector< Rect_<int> > faces; |
||||
haar_cascade.detectMultiScale(gray, faces); |
||||
// At this point you have the position of the faces in
|
||||
// faces. Now we'll get the faces, make a prediction and
|
||||
// annotate it in the video. Cool or what?
|
||||
for(int i = 0; i < faces.size(); i++) { |
||||
// Process face by face:
|
||||
Rect face_i = faces[i]; |
||||
// Crop the face from the image. So simple with OpenCV C++:
|
||||
Mat face = gray(face_i); |
||||
// Resizing the face is necessary for Eigenfaces and Fisherfaces. You can easily
|
||||
// verify this, by reading through the face recognition tutorial coming with OpenCV.
|
||||
// Resizing IS NOT NEEDED for Local Binary Patterns Histograms, so preparing the
|
||||
// input data really depends on the algorithm used.
|
||||
//
|
||||
// I strongly encourage you to play around with the algorithms. See which work best
|
||||
// in your scenario, LBPH should always be a contender for robust face recognition.
|
||||
//
|
||||
// Since I am showing the Fisherfaces algorithm here, I also show how to resize the
|
||||
// face you have just found:
|
||||
Mat face_resized; |
||||
cv::resize(face, face_resized, Size(im_width, im_height), 1.0, 1.0, INTER_CUBIC); |
||||
// Now perform the prediction, see how easy that is:
|
||||
int prediction = model->predict(face_resized); |
||||
// And finally write all we've found out to the original image!
|
||||
// First of all draw a green rectangle around the detected face:
|
||||
rectangle(original, face_i, CV_RGB(0, 255,0), 1); |
||||
// Create the text we will annotate the box with:
|
||||
string box_text = format("Prediction = %d", prediction); |
||||
// Calculate the position for annotated text (make sure we don't
|
||||
// put illegal values in there):
|
||||
int pos_x = std::max(face_i.tl().x - 10, 0); |
||||
int pos_y = std::max(face_i.tl().y - 10, 0); |
||||
// And now put it into the image:
|
||||
putText(original, box_text, Point(pos_x, pos_y), FONT_HERSHEY_PLAIN, 1.0, CV_RGB(0,255,0), 2.0); |
||||
} |
||||
// Show the result:
|
||||
imshow("face_recognizer", original); |
||||
// And display it:
|
||||
char key = (char) waitKey(20); |
||||
// Exit this loop on escape:
|
||||
if(key == 27) |
||||
break; |
||||
} |
||||
return 0; |
||||
} |
@ -0,0 +1,233 @@ |
||||
Gender Classification with OpenCV |
||||
================================= |
||||
|
||||
.. contents:: Table of Contents |
||||
:depth: 3 |
||||
|
||||
Introduction |
||||
------------ |
||||
|
||||
A lot of people interested in face recognition, also want to know how to perform image classification tasks like: |
||||
|
||||
* Gender Classification (Gender Detection) |
||||
* Emotion Classification (Emotion Detection) |
||||
* Glasses Classification (Glasses Detection) |
||||
* ... |
||||
|
||||
This is has become very, very easy with the new :ocv:class:`FaceRecognizer` class. In this tutorial I'll show you how to perform gender classification with OpenCV on a set of face images. You'll also learn how to align your images to enhance the recognition results. If you want to do emotion classification instead of gender classification, all you need to do is to update is your training data and the configuration you pass to the demo. |
||||
|
||||
Prerequisites |
||||
-------------- |
||||
|
||||
For gender classification of faces, you'll need some images of male and female faces first. I've decided to search faces of celebrities using `Google Images <http://www.google.com/images>`_ with the faces filter turned on (my god, they have great algorithms at `Google <http://www.google.com>`_!). My database has 8 male and 5 female subjects, each with 10 images. Here are the names, if you don't know who to search: |
||||
|
||||
* Angelina Jolie |
||||
* Arnold Schwarzenegger |
||||
* Brad Pitt |
||||
* Emma Watson |
||||
* George Clooney |
||||
* Jennifer Lopez |
||||
* Johnny Depp |
||||
* Justin Timberlake |
||||
* Katy Perry |
||||
* Keanu Reeves |
||||
* Naomi Watts |
||||
* Patrick Stewart |
||||
* Tom Cruise |
||||
|
||||
Once you have acquired some images, you'll need to read them. In the demo application I have decided to read the images from a very simple CSV file. Why? Because it's the simplest platform-independent approach I can think of. However, if you know a simpler solution please ping me about it. Basically all the CSV file needs to contain are lines composed of a ``filename`` followed by a ``;`` followed by the ``label`` (as *integer number*), making up a line like this: |
||||
|
||||
.. code-block:: none |
||||
|
||||
/path/to/image.ext;0 |
||||
|
||||
Let's dissect the line. ``/path/to/image.ext`` is the path to an image, probably something like this if you are in Windows: ``C:/faces/person0/image0.jpg``. Then there is the separator ``;`` and finally we assign a label ``0`` to the image. Think of the label as the subject (the person, the gender or whatever comes to your mind). In the gender classification scenario, the label is the gender the person has. I'll give the label ``0`` to *male* persons and the label ``1`` is for *female* subjects. So my CSV file looks like this: |
||||
|
||||
.. code-block:: none |
||||
|
||||
/home/philipp/facerec/data/gender/male/keanu_reeves/keanu_reeves_01.jpg;0 |
||||
/home/philipp/facerec/data/gender/male/keanu_reeves/keanu_reeves_02.jpg;0 |
||||
/home/philipp/facerec/data/gender/male/keanu_reeves/keanu_reeves_03.jpg;0 |
||||
... |
||||
/home/philipp/facerec/data/gender/female/katy_perry/katy_perry_01.jpg;1 |
||||
/home/philipp/facerec/data/gender/female/katy_perry/katy_perry_02.jpg;1 |
||||
/home/philipp/facerec/data/gender/female/katy_perry/katy_perry_03.jpg;1 |
||||
... |
||||
/home/philipp/facerec/data/gender/male/brad_pitt/brad_pitt_01.jpg;0 |
||||
/home/philipp/facerec/data/gender/male/brad_pitt/brad_pitt_02.jpg;0 |
||||
/home/philipp/facerec/data/gender/male/brad_pitt/brad_pitt_03.jpg;0 |
||||
... |
||||
/home/philipp/facerec/data/gender/female/emma_watson/emma_watson_08.jpg;1 |
||||
/home/philipp/facerec/data/gender/female/emma_watson/emma_watson_02.jpg;1 |
||||
/home/philipp/facerec/data/gender/female/emma_watson/emma_watson_03.jpg;1 |
||||
|
||||
All images for this example were chosen to have a frontal face perspective. They have been cropped, scaled and rotated to be aligned at the eyes, just like this set of George Clooney images: |
||||
|
||||
.. image:: ../img/tutorial/gender_classification/clooney_set.png |
||||
:align: center |
||||
|
||||
You really don't want to create the CSV file by hand. And you really don't want scale, rotate & translate the images manually. I have prepared you two Python scripts ``create_csv.py`` and ``crop_face.py``, you can find them in the ``src`` folder coming with this documentation. You'll see how to use them in the :ref:`appendixfgc`. |
||||
|
||||
Fisherfaces for Gender Classification |
||||
-------------------------------------- |
||||
|
||||
If you want to decide whether a person is *male* or *female*, you have to learn the discriminative features of both classes. The Eigenfaces method is based on the Principal Component Analysis, which is an unsupervised statistical model and not suitable for this task. Please see the Face Recognition tutorial for insights into the algorithms. The Fisherfaces instead yields a class-specific linear projection, so it is much better suited for the gender classification task. `http://www.bytefish.de/blog/gender_classification <http://www.bytefish.de/blog/gender_classification>`_ shows the recognition rate of the Fisherfaces method for gender classification. |
||||
|
||||
The Fisherfaces method achieves a 98% recognition rate in a subject-independent cross-validation. A subject-independent cross-validation means *images of the person under test are never used for learning the model*. And could you believe it: you can simply use the facerec_fisherfaces demo, that's inlcuded in OpenCV. |
||||
|
||||
Fisherfaces in OpenCV |
||||
--------------------- |
||||
|
||||
The source code for this demo application is also available in the ``src`` folder coming with this documentation: |
||||
|
||||
* :download:`src/facerec_fisherfaces.cpp <../src/facerec_fisherfaces.cpp>` |
||||
|
||||
.. literalinclude:: ../src/facerec_fisherfaces.cpp |
||||
:language: cpp |
||||
:linenos: |
||||
|
||||
Running the Demo |
||||
---------------- |
||||
|
||||
If you are in Windows, then simply start the demo by running (from command line): |
||||
|
||||
.. code-block:: none |
||||
|
||||
facerec_fisherfaces.exe C:/path/to/your/csv.ext |
||||
|
||||
If you are in Linux, then simply start the demo by running: |
||||
|
||||
.. code-block:: none |
||||
|
||||
./facerec_fisherfaces /path/to/your/csv.ext |
||||
|
||||
If you don't want to display the images, but save them, then pass the desired path to the demo. It works like this in Windows: |
||||
|
||||
.. code-block:: none |
||||
|
||||
facerec_fisherfaces.exe C:/path/to/your/csv.ext C:/path/to/store/results/at |
||||
|
||||
And in Linux: |
||||
|
||||
.. code-block:: none |
||||
|
||||
./facerec_fisherfaces /path/to/your/csv.ext /path/to/store/results/at |
||||
|
||||
Results |
||||
------- |
||||
|
||||
If you run the program with your CSV file as parameter, you'll see the Fisherface that separates between male and female images. I've decided to apply a Jet colormap in this demo, so you can see which features the method identifies: |
||||
|
||||
.. image:: ../img/tutorial/gender_classification/fisherface_0.png |
||||
|
||||
The demo also shows the average face of the male and female training images you have passed: |
||||
|
||||
.. image:: ../img/tutorial/gender_classification/mean.png |
||||
|
||||
Moreover it the demo should yield the prediction for the correct gender: |
||||
|
||||
.. code-block:: none |
||||
|
||||
Predicted class = 1 / Actual class = 1. |
||||
|
||||
And for advanced users I have also shown the Eigenvalue for the Fisherface: |
||||
|
||||
.. code-block:: none |
||||
|
||||
Eigenvalue #0 = 152.49493 |
||||
|
||||
And the Fisherfaces reconstruction: |
||||
|
||||
.. image:: ../img/tutorial/gender_classification/fisherface_reconstruction_0.png |
||||
|
||||
I hope this gives you an idea how to approach gender classification and the other image classification tasks. |
||||
|
||||
.. _appendixfgc: |
||||
|
||||
Appendix |
||||
-------- |
||||
|
||||
Creating the CSV File |
||||
+++++++++++++++++++++ |
||||
|
||||
You don't really want to create the CSV file by hand. I have prepared you a little Python script ``create_csv.py`` (you find it at ``/src/create_csv.py`` coming with this tutorial) that automatically creates you a CSV file. If you have your images in hierarchie like this (``/basepath/<subject>/<image.ext>``): |
||||
|
||||
.. code-block:: none |
||||
|
||||
philipp@mango:~/facerec/data/at$ tree |
||||
. |
||||
|-- s1 |
||||
| |-- 1.pgm |
||||
| |-- ... |
||||
| |-- 10.pgm |
||||
|-- s2 |
||||
| |-- 1.pgm |
||||
| |-- ... |
||||
| |-- 10.pgm |
||||
... |
||||
|-- s40 |
||||
| |-- 1.pgm |
||||
| |-- ... |
||||
| |-- 10.pgm |
||||
|
||||
|
||||
Then simply call ``create_csv.py`` with the path to the folder, just like this and you could save the output: |
||||
|
||||
.. code-block:: none |
||||
|
||||
philipp@mango:~/facerec/data$ python create_csv.py |
||||
at/s13/2.pgm;0 |
||||
at/s13/7.pgm;0 |
||||
at/s13/6.pgm;0 |
||||
at/s13/9.pgm;0 |
||||
at/s13/5.pgm;0 |
||||
at/s13/3.pgm;0 |
||||
at/s13/4.pgm;0 |
||||
at/s13/10.pgm;0 |
||||
at/s13/8.pgm;0 |
||||
at/s13/1.pgm;0 |
||||
at/s17/2.pgm;1 |
||||
at/s17/7.pgm;1 |
||||
at/s17/6.pgm;1 |
||||
at/s17/9.pgm;1 |
||||
at/s17/5.pgm;1 |
||||
at/s17/3.pgm;1 |
||||
[...] |
||||
|
||||
Here is the script, if you can't find it: |
||||
|
||||
.. literalinclude:: ../src/create_csv.py |
||||
:language: python |
||||
:linenos: |
||||
|
||||
Aligning Face Images |
||||
++++++++++++++++++++ |
||||
|
||||
An accurate alignment of your image data is especially important in tasks like emotion detection, were you need as much detail as possible. Believe me... You don't want to do this by hand. So I've prepared you a tiny Python script. The code is really easy to use. To scale, rotate and crop the face image you just need to call *CropFace(image, eye_left, eye_right, offset_pct, dest_sz)*, where: |
||||
|
||||
* *eye_left* is the position of the left eye |
||||
* *eye_right* is the position of the right eye |
||||
* *offset_pct* is the percent of the image you want to keep next to the eyes (horizontal, vertical direction) |
||||
* *dest_sz* is the size of the output image |
||||
|
||||
If you are using the same *offset_pct* and *dest_sz* for your images, they are all aligned at the eyes. |
||||
|
||||
.. literalinclude:: ../src/crop_face.py |
||||
:language: python |
||||
:linenos: |
||||
|
||||
Imagine we are given `this photo of Arnold Schwarzenegger <http://en.wikipedia.org/wiki/File:Arnold_Schwarzenegger_edit%28ws%29.jpg>`_, which is under a Public Domain license. The (x,y)-position of the eyes is approximately *(252,364)* for the left and *(420,366)* for the right eye. Now you only need to define the horizontal offset, vertical offset and the size your scaled, rotated & cropped face should have. |
||||
|
||||
Here are some examples: |
||||
|
||||
+---------------------------------+----------------------------------------------------------------------------+ |
||||
| Configuration | Cropped, Scaled, Rotated Face | |
||||
+=================================+============================================================================+ |
||||
| 0.1 (10%), 0.1 (10%), (200,200) | .. image:: ../img/tutorial/gender_classification/arnie_10_10_200_200.jpg | |
||||
+---------------------------------+----------------------------------------------------------------------------+ |
||||
| 0.2 (20%), 0.2 (20%), (200,200) | .. image:: ../img/tutorial/gender_classification/arnie_20_20_200_200.jpg | |
||||
+---------------------------------+----------------------------------------------------------------------------+ |
||||
| 0.3 (30%), 0.3 (30%), (200,200) | .. image:: ../img/tutorial/gender_classification/arnie_30_30_200_200.jpg | |
||||
+---------------------------------+----------------------------------------------------------------------------+ |
||||
| 0.2 (20%), 0.2 (20%), (70,70) | .. image:: ../img/tutorial/gender_classification/arnie_20_20_70_70.jpg | |
||||
+---------------------------------+----------------------------------------------------------------------------+ |
@ -0,0 +1,46 @@ |
||||
Saving and Loading a FaceRecognizer |
||||
=================================== |
||||
|
||||
Introduction |
||||
------------ |
||||
|
||||
Saving and loading a :ocv:class:`FaceRecognizer` is very important. Training a FaceRecognizer can be a very time-intense task, plus it's often impossible to ship the whole face database to the user of your product. The task of saving and loading a FaceRecognizer is easy with :ocv:class:`FaceRecognizer`. You only have to call :ocv:func:`FaceRecognizer::load` for loading and :ocv:func:`FaceRecognizer::save` for saving a :ocv:class:`FaceRecognizer`. |
||||
|
||||
I'll adapt the Eigenfaces example from the :doc:`../facerec_tutorial`: Imagine we want to learn the Eigenfaces of the `AT&T Facedatabase <http://www.cl.cam.ac.uk/research/dtg/attarchive/facedatabase.html>`_, store the model to a YAML file and then load it again. |
||||
|
||||
From the loaded model, we'll get a prediction, show the mean, Eigenfaces and the image reconstruction. |
||||
|
||||
Using FaceRecognizer::save and FaceRecognizer::load |
||||
----------------------------------------------------- |
||||
|
||||
The source code for this demo application is also available in the ``src`` folder coming with this documentation: |
||||
|
||||
* :download:`src/facerec_save_load.cpp <../src/facerec_save_load.cpp>` |
||||
|
||||
.. literalinclude:: ../src/facerec_save_load.cpp |
||||
:language: cpp |
||||
:linenos: |
||||
|
||||
Results |
||||
------- |
||||
|
||||
``eigenfaces_at.yml`` then contains the model state, we'll simply look at the first 10 lines with ``head eigenfaces_at.yml``: |
||||
|
||||
.. code-block:: none |
||||
|
||||
philipp@mango:~/github/libfacerec-build$ head eigenfaces_at.yml |
||||
%YAML:1.0 |
||||
num_components: 399 |
||||
mean: !!opencv-matrix |
||||
rows: 1 |
||||
cols: 10304 |
||||
dt: d |
||||
data: [ 8.5558897243107765e+01, 8.5511278195488714e+01, |
||||
8.5854636591478695e+01, 8.5796992481203006e+01, |
||||
8.5952380952380949e+01, 8.6162907268170414e+01, |
||||
8.6082706766917283e+01, 8.5776942355889716e+01, |
||||
|
||||
And here is the Reconstruction, which is the same as the original: |
||||
|
||||
.. image:: ../img/eigenface_reconstruction_opencv.png |
||||
:align: center |
@ -0,0 +1,207 @@ |
||||
Face Recognition in Videos with OpenCV |
||||
======================================= |
||||
|
||||
.. contents:: Table of Contents |
||||
:depth: 3 |
||||
|
||||
Introduction |
||||
------------ |
||||
|
||||
Whenever you hear the term *face recognition*, you instantly think of surveillance in videos. So performing face recognition in videos (e.g. webcam) is one of the most requested features I have got. I have heard your cries, so here it is. An application, that shows you how to do face recognition in videos! For the face detection part we'll use the awesome :ocv:class:`CascadeClassifier` and we'll use :ocv:class:`FaceRecognizer` for face recognition. This example uses the Fisherfaces method for face recognition, because it is robust against large changes in illumination. |
||||
|
||||
Here is what the final application looks like. As you can see I am only writing the id of the recognized person above the detected face (by the way this id is Arnold Schwarzenegger for my data set): |
||||
|
||||
.. image:: ../img/tutorial/facerec_video/facerec_video.png |
||||
:align: center |
||||
:scale: 70% |
||||
|
||||
This demo is a basis for your research and it shows you how to implement face recognition in videos. You probably want to extend the application and make it more sophisticated: You could combine the id with the name, then show the confidence of the prediction, recognize the emotion... and and and. But before you send mails, asking what these Haar-Cascade thing is or what a CSV is: Make sure you have read the entire tutorial. It's all explained in here. If you just want to scroll down to the code, please note: |
||||
|
||||
* The available Haar-Cascades for face detection are located in the ``data`` folder of your OpenCV installation! One of the available Haar-Cascades for face detection is for example ``/path/to/opencv/data/haarcascades/haarcascade_frontalface_default.xml``. |
||||
|
||||
I encourage you to experiment with the application. Play around with the available :ocv:class:`FaceRecognizer` implementations, try the available cascades in OpenCV and see if you can improve your results! |
||||
|
||||
Prerequisites |
||||
-------------- |
||||
|
||||
You want to do face recognition, so you need some face images to learn a :ocv:class:`FaceRecognizer` on. I have decided to reuse the images from the gender classification example: :doc:`facerec_gender_classification`. |
||||
|
||||
I have the following celebrities in my training data set: |
||||
|
||||
* Angelina Jolie |
||||
* Arnold Schwarzenegger |
||||
* Brad Pitt |
||||
* George Clooney |
||||
* Johnny Depp |
||||
* Justin Timberlake |
||||
* Katy Perry |
||||
* Keanu Reeves |
||||
* Patrick Stewart |
||||
* Tom Cruise |
||||
|
||||
In the demo I have decided to read the images from a very simple CSV file. Why? Because it's the simplest platform-independent approach I can think of. However, if you know a simpler solution please ping me about it. Basically all the CSV file needs to contain are lines composed of a ``filename`` followed by a ``;`` followed by the ``label`` (as *integer number*), making up a line like this: |
||||
|
||||
.. code-block:: none |
||||
|
||||
/path/to/image.ext;0 |
||||
|
||||
Let's dissect the line. ``/path/to/image.ext`` is the path to an image, probably something like this if you are in Windows: ``C:/faces/person0/image0.jpg``. Then there is the separator ``;`` and finally we assign a label ``0`` to the image. Think of the label as the subject (the person, the gender or whatever comes to your mind). In the face recognition scenario, the label is the person this image belongs to. In the gender classification scenario, the label is the gender the person has. So my CSV file looks like this: |
||||
|
||||
.. code-block:: none |
||||
|
||||
/home/philipp/facerec/data/c/keanu_reeves/keanu_reeves_01.jpg;0 |
||||
/home/philipp/facerec/data/c/keanu_reeves/keanu_reeves_02.jpg;0 |
||||
/home/philipp/facerec/data/c/keanu_reeves/keanu_reeves_03.jpg;0 |
||||
... |
||||
/home/philipp/facerec/data/c/katy_perry/katy_perry_01.jpg;1 |
||||
/home/philipp/facerec/data/c/katy_perry/katy_perry_02.jpg;1 |
||||
/home/philipp/facerec/data/c/katy_perry/katy_perry_03.jpg;1 |
||||
... |
||||
/home/philipp/facerec/data/c/brad_pitt/brad_pitt_01.jpg;2 |
||||
/home/philipp/facerec/data/c/brad_pitt/brad_pitt_02.jpg;2 |
||||
/home/philipp/facerec/data/c/brad_pitt/brad_pitt_03.jpg;2 |
||||
... |
||||
/home/philipp/facerec/data/c1/crop_arnold_schwarzenegger/crop_08.jpg;6 |
||||
/home/philipp/facerec/data/c1/crop_arnold_schwarzenegger/crop_05.jpg;6 |
||||
/home/philipp/facerec/data/c1/crop_arnold_schwarzenegger/crop_02.jpg;6 |
||||
/home/philipp/facerec/data/c1/crop_arnold_schwarzenegger/crop_03.jpg;6 |
||||
|
||||
All images for this example were chosen to have a frontal face perspective. They have been cropped, scaled and rotated to be aligned at the eyes, just like this set of George Clooney images: |
||||
|
||||
.. image:: ../img/tutorial/gender_classification/clooney_set.png |
||||
:align: center |
||||
|
||||
Face Recongition from Videos |
||||
----------------------------- |
||||
|
||||
The source code for the demo is available in the ``src`` folder coming with this documentation: |
||||
|
||||
* :download:`src/facerec_video.cpp <../src/facerec_video.cpp>` |
||||
|
||||
This demo uses the :ocv:class:`CascadeClassifier`: |
||||
|
||||
.. literalinclude:: ../src/facerec_video.cpp |
||||
:language: cpp |
||||
:linenos: |
||||
|
||||
Running the Demo |
||||
---------------- |
||||
|
||||
You'll need: |
||||
|
||||
* The path to a valid Haar-Cascade for detecting a face with a :ocv:class:`CascadeClassifier`. |
||||
* The path to a valid CSV File for learning a :ocv:class:`FaceRecognizer`. |
||||
* A webcam and its device id (you don't know the device id? Simply start from 0 on and see what happens). |
||||
|
||||
If you are in Windows, then simply start the demo by running (from command line): |
||||
|
||||
.. code-block:: none |
||||
|
||||
facerec_video.exe <C:/path/to/your/haar_cascade.xml> <C:/path/to/your/csv.ext> <video device> |
||||
|
||||
If you are in Linux, then simply start the demo by running: |
||||
|
||||
.. code-block:: none |
||||
|
||||
./facerec_video </path/to/your/haar_cascade.xml> </path/to/your/csv.ext> <video device> |
||||
|
||||
An example. If the haar-cascade is at ``C:/opencv/data/haarcascades/haarcascade_frontalface_default.xml``, the CSV file is at ``C:/facerec/data/celebrities.txt`` and I have a webcam with deviceId ``1``, then I would call the demo with: |
||||
|
||||
.. code-block:: none |
||||
|
||||
facerec_video.exe C:/opencv/data/haarcascades/haarcascade_frontalface_default.xml C:/facerec/data/celebrities.txt 1 |
||||
|
||||
That's it. |
||||
|
||||
Results |
||||
------- |
||||
|
||||
Enjoy! |
||||
|
||||
Appendix |
||||
-------- |
||||
|
||||
Creating the CSV File |
||||
+++++++++++++++++++++ |
||||
|
||||
You don't really want to create the CSV file by hand. I have prepared you a little Python script ``create_csv.py`` (you find it at ``/src/create_csv.py`` coming with this tutorial) that automatically creates you a CSV file. If you have your images in hierarchie like this (``/basepath/<subject>/<image.ext>``): |
||||
|
||||
.. code-block:: none |
||||
|
||||
philipp@mango:~/facerec/data/at$ tree |
||||
. |
||||
|-- s1 |
||||
| |-- 1.pgm |
||||
| |-- ... |
||||
| |-- 10.pgm |
||||
|-- s2 |
||||
| |-- 1.pgm |
||||
| |-- ... |
||||
| |-- 10.pgm |
||||
... |
||||
|-- s40 |
||||
| |-- 1.pgm |
||||
| |-- ... |
||||
| |-- 10.pgm |
||||
|
||||
|
||||
Then simply call ``create_csv.py`` with the path to the folder, just like this and you could save the output: |
||||
|
||||
.. code-block:: none |
||||
|
||||
philipp@mango:~/facerec/data$ python create_csv.py |
||||
at/s13/2.pgm;0 |
||||
at/s13/7.pgm;0 |
||||
at/s13/6.pgm;0 |
||||
at/s13/9.pgm;0 |
||||
at/s13/5.pgm;0 |
||||
at/s13/3.pgm;0 |
||||
at/s13/4.pgm;0 |
||||
at/s13/10.pgm;0 |
||||
at/s13/8.pgm;0 |
||||
at/s13/1.pgm;0 |
||||
at/s17/2.pgm;1 |
||||
at/s17/7.pgm;1 |
||||
at/s17/6.pgm;1 |
||||
at/s17/9.pgm;1 |
||||
at/s17/5.pgm;1 |
||||
at/s17/3.pgm;1 |
||||
[...] |
||||
|
||||
Here is the script, if you can't find it: |
||||
|
||||
.. literalinclude:: ../src/create_csv.py |
||||
:language: python |
||||
:linenos: |
||||
|
||||
Aligning Face Images |
||||
++++++++++++++++++++ |
||||
|
||||
An accurate alignment of your image data is especially important in tasks like emotion detection, were you need as much detail as possible. Believe me... You don't want to do this by hand. So I've prepared you a tiny Python script. The code is really easy to use. To scale, rotate and crop the face image you just need to call *CropFace(image, eye_left, eye_right, offset_pct, dest_sz)*, where: |
||||
|
||||
* *eye_left* is the position of the left eye |
||||
* *eye_right* is the position of the right eye |
||||
* *offset_pct* is the percent of the image you want to keep next to the eyes (horizontal, vertical direction) |
||||
* *dest_sz* is the size of the output image |
||||
|
||||
If you are using the same *offset_pct* and *dest_sz* for your images, they are all aligned at the eyes. |
||||
|
||||
.. literalinclude:: ../src/crop_face.py |
||||
:language: python |
||||
:linenos: |
||||
|
||||
Imagine we are given `this photo of Arnold Schwarzenegger <http://en.wikipedia.org/wiki/File:Arnold_Schwarzenegger_edit%28ws%29.jpg>`_, which is under a Public Domain license. The (x,y)-position of the eyes is approximately *(252,364)* for the left and *(420,366)* for the right eye. Now you only need to define the horizontal offset, vertical offset and the size your scaled, rotated & cropped face should have. |
||||
|
||||
Here are some examples: |
||||
|
||||
+---------------------------------+----------------------------------------------------------------------------+ |
||||
| Configuration | Cropped, Scaled, Rotated Face | |
||||
+=================================+============================================================================+ |
||||
| 0.1 (10%), 0.1 (10%), (200,200) | .. image:: ../img/tutorial/gender_classification/arnie_10_10_200_200.jpg | |
||||
+---------------------------------+----------------------------------------------------------------------------+ |
||||
| 0.2 (20%), 0.2 (20%), (200,200) | .. image:: ../img/tutorial/gender_classification/arnie_20_20_200_200.jpg | |
||||
+---------------------------------+----------------------------------------------------------------------------+ |
||||
| 0.3 (30%), 0.3 (30%), (200,200) | .. image:: ../img/tutorial/gender_classification/arnie_30_30_200_200.jpg | |
||||
+---------------------------------+----------------------------------------------------------------------------+ |
||||
| 0.2 (20%), 0.2 (20%), (70,70) | .. image:: ../img/tutorial/gender_classification/arnie_20_20_70_70.jpg | |
||||
+---------------------------------+----------------------------------------------------------------------------+ |
@ -0,0 +1,44 @@ |
||||
/*
|
||||
By downloading, copying, installing or using the software you agree to this |
||||
license. If you do not agree to this license, do not download, install, |
||||
copy or use the software. |
||||
|
||||
License Agreement |
||||
For Open Source Computer Vision Library |
||||
(3-clause BSD License) |
||||
|
||||
Copyright (C) 2013, OpenCV Foundation, all rights reserved. |
||||
Third party copyrights are property of their respective owners. |
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, |
||||
are permitted provided that the following conditions are met: |
||||
|
||||
* Redistributions of source code must retain the above copyright notice, |
||||
this list of conditions and the following disclaimer. |
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice, |
||||
this list of conditions and the following disclaimer in the documentation |
||||
and/or other materials provided with the distribution. |
||||
|
||||
* Neither the names of the copyright holders nor the names of the contributors |
||||
may be used to endorse or promote products derived from this software |
||||
without specific prior written permission. |
||||
|
||||
This software is provided by the copyright holders and contributors "as is" and |
||||
any express or implied warranties, including, but not limited to, the implied |
||||
warranties of merchantability and fitness for a particular purpose are |
||||
disclaimed. In no event shall copyright holders or contributors be liable for |
||||
any direct, indirect, incidental, special, exemplary, or consequential damages |
||||
(including, but not limited to, procurement of substitute goods or services; |
||||
loss of use, data, or profits; or business interruption) however caused |
||||
and on any theory of liability, whether in contract, strict liability, |
||||
or tort (including negligence or otherwise) arising in any way out of |
||||
the use of this software, even if advised of the possibility of such damage. |
||||
*/ |
||||
|
||||
#ifndef __OPENCV_FACE_HPP__ |
||||
#define __OPENCV_FACE_HPP__ |
||||
|
||||
#include "opencv2/face/facerec.hpp" |
||||
|
||||
#endif |
@ -0,0 +1,63 @@ |
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
// Copyright (c) 2011,2012. Philipp Wagner <bytefish[at]gmx[dot]de>.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
|
||||
#ifndef __OPENCV_FACEREC_HPP__ |
||||
#define __OPENCV_FACEREC_HPP__ |
||||
|
||||
#include "opencv2/core.hpp" |
||||
|
||||
namespace cv { namespace face { |
||||
|
||||
class CV_EXPORTS_W FaceRecognizer : public Algorithm |
||||
{ |
||||
public: |
||||
//! virtual destructor
|
||||
virtual ~FaceRecognizer() {} |
||||
|
||||
// Trains a FaceRecognizer.
|
||||
CV_WRAP virtual void train(InputArrayOfArrays src, InputArray labels) = 0; |
||||
|
||||
// Updates a FaceRecognizer.
|
||||
CV_WRAP virtual void update(InputArrayOfArrays src, InputArray labels) = 0; |
||||
|
||||
// Gets a prediction from a FaceRecognizer.
|
||||
virtual int predict(InputArray src) const = 0; |
||||
|
||||
// Predicts the label and confidence for a given sample.
|
||||
CV_WRAP virtual void predict(InputArray src, CV_OUT int &label, CV_OUT double &confidence) const = 0; |
||||
|
||||
// Serializes this object to a given filename.
|
||||
CV_WRAP virtual void save(const String& filename) const = 0; |
||||
|
||||
// Deserializes this object from a given filename.
|
||||
CV_WRAP virtual void load(const String& filename) = 0; |
||||
|
||||
// Serializes this object to a given cv::FileStorage.
|
||||
virtual void save(FileStorage& fs) const = 0; |
||||
|
||||
// Deserializes this object from a given cv::FileStorage.
|
||||
virtual void load(const FileStorage& fs) = 0; |
||||
|
||||
// Sets additional string info for the label
|
||||
virtual void setLabelInfo(int label, const String& strInfo) = 0; |
||||
|
||||
// Gets string info by label
|
||||
virtual String getLabelInfo(int label) const = 0; |
||||
|
||||
// Gets labels by string
|
||||
virtual std::vector<int> getLabelsByString(const String& str) const = 0; |
||||
}; |
||||
|
||||
CV_EXPORTS_W Ptr<FaceRecognizer> createEigenFaceRecognizer(int num_components = 0, double threshold = DBL_MAX); |
||||
CV_EXPORTS_W Ptr<FaceRecognizer> createFisherFaceRecognizer(int num_components = 0, double threshold = DBL_MAX); |
||||
CV_EXPORTS_W Ptr<FaceRecognizer> createLBPHFaceRecognizer(int radius=1, int neighbors=8, int grid_x=8, int grid_y=8, double threshold = DBL_MAX); |
||||
|
||||
bool initModule_facerec(); |
||||
|
||||
}} //namespace cv::face
|
||||
|
||||
#endif //__OPENCV_FACEREC_HPP__
|
@ -0,0 +1,400 @@ |
||||
/path/to/at/s13/2.pgm;12 |
||||
/path/to/at/s13/7.pgm;12 |
||||
/path/to/at/s13/6.pgm;12 |
||||
/path/to/at/s13/9.pgm;12 |
||||
/path/to/at/s13/5.pgm;12 |
||||
/path/to/at/s13/3.pgm;12 |
||||
/path/to/at/s13/4.pgm;12 |
||||
/path/to/at/s13/10.pgm;12 |
||||
/path/to/at/s13/8.pgm;12 |
||||
/path/to/at/s13/1.pgm;12 |
||||
/path/to/at/s17/2.pgm;16 |
||||
/path/to/at/s17/7.pgm;16 |
||||
/path/to/at/s17/6.pgm;16 |
||||
/path/to/at/s17/9.pgm;16 |
||||
/path/to/at/s17/5.pgm;16 |
||||
/path/to/at/s17/3.pgm;16 |
||||
/path/to/at/s17/4.pgm;16 |
||||
/path/to/at/s17/10.pgm;16 |
||||
/path/to/at/s17/8.pgm;16 |
||||
/path/to/at/s17/1.pgm;16 |
||||
/path/to/at/s32/2.pgm;31 |
||||
/path/to/at/s32/7.pgm;31 |
||||
/path/to/at/s32/6.pgm;31 |
||||
/path/to/at/s32/9.pgm;31 |
||||
/path/to/at/s32/5.pgm;31 |
||||
/path/to/at/s32/3.pgm;31 |
||||
/path/to/at/s32/4.pgm;31 |
||||
/path/to/at/s32/10.pgm;31 |
||||
/path/to/at/s32/8.pgm;31 |
||||
/path/to/at/s32/1.pgm;31 |
||||
/path/to/at/s10/2.pgm;9 |
||||
/path/to/at/s10/7.pgm;9 |
||||
/path/to/at/s10/6.pgm;9 |
||||
/path/to/at/s10/9.pgm;9 |
||||
/path/to/at/s10/5.pgm;9 |
||||
/path/to/at/s10/3.pgm;9 |
||||
/path/to/at/s10/4.pgm;9 |
||||
/path/to/at/s10/10.pgm;9 |
||||
/path/to/at/s10/8.pgm;9 |
||||
/path/to/at/s10/1.pgm;9 |
||||
/path/to/at/s27/2.pgm;26 |
||||
/path/to/at/s27/7.pgm;26 |
||||
/path/to/at/s27/6.pgm;26 |
||||
/path/to/at/s27/9.pgm;26 |
||||
/path/to/at/s27/5.pgm;26 |
||||
/path/to/at/s27/3.pgm;26 |
||||
/path/to/at/s27/4.pgm;26 |
||||
/path/to/at/s27/10.pgm;26 |
||||
/path/to/at/s27/8.pgm;26 |
||||
/path/to/at/s27/1.pgm;26 |
||||
/path/to/at/s5/2.pgm;4 |
||||
/path/to/at/s5/7.pgm;4 |
||||
/path/to/at/s5/6.pgm;4 |
||||
/path/to/at/s5/9.pgm;4 |
||||
/path/to/at/s5/5.pgm;4 |
||||
/path/to/at/s5/3.pgm;4 |
||||
/path/to/at/s5/4.pgm;4 |
||||
/path/to/at/s5/10.pgm;4 |
||||
/path/to/at/s5/8.pgm;4 |
||||
/path/to/at/s5/1.pgm;4 |
||||
/path/to/at/s20/2.pgm;19 |
||||
/path/to/at/s20/7.pgm;19 |
||||
/path/to/at/s20/6.pgm;19 |
||||
/path/to/at/s20/9.pgm;19 |
||||
/path/to/at/s20/5.pgm;19 |
||||
/path/to/at/s20/3.pgm;19 |
||||
/path/to/at/s20/4.pgm;19 |
||||
/path/to/at/s20/10.pgm;19 |
||||
/path/to/at/s20/8.pgm;19 |
||||
/path/to/at/s20/1.pgm;19 |
||||
/path/to/at/s30/2.pgm;29 |
||||
/path/to/at/s30/7.pgm;29 |
||||
/path/to/at/s30/6.pgm;29 |
||||
/path/to/at/s30/9.pgm;29 |
||||
/path/to/at/s30/5.pgm;29 |
||||
/path/to/at/s30/3.pgm;29 |
||||
/path/to/at/s30/4.pgm;29 |
||||
/path/to/at/s30/10.pgm;29 |
||||
/path/to/at/s30/8.pgm;29 |
||||
/path/to/at/s30/1.pgm;29 |
||||
/path/to/at/s39/2.pgm;38 |
||||
/path/to/at/s39/7.pgm;38 |
||||
/path/to/at/s39/6.pgm;38 |
||||
/path/to/at/s39/9.pgm;38 |
||||
/path/to/at/s39/5.pgm;38 |
||||
/path/to/at/s39/3.pgm;38 |
||||
/path/to/at/s39/4.pgm;38 |
||||
/path/to/at/s39/10.pgm;38 |
||||
/path/to/at/s39/8.pgm;38 |
||||
/path/to/at/s39/1.pgm;38 |
||||
/path/to/at/s35/2.pgm;34 |
||||
/path/to/at/s35/7.pgm;34 |
||||
/path/to/at/s35/6.pgm;34 |
||||
/path/to/at/s35/9.pgm;34 |
||||
/path/to/at/s35/5.pgm;34 |
||||
/path/to/at/s35/3.pgm;34 |
||||
/path/to/at/s35/4.pgm;34 |
||||
/path/to/at/s35/10.pgm;34 |
||||
/path/to/at/s35/8.pgm;34 |
||||
/path/to/at/s35/1.pgm;34 |
||||
/path/to/at/s23/2.pgm;22 |
||||
/path/to/at/s23/7.pgm;22 |
||||
/path/to/at/s23/6.pgm;22 |
||||
/path/to/at/s23/9.pgm;22 |
||||
/path/to/at/s23/5.pgm;22 |
||||
/path/to/at/s23/3.pgm;22 |
||||
/path/to/at/s23/4.pgm;22 |
||||
/path/to/at/s23/10.pgm;22 |
||||
/path/to/at/s23/8.pgm;22 |
||||
/path/to/at/s23/1.pgm;22 |
||||
/path/to/at/s4/2.pgm;3 |
||||
/path/to/at/s4/7.pgm;3 |
||||
/path/to/at/s4/6.pgm;3 |
||||
/path/to/at/s4/9.pgm;3 |
||||
/path/to/at/s4/5.pgm;3 |
||||
/path/to/at/s4/3.pgm;3 |
||||
/path/to/at/s4/4.pgm;3 |
||||
/path/to/at/s4/10.pgm;3 |
||||
/path/to/at/s4/8.pgm;3 |
||||
/path/to/at/s4/1.pgm;3 |
||||
/path/to/at/s9/2.pgm;8 |
||||
/path/to/at/s9/7.pgm;8 |
||||
/path/to/at/s9/6.pgm;8 |
||||
/path/to/at/s9/9.pgm;8 |
||||
/path/to/at/s9/5.pgm;8 |
||||
/path/to/at/s9/3.pgm;8 |
||||
/path/to/at/s9/4.pgm;8 |
||||
/path/to/at/s9/10.pgm;8 |
||||
/path/to/at/s9/8.pgm;8 |
||||
/path/to/at/s9/1.pgm;8 |
||||
/path/to/at/s37/2.pgm;36 |
||||
/path/to/at/s37/7.pgm;36 |
||||
/path/to/at/s37/6.pgm;36 |
||||
/path/to/at/s37/9.pgm;36 |
||||
/path/to/at/s37/5.pgm;36 |
||||
/path/to/at/s37/3.pgm;36 |
||||
/path/to/at/s37/4.pgm;36 |
||||
/path/to/at/s37/10.pgm;36 |
||||
/path/to/at/s37/8.pgm;36 |
||||
/path/to/at/s37/1.pgm;36 |
||||
/path/to/at/s24/2.pgm;23 |
||||
/path/to/at/s24/7.pgm;23 |
||||
/path/to/at/s24/6.pgm;23 |
||||
/path/to/at/s24/9.pgm;23 |
||||
/path/to/at/s24/5.pgm;23 |
||||
/path/to/at/s24/3.pgm;23 |
||||
/path/to/at/s24/4.pgm;23 |
||||
/path/to/at/s24/10.pgm;23 |
||||
/path/to/at/s24/8.pgm;23 |
||||
/path/to/at/s24/1.pgm;23 |
||||
/path/to/at/s19/2.pgm;18 |
||||
/path/to/at/s19/7.pgm;18 |
||||
/path/to/at/s19/6.pgm;18 |
||||
/path/to/at/s19/9.pgm;18 |
||||
/path/to/at/s19/5.pgm;18 |
||||
/path/to/at/s19/3.pgm;18 |
||||
/path/to/at/s19/4.pgm;18 |
||||
/path/to/at/s19/10.pgm;18 |
||||
/path/to/at/s19/8.pgm;18 |
||||
/path/to/at/s19/1.pgm;18 |
||||
/path/to/at/s8/2.pgm;7 |
||||
/path/to/at/s8/7.pgm;7 |
||||
/path/to/at/s8/6.pgm;7 |
||||
/path/to/at/s8/9.pgm;7 |
||||
/path/to/at/s8/5.pgm;7 |
||||
/path/to/at/s8/3.pgm;7 |
||||
/path/to/at/s8/4.pgm;7 |
||||
/path/to/at/s8/10.pgm;7 |
||||
/path/to/at/s8/8.pgm;7 |
||||
/path/to/at/s8/1.pgm;7 |
||||
/path/to/at/s21/2.pgm;20 |
||||
/path/to/at/s21/7.pgm;20 |
||||
/path/to/at/s21/6.pgm;20 |
||||
/path/to/at/s21/9.pgm;20 |
||||
/path/to/at/s21/5.pgm;20 |
||||
/path/to/at/s21/3.pgm;20 |
||||
/path/to/at/s21/4.pgm;20 |
||||
/path/to/at/s21/10.pgm;20 |
||||
/path/to/at/s21/8.pgm;20 |
||||
/path/to/at/s21/1.pgm;20 |
||||
/path/to/at/s1/2.pgm;0 |
||||
/path/to/at/s1/7.pgm;0 |
||||
/path/to/at/s1/6.pgm;0 |
||||
/path/to/at/s1/9.pgm;0 |
||||
/path/to/at/s1/5.pgm;0 |
||||
/path/to/at/s1/3.pgm;0 |
||||
/path/to/at/s1/4.pgm;0 |
||||
/path/to/at/s1/10.pgm;0 |
||||
/path/to/at/s1/8.pgm;0 |
||||
/path/to/at/s1/1.pgm;0 |
||||
/path/to/at/s7/2.pgm;6 |
||||
/path/to/at/s7/7.pgm;6 |
||||
/path/to/at/s7/6.pgm;6 |
||||
/path/to/at/s7/9.pgm;6 |
||||
/path/to/at/s7/5.pgm;6 |
||||
/path/to/at/s7/3.pgm;6 |
||||
/path/to/at/s7/4.pgm;6 |
||||
/path/to/at/s7/10.pgm;6 |
||||
/path/to/at/s7/8.pgm;6 |
||||
/path/to/at/s7/1.pgm;6 |
||||
/path/to/at/s16/2.pgm;15 |
||||
/path/to/at/s16/7.pgm;15 |
||||
/path/to/at/s16/6.pgm;15 |
||||
/path/to/at/s16/9.pgm;15 |
||||
/path/to/at/s16/5.pgm;15 |
||||
/path/to/at/s16/3.pgm;15 |
||||
/path/to/at/s16/4.pgm;15 |
||||
/path/to/at/s16/10.pgm;15 |
||||
/path/to/at/s16/8.pgm;15 |
||||
/path/to/at/s16/1.pgm;15 |
||||
/path/to/at/s36/2.pgm;35 |
||||
/path/to/at/s36/7.pgm;35 |
||||
/path/to/at/s36/6.pgm;35 |
||||
/path/to/at/s36/9.pgm;35 |
||||
/path/to/at/s36/5.pgm;35 |
||||
/path/to/at/s36/3.pgm;35 |
||||
/path/to/at/s36/4.pgm;35 |
||||
/path/to/at/s36/10.pgm;35 |
||||
/path/to/at/s36/8.pgm;35 |
||||
/path/to/at/s36/1.pgm;35 |
||||
/path/to/at/s25/2.pgm;24 |
||||
/path/to/at/s25/7.pgm;24 |
||||
/path/to/at/s25/6.pgm;24 |
||||
/path/to/at/s25/9.pgm;24 |
||||
/path/to/at/s25/5.pgm;24 |
||||
/path/to/at/s25/3.pgm;24 |
||||
/path/to/at/s25/4.pgm;24 |
||||
/path/to/at/s25/10.pgm;24 |
||||
/path/to/at/s25/8.pgm;24 |
||||
/path/to/at/s25/1.pgm;24 |
||||
/path/to/at/s14/2.pgm;13 |
||||
/path/to/at/s14/7.pgm;13 |
||||
/path/to/at/s14/6.pgm;13 |
||||
/path/to/at/s14/9.pgm;13 |
||||
/path/to/at/s14/5.pgm;13 |
||||
/path/to/at/s14/3.pgm;13 |
||||
/path/to/at/s14/4.pgm;13 |
||||
/path/to/at/s14/10.pgm;13 |
||||
/path/to/at/s14/8.pgm;13 |
||||
/path/to/at/s14/1.pgm;13 |
||||
/path/to/at/s34/2.pgm;33 |
||||
/path/to/at/s34/7.pgm;33 |
||||
/path/to/at/s34/6.pgm;33 |
||||
/path/to/at/s34/9.pgm;33 |
||||
/path/to/at/s34/5.pgm;33 |
||||
/path/to/at/s34/3.pgm;33 |
||||
/path/to/at/s34/4.pgm;33 |
||||
/path/to/at/s34/10.pgm;33 |
||||
/path/to/at/s34/8.pgm;33 |
||||
/path/to/at/s34/1.pgm;33 |
||||
/path/to/at/s11/2.pgm;10 |
||||
/path/to/at/s11/7.pgm;10 |
||||
/path/to/at/s11/6.pgm;10 |
||||
/path/to/at/s11/9.pgm;10 |
||||
/path/to/at/s11/5.pgm;10 |
||||
/path/to/at/s11/3.pgm;10 |
||||
/path/to/at/s11/4.pgm;10 |
||||
/path/to/at/s11/10.pgm;10 |
||||
/path/to/at/s11/8.pgm;10 |
||||
/path/to/at/s11/1.pgm;10 |
||||
/path/to/at/s26/2.pgm;25 |
||||
/path/to/at/s26/7.pgm;25 |
||||
/path/to/at/s26/6.pgm;25 |
||||
/path/to/at/s26/9.pgm;25 |
||||
/path/to/at/s26/5.pgm;25 |
||||
/path/to/at/s26/3.pgm;25 |
||||
/path/to/at/s26/4.pgm;25 |
||||
/path/to/at/s26/10.pgm;25 |
||||
/path/to/at/s26/8.pgm;25 |
||||
/path/to/at/s26/1.pgm;25 |
||||
/path/to/at/s18/2.pgm;17 |
||||
/path/to/at/s18/7.pgm;17 |
||||
/path/to/at/s18/6.pgm;17 |
||||
/path/to/at/s18/9.pgm;17 |
||||
/path/to/at/s18/5.pgm;17 |
||||
/path/to/at/s18/3.pgm;17 |
||||
/path/to/at/s18/4.pgm;17 |
||||
/path/to/at/s18/10.pgm;17 |
||||
/path/to/at/s18/8.pgm;17 |
||||
/path/to/at/s18/1.pgm;17 |
||||
/path/to/at/s29/2.pgm;28 |
||||
/path/to/at/s29/7.pgm;28 |
||||
/path/to/at/s29/6.pgm;28 |
||||
/path/to/at/s29/9.pgm;28 |
||||
/path/to/at/s29/5.pgm;28 |
||||
/path/to/at/s29/3.pgm;28 |
||||
/path/to/at/s29/4.pgm;28 |
||||
/path/to/at/s29/10.pgm;28 |
||||
/path/to/at/s29/8.pgm;28 |
||||
/path/to/at/s29/1.pgm;28 |
||||
/path/to/at/s33/2.pgm;32 |
||||
/path/to/at/s33/7.pgm;32 |
||||
/path/to/at/s33/6.pgm;32 |
||||
/path/to/at/s33/9.pgm;32 |
||||
/path/to/at/s33/5.pgm;32 |
||||
/path/to/at/s33/3.pgm;32 |
||||
/path/to/at/s33/4.pgm;32 |
||||
/path/to/at/s33/10.pgm;32 |
||||
/path/to/at/s33/8.pgm;32 |
||||
/path/to/at/s33/1.pgm;32 |
||||
/path/to/at/s12/2.pgm;11 |
||||
/path/to/at/s12/7.pgm;11 |
||||
/path/to/at/s12/6.pgm;11 |
||||
/path/to/at/s12/9.pgm;11 |
||||
/path/to/at/s12/5.pgm;11 |
||||
/path/to/at/s12/3.pgm;11 |
||||
/path/to/at/s12/4.pgm;11 |
||||
/path/to/at/s12/10.pgm;11 |
||||
/path/to/at/s12/8.pgm;11 |
||||
/path/to/at/s12/1.pgm;11 |
||||
/path/to/at/s6/2.pgm;5 |
||||
/path/to/at/s6/7.pgm;5 |
||||
/path/to/at/s6/6.pgm;5 |
||||
/path/to/at/s6/9.pgm;5 |
||||
/path/to/at/s6/5.pgm;5 |
||||
/path/to/at/s6/3.pgm;5 |
||||
/path/to/at/s6/4.pgm;5 |
||||
/path/to/at/s6/10.pgm;5 |
||||
/path/to/at/s6/8.pgm;5 |
||||
/path/to/at/s6/1.pgm;5 |
||||
/path/to/at/s22/2.pgm;21 |
||||
/path/to/at/s22/7.pgm;21 |
||||
/path/to/at/s22/6.pgm;21 |
||||
/path/to/at/s22/9.pgm;21 |
||||
/path/to/at/s22/5.pgm;21 |
||||
/path/to/at/s22/3.pgm;21 |
||||
/path/to/at/s22/4.pgm;21 |
||||
/path/to/at/s22/10.pgm;21 |
||||
/path/to/at/s22/8.pgm;21 |
||||
/path/to/at/s22/1.pgm;21 |
||||
/path/to/at/s15/2.pgm;14 |
||||
/path/to/at/s15/7.pgm;14 |
||||
/path/to/at/s15/6.pgm;14 |
||||
/path/to/at/s15/9.pgm;14 |
||||
/path/to/at/s15/5.pgm;14 |
||||
/path/to/at/s15/3.pgm;14 |
||||
/path/to/at/s15/4.pgm;14 |
||||
/path/to/at/s15/10.pgm;14 |
||||
/path/to/at/s15/8.pgm;14 |
||||
/path/to/at/s15/1.pgm;14 |
||||
/path/to/at/s2/2.pgm;1 |
||||
/path/to/at/s2/7.pgm;1 |
||||
/path/to/at/s2/6.pgm;1 |
||||
/path/to/at/s2/9.pgm;1 |
||||
/path/to/at/s2/5.pgm;1 |
||||
/path/to/at/s2/3.pgm;1 |
||||
/path/to/at/s2/4.pgm;1 |
||||
/path/to/at/s2/10.pgm;1 |
||||
/path/to/at/s2/8.pgm;1 |
||||
/path/to/at/s2/1.pgm;1 |
||||
/path/to/at/s31/2.pgm;30 |
||||
/path/to/at/s31/7.pgm;30 |
||||
/path/to/at/s31/6.pgm;30 |
||||
/path/to/at/s31/9.pgm;30 |
||||
/path/to/at/s31/5.pgm;30 |
||||
/path/to/at/s31/3.pgm;30 |
||||
/path/to/at/s31/4.pgm;30 |
||||
/path/to/at/s31/10.pgm;30 |
||||
/path/to/at/s31/8.pgm;30 |
||||
/path/to/at/s31/1.pgm;30 |
||||
/path/to/at/s28/2.pgm;27 |
||||
/path/to/at/s28/7.pgm;27 |
||||
/path/to/at/s28/6.pgm;27 |
||||
/path/to/at/s28/9.pgm;27 |
||||
/path/to/at/s28/5.pgm;27 |
||||
/path/to/at/s28/3.pgm;27 |
||||
/path/to/at/s28/4.pgm;27 |
||||
/path/to/at/s28/10.pgm;27 |
||||
/path/to/at/s28/8.pgm;27 |
||||
/path/to/at/s28/1.pgm;27 |
||||
/path/to/at/s40/2.pgm;39 |
||||
/path/to/at/s40/7.pgm;39 |
||||
/path/to/at/s40/6.pgm;39 |
||||
/path/to/at/s40/9.pgm;39 |
||||
/path/to/at/s40/5.pgm;39 |
||||
/path/to/at/s40/3.pgm;39 |
||||
/path/to/at/s40/4.pgm;39 |
||||
/path/to/at/s40/10.pgm;39 |
||||
/path/to/at/s40/8.pgm;39 |
||||
/path/to/at/s40/1.pgm;39 |
||||
/path/to/at/s3/2.pgm;2 |
||||
/path/to/at/s3/7.pgm;2 |
||||
/path/to/at/s3/6.pgm;2 |
||||
/path/to/at/s3/9.pgm;2 |
||||
/path/to/at/s3/5.pgm;2 |
||||
/path/to/at/s3/3.pgm;2 |
||||
/path/to/at/s3/4.pgm;2 |
||||
/path/to/at/s3/10.pgm;2 |
||||
/path/to/at/s3/8.pgm;2 |
||||
/path/to/at/s3/1.pgm;2 |
||||
/path/to/at/s38/2.pgm;37 |
||||
/path/to/at/s38/7.pgm;37 |
||||
/path/to/at/s38/6.pgm;37 |
||||
/path/to/at/s38/9.pgm;37 |
||||
/path/to/at/s38/5.pgm;37 |
||||
/path/to/at/s38/3.pgm;37 |
||||
/path/to/at/s38/4.pgm;37 |
||||
/path/to/at/s38/10.pgm;37 |
||||
/path/to/at/s38/8.pgm;37 |
||||
/path/to/at/s38/1.pgm;37 |
@ -0,0 +1,192 @@ |
||||
/*
|
||||
* Copyright (c) 2011. Philipp Wagner <bytefish[at]gmx[dot]de>. |
||||
* Released to public domain under terms of the BSD Simplified license. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are met: |
||||
* * Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* * Neither the name of the organization nor the names of its contributors |
||||
* may be used to endorse or promote products derived from this software |
||||
* without specific prior written permission. |
||||
* |
||||
* See <http://www.opensource.org/licenses/bsd-license>
|
||||
*/ |
||||
|
||||
#include "opencv2/core.hpp" |
||||
#include "opencv2/highgui.hpp" |
||||
#include "opencv2/imgproc.hpp" |
||||
#include "opencv2/face.hpp" |
||||
#include "opencv2/core/utility.hpp" |
||||
|
||||
#include <iostream> |
||||
#include <fstream> |
||||
#include <sstream> |
||||
#include <map> |
||||
|
||||
using namespace cv; |
||||
using namespace cv::face; |
||||
using namespace std; |
||||
|
||||
static void read_csv(const string& filename, vector<Mat>& images, vector<int>& labels, std::map<int, string>& labelsInfo, char separator = ';') { |
||||
ifstream csv(filename.c_str()); |
||||
if (!csv) CV_Error(Error::StsBadArg, "No valid input file was given, please check the given filename."); |
||||
string line, path, classlabel, info; |
||||
while (getline(csv, line)) { |
||||
stringstream liness(line); |
||||
path.clear(); classlabel.clear(); info.clear(); |
||||
getline(liness, path, separator); |
||||
getline(liness, classlabel, separator); |
||||
getline(liness, info, separator); |
||||
if(!path.empty() && !classlabel.empty()) { |
||||
cout << "Processing " << path << endl; |
||||
int label = atoi(classlabel.c_str()); |
||||
if(!info.empty()) |
||||
labelsInfo.insert(std::make_pair(label, info)); |
||||
// 'path' can be file, dir or wildcard path
|
||||
String root(path.c_str()); |
||||
vector<String> files; |
||||
glob(root, files, true); |
||||
for(vector<String>::const_iterator f = files.begin(); f != files.end(); ++f) { |
||||
cout << "\t" << *f << endl; |
||||
Mat img = imread(*f, IMREAD_GRAYSCALE); |
||||
static int w=-1, h=-1; |
||||
static bool showSmallSizeWarning = true; |
||||
if(w>0 && h>0 && (w!=img.cols || h!=img.rows)) cout << "\t* Warning: images should be of the same size!" << endl; |
||||
if(showSmallSizeWarning && (img.cols<50 || img.rows<50)) { |
||||
cout << "* Warning: for better results images should be not smaller than 50x50!" << endl; |
||||
showSmallSizeWarning = false; |
||||
} |
||||
images.push_back(img); |
||||
labels.push_back(label); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
int main(int argc, const char *argv[]) { |
||||
// Check for valid command line arguments, print usage
|
||||
// if no arguments were given.
|
||||
if (argc != 2 && argc != 3) { |
||||
cout << "Usage: " << argv[0] << " <csv> [arg2]\n" |
||||
<< "\t<csv> - path to config file in CSV format\n" |
||||
<< "\targ2 - if the 2nd argument is provided (with any value) " |
||||
<< "the advanced stuff is run and shown to console.\n" |
||||
<< "The CSV config file consists of the following lines:\n" |
||||
<< "<path>;<label>[;<comment>]\n" |
||||
<< "\t<path> - file, dir or wildcard path\n" |
||||
<< "\t<label> - non-negative integer person label\n" |
||||
<< "\t<comment> - optional comment string (e.g. person name)" |
||||
<< endl; |
||||
exit(1); |
||||
} |
||||
// Get the path to your CSV.
|
||||
string fn_csv = string(argv[1]); |
||||
// These vectors hold the images and corresponding labels.
|
||||
vector<Mat> images; |
||||
vector<int> labels; |
||||
std::map<int, string> labelsInfo; |
||||
// Read in the data. This can fail if no valid
|
||||
// input filename is given.
|
||||
try { |
||||
read_csv(fn_csv, images, labels, labelsInfo); |
||||
} catch (cv::Exception& e) { |
||||
cerr << "Error opening file \"" << fn_csv << "\". Reason: " << e.msg << endl; |
||||
// nothing more we can do
|
||||
exit(1); |
||||
} |
||||
|
||||
// Quit if there are not enough images for this demo.
|
||||
if(images.size() <= 1) { |
||||
string error_message = "This demo needs at least 2 images to work. Please add more images to your data set!"; |
||||
CV_Error(Error::StsError, error_message); |
||||
} |
||||
// The following lines simply get the last images from
|
||||
// your dataset and remove it from the vector. This is
|
||||
// done, so that the training data (which we learn the
|
||||
// cv::FaceRecognizer on) and the test data we test
|
||||
// the model with, do not overlap.
|
||||
Mat testSample = images[images.size() - 1]; |
||||
int nlabels = (int)labels.size(); |
||||
int testLabel = labels[nlabels-1]; |
||||
images.pop_back(); |
||||
labels.pop_back(); |
||||
// The following lines create an Eigenfaces model for
|
||||
// face recognition and train it with the images and
|
||||
// labels read from the given CSV file.
|
||||
// This here is a full PCA, if you just want to keep
|
||||
// 10 principal components (read Eigenfaces), then call
|
||||
// the factory method like this:
|
||||
//
|
||||
// cv::createEigenFaceRecognizer(10);
|
||||
//
|
||||
// If you want to create a FaceRecognizer with a
|
||||
// confidennce threshold, call it with:
|
||||
//
|
||||
// cv::createEigenFaceRecognizer(10, 123.0);
|
||||
//
|
||||
Ptr<FaceRecognizer> model = createEigenFaceRecognizer(); |
||||
for( int i = 0; i < nlabels; i++ ) |
||||
model->setLabelInfo(i, labelsInfo[i]); |
||||
model->train(images, labels); |
||||
string saveModelPath = "face-rec-model.txt"; |
||||
cout << "Saving the trained model to " << saveModelPath << endl; |
||||
model->save(saveModelPath); |
||||
|
||||
// The following line predicts the label of a given
|
||||
// test image:
|
||||
int predictedLabel = model->predict(testSample); |
||||
//
|
||||
// To get the confidence of a prediction call the model with:
|
||||
//
|
||||
// int predictedLabel = -1;
|
||||
// double confidence = 0.0;
|
||||
// model->predict(testSample, predictedLabel, confidence);
|
||||
//
|
||||
string result_message = format("Predicted class = %d / Actual class = %d.", predictedLabel, testLabel); |
||||
cout << result_message << endl; |
||||
if( (predictedLabel == testLabel) && !model->getLabelInfo(predictedLabel).empty() ) |
||||
cout << format("%d-th label's info: %s", predictedLabel, model->getLabelInfo(predictedLabel).c_str()) << endl; |
||||
|
||||
// advanced stuff
|
||||
if(argc>2) { |
||||
// Sometimes you'll need to get/set internal model data,
|
||||
// which isn't exposed by the public cv::FaceRecognizer.
|
||||
// Since each cv::FaceRecognizer is derived from a
|
||||
// cv::Algorithm, you can query the data.
|
||||
//
|
||||
// First we'll use it to set the threshold of the FaceRecognizer
|
||||
// to 0.0 without retraining the model. This can be useful if
|
||||
// you are evaluating the model:
|
||||
//
|
||||
model->set("threshold", 0.0); |
||||
// Now the threshold of this model is set to 0.0. A prediction
|
||||
// now returns -1, as it's impossible to have a distance below
|
||||
// it
|
||||
predictedLabel = model->predict(testSample); |
||||
cout << "Predicted class = " << predictedLabel << endl; |
||||
// Here is how to get the eigenvalues of this Eigenfaces model:
|
||||
Mat eigenvalues = model->getMat("eigenvalues"); |
||||
// And we can do the same to display the Eigenvectors (read Eigenfaces):
|
||||
Mat W = model->getMat("eigenvectors"); |
||||
// From this we will display the (at most) first 10 Eigenfaces:
|
||||
for (int i = 0; i < min(10, W.cols); i++) { |
||||
string msg = format("Eigenvalue #%d = %.5f", i, eigenvalues.at<double>(i)); |
||||
cout << msg << endl; |
||||
// get eigenvector #i
|
||||
Mat ev = W.col(i).clone(); |
||||
// Reshape to original size & normalize to [0...255] for imshow.
|
||||
Mat grayscale; |
||||
normalize(ev.reshape(1), grayscale, 0, 255, NORM_MINMAX, CV_8UC1); |
||||
// Show the image & apply a Jet colormap for better sensing.
|
||||
Mat cgrayscale; |
||||
applyColorMap(grayscale, cgrayscale, COLORMAP_JET); |
||||
imshow(format("%d", i), cgrayscale); |
||||
} |
||||
waitKey(0); |
||||
} |
||||
return 0; |
||||
} |
@ -0,0 +1,54 @@ |
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#ifndef __OPENCV_PRECOMP_H__ |
||||
#define __OPENCV_PRECOMP_H__ |
||||
|
||||
#include "opencv2/face.hpp" |
||||
#include "opencv2/imgproc.hpp" |
||||
#include "opencv2/core.hpp" |
||||
#include "opencv2/core/utility.hpp" |
||||
#include "opencv2/core/private.hpp" |
||||
|
||||
#include <map> |
||||
|
||||
#endif |
@ -0,0 +1,312 @@ |
||||
# ---------------------------------------------------------------------------- |
||||
# CMake file for Matlab/Octave support |
||||
# |
||||
# Matlab code generation and compilation is broken down into two distinct |
||||
# stages: configure time and build time. The idea is that we want to give |
||||
# the user reasonable guarantees that once they type 'make', wrapper |
||||
# generation is unlikely to fail. Therefore we run a series of tests at |
||||
# configure time to check the working status of the core components. |
||||
# |
||||
# Configure Time |
||||
# During configure time, the script attempts to ascertain whether the |
||||
# generator and mex compiler are working for a given architecture. |
||||
# Currently this involves: |
||||
# 1) Generating a simple CV_EXPORTS_W symbol and checking whether a file |
||||
# of the symbol name is generated |
||||
# 2) Compiling a simple mex gateway to check that Bridge.hpp and mex.h |
||||
# can be found, and that a file with the mexext is produced |
||||
# |
||||
# Build Time |
||||
# If the configure time tests pass, then we assume Matlab wrapper generation |
||||
# will not fail during build time. We simply glob all of the symbols in |
||||
# the OpenCV module headers, generate intermediate .cpp files, then compile |
||||
# them with mex. |
||||
# ---------------------------------------------------------------------------- |
||||
|
||||
# PREPEND |
||||
# Given a list of strings IN and a TOKEN, prepend the token to each string |
||||
# and append to OUT. This is used for passing command line "-I", "-L" and "-l" |
||||
# arguments to mex. e.g. |
||||
# prepend("-I" OUT /path/to/include/dir) --> -I/path/to/include/dir |
||||
macro(PREPEND TOKEN OUT IN) |
||||
foreach(VAR ${IN} ${ARGN}) |
||||
list(APPEND ${OUT} "${TOKEN}${VAR}") |
||||
endforeach() |
||||
endmacro() |
||||
|
||||
|
||||
# WARN_MIXED_PRECISION |
||||
# Formats a warning message if the compiler and Matlab bitness is different |
||||
macro(WARN_MIXED_PRECISION COMPILER_BITNESS MATLAB_BITNESS) |
||||
set(MSG "Your compiler is ${COMPILER_BITNESS}-bit") |
||||
set(MSG "${MSG} but your version of Matlab is ${MATLAB_BITNESS}-bit.") |
||||
set(MSG "${MSG} To build Matlab bindings, please switch to a ${MATLAB_BITNESS}-bit compiler.") |
||||
message(WARNING ${MSG}) |
||||
endmacro() |
||||
|
||||
# ---------------------------------------------------------------------------- |
||||
# Architecture checks |
||||
# ---------------------------------------------------------------------------- |
||||
# make sure we're on a supported architecture with Matlab and python installed |
||||
if (IOS OR ANDROID OR NOT MATLAB_FOUND) |
||||
ocv_module_disable(matlab) |
||||
return() |
||||
elseif (NOT PYTHON_DEFAULT_AVAILABLE) |
||||
message(WARNING "A required dependency of the matlab module (PythonLibs) was not found. Disabling Matlab bindings...") |
||||
ocv_module_disable(matlab) |
||||
return() |
||||
endif() |
||||
|
||||
|
||||
# If the user built OpenCV as X-bit, but they have a Y-bit version of Matlab, |
||||
# attempting to link to OpenCV during binding generation will fail, since |
||||
# mixed precision pointers are not allowed. Disable the bindings. |
||||
math(EXPR ARCH "${CMAKE_SIZEOF_VOID_P} * 8") |
||||
if (${ARCH} EQUAL 32 AND ${MATLAB_ARCH} MATCHES "64") |
||||
warn_mixed_precision("32" "64") |
||||
ocv_module_disable(matlab) |
||||
return() |
||||
elseif (${ARCH} EQUAL 64 AND NOT ${MATLAB_ARCH} MATCHES "64") |
||||
warn_mixed_precision("64" "32") |
||||
ocv_module_disable(matlab) |
||||
return() |
||||
endif() |
||||
|
||||
# If it's MSVC, warn the user that bindings will only be built in Release mode. |
||||
# Debug mode seems to cause issues... |
||||
if (MSVC) |
||||
message(STATUS "Warning: Matlab bindings will only be built in Release configurations") |
||||
endif() |
||||
|
||||
|
||||
# ---------------------------------------------------------------------------- |
||||
# Configure time components |
||||
# ---------------------------------------------------------------------------- |
||||
set(the_description "The Matlab/Octave bindings") |
||||
ocv_add_module(matlab BINDINGS |
||||
OPTIONAL opencv_core |
||||
opencv_imgproc opencv_ml |
||||
opencv_imgcodecs opencv_videoio opencv_highgui |
||||
opencv_objdetect opencv_flann opencv_features2d |
||||
opencv_photo opencv_video opencv_videostab |
||||
opencv_calib opencv_calib3d |
||||
opencv_stitching opencv_superres |
||||
opencv_nonfree |
||||
) |
||||
|
||||
# get the commit information |
||||
execute_process(COMMAND git log -1 --pretty=%H OUTPUT_VARIABLE GIT_COMMIT ERROR_QUIET) |
||||
string(REGEX REPLACE "(\r?\n)+$" "" GIT_COMMIT "${GIT_COMMIT}") |
||||
|
||||
# set the path to the C++ header and doc parser, and template engine |
||||
set(JINJA2_PATH ${CMAKE_SOURCE_DIR}/3rdparty) |
||||
set(HDR_PARSER_PATH ${CMAKE_SOURCE_DIR}/modules/python/src2) |
||||
set(RST_PARSER_PATH ${CMAKE_SOURCE_DIR}/modules/java/generator) |
||||
|
||||
# set mex compiler options |
||||
prepend("-I" MEX_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/include) |
||||
if (MSVC) |
||||
prepend("-L" MEX_LIB_DIR ${LIBRARY_OUTPUT_PATH}/${CMAKE_CFG_INTDIR}) |
||||
else() |
||||
prepend("-L" MEX_LIB_DIR ${LIBRARY_OUTPUT_PATH}) |
||||
endif() |
||||
set(MEX_OPTS "-largeArrayDims") |
||||
|
||||
if (BUILD_TESTS) |
||||
add_subdirectory(test) |
||||
endif() |
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) |
||||
|
||||
|
||||
# intersection of available modules and optional dependencies |
||||
# 1. populate the command-line include directories (-I/path/to/module/header, ...) |
||||
# 2. populate the command-line link libraries (-lopencv_core, ...) for Debug and Release |
||||
set(MATLAB_DEPS ${OPENCV_MODULE_${the_module}_REQ_DEPS} ${OPENCV_MODULE_${the_module}_OPT_DEPS}) |
||||
foreach(opencv_module ${MATLAB_DEPS}) |
||||
if (HAVE_${opencv_module}) |
||||
string(REPLACE "opencv_" "" module ${opencv_module}) |
||||
list(APPEND opencv_modules ${module}) |
||||
list(APPEND ${the_module}_ACTUAL_DEPS ${opencv_module}) |
||||
prepend("-I" MEX_INCLUDE_DIRS "${OPENCV_MODULE_${opencv_module}_LOCATION}/include") |
||||
prepend("-l" MEX_LIBS ${opencv_module}${OPENCV_DLLVERSION}) |
||||
prepend("-l" MEX_DEBUG_LIBS ${opencv_module}${OPENCV_DLLVERSION}${OPENCV_DEBUG_POSTFIX}) |
||||
endif() |
||||
endforeach() |
||||
|
||||
# add extra headers by hand |
||||
list(APPEND opencv_extra_hdrs "core=${OPENCV_MODULE_opencv_core_LOCATION}/include/opencv2/core/base.hpp") |
||||
list(APPEND opencv_extra_hdrs "video=${OPENCV_MODULE_opencv_video_LOCATION}/include/opencv2/video/tracking.hpp") |
||||
|
||||
# pass the OPENCV_CXX_EXTRA_FLAGS through to the mex compiler |
||||
# remove the visibility modifiers, so the mex gateway is visible |
||||
# TODO: get mex working without warnings |
||||
string(REGEX REPLACE "[^\ ]*visibility[^\ ]*" "" MEX_CXXFLAGS "${OPENCV_EXTRA_FLAGS} ${OPENCV_EXTRA_CXX_FLAGS}") |
||||
|
||||
# Configure checks |
||||
# Check to see whether the generator and the mex compiler are working. |
||||
# The checks currently test: |
||||
# - whether the python generator can be found |
||||
# - whether the python generator correctly outputs a file for a definition |
||||
# - whether the mex compiler can find the required headers |
||||
# - whether the mex compiler can compile a trivial definition |
||||
if (NOT MEX_WORKS) |
||||
# attempt to generate a gateway for a function |
||||
message(STATUS "Trying to generate Matlab code") |
||||
execute_process( |
||||
COMMAND ${PYTHON_DEFAULT_EXECUTABLE} |
||||
${CMAKE_CURRENT_SOURCE_DIR}/generator/gen_matlab.py |
||||
--jinja2 ${JINJA2_PATH} |
||||
--hdrparser ${HDR_PARSER_PATH} |
||||
--rstparser ${RST_PARSER_PATH} |
||||
--extra "test=${CMAKE_CURRENT_SOURCE_DIR}/test/test_generator.hpp" |
||||
--outdir ${CMAKE_BINARY_DIR}/junk |
||||
ERROR_VARIABLE GEN_ERROR |
||||
OUTPUT_QUIET |
||||
) |
||||
|
||||
if (GEN_ERROR) |
||||
message(${GEN_ERROR}) |
||||
message(STATUS "Error generating Matlab code. Disabling Matlab bindings...") |
||||
ocv_module_disable(matlab) |
||||
return() |
||||
else() |
||||
message(STATUS "Trying to generate Matlab code - OK") |
||||
endif() |
||||
|
||||
# attempt to compile a gateway using mex |
||||
message(STATUS "Trying to compile mex file") |
||||
execute_process( |
||||
COMMAND ${MATLAB_MEX_SCRIPT} ${MEX_OPTS} "CXXFLAGS=\$CXXFLAGS ${MEX_CXX_FLAGS}" |
||||
${MEX_INCLUDE_DIRS} ${CMAKE_CURRENT_SOURCE_DIR}/test/test_compiler.cpp |
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/junk |
||||
ERROR_VARIABLE MEX_ERROR |
||||
OUTPUT_QUIET |
||||
) |
||||
|
||||
if (MEX_ERROR) |
||||
message(${MEX_ERROR}) |
||||
message(STATUS "Error compiling mex file. Disabling Matlab bindings...") |
||||
ocv_module_disable(matlab) |
||||
return() |
||||
else() |
||||
message(STATUS "Trying to compile mex file - OK") |
||||
endif() |
||||
endif() |
||||
|
||||
# if we make it here, mex works! |
||||
set(MEX_WORKS True CACHE BOOL ADVANCED) |
||||
|
||||
|
||||
# ---------------------------------------------------------------------------- |
||||
# Build time components |
||||
# ---------------------------------------------------------------------------- |
||||
|
||||
# proxies |
||||
# these proxies are used to trigger the add_custom_commands |
||||
# (which do the real work) only when they're outdated |
||||
set(GENERATE_PROXY ${CMAKE_CURRENT_BINARY_DIR}/generate.proxy) |
||||
set(COMPILE_PROXY ${CMAKE_CURRENT_BINARY_DIR}/compile.proxy) |
||||
# TODO: Remove following line before merging with master |
||||
file(REMOVE ${GENERATE_PROXY} ${COMPILE_PROXY}) |
||||
|
||||
# generate |
||||
# call the python executable to generate the Matlab gateways |
||||
add_custom_command( |
||||
OUTPUT ${GENERATE_PROXY} |
||||
COMMAND ${PYTHON_DEFAULT_EXECUTABLE} |
||||
${CMAKE_CURRENT_SOURCE_DIR}/generator/gen_matlab.py |
||||
--jinja2 ${JINJA2_PATH} |
||||
--hdrparser ${HDR_PARSER_PATH} |
||||
--rstparser ${RST_PARSER_PATH} |
||||
--moduleroot ${CMAKE_SOURCE_DIR}/modules |
||||
--modules ${opencv_modules} |
||||
--extra ${opencv_extra_hdrs} |
||||
--outdir ${CMAKE_CURRENT_BINARY_DIR} |
||||
COMMAND ${PYTHON_DEFAULT_EXECUTABLE} |
||||
${CMAKE_CURRENT_SOURCE_DIR}/generator/build_info.py |
||||
--jinja2 ${JINJA2_PATH} |
||||
--os ${CMAKE_SYSTEM} |
||||
--arch ${ARCH} ${CMAKE_SYSTEM_PROCESSOR} |
||||
--compiler ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION} |
||||
--mex_arch ${MATLAB_ARCH} |
||||
--mex_script ${MATLAB_MEX_SCRIPT} |
||||
--cxx_flags ${MEX_CXXFLAGS} |
||||
--opencv_version ${OPENCV_VERSION} |
||||
--commit ${GIT_COMMIT} |
||||
--modules ${opencv_modules} |
||||
--configuration $<CONFIGURATION> |
||||
--outdir ${CMAKE_CURRENT_BINARY_DIR} |
||||
COMMAND ${PYTHON_DEFAULT_EXECUTABLE} |
||||
${CMAKE_CURRENT_SOURCE_DIR}/generator/cvmex.py |
||||
--jinja2 ${JINJA2_PATH} |
||||
--opts="${MEX_OPTS}" |
||||
--include_dirs="${MEX_INCLUDE_DIRS}" |
||||
--lib_dir="${MEX_LIB_DIR}" |
||||
--libs="${MEX_LIBS}" |
||||
--flags ${MEX_CXXFLAGS} |
||||
--outdir ${CMAKE_CURRENT_BINARY_DIR} |
||||
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/test/help.m ${CMAKE_CURRENT_BINARY_DIR}/+cv |
||||
COMMAND ${CMAKE_COMMAND} -E touch ${GENERATE_PROXY} |
||||
COMMENT "Generating Matlab source files" |
||||
) |
||||
|
||||
# compile |
||||
# call the mex compiler to compile the gateways |
||||
# because we don't know the source files at configure-time, this |
||||
# has to be executed in a separate script in cmake's script processing mode |
||||
add_custom_command( |
||||
OUTPUT ${COMPILE_PROXY} |
||||
COMMAND ${CMAKE_COMMAND} -DMATLAB_MEX_SCRIPT=${MATLAB_MEX_SCRIPT} |
||||
-DMATLAB_MEXEXT=${MATLAB_MEXEXT} |
||||
-DMEX_OPTS=${MEX_OPTS} |
||||
-DMEX_CXXFLAGS=${MEX_CXX_FLAGS} |
||||
-DMEX_INCLUDE_DIRS="${MEX_INCLUDE_DIRS}" |
||||
-DMEX_LIB_DIR="${MEX_LIB_DIR}" |
||||
-DCONFIGURATION="$<CONFIGURATION>" |
||||
-DMEX_LIBS="${MEX_LIBS}" |
||||
-DMEX_DEBUG_LIBS="${MEX_DEBUG_LIBS}" |
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/compile.cmake |
||||
COMMAND ${CMAKE_COMMAND} -E touch ${COMPILE_PROXY} |
||||
COMMENT "Compiling Matlab source files. This could take a while..." |
||||
) |
||||
|
||||
# targets |
||||
# opencv_matlab_sources --> opencv_matlab |
||||
add_custom_target(${the_module}_sources ALL DEPENDS ${GENERATE_PROXY}) |
||||
add_custom_target(${the_module} ALL DEPENDS ${COMPILE_PROXY}) |
||||
add_dependencies(${the_module} ${the_module}_sources ${${the_module}_ACTUAL_DEPS}) |
||||
|
||||
if (ENABLE_SOLUTION_FOLDERS) |
||||
set_target_properties(${the_module} PROPERTIES FOLDER "modules") |
||||
endif() |
||||
|
||||
|
||||
# ---------------------------------------------------------------------------- |
||||
# Install time components |
||||
# ---------------------------------------------------------------------------- |
||||
# NOTE: Trailing slashes on the DIRECTORY paths are important! |
||||
# TODO: What needs to be done with rpath???? |
||||
|
||||
# install the +cv directory verbatim |
||||
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ DESTINATION ${OPENCV_INCLUDE_INSTALL_PATH}) |
||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/+cv/ DESTINATION matlab/+cv) |
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/cv.m DESTINATION matlab) |
||||
|
||||
# update the custom mex compiler to point to the install locations |
||||
string(REPLACE ";" "\\ " MEX_OPTS "${MEX_OPTS}") |
||||
string(REPLACE ";" "\\ " MEX_LIBS "${MEX_LIBS}") |
||||
string(REPLACE " " "\\ " MEX_CXXFLAGS ${MEX_CXXFLAGS}) |
||||
string(REPLACE ";" "\\ " MEX_INCLUDE_DIRS "${MEX_INCLUDE_DIRS}") |
||||
install(CODE |
||||
"execute_process( |
||||
COMMAND ${PYTHON_DEFAULT_EXECUTABLE} |
||||
${CMAKE_CURRENT_SOURCE_DIR}/generator/cvmex.py |
||||
--jinja2 ${JINJA2_PATH} |
||||
--opts=${MEX_OPTS} |
||||
--include_dirs=-I${CMAKE_INSTALL_PREFIX}/${OPENCV_INCLUDE_INSTALL_PATH} |
||||
--lib_dir=-L${CMAKE_INSTALL_PREFIX}/${OPENCV_LIB_INSTALL_PATH} |
||||
--libs=${MEX_LIBS} |
||||
--flags=${MEX_CXXFLAGS} |
||||
--outdir ${CMAKE_INSTALL_PREFIX}/matlab |
||||
)" |
||||
) |
@ -0,0 +1,42 @@ |
||||
//////////////////////////////////////////////////////////////////////////////// |
||||
// |
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. |
||||
// |
||||
// By downloading, copying, installing or using the software you agree to this |
||||
// license. If you do not agree to this license, do not download, install, |
||||
// copy or use the software. |
||||
// |
||||
// |
||||
// License Agreement |
||||
// For Open Source Computer Vision Library |
||||
// |
||||
// Copyright (C) 2013, OpenCV Foundation, all rights reserved. |
||||
// Third party copyrights are property of their respective owners. |
||||
// |
||||
// Redistribution and use in source and binary forms, with or without |
||||
// modification, are permitted provided that the following conditions are met: |
||||
// |
||||
// * Redistribution's of source code must retain the above copyright notice, |
||||
// this list of conditions and the following disclaimer. |
||||
// |
||||
// * Redistribution's in binary form must reproduce the above copyright notice, |
||||
// this list of conditions and the following disclaimer in the documentation |
||||
// and/or other materials provided with the distribution. |
||||
// |
||||
// * The name of the copyright holders may not be used to endorse or promote |
||||
// products derived from this software without specific prior written |
||||
// permission. |
||||
// |
||||
// This software is provided by the copyright holders and contributors "as is" |
||||
// and any express or implied warranties, including, but not limited to, the |
||||
// implied warranties of merchantability and fitness for a particular purpose |
||||
// are disclaimed. In no event shall the Intel Corporation or contributors be |
||||
// liable for any direct, indirect, incidental, special, exemplary, or |
||||
// consequential damages (including, but not limited to, procurement of |
||||
// substitute goods or services; loss of use, data, or profits; or business |
||||
// interruption) however caused and on any theory of liability, whether in |
||||
// contract, strict liability, or tort (including negligence or otherwise) |
||||
// arising in any way out of the use of this software, even if advised of the |
||||
// possibility of such damage. |
||||
// |
||||
//////////////////////////////////////////////////////////////////////////////// |
@ -0,0 +1,394 @@ |
||||
OpenCV Matlab Code Generator |
||||
============================ |
||||
This module contains a code generator to automatically produce Matlab mex wrappers for other modules within the OpenCV library. Once compiled and added to the Matlab path, this gives users the ability to call OpenCV methods natively from within Matlab. |
||||
|
||||
|
||||
Build |
||||
----- |
||||
The Matlab code generator is fully integrated into the OpenCV build system. If cmake finds a Matlab installation available on the host system while configuring OpenCV, it will attempt to generate Matlab wrappers for all OpenCV modules. If cmake is having trouble finding your Matlab installation, you can explicitly point it to the root by defining the `MATLAB_ROOT_DIR` variable. For example, on a Mac you could type: |
||||
|
||||
cmake -DMATLAB_ROOT_DIR=/Applications/MATLAB_R2013a.app .. |
||||
|
||||
|
||||
Install |
||||
------- |
||||
In order to use the bindings, you will need to add them to the Matlab path. The path to add is: |
||||
|
||||
1. `${CMAKE_BUILD_DIR}/modules/matlab` if you are working from the build tree, or |
||||
2. `${CMAKE_INSTALL_PREFIX}/matlab` if you have installed OpenCV |
||||
|
||||
In Matlab, simply run: |
||||
|
||||
addpath('/path/to/opencv/matlab/'); |
||||
|
||||
|
||||
Run |
||||
--- |
||||
Once you've added the bindings directory to the Matlab path, you can start using them straight away! OpenCV calls need to be prefixed with a 'cv' qualifier, to disambiguate them from Matlab methods of the same name. For example, to compute the dft of a matrix, you might do the following: |
||||
|
||||
```matlab |
||||
% load an image (Matlab) |
||||
I = imread('cameraman.tif'); |
||||
|
||||
% compute the DFT (OpenCV) |
||||
If = cv.dft(I, cv.DFT_COMPLEX_OUTPUT); |
||||
``` |
||||
|
||||
As you can see, both OpenCV methods and constants can be used with 'cv' qualification. You can also call: |
||||
|
||||
help cv.dft |
||||
|
||||
to get help on the purpose and call signature of a particular method, or |
||||
|
||||
help cv |
||||
|
||||
to get general help regarding the OpenCV bindings. If you ever run into issues with the bindings |
||||
|
||||
cv.buildInformation(); |
||||
|
||||
will produce a printout of diagnostic information pertaining to your particular build of OS, OpenCV and Matlab. It is useful to submit this information alongside a bug report to the OpenCV team. |
||||
|
||||
Writing your own mex files |
||||
-------------------------- |
||||
The Matlab bindings come with a set of utilities to help you quickly write your own mex files using OpenCV definitions. By doing so, you have all the speed and freedom of C++, with the power of OpenCV's math expressions and optimizations. |
||||
|
||||
The first thing you need to learn how to do is write a mex-file with Matlab constructs. Following is a brief example: |
||||
|
||||
```cpp |
||||
// include useful constructs |
||||
// this automatically includes opencv core.hpp and mex.h) |
||||
#include <opencv2/matlab/bridge.hpp> |
||||
using namespace cv; |
||||
using namespace matlab; |
||||
using namespace bridge; |
||||
|
||||
// define the mex gateway |
||||
void mexFunction(int nlhs, mxArray* plhs[], |
||||
int nrhs, const mxArray* prhs[]) { |
||||
|
||||
// claim the inputs into scoped management |
||||
MxArrayVector raw(prhs, prhs+nrhs); |
||||
|
||||
// add an argument parser to automatically handle basic options |
||||
ArgumentParser parser("my function"); |
||||
parser.addVariant(1, 1, "opt"); |
||||
MxArrayVector reordered = parser.parse(raw); |
||||
|
||||
// if we get here, we know the inputs are valid and reordered. Unpack... |
||||
BridgeVector inputs(reordered.begin(), reordered.end()); |
||||
Mat required = inputs[0].toMat(); |
||||
string optional = inputs[1].empty() ? "Default string" : inputs[1].toString(); |
||||
|
||||
try { |
||||
// Do stuff... |
||||
} catch(Exception& e) { |
||||
error(e.what()); |
||||
} catch(...) { |
||||
error("Uncaught exception occurred"); |
||||
} |
||||
|
||||
// allocate an output |
||||
Bridge out = required; |
||||
plhs[0] = out.toMxArray().releaseOwnership(); |
||||
} |
||||
``` |
||||
|
||||
There are a couple of important things going on in this example. Firstly, you need to include `<opencv2/matlab/bridge.hpp>` to enable the bridging capabilities. Once you've done this, you get some nice utilities for free. `MxArray` is a class that wraps Matlab's `mxArray*` class in an OOP-style interface. `ArgumentParser` is a class that handles default, optional and named arguments for you, along with multiple possible calling syntaxes. Finally, `Bridge` is a class that allows bidirectional conversions between OpenCV/std and Matlab types. |
||||
|
||||
Once you have written your file, it can be compiled with the provided mex utility: |
||||
|
||||
cv.mex('my_function.cpp'); |
||||
|
||||
This utility automatically links in all of the necessary OpenCV libraries to make your function work. |
||||
|
||||
NOTE: OpenCV uses exceptions throughout the codebase. It is a **very** good idea to wrap your code in exception handlers to avoid crashing Matlab in the event of an exception being thrown. |
||||
|
||||
------------------------------------------------------------------ |
||||
|
||||
|
||||
Developer |
||||
========= |
||||
The following sections contain information for developers seeking to use, understand or extend the Matlab bindings. The bindings are generated in python using a powerful templating engine called Jinja2. Because Matlab mex gateways have a common structure, they are well suited to templatization. There are separate templates for formatting C++ classes, Matlab classes, C++ functions, constants (enums) and documentation. |
||||
|
||||
The task of the generator is two-fold: |
||||
|
||||
1. To parse the OpenCV headers and build a semantic tree that can be fed to the template engine |
||||
2. To define type conversions between C++/OpenCV and Matlab types |
||||
|
||||
Once a source file has been generated for each OpenCV definition, and type conversions have been established, the mex compiler is invoked to produce the mex gateway (shared object) and link in the OpenCV libraries. |
||||
|
||||
|
||||
File layout |
||||
----------- |
||||
opencv/modules/matlab (this module) |
||||
|
||||
* `CMakeLists.txt` (main cmake configuration file) |
||||
* `README.md` (this file) |
||||
* `compile.cmake` (the cmake script for compiling generated source code) |
||||
* `generator` (the folder containing generator code) |
||||
* `filters.py` (template filters) |
||||
* `gen_matlab.py` (the binding generator control script) |
||||
* `parse_tree.py` (python class to refactor the hdr_parser.py output) |
||||
* `templates` (the raw templates for populating classes, constants, functions and docs) |
||||
* `include` (C++ headers for the bindings) |
||||
* `mxarray.hpp` (C++ OOP-style interface for Matlab mxArray* class) |
||||
* `bridge.hpp` (type conversions) |
||||
* `map.hpp` (hash map interface for instance storage and method lookup) |
||||
* `test` (generator, compiler and binding test scripts) |
||||
|
||||
|
||||
Call Tree |
||||
--------- |
||||
The cmake call tree can be broken into 3 main components: |
||||
|
||||
1. configure time |
||||
2. build time |
||||
3. install time |
||||
|
||||
**Find Matlab (configure)** |
||||
The first thing to do is discover a Matlab installation on the host system. This is handled by the `OpenCVFindMatlab.cmake` in `opencv/cmake`. On Windows machines it searches the registry and path, while on *NIX machines it searches a set of canonical install paths. Once Matlab has been found, a number of variables are defined, such as the path to the mex compiler, the mex libraries, the mex include paths, the architectural extension, etc. |
||||
|
||||
**Test the generator (configure)** |
||||
Attempt to produce a source file for a simple definition. This tests whether python and pythonlibs are correctly invoked on the host. |
||||
|
||||
**Test the mex compiler (configure)** |
||||
Attempt to compile a simple definition using the mex compiler. A mex file is actually just a shared object with a special exported symbol `_mexFunction` which serves as the entry-point to the function. As such, the mex compiler is just a set of scripts configuring the system compiler. In most cases this is the same as the OpenCV compiler, but *could* be different. The test checks whether the mex and generator includes can be found, the system libraries can be linked and the passed compiler flags are compatible. |
||||
|
||||
If any of the configure time tests fail, the bindings will be disabled, but the main OpenCV configure will continue without error. The configuration summary will contain the block: |
||||
|
||||
Matlab |
||||
mex: /Applications/MATLAB_R2013a.app/bin/mex |
||||
compiler/generator: Not working (bindings will not be generated) |
||||
|
||||
**Generate the sources (build)** |
||||
Given a set of modules (the intersection of the OpenCV modules being built and the matlab module optional dependencies), the `CppHeaderParser()` from `opencv/modules/python/src2/hdr_parser.py` will parse the module headers and produce a set of definitions. |
||||
|
||||
The `ParseTree()` from `opencv/modules/matlab/generator/parse_tree.py` takes this set of definitions and refactors them into a semantic tree better suited to templatization. For example, a trivial definition from the header parser may look something like: |
||||
|
||||
```python |
||||
[fill, void, ['/S'], [cv::Mat&, mat, '', ['/I', '/O']]] |
||||
``` |
||||
|
||||
The equivalent refactored output will look like: |
||||
|
||||
```python |
||||
Function |
||||
name = 'fill' |
||||
rtype = 'void' |
||||
static = True |
||||
req = |
||||
Argument |
||||
name = 'mat' |
||||
type = 'cv::Mat' |
||||
ref = '&' |
||||
I = True |
||||
O = True |
||||
default = '' |
||||
``` |
||||
|
||||
The added semantics (Namespace, Class, Function, Argument, name, etc) make it easier for the templating engine to parse, slice and populate definitions. |
||||
|
||||
Once the definitions have been parsed, `gen_matlab.py` passes each definition to the template engine with the appropriate template (class, function, enum, doc) and the filled template gets written to the `${CMAKE_CURRENT_BUILD_DIR}/src` directory. |
||||
|
||||
The generator relies upon a proxy object called `generate.proxy` to determine when the sources are out of date and need to be re-generated. |
||||
|
||||
**Compile the sources (build)** |
||||
Once the sources have been generated, they are compiled by the mex compiler. The `compile.cmake` script in `opencv/modules/matlab/` takes responsibility for iterating over each source file in `${CMAKE_CURRENT_BUILD_DIR}/src` and compiling it with the passed includes and OpenCV libraries. |
||||
|
||||
The flags used to compile the main OpenCV libraries are also forwarded to the mex compiler. So if, for example, you compiled OpenCV with SSE support, the mex bindings will also use SSE. Likewise, if you compile OpenCV in debug mode, the bindings will link to the debug version of the libraries. |
||||
|
||||
Importantly, the mex compiler includes the `mxarray.hpp`, `bridge.hpp` and `map.hpp` files from the `opencv/modules/matlab/include` directory. `mxarray.hpp` defines a `MxArray` class which wraps Matlab's `mxArray*` type in a more friendly OOP-syle interface. `bridge.hpp` defines a `Bridge` class which is able to perform type conversions between Matlab types and std/OpenCV types. It can be extended with new definitions using the plugin interface described in that file. |
||||
|
||||
The compiler relies upon a proxy object called `compile.proxy` to determine when the generated sources are out of date and need to be re-compiled. |
||||
|
||||
**Install the files (install)** |
||||
At install time, the mex files are put into place at `${CMAKE_INSTALL_PREFIX}/matlab` and their linkages updated. |
||||
|
||||
|
||||
Jinja2 |
||||
------ |
||||
Jinja2 is a powerful templating engine, similar to python's builtin `string.Template` class but implementing the model-view-controller paradigm. For example, a trivial view could be populated as follows: |
||||
|
||||
**view.py** |
||||
|
||||
```html+django |
||||
<title>{{ title }}</title> |
||||
<ul> |
||||
{% for user in users %} |
||||
<li><a href="{{ user.url }}">{{ user.username | sanitize }}</a></li> |
||||
{% endfor %} |
||||
</ul> |
||||
``` |
||||
|
||||
**model.py** |
||||
|
||||
```python |
||||
class User(object): |
||||
__init__(self): |
||||
self.username = '' |
||||
self.url = '' |
||||
|
||||
def sanitize(text): |
||||
"""Filter for escaping html tags to prevent code injection""" |
||||
``` |
||||
|
||||
**controller.py** |
||||
|
||||
```python |
||||
def populate(users): |
||||
# initialize jinja |
||||
jtemplate = jinja2.Environment(loader=FileSystemLoader()) |
||||
|
||||
# add the filters to the engine |
||||
jtemplate['sanitize'] = sanitize |
||||
|
||||
# get the view |
||||
template = jtemplate.get_template('view') |
||||
|
||||
# populate the template with a list of User objects |
||||
populated = template.render(title='all users', users=users) |
||||
|
||||
# write to file |
||||
with open('users.html', 'wb') as f: |
||||
f.write(populated) |
||||
``` |
||||
|
||||
Thus the style and layout of the view is kept separate from the content (model). This modularity improves readability and maintainability of both the view and content and (for my own sanity) has helped significantly in debugging errors. |
||||
|
||||
File Reference |
||||
-------------- |
||||
**gen_matlab.py** |
||||
gen_matlab has the following call signature: |
||||
|
||||
gen_matlab.py --jinja2 path/to/jinja2/engine |
||||
--hdrparser path/to/hdr_parser/dir |
||||
--rstparser path/to/rst_parser/dir |
||||
--moduleroot path/to/opencv/modules |
||||
--modules [core imgproc highgui ...] |
||||
--extra namespace=/additional/header/to/parse |
||||
--outdir /path/to/place/generated/src |
||||
|
||||
**build_info.py** |
||||
build_info has the following call signature: |
||||
|
||||
build_info.py --jinja2 path/to/jinja2/engine |
||||
--os operating_system_string |
||||
--arch [bitness processor] |
||||
--compiler [id version] |
||||
--mex_arch arch_string |
||||
--mex_script /path/to/mex/script |
||||
--cxx_flags [-list -of -flags -to -passthrough] |
||||
--opencv_version version_string |
||||
--commit commit_hash_if_using_git |
||||
--modules core imgproc highgui etc |
||||
--configuration Debug/Release |
||||
--outdir path/to/place/build/info |
||||
|
||||
**cvmex.py** |
||||
cvmex.py, the custom compiler generator, has the following call signature: |
||||
|
||||
cvmex.py --jinja2 path/to/jinja2/engine |
||||
--opts [-list -of -opts] |
||||
--include_dirs [-list -of -opencv_include_directories] |
||||
--lib_dir opencv_lib_directory |
||||
--libs [-lopencv_core -lopencv_imgproc ...] |
||||
--flags [-Wall -opencv_build_flags ...] |
||||
--outdir /path/to/generated/output |
||||
|
||||
**parse_tree.py** |
||||
To build a parse tree, first parse a set of headers, then invoke the parse tree to refactor the output: |
||||
|
||||
```python |
||||
# parse a set of definitions into a dictionary of namespaces |
||||
parser = CppHeaderParser() |
||||
ns['core'] = parser.parse('path/to/opencv/core.hpp') |
||||
|
||||
# refactor into a semantic tree |
||||
parse_tree = ParseTree() |
||||
parse_tree.build(ns) |
||||
|
||||
# iterate over the tree |
||||
for namespace in parse_tree.namespaces: |
||||
for clss in namespace.classes: |
||||
# do stuff |
||||
for method in namespace.methods: |
||||
# do stuff |
||||
``` |
||||
|
||||
**mxarray.hpp** |
||||
mxarray.hpp defines a class called `MxArray` which provides an OOP-style interface for Matlab's homogeneous `mxArray*` type. To create an `MxArray`, you can either inherit an existing array |
||||
|
||||
```cpp |
||||
MxArray mat(prhs[0]); |
||||
``` |
||||
|
||||
or create a new array |
||||
|
||||
```cpp |
||||
MxArray mat(5, 5, Matlab::Traits<double>::ScalarType); |
||||
MxArray mat = MxArray::Matrix<double>(5, 5); |
||||
``` |
||||
|
||||
The default constructor allocates a `0 x 0` array. Once you have encapculated an `mxArray*` you can access its properties through member functions: |
||||
|
||||
```cpp |
||||
mat.rows(); |
||||
mat.cols(); |
||||
mat.size(); |
||||
mat.channels(); |
||||
mat.isComplex(); |
||||
mat.isNumeric(); |
||||
mat.isLogical(); |
||||
mat.isClass(); |
||||
mat.className(); |
||||
mat.real(); |
||||
mat.imag(); |
||||
``` |
||||
|
||||
The MxArray object uses scoped memory management. If you wish to pass an MxArray back to Matlab (as a lhs pointer), you need to explicitly release ownership of the array so that it is not destroyed when it leaves scope: |
||||
|
||||
```cpp |
||||
plhs[0] = mat.releaseOwnership(); |
||||
``` |
||||
|
||||
mxarray.hpp also includes a number of helper utilities that make working in mex-world a little easier. One such utility is the `ArgumentParser`. `ArgumentParser` automatically handles required and optional arguments to a method, and even enables named arguments as used in many core Matlab functions. For example, if you had a function with the following signature: |
||||
|
||||
```cpp |
||||
void f(Mat first, Mat second, Mat mask=Mat(), int dtype=-1); |
||||
``` |
||||
|
||||
then you can create an `ArgumentParser` as follows: |
||||
|
||||
```cpp |
||||
ArgumentParser parser("f"); |
||||
parser.addVariant(2, 2, "mask", "dtype"); |
||||
MxArrayVector inputs = parser.parse(prhs, prhs+nrhs); |
||||
``` |
||||
|
||||
and that will make available the following calling syntaxes: |
||||
|
||||
```matlab |
||||
f(first, second); |
||||
f(first, second, mask); |
||||
f(first, second, mask, dtype); |
||||
f(first, second, 'dtype', dtype, 'mask', mask); % optional ordering does not matter |
||||
f(first, second, 'dtype', dtype); % only second optional argument provided |
||||
f(first, second, mask, 'dtype', dtype); % mixture of ordered and named |
||||
``` |
||||
|
||||
Further, the output of the `parser.parse()` method will always contain the total number of required and optional arguments that the method can take, with unspecified arguments given by empty matrices. Thus, to check if an optional argument has been given, you can do: |
||||
|
||||
```cpp |
||||
int dtype = inputs[3].empty() ? -1 : inputs[3].scalar<double>(); |
||||
``` |
||||
|
||||
**bridge.hpp** |
||||
The bridge interface defines a `Bridge` class which provides type conversion between std/OpenCV and Matlab types. A type conversion must provide the following: |
||||
|
||||
```cpp |
||||
Bridge& operator=(const MyObject&); |
||||
MyObject toMyObject(); |
||||
operator MyObject(); |
||||
``` |
||||
|
||||
The binding generator will then automatically call the conversion operators (either explicitly or implicitly) if your `MyObject` class is encountered as an input or return from a parsed definition. |
@ -0,0 +1,49 @@ |
||||
# LISTIFY |
||||
# Given a string of space-delimited tokens, reparse as a string of |
||||
# semi-colon delimited tokens, which in CMake land is exactly equivalent |
||||
# to a list |
||||
macro(listify OUT_LIST IN_STRING) |
||||
string(REPLACE " " ";" ${OUT_LIST} ${IN_STRING}) |
||||
endmacro() |
||||
|
||||
# listify multiple-argument inputs |
||||
listify(MEX_INCLUDE_DIRS_LIST ${MEX_INCLUDE_DIRS}) |
||||
if (${CONFIGURATION} MATCHES "Debug") |
||||
listify(MEX_LIBS_LIST ${MEX_DEBUG_LIBS}) |
||||
else() |
||||
listify(MEX_LIBS_LIST ${MEX_LIBS}) |
||||
endif() |
||||
|
||||
# if it's MSVC building a Debug configuration, don't build bindings |
||||
if ("${CONFIGURATION}" MATCHES "Debug") |
||||
message(STATUS "Matlab bindings are only available in Release configurations. Skipping...") |
||||
return() |
||||
endif() |
||||
|
||||
# ----------------------------------------------------------------------------- |
||||
# Compile |
||||
# ----------------------------------------------------------------------------- |
||||
# for each generated source file: |
||||
# 1. check if the file has already been compiled |
||||
# 2. attempt compile if required |
||||
# 3. if the compile fails, throw an error and cancel compilation |
||||
file(GLOB SOURCE_FILES "${CMAKE_CURRENT_BINARY_DIR}/src/*.cpp") |
||||
foreach(SOURCE_FILE ${SOURCE_FILES}) |
||||
# strip out the filename |
||||
get_filename_component(FILENAME ${SOURCE_FILE} NAME_WE) |
||||
# compile the source file using mex |
||||
if (NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/+cv/${FILENAME}.${MATLAB_MEXEXT}) |
||||
execute_process( |
||||
COMMAND ${MATLAB_MEX_SCRIPT} ${MEX_OPTS} "CXXFLAGS=\$CXXFLAGS ${MEX_CXXFLAGS}" ${MEX_INCLUDE_DIRS_LIST} |
||||
${MEX_LIB_DIR} ${MEX_LIBS_LIST} ${SOURCE_FILE} |
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/+cv |
||||
OUTPUT_QUIET |
||||
ERROR_VARIABLE FAILED |
||||
) |
||||
endif() |
||||
# TODO: If a mex file fails to compile, should we error out? |
||||
# TODO: Warnings are currently treated as errors... |
||||
if (FAILED) |
||||
message(FATAL_ERROR "Failed to compile ${FILENAME}: ${FAILED}") |
||||
endif() |
||||
endforeach() |
@ -0,0 +1,75 @@ |
||||
#!/usr/bin/env python |
||||
|
||||
def substitute(build, output_dir): |
||||
|
||||
# setup the template engine |
||||
template_dir = os.path.join(os.path.dirname(__file__), 'templates') |
||||
jtemplate = Environment(loader=FileSystemLoader(template_dir), trim_blocks=True, lstrip_blocks=True) |
||||
|
||||
# add the filters |
||||
jtemplate.filters['csv'] = csv |
||||
jtemplate.filters['stripExtraSpaces'] = stripExtraSpaces |
||||
|
||||
# load the template |
||||
template = jtemplate.get_template('template_build_info.m') |
||||
|
||||
# create the build directory |
||||
output_dir = output_dir+'/+cv' |
||||
if not os.path.isdir(output_dir): |
||||
os.mkdir(output_dir) |
||||
|
||||
# populate template |
||||
populated = template.render(build=build, time=time) |
||||
with open(os.path.join(output_dir, 'buildInformation.m'), 'wb') as f: |
||||
f.write(populated.encode('utf-8')) |
||||
|
||||
if __name__ == "__main__": |
||||
""" |
||||
Usage: python build_info.py --jinja2 /path/to/jinja2/engine |
||||
--os os_version_string |
||||
--arch [bitness processor] |
||||
--compiler [id version] |
||||
--mex_arch arch_string |
||||
--mex_script /path/to/mex/script |
||||
--cxx_flags [-list -of -flags -to -passthrough] |
||||
--opencv_version version_string |
||||
--commit commit_hash_if_using_git |
||||
--modules [core imgproc highgui etc] |
||||
--configuration Debug/Release |
||||
--outdir /path/to/write/build/info |
||||
|
||||
build_info.py generates a Matlab function that can be invoked with a call to |
||||
>> cv.buildInformation(); |
||||
|
||||
This function prints a summary of the user's OS, OpenCV and Matlab build |
||||
given the information passed to this module. build_info.py invokes Jinja2 |
||||
on the template_build_info.m template. |
||||
""" |
||||
|
||||
# parse the input options |
||||
import sys, re, os, time |
||||
from argparse import ArgumentParser |
||||
parser = ArgumentParser() |
||||
parser.add_argument('--jinja2') |
||||
parser.add_argument('--os') |
||||
parser.add_argument('--arch', nargs=2) |
||||
parser.add_argument('--compiler', nargs='+') |
||||
parser.add_argument('--mex_arch') |
||||
parser.add_argument('--mex_script') |
||||
parser.add_argument('--mex_opts', default=['-largeArrayDims'], nargs='*') |
||||
parser.add_argument('--cxx_flags', default=[], nargs='*') |
||||
parser.add_argument('--opencv_version', default='', nargs='?') |
||||
parser.add_argument('--commit', default='Not in working git tree', nargs='?') |
||||
parser.add_argument('--modules', nargs='+') |
||||
parser.add_argument('--configuration') |
||||
parser.add_argument('--outdir') |
||||
build = parser.parse_args() |
||||
|
||||
# add jinja to the path |
||||
sys.path.append(build.jinja2) |
||||
|
||||
from filters import * |
||||
from jinja2 import Environment, FileSystemLoader |
||||
|
||||
# populate the build info template |
||||
substitute(build, build.outdir) |
@ -0,0 +1,63 @@ |
||||
#!/usr/bin/env python |
||||
|
||||
def substitute(cv, output_dir): |
||||
|
||||
# setup the template engine |
||||
template_dir = os.path.join(os.path.dirname(__file__), 'templates') |
||||
jtemplate = Environment(loader=FileSystemLoader(template_dir), trim_blocks=True, lstrip_blocks=True) |
||||
|
||||
# add the filters |
||||
jtemplate.filters['cellarray'] = cellarray |
||||
jtemplate.filters['split'] = split |
||||
jtemplate.filters['csv'] = csv |
||||
|
||||
# load the template |
||||
template = jtemplate.get_template('template_cvmex_base.m') |
||||
|
||||
# create the build directory |
||||
output_dir = output_dir+'/+cv' |
||||
if not os.path.isdir(output_dir): |
||||
os.mkdir(output_dir) |
||||
|
||||
# populate template |
||||
populated = template.render(cv=cv, time=time) |
||||
with open(os.path.join(output_dir, 'mex.m'), 'wb') as f: |
||||
f.write(populated.encode('utf-8')) |
||||
|
||||
if __name__ == "__main__": |
||||
""" |
||||
Usage: python cvmex.py --jinja2 /path/to/jinja2/engine |
||||
--opts [-list -of -opts] |
||||
--include_dirs [-list -of -opencv_include_directories] |
||||
--lib_dir opencv_lib_directory |
||||
--libs [-lopencv_core -lopencv_imgproc ...] |
||||
--flags [-Wall -opencv_build_flags ...] |
||||
--outdir /path/to/generated/output |
||||
|
||||
cvmex.py generates a custom mex compiler that automatically links OpenCV |
||||
libraries to built sources where appropriate. The calling syntax is the |
||||
same as the builtin mex compiler, with added cv qualification: |
||||
>> cv.mex(..., ...); |
||||
""" |
||||
|
||||
# parse the input options |
||||
import sys, re, os, time |
||||
from argparse import ArgumentParser |
||||
parser = ArgumentParser() |
||||
parser.add_argument('--jinja2') |
||||
parser.add_argument('--opts') |
||||
parser.add_argument('--include_dirs') |
||||
parser.add_argument('--lib_dir') |
||||
parser.add_argument('--libs') |
||||
parser.add_argument('--flags') |
||||
parser.add_argument('--outdir') |
||||
cv = parser.parse_args() |
||||
|
||||
# add jinja to the path |
||||
sys.path.append(cv.jinja2) |
||||
|
||||
from filters import * |
||||
from jinja2 import Environment, FileSystemLoader |
||||
|
||||
# populate the mex base template |
||||
substitute(cv, cv.outdir) |
@ -0,0 +1,179 @@ |
||||
from textwrap import TextWrapper |
||||
import re, os |
||||
# precompile a URL matching regular expression |
||||
urlexpr = re.compile(r"((https?):((//)|(\\\\))+[\w\d:#@%/;$()~_?\+-=\\\.&]*)", re.MULTILINE|re.UNICODE) |
||||
|
||||
def inputs(args): |
||||
'''Keeps only the input arguments in a list of elements. |
||||
In OpenCV input arguments are all arguments with names |
||||
not beginning with 'dst' |
||||
''' |
||||
try: |
||||
return [arg for arg in args['only'] if arg.I and not arg.O] |
||||
except: |
||||
return [arg for arg in args if arg.I] |
||||
|
||||
def ninputs(fun): |
||||
'''Counts the number of input arguments in the input list''' |
||||
return len(inputs(fun.req)) + len(inputs(fun.opt)) |
||||
|
||||
def outputs(args): |
||||
'''Determines whether any of the given arguments is an output |
||||
reference, and returns a list of only those elements. |
||||
In OpenCV, output references are preceeded by 'dst' |
||||
''' |
||||
try: |
||||
return [arg for arg in args['only'] if arg.O and not arg.I] |
||||
except: |
||||
return [arg for arg in args if arg.O] |
||||
|
||||
def only(args): |
||||
'''Returns exclusively the arguments which are only inputs |
||||
or only outputs''' |
||||
d = {}; |
||||
d['only'] = args |
||||
return d |
||||
|
||||
def void(arg): |
||||
'''Is the input 'void' ''' |
||||
return arg == 'void' |
||||
|
||||
def flip(arg): |
||||
'''flip the sign of the input''' |
||||
return not arg |
||||
|
||||
def noutputs(fun): |
||||
'''Counts the number of output arguments in the input list''' |
||||
return int(not void(fun.rtp)) + len(outputs(fun.req)) + len(outputs(fun.opt)) |
||||
|
||||
def convertibleToInt(string): |
||||
'''Can the input string be evaluated to an integer?''' |
||||
salt = '1+' |
||||
try: |
||||
exec(salt+string) |
||||
return True |
||||
except: |
||||
return False |
||||
|
||||
def binaryToDecimal(string): |
||||
'''Attempt to convert the input string to floating point representation''' |
||||
try: |
||||
return str(eval(string)) |
||||
except: |
||||
return string |
||||
|
||||
def formatMatlabConstant(string, table): |
||||
''' |
||||
Given a string representing a Constant, and a table of all Constants, |
||||
attempt to resolve the Constant into a valid Matlab expression |
||||
For example, the input |
||||
DEPENDENT_VALUE = 1 << FIXED_VALUE |
||||
needs to be converted to |
||||
DEPENDENT_VALUE = bitshift(1, cv.FIXED_VALUE); |
||||
''' |
||||
# split the string into expressions |
||||
words = re.split('(\W+)', string) |
||||
# add a 'cv' prefix if an expression is also a key in the lookup table |
||||
words = ''.join([('cv.'+word if word in table else word) for word in words]) |
||||
# attempt to convert arithmetic expressions and binary/hex to decimal |
||||
words = binaryToDecimal(words) |
||||
# convert any remaining bitshifts to Matlab 'bitshift' methods |
||||
shift = re.sub('[\(\) ]', '', words).split('<<') |
||||
words = 'bitshift('+shift[0]+', '+shift[1]+')' if len(shift) == 2 else words |
||||
return words |
||||
|
||||
def matlabURL(string): |
||||
"""This filter is used to construct a Matlab specific URL that calls the |
||||
system browser instead of the (insanely bad) builtin Matlab browser""" |
||||
return re.sub(urlexpr, '<a href="matlab: web(\'\\1\', \'-browser\')">\\1</a>', string) |
||||
|
||||
def capitalizeFirst(text): |
||||
'''Capitalize only the first character of the text string''' |
||||
return text[0].upper() + text[1:] |
||||
|
||||
def toUpperCamelCase(text): |
||||
'''variable_name --> VariableName''' |
||||
return ''.join([capitalizeFirst(word) for word in text.split('_')]) |
||||
|
||||
def toLowerCamelCase(text): |
||||
'''variable_name --> variableName''' |
||||
upper_camel = toUpperCamelCase(test) |
||||
return upper_camel[0].lower() + upper_camel[1:] |
||||
|
||||
def toUnderCase(text): |
||||
'''VariableName --> variable_name''' |
||||
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', text) |
||||
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower() |
||||
|
||||
def stripTags(text): |
||||
''' |
||||
strip or convert html tags from a text string |
||||
<code>content</code> --> content |
||||
<anything> --> '' |
||||
< --> < |
||||
> --> > |
||||
&le --> <= |
||||
&ge --> >= |
||||
''' |
||||
upper = lambda pattern: pattern.group(1).upper() |
||||
text = re.sub('<code>(.*?)</code>', upper, text) |
||||
text = re.sub('<([^=\s].*?)>', '', text) |
||||
text = re.sub('<', '<', text) |
||||
text = re.sub('>', '>', text) |
||||
text = re.sub('&le', '<=', text) |
||||
text = re.sub('&ge', '>=', text) |
||||
return text |
||||
|
||||
def qualify(text, name): |
||||
'''Adds uppercase 'CV.' qualification to any occurrences of name in text''' |
||||
return re.sub(name.upper(), 'CV.'+name.upper(), text) |
||||
|
||||
def slugify(text): |
||||
'''A_Function_name --> a-function-name''' |
||||
return text.lower().replace('_', '-') |
||||
|
||||
def filename(fullpath): |
||||
'''Returns only the filename without an extension from a file path |
||||
eg. /path/to/file.txt --> file |
||||
''' |
||||
return os.path.splitext(os.path.basename(fullpath))[0] |
||||
|
||||
def split(text, delimiter=' '): |
||||
'''Split a text string into a list using the specified delimiter''' |
||||
return text.split(delimiter) |
||||
|
||||
def csv(items, sep=', '): |
||||
'''format a list with a separator (comma if not specified)''' |
||||
return sep.join(item for item in items) |
||||
|
||||
def cellarray(items, escape='\''): |
||||
'''format a list of items as a matlab cell array''' |
||||
return '{' + ', '.join(escape+item+escape for item in items) + '}' |
||||
|
||||
def stripExtraSpaces(text): |
||||
'''Removes superfluous whitespace from a string, including the removal |
||||
of all leading and trailing whitespace''' |
||||
return ' '.join(text.split()) |
||||
|
||||
def comment(text, wrap=80, escape='% ', escape_first='', escape_last=''): |
||||
'''comment filter |
||||
Takes a string in text, and wraps it to wrap characters in length with |
||||
preceding comment escape sequence on each line. escape_first and |
||||
escape_last can be used for languages which define block comments. |
||||
Examples: |
||||
C++ inline comment comment(80, '// ') |
||||
C block comment: comment(80, ' * ', '/*', ' */') |
||||
Matlab comment: comment(80, '% ') |
||||
Matlab block comment: comment(80, '', '%{', '%}') |
||||
Python docstrings: comment(80, '', '\'\'\'', '\'\'\'') |
||||
''' |
||||
|
||||
tw = TextWrapper(width=wrap-len(escape)) |
||||
if escape_first: |
||||
escape_first = escape_first+'\n' |
||||
if escape_last: |
||||
escape_last = '\n'+escape_last |
||||
escapn = '\n'+escape |
||||
lines = text.split('\n') |
||||
wlines = (tw.wrap(line) for line in lines) |
||||
return escape_first+escape+escapn.join(escapn.join(line) for line in wlines)+escape_last |
@ -0,0 +1,198 @@ |
||||
#!/usr/bin/env python |
||||
import sys, re, os, time |
||||
from string import Template |
||||
from parse_tree import ParseTree, todict, constants |
||||
from filters import * |
||||
|
||||
class MatlabWrapperGenerator(object): |
||||
""" |
||||
MatlabWrapperGenerator is a class for generating Matlab mex sources from |
||||
a set of C++ headers. MatlabWrapperGenerator objects can be default |
||||
constructed. Given an instance, the gen() method performs the translation. |
||||
""" |
||||
|
||||
def gen(self, module_root, modules, extras, output_dir): |
||||
""" |
||||
Generate a set of Matlab mex source files by parsing exported symbols |
||||
in a set of C++ headers. The headers can be input in one (or both) of |
||||
two methods: |
||||
1. specify module_root and modules |
||||
Given a path to the OpenCV module root and a list of module names, |
||||
the headers to parse are implicitly constructed. |
||||
2. specifiy header locations explicitly in extras |
||||
Each element in the list of extras must be of the form: |
||||
'namespace=/full/path/to/extra/header.hpp' where 'namespace' is |
||||
the namespace in which the definitions should be added. |
||||
The output_dir specifies the directory to write the generated sources |
||||
to. |
||||
""" |
||||
# dynamically import the parsers |
||||
from jinja2 import Environment, FileSystemLoader |
||||
import hdr_parser |
||||
import rst_parser |
||||
|
||||
# parse each of the files and store in a dictionary |
||||
# as a separate "namespace" |
||||
parser = hdr_parser.CppHeaderParser() |
||||
rst = rst_parser.RstParser(parser) |
||||
rst_parser.verbose = False |
||||
rst_parser.show_warnings = False |
||||
rst_parser.show_errors = False |
||||
rst_parser.show_critical_errors = False |
||||
|
||||
ns = dict((key, []) for key in modules) |
||||
doc = dict((key, []) for key in modules) |
||||
path_template = Template('${module}/include/opencv2/${module}.hpp') |
||||
|
||||
for module in modules: |
||||
# construct a header path from the module root and a path template |
||||
header = os.path.join(module_root, path_template.substitute(module=module)) |
||||
# parse the definitions |
||||
ns[module] = parser.parse(header) |
||||
# parse the documentation |
||||
rst.parse(module, os.path.join(module_root, module)) |
||||
doc[module] = rst.definitions |
||||
rst.definitions = {} |
||||
|
||||
for extra in extras: |
||||
module = extra.split("=")[0] |
||||
header = extra.split("=")[1] |
||||
ns[module] = ns[module] + parser.parse(header) if module in ns else parser.parse(header) |
||||
|
||||
# cleanify the parser output |
||||
parse_tree = ParseTree() |
||||
parse_tree.build(ns) |
||||
|
||||
# setup the template engine |
||||
template_dir = os.path.join(os.path.dirname(__file__), 'templates') |
||||
jtemplate = Environment(loader=FileSystemLoader(template_dir), trim_blocks=True, lstrip_blocks=True) |
||||
|
||||
# add the custom filters |
||||
jtemplate.filters['formatMatlabConstant'] = formatMatlabConstant |
||||
jtemplate.filters['convertibleToInt'] = convertibleToInt |
||||
jtemplate.filters['toUpperCamelCase'] = toUpperCamelCase |
||||
jtemplate.filters['toLowerCamelCase'] = toLowerCamelCase |
||||
jtemplate.filters['toUnderCase'] = toUnderCase |
||||
jtemplate.filters['matlabURL'] = matlabURL |
||||
jtemplate.filters['stripTags'] = stripTags |
||||
jtemplate.filters['filename'] = filename |
||||
jtemplate.filters['comment'] = comment |
||||
jtemplate.filters['inputs'] = inputs |
||||
jtemplate.filters['ninputs'] = ninputs |
||||
jtemplate.filters['outputs'] = outputs |
||||
jtemplate.filters['noutputs'] = noutputs |
||||
jtemplate.filters['qualify'] = qualify |
||||
jtemplate.filters['slugify'] = slugify |
||||
jtemplate.filters['only'] = only |
||||
jtemplate.filters['void'] = void |
||||
jtemplate.filters['not'] = flip |
||||
|
||||
# load the templates |
||||
tfunction = jtemplate.get_template('template_function_base.cpp') |
||||
tclassm = jtemplate.get_template('template_class_base.m') |
||||
tclassc = jtemplate.get_template('template_class_base.cpp') |
||||
tdoc = jtemplate.get_template('template_doc_base.m') |
||||
tconst = jtemplate.get_template('template_map_base.m') |
||||
|
||||
# create the build directory |
||||
output_source_dir = output_dir+'/src' |
||||
output_private_dir = output_source_dir+'/private' |
||||
output_class_dir = output_dir+'/+cv' |
||||
output_map_dir = output_dir+'/map' |
||||
if not os.path.isdir(output_source_dir): |
||||
os.makedirs(output_source_dir) |
||||
if not os.path.isdir(output_private_dir): |
||||
os.makedirs(output_private_dir) |
||||
if not os.path.isdir(output_class_dir): |
||||
os.makedirs(output_class_dir) |
||||
if not os.path.isdir(output_map_dir): |
||||
os.makedirs(output_map_dir) |
||||
|
||||
# populate templates |
||||
for namespace in parse_tree.namespaces: |
||||
# functions |
||||
for method in namespace.methods: |
||||
populated = tfunction.render(fun=method, time=time, includes=namespace.name) |
||||
with open(output_source_dir+'/'+method.name+'.cpp', 'wb') as f: |
||||
f.write(populated.encode('utf-8')) |
||||
if namespace.name in doc and method.name in doc[namespace.name]: |
||||
populated = tdoc.render(fun=method, doc=doc[namespace.name][method.name], time=time) |
||||
with open(output_class_dir+'/'+method.name+'.m', 'wb') as f: |
||||
f.write(populated.encode('utf-8')) |
||||
# classes |
||||
for clss in namespace.classes: |
||||
# cpp converter |
||||
populated = tclassc.render(clss=clss, time=time) |
||||
with open(output_private_dir+'/'+clss.name+'Bridge.cpp', 'wb') as f: |
||||
f.write(populated.encode('utf-8')) |
||||
# matlab classdef |
||||
populated = tclassm.render(clss=clss, time=time) |
||||
with open(output_class_dir+'/'+clss.name+'.m', 'wb') as f: |
||||
f.write(populated.encode('utf-8')) |
||||
|
||||
# create a global constants lookup table |
||||
const = dict(constants(todict(parse_tree.namespaces))) |
||||
populated = tconst.render(constants=const, time=time) |
||||
with open(output_dir+'/cv.m', 'wb') as f: |
||||
f.write(populated.encode('utf-8')) |
||||
|
||||
|
||||
if __name__ == "__main__": |
||||
""" |
||||
Usage: python gen_matlab.py --jinja2 /path/to/jinja2/engine |
||||
--hdrparser /path/to/hdr_parser/dir |
||||
--rstparser /path/to/rst_parser/dir |
||||
--moduleroot /path/to/opencv/modules |
||||
--modules [core imgproc objdetect etc] |
||||
--extra namespace=/path/to/extra/header.hpp |
||||
--outdir /path/to/output/generated/srcs |
||||
|
||||
gen_matlab.py is the main control script for generating matlab source |
||||
files from given set of headers. Internally, gen_matlab: |
||||
1. constructs the headers to parse from the module root and list of modules |
||||
2. parses the headers using CppHeaderParser |
||||
3. refactors the definitions using ParseTree |
||||
4. parses .rst docs using RstParser |
||||
5. populates the templates for classes, function, enums and docs from the |
||||
definitions |
||||
|
||||
gen_matlab.py requires the following inputs: |
||||
--jinja2 the path to the Jinja2 templating engine |
||||
e.g. ${CMAKE_SOURCE_DIR}/3rdparty |
||||
--hdrparser the path to the header parser directory |
||||
(opencv/modules/python/src2) |
||||
--rstparser the path to the rst parser directory |
||||
(opencv/modules/java/generator) |
||||
--moduleroot (optional) path to the opencv directory containing the modules |
||||
--modules (optional - required if --moduleroot specified) the modules |
||||
to produce bindings for. The path to the include directories |
||||
as well as the namespaces are constructed from the modules |
||||
and the moduleroot |
||||
--extra extra headers explicitly defined to parse. This must be in |
||||
the format "namepsace=/path/to/extra/header.hpp". For example, |
||||
the core module requires the extra header: |
||||
"core=/opencv/modules/core/include/opencv2/core/core/base.hpp" |
||||
--outdir the output directory to put the generated matlab sources. In |
||||
the OpenCV build this is "${CMAKE_CURRENT_BUILD_DIR}/src" |
||||
""" |
||||
|
||||
# parse the input options |
||||
from argparse import ArgumentParser |
||||
parser = ArgumentParser() |
||||
parser.add_argument('--jinja2') |
||||
parser.add_argument('--hdrparser') |
||||
parser.add_argument('--rstparser') |
||||
parser.add_argument('--moduleroot', default='', required=False) |
||||
parser.add_argument('--modules', nargs='*', default=[], required=False) |
||||
parser.add_argument('--extra', nargs='*', default=[], required=False) |
||||
parser.add_argument('--outdir') |
||||
args = parser.parse_args() |
||||
|
||||
# add the hdr_parser and rst_parser modules to the path |
||||
sys.path.append(args.jinja2) |
||||
sys.path.append(args.hdrparser) |
||||
sys.path.append(args.rstparser) |
||||
|
||||
# create the generator |
||||
mwg = MatlabWrapperGenerator() |
||||
mwg.gen(args.moduleroot, args.modules, args.extra, args.outdir) |
@ -0,0 +1,359 @@ |
||||
import collections |
||||
from textwrap import fill |
||||
from filters import * |
||||
try: |
||||
# Python 2.7+ |
||||
basestring |
||||
except NameError: |
||||
# Python 3.3+ |
||||
basestring = str |
||||
|
||||
class ParseTree(object): |
||||
""" |
||||
The ParseTree class produces a semantic tree of C++ definitions given |
||||
the output of the CppHeaderParser (from opencv/modules/python/src2/hdr_parser.py) |
||||
|
||||
The full hierarchy is as follows: |
||||
|
||||
Namespaces |
||||
| |
||||
|- name |
||||
|- Classes |
||||
| |
||||
|- name |
||||
|- Methods |
||||
|- Constants |
||||
|- Methods |
||||
| |
||||
|- name |
||||
|- static (T/F) |
||||
|- return type |
||||
|- required Arguments |
||||
| |
||||
|- name |
||||
|- const (T/F) |
||||
|- reference ('&'/'*') |
||||
|- type |
||||
|- input |
||||
|- output (pass return by reference) |
||||
|- default value |
||||
|- optional Arguments |
||||
|- Constants |
||||
| |
||||
|- name |
||||
|- const (T/F) |
||||
|- reference ('&'/'*') |
||||
|- type |
||||
|- value |
||||
|
||||
The semantic tree contains substantial information for easily introspecting |
||||
information about objects. How many methods does the 'core' namespace have? |
||||
Does the 'randn' method have any return by reference (output) arguments? |
||||
How many required and optional arguments does the 'add' method have? Is the |
||||
variable passed by reference or raw pointer? |
||||
|
||||
Individual definitions from the parse tree (Classes, Functions, Constants) |
||||
are passed to the Jinja2 template engine where they are manipulated to |
||||
produce Matlab mex sources. |
||||
|
||||
A common call tree for constructing and using a ParseTree object is: |
||||
|
||||
# parse a set of definitions into a dictionary of namespaces |
||||
parser = CppHeaderParser() |
||||
ns['core'] = parser.parse('path/to/opencv/core.hpp') |
||||
|
||||
# refactor into a semantic tree |
||||
parse_tree = ParseTree() |
||||
parse_tree.build(ns) |
||||
|
||||
# iterate over the tree |
||||
for namespace in parse_tree.namespaces: |
||||
for clss in namespace.classes: |
||||
# do stuff |
||||
for method in namespace.methods: |
||||
# do stuff |
||||
|
||||
Calling 'print' on a ParseTree object will reconstruct the definitions |
||||
to produce an output resembling the original C++ code. |
||||
""" |
||||
def __init__(self, namespaces=None): |
||||
self.namespaces = namespaces if namespaces else [] |
||||
|
||||
def __str__(self): |
||||
return '\n\n\n'.join(ns.__str__() for ns in self.namespaces) |
||||
|
||||
def build(self, namespaces): |
||||
babel = Translator() |
||||
for name, definitions in namespaces.items(): |
||||
class_tree = {} |
||||
methods = [] |
||||
constants = [] |
||||
for defn in definitions: |
||||
obj = babel.translate(defn) |
||||
if obj is None: |
||||
continue |
||||
if type(obj) is Class or obj.clss: |
||||
self.insertIntoClassTree(obj, class_tree) |
||||
elif type(obj) is Method: |
||||
methods.append(obj) |
||||
elif type(obj) is Constant: |
||||
constants.append(obj) |
||||
else: |
||||
raise TypeError('Unexpected object type: '+str(type(obj))) |
||||
self.namespaces.append(Namespace(name, constants, list(class_tree.values()), methods)) |
||||
|
||||
def insertIntoClassTree(self, obj, class_tree): |
||||
cname = obj.name if type(obj) is Class else obj.clss |
||||
if not cname: |
||||
return |
||||
if not cname in class_tree: |
||||
# add a new class to the tree |
||||
class_tree[cname] = Class(cname) |
||||
# insert the definition into the class |
||||
val = class_tree[cname] |
||||
if type(obj) is Method: |
||||
val.methods.append(obj) |
||||
elif type(obj) is Constant: |
||||
val.constants.append(obj) |
||||
else: |
||||
raise TypeError('Unexpected object type: '+str(type(obj))) |
||||
|
||||
|
||||
|
||||
class Translator(object): |
||||
""" |
||||
The Translator class does the heavy lifting of translating the nested |
||||
list representation of the hdr_parser into individual definitions that |
||||
are inserted into the ParseTree. |
||||
Translator consists of a top-level method: translate() |
||||
along with a number of helper methods: translateClass(), translateMethod(), |
||||
translateArgument(), translateConstant(), translateName(), and |
||||
translateClassName() |
||||
""" |
||||
def translate(self, defn): |
||||
# --- class --- |
||||
# classes have 'class' prefixed on their name |
||||
if 'class' in defn[0].split(' ') or 'struct' in defn[0].split(' '): |
||||
return self.translateClass(defn) |
||||
# --- operators! --- |
||||
#TODO: implement operators: http://www.mathworks.com.au/help/matlab/matlab_oop/implementing-operators-for-your-class.html |
||||
if 'operator' in defn[0]: |
||||
return |
||||
# --- constant --- |
||||
elif convertibleToInt(defn[1]): |
||||
return self.translateConstant(defn) |
||||
# --- function --- |
||||
# functions either need to have input arguments, or not uppercase names |
||||
elif defn[3] or not self.translateName(defn[0]).split('_')[0].isupper(): |
||||
return self.translateMethod(defn) |
||||
# --- constant --- |
||||
else: |
||||
return self.translateConstant(defn) |
||||
|
||||
def translateClass(self, defn): |
||||
return Class() |
||||
|
||||
def translateMethod(self, defn, class_tree=None): |
||||
name = self.translateName(defn[0]) |
||||
clss = self.translateClassName(defn[0]) |
||||
rtp = defn[1] |
||||
static = True if 'S' in ''.join(defn[2]) else False |
||||
args = defn[3] |
||||
req = [] |
||||
opt = [] |
||||
for arg in args: |
||||
if arg: |
||||
a = self.translateArgument(arg) |
||||
opt.append(a) if a.default else req.append(a) |
||||
return Method(name, clss, static, '', rtp, False, req, opt) |
||||
|
||||
def translateConstant(self, defn): |
||||
const = True if 'const' in defn[0] else False |
||||
name = self.translateName(defn[0]) |
||||
clss = self.translateClassName(defn[0]) |
||||
tp = 'int' |
||||
val = defn[1] |
||||
return Constant(name, clss, tp, const, '', val) |
||||
|
||||
def translateArgument(self, defn): |
||||
ref = '*' if '*' in defn[0] else '' |
||||
ref = '&' if '&' in defn[0] else ref |
||||
const = ' const ' in ' '+defn[0]+' ' |
||||
tp = " ".join([word for word in defn[0].replace(ref, '').split() if not ' const ' in ' '+word+' ']) |
||||
name = defn[1] |
||||
default = defn[2] if defn[2] else '' |
||||
modifiers = ''.join(defn[3]) |
||||
I = True if not modifiers or 'I' in modifiers else False |
||||
O = True if 'O' in modifiers else False |
||||
return Argument(name, tp, const, I, O, ref, default) |
||||
|
||||
def translateName(self, name): |
||||
return name.split(' ')[-1].split('.')[-1] |
||||
|
||||
def translateClassName(self, name): |
||||
name = name.split(' ')[-1] |
||||
parts = name.split('.') |
||||
return parts[-2] if len(parts) > 1 and not parts[-2] == 'cv' else '' |
||||
|
||||
|
||||
|
||||
class Namespace(object): |
||||
""" |
||||
Namespace |
||||
| |
||||
|- name |
||||
|- Constants |
||||
|- Methods |
||||
|- Constants |
||||
""" |
||||
def __init__(self, name='', constants=None, classes=None, methods=None): |
||||
self.name = name |
||||
self.constants = constants if constants else [] |
||||
self.classes = classes if classes else [] |
||||
self.methods = methods if methods else [] |
||||
|
||||
def __str__(self): |
||||
return 'namespace '+self.name+' {\n\n'+\ |
||||
('\n'.join(c.__str__() for c in self.constants)+'\n\n' if self.constants else '')+\ |
||||
('\n'.join(f.__str__() for f in self.methods)+'\n\n' if self.methods else '')+\ |
||||
('\n\n'.join(o.__str__() for o in self.classes) if self.classes else '')+'\n};' |
||||
|
||||
class Class(object): |
||||
""" |
||||
Class |
||||
| |
||||
|- name |
||||
|- Methods |
||||
|- Constants |
||||
""" |
||||
def __init__(self, name='', namespace='', constants=None, methods=None): |
||||
self.name = name |
||||
self.namespace = namespace |
||||
self.constants = constants if constants else [] |
||||
self.methods = methods if methods else [] |
||||
|
||||
def __str__(self): |
||||
return 'class '+self.name+' {\n\t'+\ |
||||
('\n\t'.join(c.__str__() for c in self.constants)+'\n\n\t' if self.constants else '')+\ |
||||
('\n\t'.join(f.__str__() for f in self.methods) if self.methods else '')+'\n};' |
||||
|
||||
class Method(object): |
||||
""" |
||||
Method |
||||
int VideoWriter::read( cv::Mat& frame, const cv::Mat& mask=cv::Mat() ); |
||||
--- ----- ---- -------- ---------------- |
||||
rtp class name required optional |
||||
|
||||
name the method name |
||||
clss the class the method belongs to ('' if free) |
||||
static static? |
||||
namespace the namespace the method belongs to ('' if free) |
||||
rtp the return type |
||||
const const? |
||||
req list of required arguments |
||||
opt list of optional arguments |
||||
""" |
||||
def __init__(self, name='', clss='', static=False, namespace='', rtp='', const=False, req=None, opt=None): |
||||
self.name = name |
||||
self.clss = clss |
||||
self.constructor = True if name == clss else False |
||||
self.static = static |
||||
self.const = const |
||||
self.namespace = namespace |
||||
self.rtp = rtp |
||||
self.req = req if req else [] |
||||
self.opt = opt if opt else [] |
||||
|
||||
def __str__(self): |
||||
return (self.rtp+' ' if self.rtp else '')+self.name+'('+\ |
||||
', '.join(arg.__str__() for arg in self.req+self.opt)+\ |
||||
')'+(' const' if self.const else '')+';' |
||||
|
||||
class Argument(object): |
||||
""" |
||||
Argument |
||||
const cv::Mat& mask=cv::Mat() |
||||
----- ---- --- ---- ------- |
||||
const tp ref name default |
||||
|
||||
name the argument name |
||||
tp the argument type |
||||
const const? |
||||
I is the argument treated as an input? |
||||
O is the argument treated as an output (return by reference) |
||||
ref is the argument passed by reference? ('*'/'&') |
||||
default the default value of the argument ('' if required) |
||||
""" |
||||
def __init__(self, name='', tp='', const=False, I=True, O=False, ref='', default=''): |
||||
self.name = name |
||||
self.tp = tp |
||||
self.ref = ref |
||||
self.I = I |
||||
self.O = O |
||||
self.const = const |
||||
self.default = default |
||||
|
||||
def __str__(self): |
||||
return ('const ' if self.const else '')+self.tp+self.ref+\ |
||||
' '+self.name+('='+self.default if self.default else '') |
||||
|
||||
class Constant(object): |
||||
""" |
||||
Constant |
||||
DFT_COMPLEX_OUTPUT = 12; |
||||
---- ------- |
||||
name default |
||||
|
||||
name the name of the constant |
||||
clss the class that the constant belongs to ('' if free) |
||||
tp the type of the constant ('' if int) |
||||
const const? |
||||
ref is the constant a reference? ('*'/'&') |
||||
default default value, required for constants |
||||
""" |
||||
def __init__(self, name='', clss='', tp='', const=False, ref='', default=''): |
||||
self.name = name |
||||
self.clss = clss |
||||
self.tp = tp |
||||
self.ref = ref |
||||
self.const = const |
||||
self.default = default |
||||
|
||||
def __str__(self): |
||||
return ('const ' if self.const else '')+self.tp+self.ref+\ |
||||
' '+self.name+('='+self.default if self.default else '')+';' |
||||
|
||||
def constants(tree): |
||||
""" |
||||
recursive generator to strip all Constant objects from the ParseTree |
||||
and place them into a flat dictionary of { name, value (default) } |
||||
""" |
||||
if isinstance(tree, dict) and 'constants' in tree and isinstance(tree['constants'], list): |
||||
for node in tree['constants']: |
||||
yield (node['name'], node['default']) |
||||
if isinstance(tree, dict): |
||||
for key, val in tree.items(): |
||||
for gen in constants(val): |
||||
yield gen |
||||
if isinstance(tree, list): |
||||
for val in tree: |
||||
for gen in constants(val): |
||||
yield gen |
||||
|
||||
|
||||
def todict(obj): |
||||
""" |
||||
Recursively convert a Python object graph to sequences (lists) |
||||
and mappings (dicts) of primitives (bool, int, float, string, ...) |
||||
""" |
||||
if isinstance(obj, basestring): |
||||
return obj |
||||
elif isinstance(obj, dict): |
||||
return dict((key, todict(val)) for key, val in obj.items()) |
||||
elif isinstance(obj, collections.Iterable): |
||||
return [todict(val) for val in obj] |
||||
elif hasattr(obj, '__dict__'): |
||||
return todict(vars(obj)) |
||||
elif hasattr(obj, '__slots__'): |
||||
return todict(dict((name, getattr(obj, name)) for name in getattr(obj, '__slots__'))) |
||||
return obj |
@ -0,0 +1,149 @@ |
||||
/*
|
||||
* compose |
||||
* compose a function call |
||||
* This macro takes as input a Method object and composes |
||||
* a function call by inspecting the types and argument names |
||||
*/ |
||||
{% macro compose(fun) %} |
||||
{# ----------- Return type ------------- #} |
||||
{%- if not fun.rtp|void and not fun.constructor -%} retval = {% endif -%} |
||||
{%- if fun.constructor -%}{{fun.clss}} obj = {% endif -%} |
||||
{%- if fun.clss and not fun.constructor -%}inst.{%- else -%} cv:: {%- endif -%} |
||||
{{fun.name}}( |
||||
{#- ----------- Required ------------- -#} |
||||
{%- for arg in fun.req -%} |
||||
{%- if arg.ref == '*' -%}&{%- endif -%} |
||||
{{arg.name}} |
||||
{%- if not loop.last %}, {% endif %} |
||||
{% endfor %} |
||||
{#- ----------- Optional ------------- -#} |
||||
{% if fun.req and fun.opt %}, {% endif %} |
||||
{%- for opt in fun.opt -%} |
||||
{%- if opt.ref == '*' -%}&{%- endif -%} |
||||
{{opt.name}} |
||||
{%- if not loop.last -%}, {% endif %} |
||||
{%- endfor -%} |
||||
); |
||||
{%- endmacro %} |
||||
|
||||
|
||||
/*
|
||||
* composeMatlab |
||||
* compose a Matlab function call |
||||
* This macro takes as input a Method object and composes |
||||
* a Matlab function call by inspecting the types and argument names |
||||
*/ |
||||
{% macro composeMatlab(fun) %} |
||||
{# ----------- Return type ------------- #} |
||||
{%- if fun|noutputs > 1 -%}[{% endif -%} |
||||
{%- if not fun.rtp|void -%}LVALUE{% endif -%} |
||||
{%- if not fun.rtp|void and fun|noutputs > 1 -%},{% endif -%} |
||||
{# ------------- Outputs ------------- -#} |
||||
{%- for arg in fun.req|outputs + fun.opt|outputs -%} |
||||
{{arg.name}} |
||||
{%- if arg.I -%}_out{%- endif -%} |
||||
{%- if not loop.last %}, {% endif %} |
||||
{% endfor %} |
||||
{%- if fun|noutputs > 1 -%}]{% endif -%} |
||||
{%- if fun|noutputs %} = {% endif -%} |
||||
cv.{{fun.name}}( |
||||
{#- ------------ Inputs -------------- -#} |
||||
{%- for arg in fun.req|inputs + fun.opt|inputs -%} |
||||
{{arg.name}} |
||||
{%- if arg.O -%}_in{%- endif -%} |
||||
{%- if not loop.last %}, {% endif -%} |
||||
{% endfor -%} |
||||
); |
||||
{%- endmacro %} |
||||
|
||||
|
||||
/*
|
||||
* composeVariant |
||||
* compose a variant call for the ArgumentParser |
||||
*/ |
||||
{% macro composeVariant(fun) %} |
||||
addVariant("{{ fun.name }}", {{ fun.req|inputs|length }}, {{ fun.opt|inputs|length }} |
||||
{%- if fun.opt|inputs|length %}, {% endif -%} |
||||
{%- for arg in fun.opt|inputs -%} |
||||
"{{arg.name}}" |
||||
{%- if not loop.last %}, {% endif -%} |
||||
{% endfor -%} |
||||
) |
||||
{%- endmacro %} |
||||
|
||||
|
||||
/*
|
||||
* composeWithExceptionHandler |
||||
* compose a function call wrapped in exception traps |
||||
* This macro takes an input a Method object and composes a function |
||||
* call through the compose() macro, then wraps the return in traps |
||||
* for cv::Exceptions, std::exceptions, and all generic exceptions |
||||
* and returns a useful error message to the Matlab interpreter |
||||
*/ |
||||
{%- macro composeWithExceptionHandler(fun) -%} |
||||
// call the opencv function
|
||||
// [out =] namespace.fun(src1, ..., srcn, dst1, ..., dstn, opt1, ..., optn);
|
||||
try { |
||||
{{ compose(fun) }} |
||||
} catch(cv::Exception& e) { |
||||
error(std::string("cv::exception caught: ").append(e.what()).c_str()); |
||||
} catch(std::exception& e) { |
||||
error(std::string("std::exception caught: ").append(e.what()).c_str()); |
||||
} catch(...) { |
||||
error("Uncaught exception occurred in {{fun.name}}"); |
||||
} |
||||
{%- endmacro %} |
||||
|
||||
|
||||
/*
|
||||
* handleInputs |
||||
* unpack input arguments from the Bridge |
||||
* Given an input Bridge object, this unpacks the object from the Bridge and |
||||
* casts them into the correct type |
||||
*/ |
||||
{%- macro handleInputs(fun) %} |
||||
|
||||
{% if fun|ninputs or (fun|noutputs and not fun.constructor) %} |
||||
// unpack the arguments
|
||||
{# ----------- Inputs ------------- #} |
||||
{% for arg in fun.req|inputs %} |
||||
{{arg.tp}} {{arg.name}} = inputs[{{ loop.index0 }}].to{{arg.tp|toUpperCamelCase}}(); |
||||
{% endfor %} |
||||
{% for opt in fun.opt|inputs %} |
||||
{{opt.tp}} {{opt.name}} = inputs[{{loop.index0 + fun.req|inputs|length}}].empty() ? ({{opt.tp}}) {% if opt.ref == '*' -%} {{opt.tp}}() {%- else -%} {{opt.default}} {%- endif %} : inputs[{{loop.index0 + fun.req|inputs|length}}].to{{opt.tp|toUpperCamelCase}}(); |
||||
{% endfor %} |
||||
{# ----------- Outputs ------------ #} |
||||
{% for arg in fun.req|only|outputs %} |
||||
{{arg.tp}} {{arg.name}}; |
||||
{% endfor %} |
||||
{% for opt in fun.opt|only|outputs %} |
||||
{{opt.tp}} {{opt.name}}; |
||||
{% endfor %} |
||||
{% if not fun.rtp|void and not fun.constructor %} |
||||
{{fun.rtp}} retval; |
||||
{% endif %} |
||||
{% endif %} |
||||
|
||||
{%- endmacro %} |
||||
|
||||
/*
|
||||
* handleOutputs |
||||
* pack outputs into the bridge |
||||
* Given a set of outputs, this methods assigns them into the bridge for |
||||
* return to the calling method |
||||
*/ |
||||
{%- macro handleOutputs(fun) %} |
||||
|
||||
{% if fun|noutputs %} |
||||
// assign the outputs into the bridge
|
||||
{% if not fun.rtp|void and not fun.constructor %} |
||||
outputs[0] = retval; |
||||
{% endif %} |
||||
{% for arg in fun.req|outputs %} |
||||
outputs[{{loop.index0 + fun.rtp|void|not}}] = {{arg.name}}; |
||||
{% endfor %} |
||||
{% for opt in fun.opt|outputs %} |
||||
outputs[{{loop.index0 + fun.rtp|void|not + fun.req|outputs|length}}] = {{opt.name}}; |
||||
{% endfor %} |
||||
{% endif %} |
||||
{%- endmacro %} |
@ -0,0 +1,41 @@ |
||||
function buildInformation() |
||||
%CV.BUILDINFORMATION display OpenCV Toolbox build information |
||||
% |
||||
% Call CV.BUILDINFORMATION() to get a printout of diagonstic information |
||||
% pertaining to your particular build of the OpenCV Toolbox. If you ever |
||||
% run into issues with the Toolbox, it is useful to submit this |
||||
% information alongside a bug report to the OpenCV team. |
||||
% |
||||
% Copyright {{ time.strftime("%Y", time.localtime()) }} The OpenCV Foundation |
||||
% |
||||
info = { |
||||
' ------------------------------------------------------------------------' |
||||
' <strong>OpenCV Toolbox</strong>' |
||||
' Build and diagnostic information' |
||||
' ------------------------------------------------------------------------' |
||||
'' |
||||
' <strong>Platform</strong>' |
||||
' OS: {{ build.os }}' |
||||
' Architecture: {{ build.arch[0] }}-bit {{ build.arch[1] }}' |
||||
' Compiler: {{ build.compiler | csv(' ') }}' |
||||
'' |
||||
' <strong>Matlab</strong>' |
||||
[' Version: ' version()] |
||||
[' Mex extension: ' mexext()] |
||||
' Architecture: {{ build.mex_arch }}' |
||||
' Mex path: {{ build.mex_script }}' |
||||
' Mex flags: {{ build.mex_opts | csv(' ') }}' |
||||
' CXX flags: {{ build.cxx_flags | csv(' ') | stripExtraSpaces | wordwrap(60, True, '\'\n\' ') }}' |
||||
'' |
||||
' <strong>OpenCV</strong>' |
||||
' Version: {{ build.opencv_version }}' |
||||
' Commit: {{ build.commit }}' |
||||
' Configuration: {{ build.configuration }}' |
||||
' Modules: {{ build.modules | csv | wordwrap(60, True, '\'\n\' ') }}' |
||||
'' |
||||
}; |
||||
|
||||
info = cellfun(@(x) [x '\n'], info, 'UniformOutput', false); |
||||
info = horzcat(info{:}); |
||||
fprintf(info); |
||||
end |
@ -0,0 +1,98 @@ |
||||
{% import 'functional.cpp' as functional %} |
||||
/*
|
||||
* file: {{clss.name}}Bridge.cpp |
||||
* author: A trusty code generator |
||||
* date: {{time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime())}} |
||||
* |
||||
* This file was autogenerated, do not modify. |
||||
* See LICENSE for full modification and redistribution details. |
||||
* Copyright {{time.strftime("%Y", time.localtime())}} The OpenCV Foundation |
||||
*/ |
||||
#include <mex.h> |
||||
#include <vector> |
||||
#include <string> |
||||
#include <opencv2/matlab/map.hpp> |
||||
#include <opencv2/matlab/bridge.hpp> |
||||
#include <opencv2/core.hpp> |
||||
using namespace cv; |
||||
using namespace matlab; |
||||
using namespace bridge; |
||||
|
||||
namespace { |
||||
|
||||
typedef std::vector<Bridge> (*)({{clss.name}}&, const std::vector<Bridge>&) MethodSignature; |
||||
|
||||
{% for function in clss.methods %} |
||||
|
||||
{% if function.constructor %} |
||||
// wrapper for {{function.name}}() constructor
|
||||
{{ function.clss }} {{function.name}}(const std::vector<Bridge>& inputs) { |
||||
{{ functional.handleInputs(function) }} |
||||
{{ functional.compose(function) }} |
||||
return obj; |
||||
} |
||||
{% else %} |
||||
// wrapper for {{function.name}}() method
|
||||
std::vector<Bridge> {{function.name}}({{clss.name}}& inst, const std::vector<Bridge>& inputs) { |
||||
std::vector<Bridge> outputs{% if function|noutputs %}({{function|noutputs}}){% endif %}; |
||||
{{ functional.handleInputs(function) }} |
||||
{{ functional.composeWithExceptionHandler(function) }} |
||||
{{ functional.handleOutputs(function) }} |
||||
return outputs; |
||||
} |
||||
{% endif %} |
||||
{% endfor %} |
||||
|
||||
Map<std::string, MethodSignature> createMethodMap() { |
||||
Map<std::string, MethodSignature> m; |
||||
{% for function in clss.methods %} |
||||
m["{{function.name}}"] = &{{function.name}}; |
||||
{% endfor %} |
||||
|
||||
return m; |
||||
} |
||||
static const Map<std::string, MethodSignature> methods = createMethodMap(); |
||||
|
||||
// map of created {{clss.name}} instances. Don't trust the user to keep them safe...
|
||||
static Map<void *, {{clss.name}}> instances; |
||||
|
||||
/*
|
||||
* {{ clss.name }} |
||||
* Gateway routine |
||||
* nlhs - number of return arguments |
||||
* plhs - pointers to return arguments |
||||
* nrhs - number of input arguments |
||||
* prhs - pointers to input arguments |
||||
*/ |
||||
void mexFunction(int nlhs, mxArray* plhs[], |
||||
int nrhs, const mxArray* prhs[]) { |
||||
|
||||
// parse the inputs
|
||||
Bridge method_name(prhs[0]); |
||||
|
||||
Bridge handle(prhs[1]); |
||||
std::vector<Bridge> brhs(prhs+2, prhs+nrhs); |
||||
|
||||
// retrieve the instance of interest
|
||||
try { |
||||
{{clss.name}}& inst = instances.at(handle.address()); |
||||
} catch (const std::out_of_range& e) { |
||||
mexErrMsgTxt("Invalid object instance provided"); |
||||
} |
||||
|
||||
// invoke the correct method on the data
|
||||
try { |
||||
std::vector<Bridge> blhs = (*methods.at(method_name))(inst, brhs); |
||||
} catch (const std::out_of_range& e) { |
||||
mexErrMsgTxt("Unknown method specified"); |
||||
} |
||||
|
||||
{% block postfun %} |
||||
{% endblock %} |
||||
|
||||
{% block cleanup %} |
||||
{% endblock %} |
||||
|
||||
} |
||||
|
||||
} // end namespace
|
@ -0,0 +1,31 @@ |
||||
% {{clss.name | upper}} |
||||
% Matlab handle class for OpenCV object classes |
||||
% |
||||
% This file was autogenerated, do not modify. |
||||
% See LICENSE for full modification and redistribution details. |
||||
% Copyright {{time.strftime("%Y", time.localtime())}} The OpenCV Foundation |
||||
classdef {{clss.name}} < handle |
||||
properties (SetAccess = private, Hidden = true) |
||||
ptr_ = 0; % handle to the underlying c++ clss instance |
||||
end |
||||
|
||||
methods |
||||
% constructor |
||||
function this = {{clss.name}}(varargin) |
||||
this.ptr_ = {{clss.name}}Bridge('new', varargin{:}); |
||||
end |
||||
|
||||
% destructor |
||||
function delete(this) |
||||
{{clss.name}}Bridge(this.ptr_, 'delete'); |
||||
end |
||||
|
||||
{% for function in clss.functions %} |
||||
% {{function.__str__()}} |
||||
function varargout = {{function.name}}(this, varargin) |
||||
[varargout{1:nargout}] = {{clss.name}}Bridge('{{function.name}}', this.ptr_, varargin{:}); |
||||
end |
||||
|
||||
{% endfor %} |
||||
end |
||||
end |
@ -0,0 +1,46 @@ |
||||
function mex(varargin) |
||||
%CV.MEX compile MEX-function with OpenCV linkages |
||||
% |
||||
% Usage: |
||||
% CV.MEX [options ...] file [file file ...] |
||||
% |
||||
% Description: |
||||
% CV.MEX compiles one or more C/C++ source files into a shared-library |
||||
% called a mex-file. This function is equivalent to the builtin MEX |
||||
% routine, with the notable exception that it automatically resolves |
||||
% OpenCV includes, and links in the OpenCV libraries where appropriate. |
||||
% It also forwards the flags used to build OpenCV, so architecture- |
||||
% specific optimizations can be used. |
||||
% |
||||
% CV.MEX is designed to be used in situations where the source(s) you |
||||
% are compiling contain OpenCV definitions. In such cases, it streamlines |
||||
% the finding and including of appropriate OpenCV libraries. |
||||
% |
||||
% See also: mex |
||||
% |
||||
% Copyright {{ time.strftime("%Y", time.localtime()) }} The OpenCV Foundation |
||||
% |
||||
|
||||
% forward the OpenCV build flags (C++ only) |
||||
EXTRA_FLAGS = ['"CXXFLAGS="\$CXXFLAGS '... |
||||
'{{ cv.flags | trim | wordwrap(60, false, '\'...\n \'') }}""']; |
||||
|
||||
% add the OpenCV include dirs |
||||
INCLUDE_DIRS = {{ cv.include_dirs | split | cellarray | wordwrap(60, false, '...\n ') }}; |
||||
|
||||
% add the lib dir (singular in both build tree and install tree) |
||||
LIB_DIR = '{{ cv.lib_dir }}'; |
||||
|
||||
% add the OpenCV libs. Only the used libs will actually be linked |
||||
LIBS = {{ cv.libs | split | cellarray | wordwrap(60, false, '...\n ') }}; |
||||
|
||||
% add the mex opts (usually at least -largeArrayDims) |
||||
OPTS = {{ cv.opts | split | cellarray | wordwrap(60, false, '...\n ') }}; |
||||
|
||||
% merge all of the default options (EXTRA_FLAGS, LIBS, etc) and the options |
||||
% and files passed by the user (varargin) into a single cell array |
||||
merged = [ {EXTRA_FLAGS}, INCLUDE_DIRS, {LIB_DIR}, LIBS, OPTS, varargin ]; |
||||
|
||||
% expand the merged argument list into the builtin mex utility |
||||
mex(merged{:}); |
||||
end |
@ -0,0 +1,62 @@ |
||||
{% import 'functional.cpp' as functional %} |
||||
{{ ('CV.' + fun.name | upper + ' ' + doc.brief | stripTags) | comment(75, '%') | matlabURL }} |
||||
% |
||||
% {{ functional.composeMatlab(fun) | upper }} |
||||
{% if doc.long %} |
||||
{{ doc.long | stripTags | qualify(fun.name) | comment(75, '% ') | matlabURL }} |
||||
{% endif %} |
||||
% |
||||
{# ----------------------- Returns --------------------- #} |
||||
{% if fun.rtp|void|not or fun.req|outputs|length or fun.opt|outputs|length %} |
||||
% Returns: |
||||
{% if fun.rtp|void|not %} |
||||
% LVALUE |
||||
{% endif %} |
||||
{% for arg in fun.req|outputs + fun.opt|outputs %} |
||||
{% set uname = arg.name | upper + ('_OUT' if arg.I else '') %} |
||||
{% if arg.name in doc.params %} |
||||
{{ (uname + ' ' + doc.params[arg.name]) | stripTags | comment(75, '% ') }} |
||||
{% else %} |
||||
{{ uname }} |
||||
{% endif %} |
||||
{% endfor %} |
||||
% |
||||
{% endif %} |
||||
{# ----------------- Required Inputs ------------------- #} |
||||
{% if fun.req|inputs|length %} |
||||
% Required Inputs: |
||||
{% for arg in fun.req|inputs %} |
||||
{% set uname = arg.name | upper + ('_IN' if arg.O else '') %} |
||||
{% if arg.name in doc.params %} |
||||
{{ (uname + ' ' + doc.params[arg.name]) | stripTags | comment(75, '% ') }} |
||||
{% else %} |
||||
{% endif %} |
||||
{% endfor %} |
||||
% |
||||
{% endif %} |
||||
{# ------------------ Optional Inputs ------------------- #} |
||||
{% if fun.opt|inputs|length %} |
||||
% Optional Inputs: |
||||
{% for arg in fun.opt|inputs %} |
||||
{% set uname = arg.name | upper + ('_IN' if arg.O else '') + ' (default: ' + arg.default + ')' %} |
||||
{% if arg.name in doc.params %} |
||||
{{ (uname + ' ' + doc.params[arg.name]) | stripTags | comment(75, '% ') }} |
||||
{% else %} |
||||
{{ uname }} |
||||
{% endif %} |
||||
{% endfor %} |
||||
% |
||||
{% endif %} |
||||
{# ---------------------- See also --------------------- #} |
||||
{% if 'seealso' in doc %} |
||||
% See also: {% for item in doc['seealso'] %} |
||||
cv.{{ item }}{% if not loop.last %}, {% endif %} |
||||
{% endfor %} |
||||
|
||||
% |
||||
{% endif %} |
||||
{# ----------------------- Online ---------------------- #} |
||||
{% set url = 'http://docs.opencv.org/modules/' + doc.module + '/doc/' + (doc.file|filename) + '.html#' + (fun.name|slugify) %} |
||||
% Online docs: {{ url | matlabURL }} |
||||
% Copyright {{ time.strftime("%Y", time.localtime()) }} The OpenCV Foundation |
||||
% |
@ -0,0 +1,60 @@ |
||||
{% import 'functional.cpp' as functional %} |
||||
/*
|
||||
* file: {{fun.name}}.cpp |
||||
* author: A trusty code generator |
||||
* date: {{time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime())}} |
||||
* |
||||
* This file was autogenerated, do not modify. |
||||
* See LICENSE for full modification and redistribution details. |
||||
* Copyright {{time.strftime("%Y", time.localtime())}} The OpenCV Foundation |
||||
*/ |
||||
#include <string> |
||||
#include <vector> |
||||
#include <cassert> |
||||
#include <exception> |
||||
#include <opencv2/matlab/bridge.hpp> |
||||
#include <opencv2/{{includes}}.hpp> |
||||
using namespace cv; |
||||
using namespace matlab; |
||||
using namespace bridge; |
||||
|
||||
/*
|
||||
* {{ fun.name }} |
||||
* {{ fun }} |
||||
* Gateway routine |
||||
* nlhs - number of return arguments |
||||
* plhs - pointers to return arguments |
||||
* nrhs - number of input arguments |
||||
* prhs - pointers to input arguments |
||||
*/ |
||||
void mexFunction(int nlhs, mxArray*{% if fun|noutputs %} plhs[]{% else %}*{% endif %}, |
||||
int nrhs, const mxArray*{% if fun|ninputs %} prhs[]{% else %}*{% endif %}) { |
||||
|
||||
{% if fun|ninputs %} |
||||
// parse the inputs
|
||||
ArgumentParser parser("{{fun.name}}"); |
||||
parser.{{ functional.composeVariant(fun) }}; |
||||
MxArrayVector sorted = parser.parse(MxArrayVector(prhs, prhs+nrhs)); |
||||
{% endif %} |
||||
|
||||
{% if fun|ninputs or fun|noutputs %} |
||||
// setup
|
||||
{% if fun|ninputs %} |
||||
BridgeVector inputs(sorted.begin(), sorted.end()); |
||||
{% endif -%} |
||||
{%- if fun|noutputs %} |
||||
BridgeVector outputs({{fun|noutputs}}); |
||||
{% endif %} |
||||
{% endif %} |
||||
|
||||
{{ functional.handleInputs(fun) }} |
||||
{{ functional.composeWithExceptionHandler(fun) }} |
||||
{{ functional.handleOutputs(fun) }} |
||||
|
||||
{% if fun|noutputs %} |
||||
// push the outputs back to matlab
|
||||
for (size_t n = 0; n < static_cast<size_t>(std::max(nlhs,1)); ++n) { |
||||
plhs[n] = outputs[n].toMxArray().releaseOwnership(); |
||||
} |
||||
{% endif %} |
||||
} |
@ -0,0 +1,71 @@ |
||||
% ------------------------------------------------------------------------ |
||||
% <strong>OpenCV Toolbox</strong> |
||||
% Matlab bindings for the OpenCV library |
||||
% ------------------------------------------------------------------------ |
||||
% |
||||
% The OpenCV Toolbox allows you to make calls to native OpenCV methods |
||||
% and classes directly from within Matlab. |
||||
% |
||||
% <strong>PATHS</strong> |
||||
% To call OpenCV methods from anywhere in your workspace, add the |
||||
% directory containing this file to the path: |
||||
% |
||||
% addpath(fileparts(which('cv'))); |
||||
% |
||||
% The OpenCV Toolbox contains two important locations: |
||||
% cv.m - This file, containing OpenCV enums |
||||
% +cv/ - The directory containing the OpenCV methods and classes |
||||
% |
||||
% <strong>CALLING SYNTAX</strong> |
||||
% To call an OpenCV method, class or enum, it must be prefixed with the |
||||
% 'cv' qualifier. For example: |
||||
% |
||||
% % perform a Fourier transform |
||||
% Xf = cv.dft(X, cv.DFT_COMPLEX_OUTPUT); |
||||
% |
||||
% % create a VideoCapture object, and open a file |
||||
% camera = cv.VideoCapture(); |
||||
% camera.open('/path/to/file'); |
||||
% |
||||
% You can specify optional arguments by name, similar to how python |
||||
% and many builtin Matlab functions work. For example, the cv.dft |
||||
% method used above has an optional 'nonzeroRows' argument. If |
||||
% you want to specify that, but keep the default 'flags' behaviour, |
||||
% simply call the method as: |
||||
% |
||||
% Xf = cv.dft(X, 'nonzeroRows', 7); |
||||
% |
||||
% <strong>HELP</strong> |
||||
% Each method has its own help file containing information about the |
||||
% arguments, return values, and what operation the method performs. |
||||
% You can access this help information by typing: |
||||
% |
||||
% help cv.methodName |
||||
% |
||||
% The full list of methods can be found by inspecting the +cv/ |
||||
% directory. Note that the methods available to you will depend |
||||
% on which modules you configured OpenCV to build. |
||||
% |
||||
% <strong>DIAGNOSTICS</strong> |
||||
% If you are having problems with the OpenCV Toolbox and need to send a |
||||
% bug report to the OpenCV team, you can get a printout of diagnostic |
||||
% information to submit along with your report by typing: |
||||
% |
||||
% <a href="matlab: cv.buildInformation()">cv.buildInformation();</a> |
||||
% |
||||
% <strong>OTHER RESOURCES</strong> |
||||
% OpenCV documentation online: <a href="matlab: web('http://docs.opencv.org', '-browser')">http://docs.opencv.org</a> |
||||
% OpenCV issue tracker: <a href="matlab: web('http://code.opencv.org', '-browser')">http://code.opencv.org</a> |
||||
% OpenCV Q&A: <a href="matlab: web('http://answers.opencv.org', '-browser')">http://answers.opencv.org</a> |
||||
% |
||||
% See also: cv.help, <a href="matlab: cv.buildInformation()">cv.buildInformation</a> |
||||
% |
||||
% Copyright {{ time.strftime("%Y", time.localtime()) }} The OpenCV Foundation |
||||
% |
||||
classdef cv |
||||
properties (Constant = true) |
||||
{% for key, val in constants.items() %} |
||||
{{key}} = {{val|formatMatlabConstant(constants)}}; |
||||
{% endfor %} |
||||
end |
||||
end |
@ -0,0 +1,616 @@ |
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this
|
||||
// license. If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2013, OpenCV Foundation, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote
|
||||
// products derived from this software without specific prior written
|
||||
// permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is"
|
||||
// and any express or implied warranties, including, but not limited to, the
|
||||
// implied warranties of merchantability and fitness for a particular purpose
|
||||
// are disclaimed. In no event shall the Intel Corporation or contributors be
|
||||
// liable for any direct, indirect, incidental, special, exemplary, or
|
||||
// consequential damages (including, but not limited to, procurement of
|
||||
// substitute goods or services; loss of use, data, or profits; or business
|
||||
// interruption) however caused and on any theory of liability, whether in
|
||||
// contract, strict liability, or tort (including negligence or otherwise)
|
||||
// arising in any way out of the use of this software, even if advised of the
|
||||
// possibility of such damage.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#ifndef OPENCV_BRIDGE_HPP_ |
||||
#define OPENCV_BRIDGE_HPP_ |
||||
|
||||
#include "mxarray.hpp" |
||||
#include <vector> |
||||
#include <string> |
||||
#include <opencv2/core.hpp> |
||||
#include <opencv2/imgproc.hpp> |
||||
#include <opencv2/calib3d.hpp> |
||||
#include <opencv2/photo.hpp> |
||||
#include <opencv2/video.hpp> |
||||
|
||||
namespace cv { |
||||
namespace bridge { |
||||
|
||||
/*
|
||||
* Custom typedefs |
||||
* Parsed names from the hdr_parser |
||||
*/ |
||||
typedef std::vector<cv::Mat> vector_Mat; |
||||
typedef std::vector<cv::Point> vector_Point; |
||||
typedef std::vector<int> vector_int; |
||||
typedef std::vector<float> vector_float; |
||||
typedef std::vector<cv::String> vector_String; |
||||
typedef std::vector<unsigned char> vector_uchar; |
||||
typedef std::vector<std::vector<char> > vector_vector_char; |
||||
typedef std::vector<std::vector<cv::DMatch> > vector_vector_DMatch; |
||||
typedef std::vector<cv::Rect> vector_Rect; |
||||
typedef std::vector<cv::KeyPoint> vector_KeyPoint; |
||||
typedef cv::Ptr<cv::StereoBM> Ptr_StereoBM; |
||||
typedef cv::Ptr<cv::StereoSGBM> Ptr_StereoSGBM; |
||||
typedef cv::Ptr<cv::FeatureDetector> Ptr_FeatureDetector; |
||||
typedef cv::Ptr<CLAHE> Ptr_CLAHE; |
||||
typedef cv::Ptr<LineSegmentDetector> Ptr_LineSegmentDetector; |
||||
typedef cv::Ptr<AlignMTB> Ptr_AlignMTB; |
||||
typedef cv::Ptr<CalibrateDebevec> Ptr_CalibrateDebevec; |
||||
typedef cv::Ptr<CalibrateRobertson> Ptr_CalibrateRobertson; |
||||
typedef cv::Ptr<DenseOpticalFlow> Ptr_DenseOpticalFlow; |
||||
typedef cv::Ptr<MergeDebevec> Ptr_MergeDebevec; |
||||
typedef cv::Ptr<MergeMertens> Ptr_MergeMertens; |
||||
typedef cv::Ptr<MergeRobertson> Ptr_MergeRobertson; |
||||
typedef cv::Ptr<Tonemap> Ptr_Tonemap; |
||||
typedef cv::Ptr<TonemapDrago> Ptr_TonemapDrago; |
||||
typedef cv::Ptr<TonemapDurand> Ptr_TonemapDurand; |
||||
typedef cv::Ptr<TonemapMantiuk> Ptr_TonemapMantiuk; |
||||
typedef cv::Ptr<TonemapReinhard> Ptr_TonemapReinhard; |
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// PREDECLARATIONS
|
||||
// ----------------------------------------------------------------------------
|
||||
class Bridge; |
||||
typedef std::vector<Bridge> BridgeVector; |
||||
|
||||
template <typename InputScalar, typename OutputScalar> |
||||
void deepCopyAndTranspose(const cv::Mat& src, matlab::MxArray& dst); |
||||
|
||||
template <typename InputScalar, typename OutputScalar> |
||||
void deepCopyAndTranspose(const matlab::MxArray& src, cv::Mat& dst); |
||||
|
||||
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// BRIDGE
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/*!
|
||||
* @class Bridge |
||||
* @brief Type conversion class for converting OpenCV and native C++ types |
||||
* |
||||
* Bridge provides an interface for converting between OpenCV/C++ types |
||||
* to Matlab's mxArray format. |
||||
* |
||||
* Each type conversion requires three operators: |
||||
* // conversion from ObjectType --> Bridge
|
||||
* Bridge& operator=(const ObjectType&); |
||||
* // implicit conversion from Bridge --> ObjectType
|
||||
* operator ObjectType(); |
||||
* // explicit conversion from Bridge --> ObjectType
|
||||
* ObjectType toObjectType(); |
||||
* |
||||
* The bridging class provides common conversions between OpenCV types, |
||||
* std and stl types to Matlab's mxArray format. By inheriting Bridge, |
||||
* you can add your own custom type conversions. |
||||
* |
||||
* Because Matlab uses a homogeneous storage type, all operations are provided |
||||
* relative to Matlab's type. That is, Bridge always stores an matlab::MxArray object |
||||
* and converts to and from other object types on demand. |
||||
* |
||||
* NOTE: for the explicit conversion function, the object name must be |
||||
* in UpperCamelCase, for example: |
||||
* int --> toInt |
||||
* my_object --> MyObject |
||||
* my_Object --> MyObject |
||||
* myObject --> MyObject |
||||
* this is because the binding generator standardises the calling syntax. |
||||
* |
||||
* Bridge attempts to make as few assumptions as possible, however in |
||||
* some cases where 1-to-1 mappings don't exist, some assumptions are necessary. |
||||
* In particular: |
||||
* - conversion from of a 2-channel Mat to an mxArray will result in a complex |
||||
* output |
||||
* - conversion from multi-channel interleaved Mats will result in |
||||
* multichannel planar mxArrays |
||||
* |
||||
*/ |
||||
class Bridge { |
||||
private: |
||||
matlab::MxArray ptr_; |
||||
public: |
||||
// bridges are default constructible
|
||||
Bridge() {} |
||||
virtual ~Bridge() {} |
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Bridge Properties
|
||||
// --------------------------------------------------------------------------
|
||||
bool empty() const { return ptr_.empty(); } |
||||
|
||||
/*! @brief unpack an object from Matlab into C++
|
||||
* |
||||
* this function checks whether the given bridge is derived from an |
||||
* object in Matlab. If so, it converts it to a (platform dependent) |
||||
* pointer to the underlying C++ object. |
||||
* |
||||
* NOTE! This function assumes that the C++ pointer is stored in inst_ |
||||
*/ |
||||
template <typename Object> |
||||
Object* getObjectByName(const std::string& name) { |
||||
// check that the object is actually of correct type before unpacking
|
||||
// TODO: Traverse class hierarchy?
|
||||
if (!ptr_.isClass(name)) { |
||||
matlab::error(std::string("Expected class ").append(std::string(name)) |
||||
.append(" but was given ").append(ptr_.className())); |
||||
} |
||||
// get the instance field
|
||||
matlab::MxArray inst = ptr_.field("inst_"); |
||||
Object* obj = NULL; |
||||
// make sure the pointer is the correct size for the system
|
||||
if (sizeof(void *) == 8 && inst.ID() == mxUINT64_CLASS) { |
||||
// 64-bit pointers
|
||||
// TODO: Do we REALLY REALLY need to reinterpret_cast?
|
||||
obj = reinterpret_cast<Object *>(inst.scalar<uint64_t>()); |
||||
} else if (sizeof(void *) == 4 && inst.ID() == mxUINT32_CLASS) { |
||||
// 32-bit pointers
|
||||
obj = reinterpret_cast<Object *>(inst.scalar<uint32_t>()); |
||||
} else { |
||||
matlab::error("Incorrect pointer type stored for architecture"); |
||||
} |
||||
|
||||
// finally check if the object is NULL
|
||||
matlab::conditionalError(obj, std::string("Object ").append(std::string(name)).append(std::string(" is NULL"))); |
||||
return obj; |
||||
} |
||||
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// MATLAB TYPES
|
||||
// --------------------------------------------------------------------------
|
||||
Bridge& operator=(const mxArray* obj) { ptr_ = obj; return *this; } |
||||
Bridge& operator=(const matlab::MxArray& obj) { ptr_ = obj; return *this; } |
||||
Bridge(const matlab::MxArray& obj) : ptr_(obj) {} |
||||
Bridge(const mxArray* obj) : ptr_(obj) {} |
||||
matlab::MxArray toMxArray() { return ptr_; } |
||||
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// MATRIX CONVERSIONS
|
||||
// --------------------------------------------------------------------------
|
||||
Bridge& operator=(const cv::Mat& mat); |
||||
cv::Mat toMat() const; |
||||
operator cv::Mat() const { return toMat(); } |
||||
|
||||
template <typename Scalar> |
||||
static matlab::MxArray FromMat(const cv::Mat& mat) { |
||||
matlab::MxArray arr(mat.rows, mat.cols, mat.channels(), matlab::Traits<Scalar>::ScalarType); |
||||
switch (mat.depth()) { |
||||
case CV_8U: deepCopyAndTranspose<uint8_t, Scalar>(mat, arr); break; |
||||
case CV_8S: deepCopyAndTranspose<int8_t, Scalar>(mat, arr); break; |
||||
case CV_16U: deepCopyAndTranspose<uint16_t, Scalar>(mat, arr); break; |
||||
case CV_16S: deepCopyAndTranspose<int16_t, Scalar>(mat, arr); break; |
||||
case CV_32S: deepCopyAndTranspose<int32_t, Scalar>(mat, arr); break; |
||||
case CV_32F: deepCopyAndTranspose<float, Scalar>(mat, arr); break; |
||||
case CV_64F: deepCopyAndTranspose<double, Scalar>(mat, arr); break; |
||||
default: matlab::error("Attempted to convert from unknown class"); |
||||
} |
||||
return arr; |
||||
} |
||||
|
||||
template <typename Scalar> |
||||
cv::Mat toMat() const { |
||||
cv::Mat mat(ptr_.rows(), ptr_.cols(), CV_MAKETYPE(cv::DataType<Scalar>::type, ptr_.channels())); |
||||
switch (ptr_.ID()) { |
||||
case mxINT8_CLASS: deepCopyAndTranspose<int8_t, Scalar>(ptr_, mat); break; |
||||
case mxUINT8_CLASS: deepCopyAndTranspose<uint8_t, Scalar>(ptr_, mat); break; |
||||
case mxINT16_CLASS: deepCopyAndTranspose<int16_t, Scalar>(ptr_, mat); break; |
||||
case mxUINT16_CLASS: deepCopyAndTranspose<uint16_t, Scalar>(ptr_, mat); break; |
||||
case mxINT32_CLASS: deepCopyAndTranspose<int32_t, Scalar>(ptr_, mat); break; |
||||
case mxUINT32_CLASS: deepCopyAndTranspose<uint32_t, Scalar>(ptr_, mat); break; |
||||
case mxINT64_CLASS: deepCopyAndTranspose<int64_t, Scalar>(ptr_, mat); break; |
||||
case mxUINT64_CLASS: deepCopyAndTranspose<uint64_t, Scalar>(ptr_, mat); break; |
||||
case mxSINGLE_CLASS: deepCopyAndTranspose<float, Scalar>(ptr_, mat); break; |
||||
case mxDOUBLE_CLASS: deepCopyAndTranspose<double, Scalar>(ptr_, mat); break; |
||||
case mxCHAR_CLASS: deepCopyAndTranspose<char, Scalar>(ptr_, mat); break; |
||||
case mxLOGICAL_CLASS: deepCopyAndTranspose<int8_t, Scalar>(ptr_, mat); break; |
||||
default: matlab::error("Attempted to convert from unknown class"); |
||||
} |
||||
return mat; |
||||
} |
||||
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// INTEGRAL TYPES
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
// --------------------------- string --------------------------------------
|
||||
Bridge& operator=(const std::string& ) { return *this; } |
||||
std::string toString() { |
||||
return ptr_.toString(); |
||||
} |
||||
operator std::string() { return toString(); } |
||||
|
||||
// --------------------------- bool --------------------------------------
|
||||
Bridge& operator=(const bool& ) { return *this; } |
||||
bool toBool() { return 0; } |
||||
operator bool() { return toBool(); } |
||||
|
||||
// --------------------------- double --------------------------------------
|
||||
Bridge& operator=(const double& ) { return *this; } |
||||
double toDouble() { return ptr_.scalar<double>(); } |
||||
operator double() { return toDouble(); } |
||||
|
||||
// --------------------------- float ---------------------------------------
|
||||
Bridge& operator=(const float& ) { return *this; } |
||||
float toFloat() { return ptr_.scalar<float>(); } |
||||
operator float() { return toFloat(); } |
||||
|
||||
// --------------------------- int --------------------------------------
|
||||
Bridge& operator=(const int& ) { return *this; } |
||||
int toInt() { return ptr_.scalar<int>(); } |
||||
operator int() { return toInt(); } |
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// CORE OPENCV TYPES
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
// -------------------------- Point --------------------------------------
|
||||
Bridge& operator=(const cv::Point& ) { return *this; } |
||||
cv::Point toPoint() const { return cv::Point(); } |
||||
operator cv::Point() const { return toPoint(); } |
||||
|
||||
// -------------------------- Point2f ------------------------------------
|
||||
Bridge& operator=(const cv::Point2f& ) { return *this; } |
||||
cv::Point2f toPoint2f() const { return cv::Point2f(); } |
||||
operator cv::Point2f() const { return toPoint2f(); } |
||||
|
||||
// -------------------------- Point2d ------------------------------------
|
||||
Bridge& operator=(const cv::Point2d& ) { return *this; } |
||||
cv::Point2d toPoint2d() const { return cv::Point2d(); } |
||||
operator cv::Point2d() const { return toPoint2d(); } |
||||
|
||||
// -------------------------- Size ---------------------------------------
|
||||
Bridge& operator=(const cv::Size& ) { return *this; } |
||||
cv::Size toSize() const { return cv::Size(); } |
||||
operator cv::Size() const { return toSize(); } |
||||
|
||||
// -------------------------- Moments --------------------------------------
|
||||
Bridge& operator=(const cv::Moments& ) { return *this; } |
||||
cv::Moments toMoments() const { return cv::Moments(); } |
||||
operator cv::Moments() const { return toMoments(); } |
||||
|
||||
// -------------------------- Scalar --------------------------------------
|
||||
Bridge& operator=(const cv::Scalar& ) { return *this; } |
||||
cv::Scalar toScalar() { return cv::Scalar(); } |
||||
operator cv::Scalar() { return toScalar(); } |
||||
|
||||
// -------------------------- Rect -----------------------------------------
|
||||
Bridge& operator=(const cv::Rect& ) { return *this; } |
||||
cv::Rect toRect() { return cv::Rect(); } |
||||
operator cv::Rect() { return toRect(); } |
||||
|
||||
// ---------------------- RotatedRect ---------------------------------------
|
||||
Bridge& operator=(const cv::RotatedRect& ) { return *this; } |
||||
cv::RotatedRect toRotatedRect() { return cv::RotatedRect(); } |
||||
operator cv::RotatedRect() { return toRotatedRect(); } |
||||
|
||||
// ---------------------- TermCriteria --------------------------------------
|
||||
Bridge& operator=(const cv::TermCriteria& ) { return *this; } |
||||
cv::TermCriteria toTermCriteria() { return cv::TermCriteria(); } |
||||
operator cv::TermCriteria() { return toTermCriteria(); } |
||||
|
||||
// ---------------------- RNG --------------------------------------
|
||||
Bridge& operator=(const cv::RNG& ) { return *this; } |
||||
/*! @brief explicit conversion to cv::RNG()
|
||||
* |
||||
* Converts a bridge object to a cv::RNG(). We explicitly assert that |
||||
* the object is an RNG in matlab space before attempting to deference |
||||
* its pointer |
||||
*/ |
||||
cv::RNG toRNG() { |
||||
return (*getObjectByName<cv::RNG>("RNG")); |
||||
} |
||||
operator cv::RNG() { return toRNG(); } |
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// OPENCV VECTOR TYPES
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
// -------------------- vector_Mat ------------------------------------------
|
||||
Bridge& operator=(const vector_Mat& ) { return *this; } |
||||
vector_Mat toVectorMat() { return vector_Mat(); } |
||||
operator vector_Mat() { return toVectorMat(); } |
||||
|
||||
// --------------------------- vector_int ----------------------------------
|
||||
Bridge& operator=(const vector_int& ) { return *this; } |
||||
vector_int toVectorInt() { return vector_int(); } |
||||
operator vector_int() { return toVectorInt(); } |
||||
|
||||
// --------------------------- vector_float --------------------------------
|
||||
Bridge& operator=(const vector_float& ) { return *this; } |
||||
vector_float toVectorFloat() { return vector_float(); } |
||||
operator vector_float() { return toVectorFloat(); } |
||||
|
||||
// --------------------------- vector_Rect ---------------------------------
|
||||
Bridge& operator=(const vector_Rect& ) { return *this; } |
||||
vector_Rect toVectorRect() { return vector_Rect(); } |
||||
operator vector_Rect() { return toVectorRect(); } |
||||
|
||||
// --------------------------- vector_KeyPoint -----------------------------
|
||||
Bridge& operator=(const vector_KeyPoint& ) { return *this; } |
||||
vector_KeyPoint toVectorKeyPoint() { return vector_KeyPoint(); } |
||||
operator vector_KeyPoint() { return toVectorKeyPoint(); } |
||||
|
||||
// --------------------------- vector_String -------------------------------
|
||||
Bridge& operator=(const vector_String& ) { return *this; } |
||||
vector_String toVectorString() { return vector_String(); } |
||||
operator vector_String() { return toVectorString(); } |
||||
|
||||
// ------------------------ vector_Point ------------------------------------
|
||||
Bridge& operator=(const vector_Point& ) { return *this; } |
||||
vector_Point toVectorPoint() { return vector_Point(); } |
||||
operator vector_Point() { return toVectorPoint(); } |
||||
|
||||
// ------------------------ vector_uchar ------------------------------------
|
||||
Bridge& operator=(const vector_uchar& ) { return *this; } |
||||
vector_uchar toVectorUchar() { return vector_uchar(); } |
||||
operator vector_uchar() { return toVectorUchar(); } |
||||
|
||||
// ------------------------ vector_vector_char ------------------------------
|
||||
Bridge& operator=(const vector_vector_char& ) { return *this; } |
||||
vector_vector_char toVectorVectorChar() { return vector_vector_char(); } |
||||
operator vector_vector_char() { return toVectorVectorChar(); } |
||||
|
||||
// ------------------------ vector_vector_DMatch ---------------------------
|
||||
Bridge& operator=(const vector_vector_DMatch& ) { return *this; } |
||||
vector_vector_DMatch toVectorVectorDMatch() { return vector_vector_DMatch(); } |
||||
operator vector_vector_DMatch() { return toVectorVectorDMatch(); } |
||||
|
||||
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// OPENCV COMPOUND TYPES
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
// --------------------------- Ptr_StereoBM -----------------------------
|
||||
Bridge& operator=(const Ptr_StereoBM& ) { return *this; } |
||||
Ptr_StereoBM toPtrStereoBM() { return Ptr_StereoBM(); } |
||||
operator Ptr_StereoBM() { return toPtrStereoBM(); } |
||||
|
||||
// --------------------------- Ptr_StereoSGBM ---------------------------
|
||||
Bridge& operator=(const Ptr_StereoSGBM& ) { return *this; } |
||||
Ptr_StereoSGBM toPtrStereoSGBM() { return Ptr_StereoSGBM(); } |
||||
operator Ptr_StereoSGBM() { return toPtrStereoSGBM(); } |
||||
|
||||
// --------------------------- Ptr_FeatureDetector ----------------------
|
||||
Bridge& operator=(const Ptr_FeatureDetector& ) { return *this; } |
||||
Ptr_FeatureDetector toPtrFeatureDetector() { return Ptr_FeatureDetector(); } |
||||
operator Ptr_FeatureDetector() { return toPtrFeatureDetector(); } |
||||
|
||||
// --------------------------- Ptr_CLAHE --------------------------------
|
||||
Bridge& operator=(const Ptr_CLAHE& ) { return *this; } |
||||
Ptr_CLAHE toPtrCLAHE() { return Ptr_CLAHE(); } |
||||
operator Ptr_CLAHE() { return toPtrCLAHE(); } |
||||
|
||||
// --------------------------- Ptr_LineSegmentDetector ------------------
|
||||
Bridge& operator=(const Ptr_LineSegmentDetector& ) { return *this; } |
||||
Ptr_LineSegmentDetector toPtrLineSegmentDetector() { return Ptr_LineSegmentDetector(); } |
||||
operator Ptr_LineSegmentDetector() { return toPtrLineSegmentDetector(); } |
||||
|
||||
// --------------------------- Ptr_AlignMTB -----------------------------
|
||||
Bridge& operator=(const Ptr_AlignMTB& ) { return *this; } |
||||
Ptr_AlignMTB toPtrAlignMTB() { return Ptr_AlignMTB(); } |
||||
operator Ptr_AlignMTB() { return toPtrAlignMTB(); } |
||||
|
||||
// --------------------------- Ptr_CalibrateDebevec -------------------
|
||||
Bridge& operator=(const Ptr_CalibrateDebevec& ) { return *this; } |
||||
Ptr_CalibrateDebevec toPtrCalibrateDebevec() { return Ptr_CalibrateDebevec(); } |
||||
operator Ptr_CalibrateDebevec() { return toPtrCalibrateDebevec(); } |
||||
|
||||
// --------------------------- Ptr_CalibrateRobertson -------------------
|
||||
Bridge& operator=(const Ptr_CalibrateRobertson& ) { return *this; } |
||||
Ptr_CalibrateRobertson toPtrCalibrateRobertson() { return Ptr_CalibrateRobertson(); } |
||||
operator Ptr_CalibrateRobertson() { return toPtrCalibrateRobertson(); } |
||||
|
||||
// --------------------------- Ptr_DenseOpticalFlow -------------------
|
||||
Bridge& operator=(const Ptr_DenseOpticalFlow& ) { return *this; } |
||||
Ptr_DenseOpticalFlow toPtrDenseOpticalFlow() { return Ptr_DenseOpticalFlow(); } |
||||
operator Ptr_DenseOpticalFlow() { return toPtrDenseOpticalFlow(); } |
||||
|
||||
// --------------------------- Ptr_MergeDebevec -----------------------
|
||||
Bridge& operator=(const Ptr_MergeDebevec& ) { return *this; } |
||||
Ptr_MergeDebevec toPtrMergeDebevec() { return Ptr_MergeDebevec(); } |
||||
operator Ptr_MergeDebevec() { return toPtrMergeDebevec(); } |
||||
|
||||
// --------------------------- Ptr_MergeMertens -----------------------
|
||||
Bridge& operator=(const Ptr_MergeMertens& ) { return *this; } |
||||
Ptr_MergeMertens toPtrMergeMertens() { return Ptr_MergeMertens(); } |
||||
operator Ptr_MergeMertens() { return toPtrMergeMertens(); } |
||||
|
||||
// --------------------------- Ptr_MergeRobertson -----------------------
|
||||
Bridge& operator=(const Ptr_MergeRobertson& ) { return *this; } |
||||
Ptr_MergeRobertson toPtrMergeRobertson() { return Ptr_MergeRobertson(); } |
||||
operator Ptr_MergeRobertson() { return toPtrMergeRobertson(); } |
||||
|
||||
// --------------------------- Ptr_Tonemap ------------------------------
|
||||
Bridge& operator=(const Ptr_Tonemap& ) { return *this; } |
||||
Ptr_Tonemap toPtrTonemap() { return Ptr_Tonemap(); } |
||||
operator Ptr_Tonemap() { return toPtrTonemap(); } |
||||
|
||||
// --------------------------- Ptr_TonemapDrago -------------------------
|
||||
Bridge& operator=(const Ptr_TonemapDrago& ) { return *this; } |
||||
Ptr_TonemapDrago toPtrTonemapDrago() { return Ptr_TonemapDrago(); } |
||||
operator Ptr_TonemapDrago() { return toPtrTonemapDrago(); } |
||||
|
||||
// --------------------------- Ptr_TonemapDurand ------------------------
|
||||
Bridge& operator=(const Ptr_TonemapDurand& ) { return *this; } |
||||
Ptr_TonemapDurand toPtrTonemapDurand() { return Ptr_TonemapDurand(); } |
||||
operator Ptr_TonemapDurand() { return toPtrTonemapDurand(); } |
||||
|
||||
// --------------------------- Ptr_TonemapMantiuk -----------------------
|
||||
Bridge& operator=(const Ptr_TonemapMantiuk& ) { return *this; } |
||||
Ptr_TonemapMantiuk toPtrTonemapMantiuk() { return Ptr_TonemapMantiuk(); } |
||||
operator Ptr_TonemapMantiuk() { return toPtrTonemapMantiuk(); } |
||||
|
||||
// --------------------------- Ptr_TonemapReinhard ----------------------
|
||||
Bridge& operator=(const Ptr_TonemapReinhard& ) { return *this; } |
||||
Ptr_TonemapReinhard toPtrTonemapReinhard() { return Ptr_TonemapReinhard(); } |
||||
operator Ptr_TonemapReinhard() { return toPtrTonemapReinhard(); } |
||||
}; // class Bridge
|
||||
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// SPECIALIZATIONS
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
/*!
|
||||
* @brief template specialization for inheriting types |
||||
* |
||||
* This template specialization attempts to preserve the best mapping |
||||
* between OpenCV and Matlab types. Matlab uses double types almost universally, so |
||||
* all floating float types are converted to doubles. |
||||
* Unfortunately OpenCV does not have a native logical type, so |
||||
* that gets mapped to an unsigned 8-bit value |
||||
*/ |
||||
template <> |
||||
matlab::MxArray Bridge::FromMat<matlab::InheritType>(const cv::Mat& mat) { |
||||
switch (mat.depth()) { |
||||
case CV_8U: return FromMat<uint8_t>(mat); |
||||
case CV_8S: return FromMat<int8_t>(mat); |
||||
case CV_16U: return FromMat<uint16_t>(mat); |
||||
case CV_16S: return FromMat<int16_t>(mat); |
||||
case CV_32S: return FromMat<int32_t>(mat); |
||||
case CV_32F: return FromMat<double>(mat); //NOTE: Matlab uses double as native type!
|
||||
case CV_64F: return FromMat<double>(mat); |
||||
default: matlab::error("Attempted to convert from unknown class"); |
||||
} |
||||
return matlab::MxArray(); |
||||
} |
||||
|
||||
/*!
|
||||
* @brief template specialization for inheriting types |
||||
* |
||||
* This template specialization attempts to preserve the best mapping |
||||
* between Matlab and OpenCV types. OpenCV has poor support for double precision |
||||
* types, so all floating point types are cast to float. Logicals get cast |
||||
* to unsignd 8-bit value. |
||||
*/ |
||||
template <> |
||||
cv::Mat Bridge::toMat<matlab::InheritType>() const { |
||||
switch (ptr_.ID()) { |
||||
case mxINT8_CLASS: return toMat<int8_t>(); |
||||
case mxUINT8_CLASS: return toMat<uint8_t>(); |
||||
case mxINT16_CLASS: return toMat<int16_t>(); |
||||
case mxUINT16_CLASS: return toMat<uint16_t>(); |
||||
case mxINT32_CLASS: return toMat<int32_t>(); |
||||
case mxUINT32_CLASS: return toMat<int32_t>(); |
||||
case mxINT64_CLASS: return toMat<int64_t>(); |
||||
case mxUINT64_CLASS: return toMat<int64_t>(); |
||||
case mxSINGLE_CLASS: return toMat<float>(); |
||||
case mxDOUBLE_CLASS: return toMat<float>(); //NOTE: OpenCV uses float as native type!
|
||||
case mxCHAR_CLASS: return toMat<int8_t>(); |
||||
case mxLOGICAL_CLASS: return toMat<int8_t>(); |
||||
default: matlab::error("Attempted to convert from unknown class"); |
||||
} |
||||
return cv::Mat(); |
||||
} |
||||
|
||||
Bridge& Bridge::operator=(const cv::Mat& mat) { ptr_ = FromMat<matlab::InheritType>(mat); return *this; } |
||||
cv::Mat Bridge::toMat() const { return toMat<matlab::InheritType>(); } |
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// MATRIX TRANSPOSE
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
template <typename InputScalar, typename OutputScalar> |
||||
void deepCopyAndTranspose(const cv::Mat& in, matlab::MxArray& out) { |
||||
matlab::conditionalError(static_cast<size_t>(in.rows) == out.rows(), "Matrices must have the same number of rows"); |
||||
matlab::conditionalError(static_cast<size_t>(in.cols) == out.cols(), "Matrices must have the same number of cols"); |
||||
matlab::conditionalError(static_cast<size_t>(in.channels()) == out.channels(), "Matrices must have the same number of channels"); |
||||
std::vector<cv::Mat> channels; |
||||
cv::split(in, channels); |
||||
for (size_t c = 0; c < out.channels(); ++c) { |
||||
cv::transpose(channels[c], channels[c]); |
||||
cv::Mat outmat(out.cols(), out.rows(), cv::DataType<OutputScalar>::type, |
||||
static_cast<void *>(out.real<OutputScalar>() + out.cols()*out.rows()*c)); |
||||
channels[c].convertTo(outmat, cv::DataType<OutputScalar>::type); |
||||
} |
||||
|
||||
//const InputScalar* inp = in.ptr<InputScalar>(0);
|
||||
//OutputScalar* outp = out.real<OutputScalar>();
|
||||
//gemt('R', out.rows(), out.cols(), inp, in.step1(), outp, out.rows());
|
||||
} |
||||
|
||||
template <typename InputScalar, typename OutputScalar> |
||||
void deepCopyAndTranspose(const matlab::MxArray& in, cv::Mat& out) { |
||||
matlab::conditionalError(in.rows() == static_cast<size_t>(out.rows), "Matrices must have the same number of rows"); |
||||
matlab::conditionalError(in.cols() == static_cast<size_t>(out.cols), "Matrices must have the same number of cols"); |
||||
matlab::conditionalError(in.channels() == static_cast<size_t>(out.channels()), "Matrices must have the same number of channels"); |
||||
std::vector<cv::Mat> channels; |
||||
for (size_t c = 0; c < in.channels(); ++c) { |
||||
cv::Mat outmat; |
||||
cv::Mat inmat(in.cols(), in.rows(), cv::DataType<InputScalar>::type, |
||||
static_cast<void *>(const_cast<InputScalar *>(in.real<InputScalar>() + in.cols()*in.rows()*c))); |
||||
inmat.convertTo(outmat, cv::DataType<OutputScalar>::type); |
||||
cv::transpose(outmat, outmat); |
||||
channels.push_back(outmat); |
||||
} |
||||
cv::merge(channels, out); |
||||
|
||||
//const InputScalar* inp = in.real<InputScalar>();
|
||||
//OutputScalar* outp = out.ptr<OutputScalar>(0);
|
||||
//gemt('C', in.rows(), in.cols(), inp, in.rows(), outp, out.step1());
|
||||
} |
||||
|
||||
|
||||
|
||||
} // namespace bridge
|
||||
} // namespace cv
|
||||
|
||||
#endif |
@ -0,0 +1,91 @@ |
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this
|
||||
// license. If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2013, OpenCV Foundation, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote
|
||||
// products derived from this software without specific prior written
|
||||
// permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is"
|
||||
// and any express or implied warranties, including, but not limited to, the
|
||||
// implied warranties of merchantability and fitness for a particular purpose
|
||||
// are disclaimed. In no event shall the Intel Corporation or contributors be
|
||||
// liable for any direct, indirect, incidental, special, exemplary, or
|
||||
// consequential damages (including, but not limited to, procurement of
|
||||
// substitute goods or services; loss of use, data, or profits; or business
|
||||
// interruption) however caused and on any theory of liability, whether in
|
||||
// contract, strict liability, or tort (including negligence or otherwise)
|
||||
// arising in any way out of the use of this software, even if advised of the
|
||||
// possibility of such damage.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#ifndef OPENCV_MAP_HPP_ |
||||
#define OPENCV_MAP_HPP_ |
||||
|
||||
namespace matlab { |
||||
#if __cplusplus >= 201103L |
||||
|
||||
// If we have C++11 support, we just want to use unordered_map
|
||||
#include <unordered_map> |
||||
template <typename KeyType, typename ValueType> |
||||
using Map = std::unordered_map<KeyType, ValueType>; |
||||
|
||||
#else |
||||
|
||||
// If we don't have C++11 support, we wrap another map implementation
|
||||
// in the same public API as unordered_map
|
||||
#include <map> |
||||
#include <stdexcept> |
||||
|
||||
template <typename KeyType, typename ValueType> |
||||
class Map { |
||||
private: |
||||
std::map<KeyType, ValueType> map_; |
||||
public: |
||||
// map[key] = val;
|
||||
ValueType& operator[] (const KeyType& k) { |
||||
return map_[k]; |
||||
} |
||||
|
||||
// map.at(key) = val (throws)
|
||||
ValueType& at(const KeyType& k) { |
||||
typename std::map<KeyType, ValueType>::iterator it; |
||||
it = map_.find(k); |
||||
if (it == map_.end()) throw std::out_of_range("Key not found"); |
||||
return *it; |
||||
} |
||||
|
||||
// val = map.at(key) (throws, const)
|
||||
const ValueType& at(const KeyType& k) const { |
||||
typename std::map<KeyType, ValueType>::const_iterator it; |
||||
it = map_.find(k); |
||||
if (it == map_.end()) throw std::out_of_range("Key not found"); |
||||
return *it; |
||||
} |
||||
}; |
||||
|
||||
} // namespace matlab
|
||||
|
||||
#endif |
||||
#endif |
@ -0,0 +1,684 @@ |
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this
|
||||
// license. If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2013, OpenCV Foundation, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote
|
||||
// products derived from this software without specific prior written
|
||||
// permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is"
|
||||
// and any express or implied warranties, including, but not limited to, the
|
||||
// implied warranties of merchantability and fitness for a particular purpose
|
||||
// are disclaimed. In no event shall the Intel Corporation or contributors be
|
||||
// liable for any direct, indirect, incidental, special, exemplary, or
|
||||
// consequential damages (including, but not limited to, procurement of
|
||||
// substitute goods or services; loss of use, data, or profits; or business
|
||||
// interruption) however caused and on any theory of liability, whether in
|
||||
// contract, strict liability, or tort (including negligence or otherwise)
|
||||
// arising in any way out of the use of this software, even if advised of the
|
||||
// possibility of such damage.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#ifndef OPENCV_MXARRAY_HPP_ |
||||
#define OPENCV_MXARRAY_HPP_ |
||||
|
||||
#include <mex.h> |
||||
#include <stdint.h> |
||||
#include <cstdarg> |
||||
#include <algorithm> |
||||
#include <string> |
||||
#include <vector> |
||||
#include <sstream> |
||||
#if __cplusplus > 201103 |
||||
#include <unordered_set> |
||||
typedef std::unordered_set<std::string> StringSet; |
||||
#else |
||||
#include <set> |
||||
typedef std::set<std::string> StringSet; |
||||
#endif |
||||
|
||||
/*
|
||||
* All recent versions of Matlab ship with the MKL library which contains |
||||
* a blas extension called mkl_?omatcopy(). This defines an out-of-place |
||||
* copy and transpose operation. |
||||
* |
||||
* The mkl library is in ${MATLAB_ROOT}/bin/${MATLAB_MEXEXT}/libmkl... |
||||
* Matlab does not ship headers for the mkl functions, so we define them |
||||
* here. |
||||
* |
||||
*/ |
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
|
||||
namespace matlab { |
||||
// ----------------------------------------------------------------------------
|
||||
// PREDECLARATIONS
|
||||
// ----------------------------------------------------------------------------
|
||||
class MxArray; |
||||
typedef std::vector<MxArray> MxArrayVector; |
||||
|
||||
/*!
|
||||
* @brief raise error if condition fails |
||||
* |
||||
* This is a conditional wrapper for mexErrMsgTxt. If the conditional |
||||
* expression fails, an error is raised and the mex function returns |
||||
* to Matlab, otherwise this function does nothing |
||||
*/ |
||||
static void conditionalError(bool expr, const std::string& str) { |
||||
if (!expr) mexErrMsgTxt(std::string("condition failed: ").append(str).c_str()); |
||||
} |
||||
|
||||
/*!
|
||||
* @brief raise an error |
||||
* |
||||
* This function is a wrapper around mexErrMsgTxt |
||||
*/ |
||||
static void error(const std::string& str) { |
||||
mexErrMsgTxt(str.c_str()); |
||||
} |
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// MATLAB TRAITS
|
||||
// ----------------------------------------------------------------------------
|
||||
class DefaultTraits {}; |
||||
class InheritType {}; |
||||
|
||||
template<typename _Tp = DefaultTraits> class Traits { |
||||
public: |
||||
static const mxClassID ScalarType = mxUNKNOWN_CLASS; |
||||
static const mxComplexity Complex = mxCOMPLEX; |
||||
static const mxComplexity Real = mxREAL; |
||||
static std::string ToString() { return "Unknown/Unsupported"; } |
||||
}; |
||||
// bool
|
||||
template<> class Traits<bool> { |
||||
public: |
||||
static const mxClassID ScalarType = mxLOGICAL_CLASS; |
||||
static std::string ToString() { return "boolean"; } |
||||
}; |
||||
// uint8_t
|
||||
template<> class Traits<uint8_t> { |
||||
public: |
||||
static const mxClassID ScalarType = mxUINT8_CLASS; |
||||
static std::string ToString() { return "uint8_t"; } |
||||
}; |
||||
// int8_t
|
||||
template<> class Traits<int8_t> { |
||||
public: |
||||
static const mxClassID ScalarType = mxINT8_CLASS; |
||||
static std::string ToString() { return "int8_t"; } |
||||
}; |
||||
// uint16_t
|
||||
template<> class Traits<uint16_t> { |
||||
public: |
||||
static const mxClassID ScalarType = mxUINT16_CLASS; |
||||
static std::string ToString() { return "uint16_t"; } |
||||
}; |
||||
// int16_t
|
||||
template<> class Traits<int16_t> { |
||||
public: |
||||
static const mxClassID ScalarType = mxINT16_CLASS; |
||||
static std::string ToString() { return "int16_t"; } |
||||
}; |
||||
// uint32_t
|
||||
template<> class Traits<uint32_t> { |
||||
public: |
||||
static const mxClassID ScalarType = mxUINT32_CLASS; |
||||
static std::string ToString() { return "uint32_t"; } |
||||
}; |
||||
// int32_t
|
||||
template<> class Traits<int32_t> { |
||||
public: |
||||
static const mxClassID ScalarType = mxINT32_CLASS; |
||||
static std::string ToString() { return "int32_t"; } |
||||
}; |
||||
// uint64_t
|
||||
template<> class Traits<uint64_t> { |
||||
public: |
||||
static const mxClassID ScalarType = mxUINT64_CLASS; |
||||
static std::string ToString() { return "uint64_t"; } |
||||
}; |
||||
// int64_t
|
||||
template<> class Traits<int64_t> { |
||||
public: |
||||
static const mxClassID ScalarType = mxINT64_CLASS; |
||||
static std::string ToString() { return "int64_t"; } |
||||
}; |
||||
// float
|
||||
template<> class Traits<float> { |
||||
public: |
||||
static const mxClassID ScalarType = mxSINGLE_CLASS; |
||||
static std::string ToString() { return "float"; } |
||||
}; |
||||
// double
|
||||
template<> class Traits<double> { |
||||
public: |
||||
static const mxClassID ScalarType = mxDOUBLE_CLASS; |
||||
static std::string ToString() { return "double"; } |
||||
}; |
||||
// char
|
||||
template<> class Traits<char> { |
||||
public: |
||||
static const mxClassID ScalarType = mxCHAR_CLASS; |
||||
static std::string ToString() { return "char"; } |
||||
}; |
||||
// inherited type
|
||||
template<> class Traits<matlab::InheritType> { |
||||
public: |
||||
static std::string ToString() { return "Inherited type"; } |
||||
}; |
||||
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// MXARRAY
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
/*!
|
||||
* @class MxArray |
||||
* @brief A thin wrapper around Matlab's mxArray types |
||||
* |
||||
* MxArray provides a thin object oriented wrapper around Matlab's |
||||
* native mxArray type which exposes most of the functionality of the |
||||
* Matlab interface, but in a more C++ manner. MxArray objects are scoped, |
||||
* so you can freely create and destroy them without worrying about memory |
||||
* management. If you wish to pass the underlying mxArray* representation |
||||
* back to Matlab as an lvalue, see the releaseOwnership() method |
||||
* |
||||
* MxArrays can be directly converted into OpenCV mat objects and std::string |
||||
* objects, since there is a natural mapping between these types. More |
||||
* complex types are mapped through the Bridge which does custom conversions |
||||
* such as MxArray --> cv::Keypoints, etc |
||||
*/ |
||||
class MxArray { |
||||
private: |
||||
mxArray* ptr_; |
||||
bool owns_; |
||||
|
||||
/*!
|
||||
* @brief swap all members of this and other |
||||
* |
||||
* the swap method is used by the assignment and move constructors |
||||
* to swap the members of two MxArrays, leaving both in destructible states |
||||
*/ |
||||
friend void swap(MxArray& first, MxArray& second) { |
||||
using std::swap; |
||||
swap(first.ptr_, second.ptr_); |
||||
swap(first.owns_, second.owns_); |
||||
} |
||||
|
||||
void dealloc() { |
||||
if (owns_ && ptr_) { mxDestroyArray(ptr_); ptr_ = NULL; owns_ = false; } |
||||
} |
||||
public: |
||||
// --------------------------------------------------------------------------
|
||||
// CONSTRUCTORS
|
||||
// --------------------------------------------------------------------------
|
||||
/*!
|
||||
* @brief default constructor |
||||
* |
||||
* Construct a valid 0x0 matrix (so all other methods do not need validity checks) |
||||
*/ |
||||
MxArray() : ptr_(mxCreateDoubleMatrix(0, 0, matlab::Traits<>::Real)), owns_(true) {} |
||||
|
||||
/*!
|
||||
* @brief destructor |
||||
* |
||||
* The destructor deallocates any data allocated by mxCreate* methods only |
||||
* if the object is owned |
||||
*/ |
||||
virtual ~MxArray() { |
||||
dealloc(); |
||||
} |
||||
|
||||
/*!
|
||||
* @brief inheriting constructor |
||||
* |
||||
* Inherit an mxArray from Matlab. Don't claim ownership of the array, |
||||
* just encapsulate it |
||||
*/ |
||||
MxArray(const mxArray* ptr) : ptr_(const_cast<mxArray *>(ptr)), owns_(false) {} |
||||
MxArray& operator=(const mxArray* ptr) { |
||||
dealloc(); |
||||
ptr_ = const_cast<mxArray *>(ptr); |
||||
owns_ = false; |
||||
return *this; |
||||
} |
||||
|
||||
/*!
|
||||
* @brief explicit typed constructor |
||||
* |
||||
* This constructor explicitly creates an MxArray of the given size and type. |
||||
*/ |
||||
MxArray(size_t m, size_t n, size_t k, mxClassID id, mxComplexity com = matlab::Traits<>::Real) |
||||
: ptr_(NULL), owns_(true) { |
||||
mwSize dims[] = { static_cast<mwSize>(m), static_cast<mwSize>(n), static_cast<mwSize>(k) }; |
||||
ptr_ = mxCreateNumericArray(3, dims, id, com); |
||||
} |
||||
|
||||
/*!
|
||||
* @brief explicit tensor constructor |
||||
* |
||||
* Explicitly construct a tensor of given size and type. Since constructors cannot |
||||
* be explicitly templated, this is a static factory method |
||||
*/ |
||||
template <typename Scalar> |
||||
static MxArray Tensor(size_t m, size_t n, size_t k=1) { |
||||
return MxArray(m, n, k, matlab::Traits<Scalar>::ScalarType); |
||||
} |
||||
|
||||
/*!
|
||||
* @brief explicit matrix constructor |
||||
* |
||||
* Explicitly construct a matrix of given size and type. Since constructors cannot |
||||
* be explicitly templated, this is a static factory method |
||||
*/ |
||||
template <typename Scalar> |
||||
static MxArray Matrix(size_t m, size_t n) { |
||||
return MxArray(m, n, 1, matlab::Traits<Scalar>::ScalarType); |
||||
} |
||||
|
||||
/*!
|
||||
* @brief explicit vector constructor |
||||
* |
||||
* Explicitly construct a vector of given size and type. Since constructors cannot |
||||
* be explicitly templated, this is a static factory method |
||||
*/ |
||||
template <typename Scalar> |
||||
static MxArray Vector(size_t m) { |
||||
return MxArray(m, 1, 1, matlab::Traits<Scalar>::ScalarType); |
||||
} |
||||
|
||||
/*!
|
||||
* @brief explicit scalar constructor |
||||
* |
||||
* Explicitly construct a scalar of given type. Since constructors cannot |
||||
* be explicitly templated, this is a static factory method |
||||
*/ |
||||
template <typename ScalarType> |
||||
static MxArray Scalar(ScalarType value = 0) { |
||||
MxArray s(1, 1, 1, matlab::Traits<ScalarType>::ScalarType); |
||||
s.real<ScalarType>()[0] = value; |
||||
return s; |
||||
} |
||||
|
||||
/*!
|
||||
* @brief copy constructor |
||||
* |
||||
* All copies are deep copies. If you have a C++11 compatible compiler, prefer |
||||
* move construction to copy construction |
||||
*/ |
||||
MxArray(const MxArray& other) : ptr_(mxDuplicateArray(other.ptr_)), owns_(true) {} |
||||
|
||||
/*!
|
||||
* @brief copy-and-swap assignment |
||||
* |
||||
* This assignment operator uses the copy and swap idiom to provide a strong |
||||
* exception guarantee when swapping two objects. |
||||
* |
||||
* Note in particular that the other MxArray is passed by value, thus invoking |
||||
* the copy constructor which performs a deep copy of the input. The members of |
||||
* this and other are then swapped |
||||
*/ |
||||
MxArray& operator=(MxArray other) { |
||||
swap(*this, other); |
||||
return *this; |
||||
} |
||||
#if __cplusplus >= 201103L |
||||
/*
|
||||
* @brief C++11 move constructor |
||||
* |
||||
* When C++11 support is available, move construction is used to move returns |
||||
* out of functions, etc. This is much fast than copy construction, since the |
||||
* move constructed object replaced itself with a default constructed MxArray, |
||||
* which is of size 0 x 0. |
||||
*/ |
||||
MxArray(MxArray&& other) : MxArray() { |
||||
swap(*this, other); |
||||
} |
||||
#endif |
||||
|
||||
/*
|
||||
* @brief release ownership to allow return into Matlab workspace |
||||
* |
||||
* MxArray is not directly convertible back to mxArray types through assignment |
||||
* because the MxArray may have been allocated on the free store, making it impossible |
||||
* to know whether the returned pointer will be released by someone else or not. |
||||
* |
||||
* Since Matlab requires mxArrays be passed back into the workspace, the only way |
||||
* to achieve that is through this function, which explicitly releases ownership |
||||
* of the object, assuming the Matlab interpreter receving the object will delete |
||||
* it at a later time |
||||
* |
||||
* e.g. |
||||
* { |
||||
* MxArray A = MxArray::Matrix<double>(5, 5); // allocates memory
|
||||
* MxArray B = MxArray::Matrix<double>(5, 5); // ditto
|
||||
* plhs[0] = A; // not allowed!!
|
||||
* plhs[0] = A.releaseOwnership(); // makes explicit that ownership is being released
|
||||
* } // end of scope. B is released, A isn't
|
||||
* |
||||
*/ |
||||
mxArray* releaseOwnership() { |
||||
owns_ = false; |
||||
return ptr_; |
||||
} |
||||
|
||||
MxArray field(const std::string& name) { return MxArray(mxGetField(ptr_, 0, name.c_str())); } |
||||
|
||||
template <typename Scalar> |
||||
Scalar* real() { return static_cast<Scalar *>(mxGetData(ptr_)); } |
||||
|
||||
template <typename Scalar> |
||||
Scalar* imag() { return static_cast<Scalar *>(mxGetImagData(ptr_)); } |
||||
|
||||
template <typename Scalar> |
||||
const Scalar* real() const { return static_cast<const Scalar *>(mxGetData(ptr_)); } |
||||
|
||||
template <typename Scalar> |
||||
const Scalar* imag() const { return static_cast<const Scalar *>(mxGetData(ptr_)); } |
||||
|
||||
template <typename Scalar> |
||||
Scalar scalar() const { return static_cast<Scalar *>(mxGetData(ptr_))[0]; } |
||||
|
||||
std::string toString() const { |
||||
conditionalError(isString(), "Attempted to convert non-string type to string"); |
||||
std::string str(size(), '\0'); |
||||
mxGetString(ptr_, const_cast<char *>(str.data()), str.size()+1); |
||||
return str; |
||||
} |
||||
|
||||
size_t size() const { return mxGetNumberOfElements(ptr_); } |
||||
bool empty() const { return size() == 0; } |
||||
size_t rows() const { return mxGetDimensions(ptr_)[0]; } |
||||
size_t cols() const { return mxGetDimensions(ptr_)[1]; } |
||||
size_t channels() const { return (mxGetNumberOfDimensions(ptr_) > 2) ? mxGetDimensions(ptr_)[2] : 1; } |
||||
bool isComplex() const { return mxIsComplex(ptr_); } |
||||
bool isNumeric() const { return mxIsNumeric(ptr_); } |
||||
bool isLogical() const { return mxIsLogical(ptr_); } |
||||
bool isString() const { return mxIsChar(ptr_); } |
||||
bool isCell() const { return mxIsCell(ptr_); } |
||||
bool isStructure() const { return mxIsStruct(ptr_); } |
||||
bool isClass(const std::string& name) const { return mxIsClass(ptr_, name.c_str()); } |
||||
std::string className() const { return std::string(mxGetClassName(ptr_)); } |
||||
mxClassID ID() const { return mxGetClassID(ptr_); } |
||||
|
||||
}; |
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// ARGUMENT PARSER
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/*! @class ArgumentParser
|
||||
* @brief parses inputs to a method and resolves the argument names. |
||||
* |
||||
* The ArgumentParser resolves the inputs to a method. It checks that all |
||||
* required arguments are specified and also allows named optional arguments. |
||||
* For example, the C++ function: |
||||
* void randn(Mat& mat, Mat& mean=Mat(), Mat& std=Mat()); |
||||
* could be called in Matlab using any of the following signatures: |
||||
* \code |
||||
* out = randn(in); |
||||
* out = randn(in, 0, 1); |
||||
* out = randn(in, 'mean', 0, 'std', 1); |
||||
* \endcode |
||||
* |
||||
* ArgumentParser also enables function overloading by allowing users |
||||
* to add variants to a method. For example, there may be two C++ sum() methods: |
||||
* \code |
||||
* double sum(Mat& mat); % sum elements of a matrix |
||||
* Mat sum(Mat& A, Mat& B); % add two matrices |
||||
* \endcode |
||||
* |
||||
* by adding two variants to ArgumentParser, the correct underlying sum |
||||
* method can be called. If the function call is ambiguous, the |
||||
* ArgumentParser will fail with an error message. |
||||
* |
||||
* The previous example could be parsed as: |
||||
* \code |
||||
* // set up the Argument parser
|
||||
* ArgumentParser arguments; |
||||
* arguments.addVariant("elementwise", 1); |
||||
* arguments.addVariant("matrix", 2); |
||||
* |
||||
* // parse the arguments
|
||||
* std::vector<MxArray> inputs; |
||||
* inputs = arguments.parse(std::vector<MxArray>(prhs, prhs+nrhs)); |
||||
* |
||||
* // if we get here, one unique variant is valid
|
||||
* if (arguments.variantIs("elementwise")) { |
||||
* // call elementwise sum()
|
||||
* } |
||||
* \endcode |
||||
*/ |
||||
class ArgumentParser { |
||||
private: |
||||
struct Variant; |
||||
typedef std::string String; |
||||
typedef std::vector<std::string> StringVector; |
||||
typedef std::vector<size_t> IndexVector; |
||||
typedef std::vector<Variant> VariantVector; |
||||
|
||||
/* @class Variant
|
||||
* @brief Describes a variant of arguments to a method |
||||
* |
||||
* When addVariant() is called on an instance to ArgumentParser, this class
|
||||
* holds the the information that decribes that variant. The parse() method |
||||
* of ArgumentParser then attempts to match a Variant, given a set of |
||||
* inputs for a method invocation. |
||||
*/ |
||||
class Variant { |
||||
private: |
||||
String name_; |
||||
size_t Nreq_; |
||||
size_t Nopt_; |
||||
StringVector keys_; |
||||
IndexVector order_; |
||||
bool valid_; |
||||
size_t nparsed_; |
||||
size_t nkeys_; |
||||
size_t working_opt_; |
||||
bool expecting_val_; |
||||
bool using_named_; |
||||
size_t find(const String& key) const { |
||||
return std::find(keys_.begin(), keys_.end(), key) - keys_.begin(); |
||||
} |
||||
public: |
||||
/*! @brief default constructor */ |
||||
Variant() : Nreq_(0), Nopt_(0), valid_(false) {} |
||||
/*! @brief construct a new variant spec */ |
||||
Variant(const String& name, size_t Nreq, size_t Nopt, const StringVector& keys) |
||||
: name_(name), Nreq_(Nreq), Nopt_(Nopt), keys_(keys), |
||||
order_(Nreq+Nopt, Nreq+2*Nopt), valid_(true), nparsed_(0), nkeys_(0), |
||||
working_opt_(0), expecting_val_(false), using_named_(false) {} |
||||
/*! @brief the name of the variant */ |
||||
String name() const { return name_; } |
||||
/*! @brief return the total number of arguments the variant can take */ |
||||
size_t size() const { return Nreq_ + Nopt_; } |
||||
/*! @brief has the variant been fulfilled? */ |
||||
bool fulfilled() const { return (valid_ && nparsed_ >= Nreq_ && !expecting_val_); } |
||||
/*! @brief is the variant in a valid state (though not necessarily fulfilled) */ |
||||
bool valid() const { return valid_; } |
||||
/*! @brief check if the named argument exists in the variant */ |
||||
bool exist(const String& key) const { return find(key) != keys_.size(); } |
||||
/*! @brief retrieve the order mapping raw inputs to their position in the variant */ |
||||
const IndexVector& order() const { return order_; } |
||||
size_t order(size_t n) const { return order_[n]; } |
||||
/*! @brief attempt to parse the next argument as a value */ |
||||
bool parseNextAsValue() { |
||||
if (!valid_) {} |
||||
else if ((using_named_ && !expecting_val_) || (nparsed_-nkeys_ == Nreq_+Nopt_)) { valid_ = false; } |
||||
else if (nparsed_ < Nreq_) { order_[nparsed_] = nparsed_; } |
||||
else if (!using_named_) { order_[nparsed_] = nparsed_; } |
||||
else if (using_named_ && expecting_val_) { order_[Nreq_ + working_opt_] = nparsed_; } |
||||
nparsed_++; |
||||
expecting_val_ = false; |
||||
return valid_; |
||||
} |
||||
/*! @biref attempt to parse the next argument as a name (key) */ |
||||
bool parseNextAsKey(const String& key) { |
||||
if (!valid_) {} |
||||
else if ((nparsed_ < Nreq_) || (nparsed_-nkeys_ == Nreq_+Nopt_)) { valid_ = false; } |
||||
else if (using_named_ && expecting_val_) { valid_ = false; } |
||||
else if ((working_opt_ = find(key)) == keys_.size()) { valid_ = false; } |
||||
else { using_named_ = true; expecting_val_ = true; nkeys_++; nparsed_++; } |
||||
return valid_; |
||||
} |
||||
String toString(const String& method_name="f") const { |
||||
int req_begin = 0, req_end = 0, opt_begin = 0, opt_end = 0; |
||||
std::ostringstream s; |
||||
// f(...)
|
||||
s << method_name << "("; |
||||
// required arguments
|
||||
req_begin = s.str().size(); |
||||
for (size_t n = 0; n < Nreq_; ++n) { s << "src" << n+1 << (n != Nreq_-1 ? ", " : ""); } |
||||
req_end = s.str().size(); |
||||
if (Nreq_ && Nopt_) s << ", "; |
||||
// optional arguments
|
||||
opt_begin = s.str().size(); |
||||
for (size_t n = 0; n < keys_.size(); ++n) { s << "'" << keys_[n] << "', " << keys_[n] << (n != Nopt_-1 ? ", " : ""); } |
||||
opt_end = s.str().size(); |
||||
s << ");"; |
||||
if (Nreq_ + Nopt_ == 0) return s.str(); |
||||
// underscores
|
||||
String under = String(req_begin, ' ') + String(req_end-req_begin, '-') |
||||
+ String(std::max(opt_begin-req_end,0), ' ') + String(opt_end-opt_begin, '-'); |
||||
s << "\n" << under; |
||||
// required and optional sets
|
||||
String req_set(req_end-req_begin, ' '); |
||||
String opt_set(opt_end-opt_begin, ' '); |
||||
if (!req_set.empty() && req_set.size() < 8) req_set.replace((req_set.size()-3)/2, 3, "req"); |
||||
if (req_set.size() > 7) req_set.replace((req_set.size()-8)/2, 8, "required"); |
||||
if (!opt_set.empty() && opt_set.size() < 8) opt_set.replace((opt_set.size()-3)/2, 3, "opt"); |
||||
if (opt_set.size() > 7) opt_set.replace((opt_set.size()-8)/2, 8, "optional"); |
||||
String set = String(req_begin, ' ') + req_set + String(std::max(opt_begin-req_end,0), ' ') + opt_set; |
||||
s << "\n" << set; |
||||
return s.str(); |
||||
} |
||||
}; |
||||
/*! @brief given an input and output vector of arguments, and a variant spec, sort */ |
||||
void sortArguments(Variant& v, MxArrayVector& in, MxArrayVector& out) { |
||||
// allocate the output array with ALL arguments
|
||||
out.resize(v.size()); |
||||
// reorder the inputs based on the variant ordering
|
||||
for (size_t n = 0; n < v.size(); ++n) { |
||||
if (v.order(n) >= in.size()) continue; |
||||
swap(in[v.order(n)], out[n]); |
||||
} |
||||
} |
||||
VariantVector variants_; |
||||
String valid_; |
||||
String method_name_; |
||||
public: |
||||
ArgumentParser(const String& method_name) : method_name_(method_name) {} |
||||
|
||||
/*! @brief add a function call variant to the parser
|
||||
* |
||||
* Adds a function-call signature to the parser. The function call *must* be |
||||
* unique either in its number of arguments, or in the named-syntax. |
||||
* Currently this function does not check whether that invariant stands true. |
||||
* |
||||
* This function is variadic. If should be called as follows: |
||||
* addVariant(2, 2, 'opt_1_name', 'opt_2_name'); |
||||
*/ |
||||
void addVariant(const String& name, size_t nreq, size_t nopt = 0, ...) { |
||||
StringVector keys; |
||||
va_list opt; |
||||
va_start(opt, nopt); |
||||
for (size_t n = 0; n < nopt; ++n) keys.push_back(va_arg(opt, const char*)); |
||||
addVariant(name, nreq, nopt, keys); |
||||
} |
||||
void addVariant(const String& name, size_t nreq, size_t nopt, StringVector keys) { |
||||
variants_.push_back(Variant(name, nreq, nopt, keys)); |
||||
} |
||||
|
||||
/*! @brief check if the valid variant is the key name */ |
||||
bool variantIs(const String& name) { |
||||
return name.compare(valid_) == 0; |
||||
} |
||||
|
||||
/*! @brief parse a vector of input arguments
|
||||
* |
||||
* This method parses a vector of input arguments, attempting to match them |
||||
* to a Variant spec. For each input, the method attempts to cull any |
||||
* Variants which don't match the given inputs so far. |
||||
* |
||||
* Once all inputs have been parsed, if there is one unique spec remaining, |
||||
* the output MxArray vector gets populated with the arguments, with named |
||||
* arguments removed. Any optional arguments that have not been encountered |
||||
* are set to an empty array. |
||||
* |
||||
* If multiple variants or no variants match the given call, an error |
||||
* message is emitted |
||||
*/ |
||||
MxArrayVector parse(const MxArrayVector& inputs) { |
||||
// allocate the outputs
|
||||
String variant_string; |
||||
MxArrayVector outputs; |
||||
VariantVector candidates = variants_; |
||||
|
||||
// iterate over the inputs, attempting to match a variant
|
||||
for (MxArrayVector::const_iterator input = inputs.begin(); input != inputs.end(); ++input) { |
||||
String name = input->isString() ? input->toString() : String(); |
||||
for (VariantVector::iterator candidate = candidates.begin(); candidate < candidates.end(); ++candidate) { |
||||
candidate->exist(name) ? candidate->parseNextAsKey(name) : candidate->parseNextAsValue(); |
||||
} |
||||
} |
||||
|
||||
// make sure the candidates have been fulfilled
|
||||
for (VariantVector::iterator candidate = candidates.begin(); candidate < candidates.end(); ++candidate) { |
||||
if (!candidate->fulfilled()) candidate = candidates.erase(candidate)--; |
||||
} |
||||
|
||||
// if there is not a unique candidate, throw an error
|
||||
for (VariantVector::iterator variant = variants_.begin(); variant != variants_.end(); ++variant) { |
||||
variant_string += "\n" + variant->toString(method_name_); |
||||
} |
||||
|
||||
// if there is not a unique candidate, throw an error
|
||||
if (candidates.size() > 1) { |
||||
error(String("Call to method is ambiguous. Valid variants are:") |
||||
.append(variant_string).append("\nUse named arguments to disambiguate call")); |
||||
} |
||||
if (candidates.size() == 0) { |
||||
error(String("No matching method signatures for given arguments. Valid variants are:").append(variant_string)); |
||||
} |
||||
|
||||
// Unique candidate!
|
||||
valid_ = candidates[0].name(); |
||||
sortArguments(candidates[0], const_cast<MxArrayVector&>(inputs), outputs); |
||||
return outputs; |
||||
} |
||||
}; |
||||
|
||||
} // namespace matlab
|
||||
|
||||
#endif |
@ -0,0 +1,141 @@ |
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this
|
||||
// license. If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2013, OpenCV Foundation, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote
|
||||
// products derived from this software without specific prior written
|
||||
// permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is"
|
||||
// and any express or implied warranties, including, but not limited to, the
|
||||
// implied warranties of merchantability and fitness for a particular purpose
|
||||
// are disclaimed. In no event shall the Intel Corporation or contributors be
|
||||
// liable for any direct, indirect, incidental, special, exemplary, or
|
||||
// consequential damages (including, but not limited to, procurement of
|
||||
// substitute goods or services; loss of use, data, or profits; or business
|
||||
// interruption) however caused and on any theory of liability, whether in
|
||||
// contract, strict liability, or tort (including negligence or otherwise)
|
||||
// arising in any way out of the use of this software, even if advised of the
|
||||
// possibility of such damage.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#ifndef OPENCV_TRANSPOSE_HPP_ |
||||
#define OPENCV_TRANSPOSE_HPP_ |
||||
|
||||
template <typename InputScalar, typename OutputScalar> |
||||
void transposeBlock(const size_t M, const size_t N, const InputScalar* src, size_t lda, OutputScalar* dst, size_t ldb) { |
||||
InputScalar cache[16]; |
||||
// copy the source into the cache contiguously
|
||||
for (size_t n = 0; n < N; ++n) |
||||
for (size_t m = 0; m < M; ++m) |
||||
cache[m+n*4] = src[m+n*lda]; |
||||
// copy the destination out of the cache contiguously
|
||||
for (size_t m = 0; m < M; ++m) |
||||
for (size_t n = 0; n < N; ++n) |
||||
dst[n+m*ldb] = cache[m+n*4]; |
||||
} |
||||
|
||||
template <typename InputScalar, typename OutputScalar> |
||||
void transpose4x4(const InputScalar* src, size_t lda, OutputScalar* dst, size_t ldb) { |
||||
InputScalar cache[16]; |
||||
// copy the source into the cache contiguously
|
||||
cache[0] = src[0]; cache[1] = src[1]; cache[2] = src[2]; cache[3] = src[3]; src+=lda; |
||||
cache[4] = src[0]; cache[5] = src[1]; cache[6] = src[2]; cache[7] = src[3]; src+=lda; |
||||
cache[8] = src[0]; cache[9] = src[1]; cache[10] = src[2]; cache[11] = src[3]; src+=lda; |
||||
cache[12] = src[0]; cache[13] = src[1]; cache[14] = src[2]; cache[15] = src[3]; src+=lda; |
||||
// copy the destination out of the contiguously
|
||||
dst[0] = cache[0]; dst[1] = cache[4]; dst[2] = cache[8]; dst[3] = cache[12]; dst+=ldb; |
||||
dst[0] = cache[1]; dst[1] = cache[5]; dst[2] = cache[9]; dst[3] = cache[13]; dst+=ldb; |
||||
dst[0] = cache[2]; dst[1] = cache[6]; dst[2] = cache[10]; dst[3] = cache[14]; dst+=ldb; |
||||
dst[0] = cache[3]; dst[1] = cache[7]; dst[2] = cache[11]; dst[3] = cache[15]; dst+=ldb; |
||||
} |
||||
|
||||
|
||||
/*
|
||||
* Vanilla copy, transpose and cast |
||||
*/ |
||||
template <typename InputScalar, typename OutputScalar> |
||||
void gemt(const char major, const size_t M, const size_t N, const InputScalar* a, size_t lda, OutputScalar* b, size_t ldb) { |
||||
|
||||
// 1x1 transpose is just copy
|
||||
if (M == 1 && N == 1) { *b = *a; return; } |
||||
|
||||
// get the interior 4x4 blocks, and the extra skirting
|
||||
const size_t Fblock = (major == 'R') ? N/4 : M/4; |
||||
const size_t Frem = (major == 'R') ? N%4 : M%4; |
||||
const size_t Sblock = (major == 'R') ? M/4 : N/4; |
||||
const size_t Srem = (major == 'R') ? M%4 : N%4; |
||||
|
||||
// if less than 4x4, invoke the block transpose immediately
|
||||
if (M < 4 && N < 4) { transposeBlock(Frem, Srem, a, lda, b, ldb); return; } |
||||
|
||||
// transpose 4x4 blocks
|
||||
const InputScalar* aptr = a; |
||||
OutputScalar* bptr = b; |
||||
for (size_t second = 0; second < Sblock; ++second) { |
||||
aptr = a + second*lda; |
||||
bptr = b + second; |
||||
for (size_t first = 0; first < Fblock; ++first) { |
||||
transposeBlock(4, 4, aptr, lda, bptr, ldb); |
||||
//transpose4x4(aptr, lda, bptr, ldb);
|
||||
aptr+=4; |
||||
bptr+=4*ldb; |
||||
} |
||||
// transpose trailing blocks on primary dimension
|
||||
transposeBlock(Frem, 4, aptr, lda, bptr, ldb); |
||||
} |
||||
// transpose trailing blocks on secondary dimension
|
||||
aptr = a + 4*Sblock*lda; |
||||
bptr = b + 4*Sblock; |
||||
for (size_t first = 0; first < Fblock; ++first) { |
||||
transposeBlock(4, Srem, aptr, lda, bptr, ldb); |
||||
aptr+=4; |
||||
bptr+=4*ldb; |
||||
} |
||||
// transpose bottom right-hand corner
|
||||
transposeBlock(Frem, Srem, aptr, lda, bptr, ldb); |
||||
} |
||||
|
||||
#ifdef __SSE2__ |
||||
/*
|
||||
* SSE2 supported fast copy, transpose and cast |
||||
*/ |
||||
#include <emmintrin.h> |
||||
|
||||
template <> |
||||
void transpose4x4<float, float>(const float* src, size_t lda, float* dst, size_t ldb) { |
||||
__m128 row0, row1, row2, row3; |
||||
row0 = _mm_loadu_ps(src); |
||||
row1 = _mm_loadu_ps(src+lda); |
||||
row2 = _mm_loadu_ps(src+2*lda); |
||||
row3 = _mm_loadu_ps(src+3*lda); |
||||
_MM_TRANSPOSE4_PS(row0, row1, row2, row3); |
||||
_mm_storeu_ps(dst, row0); |
||||
_mm_storeu_ps(dst+ldb, row1); |
||||
_mm_storeu_ps(dst+2*ldb, row2); |
||||
_mm_storeu_ps(dst+3*ldb, row3); |
||||
} |
||||
|
||||
#endif |
||||
#endif |
@ -0,0 +1,23 @@ |
||||
set(TEST_PROXY ${CMAKE_CURRENT_BINARY_DIR}/test.proxy) |
||||
file(REMOVE ${TEST_PROXY}) |
||||
|
||||
# generate |
||||
# call the python executable to generate the Matlab gateways |
||||
add_custom_command( |
||||
OUTPUT ${TEST_PROXY} |
||||
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/OpenCVTest.m ${CMAKE_CURRENT_BINARY_DIR} |
||||
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/testsuite.m ${CMAKE_CURRENT_BINARY_DIR} |
||||
COMMAND ${CMAKE_COMMAND} -E touch ${TEST_PROXY} |
||||
COMMENT "Building Matlab tests" |
||||
) |
||||
|
||||
# targets |
||||
# opencv_matlab_sources --> opencv_matlab |
||||
add_custom_target(opencv_test_matlab ALL DEPENDS ${TEST_PROXY}) |
||||
add_dependencies(opencv_test_matlab ${the_module}) |
||||
|
||||
# run the matlab test suite |
||||
add_test(opencv_test_matlab |
||||
COMMAND ${MATLAB_BIN} "-nodisplay" "-r" "testsuite.m" |
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} |
||||
) |
@ -0,0 +1,166 @@ |
||||
% Matlab binding test cases |
||||
% Uses Matlab's builtin testing framework |
||||
classdef OpenCVTest < matlab.unittest.TestCase |
||||
|
||||
methods(Test) |
||||
|
||||
% ------------------------------------------------------------------------- |
||||
% EXCEPTIONS |
||||
% Check that errors and exceptions are thrown correctly |
||||
% ------------------------------------------------------------------------- |
||||
|
||||
% check that std exception is thrown |
||||
function stdException(testcase) |
||||
try |
||||
std_exception(); |
||||
testcase.verifyFail(); |
||||
catch |
||||
% TODO: Catch more specific exception |
||||
testcase.verifyTrue(true); |
||||
end |
||||
end |
||||
|
||||
% check that OpenCV exceptions are correctly caught |
||||
function cvException(testcase) |
||||
try |
||||
cv_exception(); |
||||
testcase.verifyFail(); |
||||
catch |
||||
% TODO: Catch more specific exception |
||||
testcase.verifyTrue(true); |
||||
end |
||||
end |
||||
|
||||
% check that all exceptions are caught |
||||
function allException(testcase) |
||||
try |
||||
exception(); |
||||
testcase.verifyFail(); |
||||
catch |
||||
% TODO: Catch more specific exception |
||||
testcase.verifyTrue(true); |
||||
end |
||||
end |
||||
|
||||
% ------------------------------------------------------------------------- |
||||
% SIZES AND FILLS |
||||
% Check that matrices are correctly filled and resized |
||||
% ------------------------------------------------------------------------- |
||||
|
||||
% check that a matrix is correctly filled with random numbers |
||||
function randomFill(testcase) |
||||
sz = [7 11]; |
||||
mat = zeros(sz); |
||||
mat = cv.randn(mat, 0, 1); |
||||
testcase.verifyEqual(size(mat), sz, 'Matrix should not change size'); |
||||
testcase.verifyNotEqual(mat, zeros(sz), 'Matrix should be nonzero'); |
||||
end |
||||
|
||||
function transpose(testcase) |
||||
m = randn(19, 81); |
||||
mt1 = transpose(m); |
||||
mt2 = cv.transpose(m); |
||||
testcase.verifyEqual(size(mt1), size(mt2), 'Matrix transposed to incorrect dimensionality'); |
||||
testcase.verifyLessThan(norm(mt1 - mt2), 1e-8, 'Too much precision lost in tranposition'); |
||||
end |
||||
|
||||
% multiple return |
||||
function multipleReturn(testcase) |
||||
A = randn(10); |
||||
A = A'*A; |
||||
[V1, D1] = eig(A); D1 = diag(D1); |
||||
[~, D2, V2] = cv.eigen(A); |
||||
testcase.verifyLessThan(norm(V1 - V2), 1e-6, 'Too much precision lost in eigenvectors'); |
||||
testcase.verifyLessThan(norm(D1 - D2), 1e-6, 'Too much precision lost in eigenvalues'); |
||||
end |
||||
|
||||
% complex output from SVD |
||||
function complexOutputSVD(testcase) |
||||
A = randn(10); |
||||
[V1, D1] = eig(A); |
||||
[~, D2, V2] = cv.eigen(A); |
||||
testcase.verifyTrue(~isreal(V2) && size(V2,3) == 1, 'Output should be complex'); |
||||
testcase.verifyLessThan(norm(V1 - V2), 1e-6, 'Too much precision lost in eigenvectors'); |
||||
end |
||||
|
||||
% complex output from Fourier Transform |
||||
function complexOutputFFT(testcase) |
||||
A = randn(10); |
||||
F1 = fft2(A); |
||||
F2 = cv.dft(A, cv.DFT_COMPLEX_OUTPUT); |
||||
testcase.verifyTrue(~isreal(F2) && size(F2,3) == 1, 'Output should be complex'); |
||||
testcase.verifyLessThan(norm(F1 - F2), 1e-6, 'Too much precision lost in eigenvectors'); |
||||
end |
||||
|
||||
% ------------------------------------------------------------------------- |
||||
% TYPE CASTS |
||||
% Check that types are correctly cast |
||||
% ------------------------------------------------------------------------- |
||||
|
||||
% ------------------------------------------------------------------------- |
||||
% PRECISION |
||||
% Check that basic operations are performed with sufficient precision |
||||
% ------------------------------------------------------------------------- |
||||
|
||||
% check that summing elements is within reasonable precision |
||||
function sumElements(testcase) |
||||
a = randn(5000); |
||||
b = sum(a(:)); |
||||
c = cv.sum(a); |
||||
testcase.verifyLessThan(norm(b - c), 1e-8, 'Matrix reduction with insufficient precision'); |
||||
end |
||||
|
||||
|
||||
% check that adding two matrices is within reasonable precision |
||||
function addPrecision(testcase) |
||||
a = randn(50); |
||||
b = randn(50); |
||||
c = a+b; |
||||
d = cv.add(a, b); |
||||
testcase.verifyLessThan(norm(c - d), 1e-8, 'Matrices are added with insufficient precision'); |
||||
end |
||||
|
||||
% check that performing gemm is within reasonable precision |
||||
function gemmPrecision(testcase) |
||||
a = randn(10, 50); |
||||
b = randn(50, 10); |
||||
c = randn(10, 10); |
||||
alpha = 2.71828; |
||||
gamma = 1.61803; |
||||
d = alpha*a*b + gamma*c; |
||||
e = cv.gemm(a, b, alpha, c, gamma); |
||||
testcase.verifyLessThan(norm(d - e), 1e-8, 'Matrices are multiplied with insufficient precision'); |
||||
end |
||||
|
||||
|
||||
% ------------------------------------------------------------------------- |
||||
% MISCELLANEOUS |
||||
% Miscellaneous tests |
||||
% ------------------------------------------------------------------------- |
||||
|
||||
% check that cv::waitKey waits for at least specified time |
||||
function waitKey(testcase) |
||||
tic(); |
||||
cv.waitKey(500); |
||||
elapsed = toc(); |
||||
testcase.verifyGreaterThan(elapsed, 0.5, 'Elapsed time should be at least 0.5 seconds'); |
||||
end |
||||
|
||||
% check that highgui window can be created and destroyed |
||||
function createAndDestroyWindow(testcase) |
||||
try |
||||
cv.namedWindow('test window'); |
||||
catch |
||||
testcase.verifyFail('could not create window'); |
||||
end |
||||
|
||||
try |
||||
cv.destroyWindow('test window'); |
||||
catch |
||||
testcase.verifyFail('could not destroy window'); |
||||
end |
||||
testcase.verifyTrue(true); |
||||
end |
||||
|
||||
end |
||||
end |
@ -0,0 +1,33 @@ |
||||
/*
|
||||
* file: exception.cpp |
||||
* author: Hilton Bristow |
||||
* date: Wed, 19 Jun 2013 11:15:15 |
||||
* |
||||
* See LICENCE for full modification and redistribution details. |
||||
* Copyright 2013 The OpenCV Foundation |
||||
*/ |
||||
#include <exception> |
||||
#include <opencv2/core.hpp> |
||||
#include "mex.h" |
||||
|
||||
/*
|
||||
* exception |
||||
* Gateway routine |
||||
* nlhs - number of return arguments |
||||
* plhs - pointers to return arguments |
||||
* nrhs - number of input arguments |
||||
* prhs - pointers to input arguments |
||||
*/ |
||||
void mexFunction(int nlhs, mxArray* plhs[], |
||||
int nrhs, const mxArray* prhs[]) { |
||||
|
||||
// call the opencv function
|
||||
// [out =] namespace.fun(src1, ..., srcn, dst1, ..., dstn, opt1, ..., optn);
|
||||
try { |
||||
throw cv::Exception(-1, "OpenCV exception thrown", __func__, __FILE__, __LINE__); |
||||
} catch(cv::Exception& e) { |
||||
mexErrMsgTxt(e.what()); |
||||
} catch(...) { |
||||
mexErrMsgTxt("Incorrect exception caught!"); |
||||
} |
||||
} |
@ -0,0 +1,29 @@ |
||||
/*
|
||||
* file: exception.cpp |
||||
* author: Hilton Bristow |
||||
* date: Wed, 19 Jun 2013 11:15:15 |
||||
* |
||||
* See LICENCE for full modification and redistribution details. |
||||
* Copyright 2013 The OpenCV Foundation |
||||
*/ |
||||
#include "mex.h" |
||||
|
||||
/*
|
||||
* exception |
||||
* Gateway routine |
||||
* nlhs - number of return arguments |
||||
* plhs - pointers to return arguments |
||||
* nrhs - number of input arguments |
||||
* prhs - pointers to input arguments |
||||
*/ |
||||
void mexFunction(int nlhs, mxArray* plhs[], |
||||
int nrhs, const mxArray* prhs[]) { |
||||
|
||||
// call the opencv function
|
||||
// [out =] namespace.fun(src1, ..., srcn, dst1, ..., dstn, opt1, ..., optn);
|
||||
try { |
||||
throw 1; |
||||
} catch(...) { |
||||
mexErrMsgTxt("Uncaught exception occurred!"); |
||||
} |
||||
} |
@ -0,0 +1,15 @@ |
||||
function help() |
||||
%CV.HELP display help information for the OpenCV Toolbox |
||||
% |
||||
% Calling: |
||||
% >> cv.help(); |
||||
% |
||||
% is equivalent to calling: |
||||
% >> help cv; |
||||
% |
||||
% It displays high-level usage information about the OpenCV toolbox |
||||
% along with resources to find out more information. |
||||
% |
||||
% See also: cv.buildInformation |
||||
help('cv'); |
||||
end |
@ -0,0 +1,32 @@ |
||||
/*
|
||||
* file: exception.cpp |
||||
* author: Hilton Bristow |
||||
* date: Wed, 19 Jun 2013 11:15:15 |
||||
* |
||||
* See LICENCE for full modification and redistribution details. |
||||
* Copyright 2013 The OpenCV Foundation |
||||
*/ |
||||
#include <exception> |
||||
#include "mex.h" |
||||
|
||||
/*
|
||||
* exception |
||||
* Gateway routine |
||||
* nlhs - number of return arguments |
||||
* plhs - pointers to return arguments |
||||
* nrhs - number of input arguments |
||||
* prhs - pointers to input arguments |
||||
*/ |
||||
void mexFunction(int nlhs, mxArray* plhs[], |
||||
int nrhs, const mxArray* prhs[]) { |
||||
|
||||
// call the opencv function
|
||||
// [out =] namespace.fun(src1, ..., srcn, dst1, ..., dstn, opt1, ..., optn);
|
||||
try { |
||||
throw std::exception(); |
||||
} catch(std::exception& e) { |
||||
mexErrMsgTxt(e.what()); |
||||
} catch(...) { |
||||
mexErrMsgTxt("Incorrect exception caught!"); |
||||
} |
||||
} |
@ -0,0 +1,31 @@ |
||||
/*
|
||||
* file: rand.cpp |
||||
* author: A trusty code generator |
||||
* date: Wed, 19 Jun 2013 11:15:15 |
||||
* |
||||
* This file was autogenerated, do not modify. |
||||
* See LICENCE for full modification and redistribution details. |
||||
* Copyright 2013 The OpenCV Foundation |
||||
*/ |
||||
#include "mex.h" |
||||
#include <vector> |
||||
|
||||
/*
|
||||
* rand |
||||
* Gateway routine |
||||
* nlhs - number of return arguments |
||||
* plhs - pointers to return arguments |
||||
* nrhs - number of input arguments |
||||
* prhs - pointers to input arguments |
||||
*/ |
||||
void mexFunction(int nlhs, mxArray* plhs[], |
||||
int nrhs, const mxArray* prhs[]) { |
||||
|
||||
// call the opencv function
|
||||
// [out =] namespace.fun(src1, ..., srcn, dst1, ..., dstn, opt1, ..., optn);
|
||||
try { |
||||
rand(); |
||||
} catch(...) { |
||||
mexErrMsgTxt("Uncaught exception occurred in rand"); |
||||
} |
||||
} |
@ -0,0 +1,15 @@ |
||||
/*
|
||||
* a rather innocuous-looking function which is actually |
||||
* part of <cstdlib>, so we can be reasonably sure its |
||||
* definition will be found |
||||
*/ |
||||
#ifndef __OPENCV_MATLAB_TEST_GENERATOR_HPP_ |
||||
#define __OPENCV_MATLAB_TEST_GENERATOR_HPP_ |
||||
|
||||
namespace cv { |
||||
|
||||
CV_EXPORTS_W int rand( ); |
||||
|
||||
}; |
||||
|
||||
#endif |
@ -0,0 +1,11 @@ |
||||
% add the opencv bindings folder |
||||
addpath .. |
||||
|
||||
%setup the tests |
||||
opencv_tests = OpenCVTest(); |
||||
|
||||
%run the tests |
||||
result = run(opencv_tests); |
||||
|
||||
% shutdown |
||||
exit(); |
@ -0,0 +1,2 @@ |
||||
set(the_description "Optical Flow Algorithms") |
||||
ocv_define_module(optflow opencv_core opencv_imgproc opencv_video opencv_highgui) |
@ -0,0 +1,53 @@ |
||||
Dense Optical Flow |
||||
=================== |
||||
|
||||
Dense optical flow algorithms compute motion for each point |
||||
|
||||
calcOpticalFlowSF |
||||
----------------- |
||||
Calculate an optical flow using "SimpleFlow" algorithm. |
||||
|
||||
.. ocv:function:: void calcOpticalFlowSF( InputArray from, InputArray to, OutputArray flow, int layers, int averaging_block_size, int max_flow ) |
||||
|
||||
.. ocv:function:: calcOpticalFlowSF( InputArray from, InputArray to, OutputArray flow, int layers, int averaging_block_size, int max_flow, double sigma_dist, double sigma_color, int postprocess_window, double sigma_dist_fix, double sigma_color_fix, double occ_thr, int upscale_averaging_radius, double upscale_sigma_dist, double upscale_sigma_color, double speed_up_thr ) |
||||
|
||||
:param prev: First 8-bit 3-channel image. |
||||
|
||||
:param next: Second 8-bit 3-channel image of the same size as ``prev`` |
||||
|
||||
:param flow: computed flow image that has the same size as ``prev`` and type ``CV_32FC2`` |
||||
|
||||
:param layers: Number of layers |
||||
|
||||
:param averaging_block_size: Size of block through which we sum up when calculate cost function for pixel |
||||
|
||||
:param max_flow: maximal flow that we search at each level |
||||
|
||||
:param sigma_dist: vector smooth spatial sigma parameter |
||||
|
||||
:param sigma_color: vector smooth color sigma parameter |
||||
|
||||
:param postprocess_window: window size for postprocess cross bilateral filter |
||||
|
||||
:param sigma_dist_fix: spatial sigma for postprocess cross bilateralf filter |
||||
|
||||
:param sigma_color_fix: color sigma for postprocess cross bilateral filter |
||||
|
||||
:param occ_thr: threshold for detecting occlusions |
||||
|
||||
:param upscale_averaging_radius: window size for bilateral upscale operation |
||||
|
||||
:param upscale_sigma_dist: spatial sigma for bilateral upscale operation |
||||
|
||||
:param upscale_sigma_color: color sigma for bilateral upscale operation |
||||
|
||||
:param speed_up_thr: threshold to detect point with irregular flow - where flow should be recalculated after upscale |
||||
|
||||
See [Tao2012]_. And site of project - http://graphics.berkeley.edu/papers/Tao-SAN-2012-05/. |
||||
|
||||
.. note:: |
||||
|
||||
* An example using the simpleFlow algorithm can be found at samples/simpleflow_demo.cpp |
||||
|
||||
|
||||
.. [Tao2012] Michael Tao, Jiamin Bai, Pushmeet Kohli and Sylvain Paris. SimpleFlow: A Non-iterative, Sublinear Optical Flow Algorithm. Computer Graphics Forum (Eurographics 2012) |
@ -0,0 +1,125 @@ |
||||
Motion Templates |
||||
================ |
||||
|
||||
Motion templates is alternative technique for detecting motion and computing its direction. |
||||
See ``samples/motempl.py``. |
||||
|
||||
updateMotionHistory |
||||
----------------------- |
||||
Updates the motion history image by a moving silhouette. |
||||
|
||||
.. ocv:function:: void updateMotionHistory( InputArray silhouette, InputOutputArray mhi, double timestamp, double duration ) |
||||
|
||||
.. ocv:pyfunction:: cv2.updateMotionHistory(silhouette, mhi, timestamp, duration) -> mhi |
||||
|
||||
:param silhouette: Silhouette mask that has non-zero pixels where the motion occurs. |
||||
|
||||
:param mhi: Motion history image that is updated by the function (single-channel, 32-bit floating-point). |
||||
|
||||
:param timestamp: Current time in milliseconds or other units. |
||||
|
||||
:param duration: Maximal duration of the motion track in the same units as ``timestamp`` . |
||||
|
||||
The function updates the motion history image as follows: |
||||
|
||||
.. math:: |
||||
|
||||
\texttt{mhi} (x,y)= \forkthree{\texttt{timestamp}}{if $\texttt{silhouette}(x,y) \ne 0$}{0}{if $\texttt{silhouette}(x,y) = 0$ and $\texttt{mhi} < (\texttt{timestamp} - \texttt{duration})$}{\texttt{mhi}(x,y)}{otherwise} |
||||
|
||||
That is, MHI pixels where the motion occurs are set to the current ``timestamp`` , while the pixels where the motion happened last time a long time ago are cleared. |
||||
|
||||
The function, together with |
||||
:ocv:func:`calcMotionGradient` and |
||||
:ocv:func:`calcGlobalOrientation` , implements a motion templates technique described in |
||||
[Davis97]_ and [Bradski00]_. |
||||
|
||||
|
||||
calcMotionGradient |
||||
---------------------- |
||||
Calculates a gradient orientation of a motion history image. |
||||
|
||||
.. ocv:function:: void calcMotionGradient( InputArray mhi, OutputArray mask, OutputArray orientation, double delta1, double delta2, int apertureSize=3 ) |
||||
|
||||
.. ocv:pyfunction:: cv2.calcMotionGradient(mhi, delta1, delta2[, mask[, orientation[, apertureSize]]]) -> mask, orientation |
||||
|
||||
:param mhi: Motion history single-channel floating-point image. |
||||
|
||||
:param mask: Output mask image that has the type ``CV_8UC1`` and the same size as ``mhi`` . Its non-zero elements mark pixels where the motion gradient data is correct. |
||||
|
||||
:param orientation: Output motion gradient orientation image that has the same type and the same size as ``mhi`` . Each pixel of the image is a motion orientation, from 0 to 360 degrees. |
||||
|
||||
:param delta1: Minimal (or maximal) allowed difference between ``mhi`` values within a pixel neighborhood. |
||||
|
||||
:param delta2: Maximal (or minimal) allowed difference between ``mhi`` values within a pixel neighborhood. That is, the function finds the minimum ( :math:`m(x,y)` ) and maximum ( :math:`M(x,y)` ) ``mhi`` values over :math:`3 \times 3` neighborhood of each pixel and marks the motion orientation at :math:`(x, y)` as valid only if |
||||
|
||||
.. math:: |
||||
|
||||
\min ( \texttt{delta1} , \texttt{delta2} ) \le M(x,y)-m(x,y) \le \max ( \texttt{delta1} , \texttt{delta2} ). |
||||
|
||||
:param apertureSize: Aperture size of the :ocv:func:`Sobel` operator. |
||||
|
||||
The function calculates a gradient orientation at each pixel |
||||
:math:`(x, y)` as: |
||||
|
||||
.. math:: |
||||
|
||||
\texttt{orientation} (x,y)= \arctan{\frac{d\texttt{mhi}/dy}{d\texttt{mhi}/dx}} |
||||
|
||||
In fact, |
||||
:ocv:func:`fastAtan2` and |
||||
:ocv:func:`phase` are used so that the computed angle is measured in degrees and covers the full range 0..360. Also, the ``mask`` is filled to indicate pixels where the computed angle is valid. |
||||
|
||||
.. note:: |
||||
|
||||
* (Python) An example on how to perform a motion template technique can be found at opencv_source_code/samples/python2/motempl.py |
||||
|
||||
calcGlobalOrientation |
||||
------------------------- |
||||
Calculates a global motion orientation in a selected region. |
||||
|
||||
.. ocv:function:: double calcGlobalOrientation( InputArray orientation, InputArray mask, InputArray mhi, double timestamp, double duration ) |
||||
|
||||
.. ocv:pyfunction:: cv2.calcGlobalOrientation(orientation, mask, mhi, timestamp, duration) -> retval |
||||
|
||||
:param orientation: Motion gradient orientation image calculated by the function :ocv:func:`calcMotionGradient` . |
||||
|
||||
:param mask: Mask image. It may be a conjunction of a valid gradient mask, also calculated by :ocv:func:`calcMotionGradient` , and the mask of a region whose direction needs to be calculated. |
||||
|
||||
:param mhi: Motion history image calculated by :ocv:func:`updateMotionHistory` . |
||||
|
||||
:param timestamp: Timestamp passed to :ocv:func:`updateMotionHistory` . |
||||
|
||||
:param duration: Maximum duration of a motion track in milliseconds, passed to :ocv:func:`updateMotionHistory` . |
||||
|
||||
The function calculates an average |
||||
motion direction in the selected region and returns the angle between |
||||
0 degrees and 360 degrees. The average direction is computed from |
||||
the weighted orientation histogram, where a recent motion has a larger |
||||
weight and the motion occurred in the past has a smaller weight, as recorded in ``mhi`` . |
||||
|
||||
|
||||
segmentMotion |
||||
------------- |
||||
Splits a motion history image into a few parts corresponding to separate independent motions (for example, left hand, right hand). |
||||
|
||||
.. ocv:function:: void segmentMotion(InputArray mhi, OutputArray segmask, vector<Rect>& boundingRects, double timestamp, double segThresh) |
||||
|
||||
.. ocv:pyfunction:: cv2.segmentMotion(mhi, timestamp, segThresh[, segmask]) -> segmask, boundingRects |
||||
|
||||
:param mhi: Motion history image. |
||||
|
||||
:param segmask: Image where the found mask should be stored, single-channel, 32-bit floating-point. |
||||
|
||||
:param boundingRects: Vector containing ROIs of motion connected components. |
||||
|
||||
:param timestamp: Current time in milliseconds or other units. |
||||
|
||||
:param segThresh: Segmentation threshold that is recommended to be equal to the interval between motion history "steps" or greater. |
||||
|
||||
|
||||
The function finds all of the motion segments and marks them in ``segmask`` with individual values (1,2,...). It also computes a vector with ROIs of motion connected components. After that the motion direction for every component can be calculated with :ocv:func:`calcGlobalOrientation` using the extracted mask of the particular component. |
||||
|
||||
|
||||
.. [Bradski00] Davis, J.W. and Bradski, G.R. "Motion Segmentation and Pose Recognition with Motion History Gradients", WACV00, 2000 |
||||
|
||||
.. [Davis97] Davis, J.W. and Bobick, A.F. "The Representation and Recognition of Action Using Temporal Templates", CVPR97, 1997 |
@ -0,0 +1,13 @@ |
||||
****************************************** |
||||
text. Optical Flow Algorithms |
||||
****************************************** |
||||
|
||||
.. highlight:: cpp |
||||
|
||||
The opencv_optflow module includes different algorithms for tracking points |
||||
|
||||
.. toctree:: |
||||
:maxdepth: 2 |
||||
|
||||
dense_optflow |
||||
motion_templates |