From fbfccffbaa04f2ac45c10d6d232bfb5caaf3c77c Mon Sep 17 00:00:00 2001 From: Valentina Kustikova Date: Sat, 9 Oct 2010 11:36:06 +0000 Subject: [PATCH] Integration object detection using Latent SVM. Sample was added. --- modules/objdetect/CMakeLists.txt | 2 +- .../include/opencv2/objdetect/objdetect.hpp | 123 ++ modules/objdetect/src/_distancetransform.h | 140 ++ modules/objdetect/src/_error.h | 16 + modules/objdetect/src/_fft.h | 81 + modules/objdetect/src/_latentsvm.h | 401 +++++ modules/objdetect/src/_lsvmparser.h | 66 + modules/objdetect/src/_matching.h | 396 +++++ modules/objdetect/src/_resizeimg.h | 11 + modules/objdetect/src/_routine.h | 36 + modules/objdetect/src/_types.h | 93 ++ modules/objdetect/src/distancetransform.cpp | 395 +++++ modules/objdetect/src/featurepyramid.cpp | 576 +++++++ modules/objdetect/src/fft.cpp | 246 +++ modules/objdetect/src/latentsvm.cpp | 611 +++++++ modules/objdetect/src/latentsvmdetector.cpp | 134 ++ modules/objdetect/src/lsvmparser.cpp | 800 +++++++++ modules/objdetect/src/matching.cpp | 1462 +++++++++++++++++ modules/objdetect/src/precomp.hpp | 2 + modules/objdetect/src/resizeimg.cpp | 244 +++ modules/objdetect/src/routine.cpp | 103 ++ samples/c/000028.jpg | Bin 0 -> 65555 bytes samples/c/cat.xml | Bin 0 -> 210308 bytes samples/c/latentsvmdetect.cpp | 49 + 24 files changed, 5986 insertions(+), 1 deletion(-) create mode 100644 modules/objdetect/src/_distancetransform.h create mode 100644 modules/objdetect/src/_error.h create mode 100644 modules/objdetect/src/_fft.h create mode 100644 modules/objdetect/src/_latentsvm.h create mode 100644 modules/objdetect/src/_lsvmparser.h create mode 100644 modules/objdetect/src/_matching.h create mode 100644 modules/objdetect/src/_resizeimg.h create mode 100644 modules/objdetect/src/_routine.h create mode 100644 modules/objdetect/src/_types.h create mode 100644 modules/objdetect/src/distancetransform.cpp create mode 100644 modules/objdetect/src/featurepyramid.cpp create mode 100644 modules/objdetect/src/fft.cpp create mode 100644 modules/objdetect/src/latentsvm.cpp create mode 100644 modules/objdetect/src/latentsvmdetector.cpp create mode 100644 modules/objdetect/src/lsvmparser.cpp create mode 100644 modules/objdetect/src/matching.cpp create mode 100644 modules/objdetect/src/resizeimg.cpp create mode 100644 modules/objdetect/src/routine.cpp create mode 100644 samples/c/000028.jpg create mode 100644 samples/c/cat.xml create mode 100644 samples/c/latentsvmdetect.cpp diff --git a/modules/objdetect/CMakeLists.txt b/modules/objdetect/CMakeLists.txt index 995748a8c7..65f4795fdc 100644 --- a/modules/objdetect/CMakeLists.txt +++ b/modules/objdetect/CMakeLists.txt @@ -1 +1 @@ -define_opencv_module(objdetect opencv_core opencv_imgproc) +define_opencv_module(objdetect opencv_core opencv_imgproc opencv_highgui) diff --git a/modules/objdetect/include/opencv2/objdetect/objdetect.hpp b/modules/objdetect/include/opencv2/objdetect/objdetect.hpp index 78e8d3ffa8..c86a81cf90 100644 --- a/modules/objdetect/include/opencv2/objdetect/objdetect.hpp +++ b/modules/objdetect/include/opencv2/objdetect/objdetect.hpp @@ -139,6 +139,129 @@ CVAPI(void) cvSetImagesForHaarClassifierCascade( CvHaarClassifierCascade* cascad CVAPI(int) cvRunHaarClassifierCascade( const CvHaarClassifierCascade* cascade, CvPoint pt, int start_stage CV_DEFAULT(0)); + +/****************************************************************************************\ +* Latent SVM Object Detection functions * +\****************************************************************************************/ + +// DataType: STRUCT position +// Structure describes the position of the filter in the feature pyramid +// l - level in the feature pyramid +// (x, y) - coordinate in level l +typedef struct +{ + unsigned int x; + unsigned int y; + unsigned int l; +} position; + +// DataType: STRUCT filterObject +// Description of the filter, which corresponds to the part of the object +// V - ideal (penalty = 0) position of the partial filter +// from the root filter position (V_i in the paper) +// penaltyFunction - vector describes penalty function (d_i in the paper) +// pf[0] * x + pf[1] * y + pf[2] * x^2 + pf[3] * y^2 +// FILTER DESCRIPTION +// Rectangular map (sizeX x sizeY), +// every cell stores feature vector (dimension = p) +// H - matrix of feature vectors +// to set and get feature vectors (i,j) +// used formula H[(j * sizeX + i) * p + k], where +// k - component of feature vector in cell (i, j) +// END OF FILTER DESCRIPTION +// xp - auxillary parameter for internal use +// size of row in feature vectors +// (yp = (int) (p / xp); p = xp * yp) +typedef struct{ + position V; + float fineFunction[4]; + unsigned int sizeX; + unsigned int sizeY; + unsigned int p; + unsigned int xp; + float *H; +} filterObject; + +// data type: STRUCT CvLatentSvmDetector +// structure contains internal representation of trained Latent SVM detector +// num_filters - total number of filters (root plus part) in model +// num_components - number of components in model +// num_part_filters - array containing number of part filters for each component +// filters - root and part filters for all model components +// b - biases for all model components +// score_threshold - confidence level threshold +typedef struct CvLatentSvmDetector +{ + int num_filters; + int num_components; + int* num_part_filters; + filterObject** filters; + float* b; + float score_threshold; +} +CvLatentSvmDetector; + +// data type: STRUCT CvObjectDetection +// structure contains the bounding box and confidence level for detected object +// rect - bounding box for a detected object +// score - confidence level +typedef struct CvObjectDetection +{ + CvRect rect; + float score; +} CvObjectDetection; + +//////////////// Object Detection using Latent SVM ////////////// + + +/* +// load trained detector from a file +// +// API +// CvLatentSvmDetector* cvLoadLatentSvmDetector(const char* filename); +// INPUT +// filename - path to the file containing the parameters of + - trained Latent SVM detector +// OUTPUT +// trained Latent SVM detector in internal representation +*/ +CVAPI(CvLatentSvmDetector*) cvLoadLatentSvmDetector(const char* filename); + +/* +// release memory allocated for CvLatentSvmDetector structure +// +// API +// void cvReleaseLatentSvmDetector(CvLatentSvmDetector** detector); +// INPUT +// detector - CvLatentSvmDetector structure to be released +// OUTPUT +*/ +CVAPI(void) cvReleaseLatentSvmDetector(CvLatentSvmDetector** detector); + +/* +// find rectangular regions in the given image that are likely +// to contain objects and corresponding confidence levels +// +// API +// CvSeq* cvLatentSvmDetectObjects(const IplImage* image, +// CvLatentSvmDetector* detector, +// CvMemStorage* storage, +// float overlap_threshold = 0.5f); +// INPUT +// image - image to detect objects in +// detector - Latent SVM detector in internal representation +// storage - memory storage to store the resultant sequence +// of the object candidate rectangles +// overlap_threshold - threshold for the non-maximum suppression algorithm + = 0.5f [here will be the reference to original paper] +// OUTPUT +// sequence of detected objects (bounding boxes and confidence levels stored in CvObjectDetection structures) +*/ +CVAPI(CvSeq*) cvLatentSvmDetectObjects(IplImage* image, + CvLatentSvmDetector* detector, + CvMemStorage* storage, + float overlap_threshold CV_DEFAULT(0.5f)); + #ifdef __cplusplus } diff --git a/modules/objdetect/src/_distancetransform.h b/modules/objdetect/src/_distancetransform.h new file mode 100644 index 0000000000..8000405113 --- /dev/null +++ b/modules/objdetect/src/_distancetransform.h @@ -0,0 +1,140 @@ +#ifndef DIST_TRANSFORM +#define DIST_TRANSFORM + +#include "precomp.hpp" +#include "_types.h" +#include "_error.h" + + +/* +// Computation the point of intersection functions +// (parabolas on the variable y) +// a(y - q1) + b(q1 - y)(q1 - y) + f[q1] +// a(y - q2) + b(q2 - y)(q2 - y) + f[q2] +// +// API +// int GetPointOfIntersection(const F_type *f, + const F_type a, const F_type b, + int q1, int q2, F_type *point); +// INPUT +// f - function on the regular grid +// a - coefficient of the function +// b - coefficient of the function +// q1 - parameter of the function +// q2 - parameter of the function +// OUTPUT +// point - point of intersection +// RESULT +// Error status +*/ +int GetPointOfIntersection(const float *f, + const float a, const float b, + int q1, int q2, float *point); + +/* +// Decision of one dimensional problem generalized distance transform +// on the regular grid at all points +// min (a(y' - y) + b(y' - y)(y' - y) + f(y')) (on y') +// +// API +// int DistanceTransformOneDimensionalProblem(const F_type *f, const int n, + const F_type a, const F_type b, + F_type *distanceTransform, + int *points); +// INPUT +// f - function on the regular grid +// n - grid dimension +// a - coefficient of optimizable function +// b - coefficient of optimizable function +// OUTPUT +// distanceTransform - values of generalized distance transform +// points - arguments that corresponds to the optimal value of function +// RESULT +// Error status +*/ +int DistanceTransformOneDimensionalProblem(const float *f, const int n, + const float a, const float b, + float *distanceTransform, + int *points); + +/* +// Computation next cycle element +// +// API +// int GetNextCycleElement(int k, int n, int q); +// INPUT +// k - index of the previous cycle element +// n - number of matrix rows +// q - parameter that equal (number_of_rows * number_of_columns - 1) +// OUTPUT +// None +// RESULT +// Next cycle element +*/ +int GetNextCycleElement(int k, int n, int q); + +/* +// Transposition of cycle elements +// +// API +// void TransposeCycleElements(F_type *a, int *cycle, int cycle_len); +// INPUT +// a - initial matrix +// cycle - cycle +// cycle_len - cycle length +// OUTPUT +// a - matrix with transposed elements +// RESULT +// None +*/ +void TransposeCycleElements(float *a, int *cycle, int cycle_len); + +/* +// Getting transposed matrix +// +// API +// void Transpose(F_type *a, int n, int m); +// INPUT +// a - initial matrix +// n - number of rows +// m - number of columns +// OUTPUT +// a - transposed matrix +// RESULT +// Error status +*/ +void Transpose(float *a, int n, int m); + +/* +// Decision of two dimensional problem generalized distance transform +// on the regular grid at all points +// min{d2(y' - y) + d4(y' - y)(y' - y) + + min(d1(x' - x) + d3(x' - x)(x' - x) + f(x',y'))} (on x', y') +// +// API +// int DistanceTransformTwoDimensionalProblem(const F_type *f, + const int n, const int m, + const F_type coeff[4], + F_type *distanceTransform, + int *pointsX, int *pointsY); +// INPUT +// f - function on the regular grid +// n - number of rows +// m - number of columns +// coeff - coefficients of optimizable function + coeff[0] = d1, coeff[1] = d2, + coeff[2] = d3, coeff[3] = d4 +// OUTPUT +// distanceTransform - values of generalized distance transform +// pointsX - arguments x' that correspond to the optimal value +// pointsY - arguments y' that correspond to the optimal value +// RESULT +// Error status +*/ +int DistanceTransformTwoDimensionalProblem(const float *f, + const int n, const int m, + const float coeff[4], + float *distanceTransform, + int *pointsX, int *pointsY); + +#endif \ No newline at end of file diff --git a/modules/objdetect/src/_error.h b/modules/objdetect/src/_error.h new file mode 100644 index 0000000000..e783dfa14f --- /dev/null +++ b/modules/objdetect/src/_error.h @@ -0,0 +1,16 @@ +#ifndef SVM_ERROR +#define SVM_ERROR + +#define LATENT_SVM_OK 0 +#define DISTANCE_TRANSFORM_OK 1 +#define DISTANCE_TRANSFORM_GET_INTERSECTION_ERROR -1 +#define DISTANCE_TRANSFORM_ERROR -2 +#define DISTANCE_TRANSFORM_EQUAL_POINTS -3 +#define LATENT_SVM_GET_FEATURE_PYRAMID_FAILED -4 +#define LATENT_SVM_SEARCH_OBJECT_FAILED -5 +#define LATENT_SVM_FAILED_SUPERPOSITION -6 +#define FILTER_OUT_OF_BOUNDARIES -7 +#define FFT_OK 2 +#define FFT_ERROR -8 + +#endif \ No newline at end of file diff --git a/modules/objdetect/src/_fft.h b/modules/objdetect/src/_fft.h new file mode 100644 index 0000000000..a2d15d4cc4 --- /dev/null +++ b/modules/objdetect/src/_fft.h @@ -0,0 +1,81 @@ +#ifndef _FFT_H +#define _FFT_H + +#include "precomp.hpp" +#include "_types.h" +#include "_error.h" + +#include + +/* +// 1-dimensional FFT +// +// API +// int fft(float *x_in, float *x_out, int n, int shift); +// INPUT +// x_in - input signal +// n - number of elements for searching Fourier image +// shift - shift between input elements +// OUTPUT +// x_out - output signal (contains 2n elements in order + Re(x_in[0]), Im(x_in[0]), Re(x_in[1]), Im(x_in[1]) and etc.) +// RESULT +// Error status +*/ +int fft(float *x_in, float *x_out, int n, int shift); + +/* +// Inverse 1-dimensional FFT +// +// API +// int fftInverse(float *x_in, float *x_out, int n, int shift); +// INPUT +// x_in - Fourier image of 1d input signal(contains 2n elements + in order Re(x_in[0]), Im(x_in[0]), + Re(x_in[1]), Im(x_in[1]) and etc.) +// n - number of elements for searching counter FFT image +// shift - shift between input elements +// OUTPUT +// x_in - input signal (contains n elements) +// RESULT +// Error status +*/ +int fftInverse(float *x_in, float *x_out, int n, int shift); + +/* +// 2-dimensional FFT +// +// API +// int fft2d(float *x_in, float *x_out, int numRows, int numColls); +// INPUT +// x_in - input signal (matrix, launched by rows) +// numRows - number of rows +// numColls - number of collumns +// OUTPUT +// x_out - output signal (contains (2 * numRows * numColls) elements + in order Re(x_in[0][0]), Im(x_in[0][0]), + Re(x_in[0][1]), Im(x_in[0][1]) and etc.) +// RESULT +// Error status +*/ +int fft2d(float *x_in, float *x_out, int numRows, int numColls); + +/* +// Inverse 2-dimensional FFT +// +// API +// int fftInverse2d(float *x_in, float *x_out, int numRows, int numColls); +// INPUT +// x_in - Fourier image of matrix (contains (2 * numRows * numColls) + elements in order Re(x_in[0][0]), Im(x_in[0][0]), + Re(x_in[0][1]), Im(x_in[0][1]) and etc.) +// numRows - number of rows +// numColls - number of collumns +// OUTPUT +// x_out - initial signal (matrix, launched by rows) +// RESULT +// Error status +*/ +int fftInverse2d(float *x_in, float *x_out, int numRows, int numColls); + +#endif \ No newline at end of file diff --git a/modules/objdetect/src/_latentsvm.h b/modules/objdetect/src/_latentsvm.h new file mode 100644 index 0000000000..7bf4a11b1f --- /dev/null +++ b/modules/objdetect/src/_latentsvm.h @@ -0,0 +1,401 @@ +/*****************************************************************************/ +/* Latent SVM prediction API */ +/*****************************************************************************/ + +#ifndef SVM_LATENTSVM +#define SVM_LATENTSVM + +#include +#include "precomp.hpp" +#include "_types.h" +#include "_error.h" +#include "_routine.h" + +////////////////////////////////////////////////////////////// +// Building feature pyramid +// (pyramid constructed both contrast and non-contrast image) +////////////////////////////////////////////////////////////// + +/* +// Getting feature pyramid +// +// API +// int getFeaturePyramid(IplImage * image, const filterObject **all_F, + const int n_f, + const int lambda, const int k, + const int startX, const int startY, + const int W, const int H, featurePyramid **maps); +// INPUT +// image - image +// lambda - resize scale +// k - size of cells +// startX - X coordinate of the image rectangle to search +// startY - Y coordinate of the image rectangle to search +// W - width of the image rectangle to search +// H - height of the image rectangle to search +// OUTPUT +// maps - feature maps for all levels +// RESULT +// Error status +*/ +int getFeaturePyramid(IplImage * image, + const int lambda, const int k, + const int startX, const int startY, + const int W, const int H, featurePyramid **maps); + +/* +// Getting feature map for the selected subimage +// +// API +// int getFeatureMaps(const IplImage * image, const int k, featureMap **map); +// INPUT +// image - selected subimage +// k - size of cells +// OUTPUT +// map - feature map +// RESULT +// Error status +*/ +int getFeatureMaps_dp(const IplImage * image, const int k, featureMap **map); + + +/* +// Feature map Normalization and Truncation +// +// API +// int normalizationAndTruncationFeatureMaps(featureMap *map, const float alfa); +// INPUT +// map - feature map +// alfa - truncation threshold +// OUTPUT +// map - truncated and normalized feature map +// RESULT +// Error status +*/ +int normalizationAndTruncationFeatureMaps(featureMap *map, const float alfa); + +/* +// Feature map reduction +// In each cell we reduce dimension of the feature vector +// according to original paper special procedure +// +// API +// int PCAFeatureMaps(featureMap *map) +// INPUT +// map - feature map +// OUTPUT +// map - feature map +// RESULT +// Error status +*/ +int PCAFeatureMaps(featureMap *map); + +////////////////////////////////////////////////////////////// +// search object +////////////////////////////////////////////////////////////// + +/* +// Transformation filter displacement from the block space +// to the space of pixels at the initial image +// +// API +// int convertPoints(int countLevel, int lambda, + int initialImageLevel, + CvPoint *points, int *levels, + CvPoint **partsDisplacement, int kPoints, int n, + int maxXBorder, + int maxYBorder); +// INPUT +// countLevel - the number of levels in the feature pyramid +// lambda - method parameter +// initialImageLevel - level of feature pyramid that contains feature map + for initial image +// points - the set of root filter positions (in the block space) +// levels - the set of levels +// partsDisplacement - displacement of part filters (in the block space) +// kPoints - number of root filter positions +// n - number of part filters +// maxXBorder - the largest root filter size (X-direction) +// maxYBorder - the largest root filter size (Y-direction) +// OUTPUT +// points - the set of root filter positions (in the space of pixels) +// partsDisplacement - displacement of part filters (in the space of pixels) +// RESULT +// Error status +*/ +int convertPoints(int countLevel, int lambda, + int initialImageLevel, + CvPoint *points, int *levels, + CvPoint **partsDisplacement, int kPoints, int n, + int maxXBorder, + int maxYBorder); + +/* +// Elimination boxes that are outside the image boudaries +// +// API +// int clippingBoxes(int width, int height, + CvPoint *points, int kPoints); +// INPUT +// width - image wediht +// height - image heigth +// points - a set of points (coordinates of top left or + bottom right corners) +// kPoints - points number +// OUTPUT +// points - updated points (if coordinates less than zero then + set zero coordinate, if coordinates more than image + size then set coordinates equal image size) +// RESULT +// Error status +*/ +#ifdef __cplusplus +extern "C" +#endif +int clippingBoxes(int width, int height, + CvPoint *points, int kPoints); + +/* +// Creation feature pyramid with nullable border +// +// API +// featurePyramid* createFeaturePyramidWithBorder(const IplImage *image, + int maxXBorder, int maxYBorder); + +// INPUT +// image - initial image +// maxXBorder - the largest root filter size (X-direction) +// maxYBorder - the largest root filter size (Y-direction) +// OUTPUT +// RESULT +// Feature pyramid with nullable border +*/ +#ifdef __cplusplus +extern "C" +#endif +featurePyramid* createFeaturePyramidWithBorder(IplImage *image, + int maxXBorder, int maxYBorder); + +/* +// Computation of the root filter displacement and values of score function +// +// API +// int searchObject(const featurePyramid *H, const filterObject **all_F, int n, + float b, + int maxXBorder, + int maxYBorder, + CvPoint **points, int **levels, int *kPoints, float *score, + CvPoint ***partsDisplacement); +// INPUT +// H - feature pyramid +// all_F - the set of filters (the first element is root filter, + other elements - part filters) +// n - the number of part filters +// b - linear term of the score function +// maxXBorder - the largest root filter size (X-direction) +// maxYBorder - the largest root filter size (Y-direction) +// OUTPUT +// points - positions (x, y) of the upper-left corner + of root filter frame +// levels - levels that correspond to each position +// kPoints - number of positions +// score - value of the score function +// partsDisplacement - part filters displacement for each position + of the root filter +// RESULT +// Error status +*/ +int searchObject(const featurePyramid *H, const filterObject **all_F, int n, + float b, + int maxXBorder, + int maxYBorder, + CvPoint **points, int **levels, int *kPoints, float *score, + CvPoint ***partsDisplacement); + +/* +// Computation of the root filter displacement and values of score function +// +// API +// int searchObjectThreshold(const featurePyramid *H, + const filterObject **all_F, int n, + float b, + int maxXBorder, int maxYBorder, + float scoreThreshold, + CvPoint **points, int **levels, int *kPoints, + float **score, CvPoint ***partsDisplacement); +// INPUT +// H - feature pyramid +// all_F - the set of filters (the first element is root filter, + other elements - part filters) +// n - the number of part filters +// b - linear term of the score function +// maxXBorder - the largest root filter size (X-direction) +// maxYBorder - the largest root filter size (Y-direction) +// scoreThreshold - score threshold +// OUTPUT +// points - positions (x, y) of the upper-left corner + of root filter frame +// levels - levels that correspond to each position +// kPoints - number of positions +// score - values of the score function +// partsDisplacement - part filters displacement for each position + of the root filter +// RESULT +// Error status +*/ +int searchObjectThreshold(const featurePyramid *H, + const filterObject **all_F, int n, + float b, + int maxXBorder, int maxYBorder, + float scoreThreshold, + CvPoint **points, int **levels, int *kPoints, + float **score, CvPoint ***partsDisplacement); + +/* +// Computation root filters displacement and values of score function +// +// API +// int searchObjectThresholdSomeComponents(const featurePyramid *H, + const filterObject **filters, + int kComponents, const int *kPartFilters, + const float *b, float scoreThreshold, + CvPoint **points, CvPoint **oppPoints, + float **score, int *kPoints); +// INPUT +// H - feature pyramid +// filters - filters (root filter then it's part filters, etc.) +// kComponents - root filters number +// kPartFilters - array of part filters number for each component +// b - array of linear terms +// scoreThreshold - score threshold +// OUTPUT +// points - root filters displacement (top left corners) +// oppPoints - root filters displacement (bottom right corners) +// score - array of score values +// kPoints - number of boxes +// RESULT +// Error status +*/ +#ifdef __cplusplus +extern "C" +#endif +int searchObjectThresholdSomeComponents(const featurePyramid *H, + const filterObject **filters, + int kComponents, const int *kPartFilters, + const float *b, float scoreThreshold, + CvPoint **points, CvPoint **oppPoints, + float **score, int *kPoints); + +/* +// Compute opposite point for filter box +// +// API +// int getOppositePoint(CvPoint point, + int sizeX, int sizeY, + float step, int degree, + CvPoint *oppositePoint); + +// INPUT +// point - coordinates of filter top left corner + (in the space of pixels) +// (sizeX, sizeY) - filter dimension in the block space +// step - scaling factor +// degree - degree of the scaling factor +// OUTPUT +// oppositePoint - coordinates of filter bottom corner + (in the space of pixels) +// RESULT +// Error status +*/ +int getOppositePoint(CvPoint point, + int sizeX, int sizeY, + float step, int degree, + CvPoint *oppositePoint); + +/* +// Drawing root filter boxes +// +// API +// int showRootFilterBoxes(const IplImage *image, + const filterObject *filter, + CvPoint *points, int *levels, int kPoints, + CvScalar color, int thickness, + int line_type, int shift); +// INPUT +// image - initial image +// filter - root filter object +// points - a set of points +// levels - levels of feature pyramid +// kPoints - number of points +// color - line color for each box +// thickness - line thickness +// line_type - line type +// shift - shift +// OUTPUT +// window contained initial image and filter boxes +// RESULT +// Error status +*/ +int showRootFilterBoxes(IplImage *image, + const filterObject *filter, + CvPoint *points, int *levels, int kPoints, + CvScalar color, int thickness, + int line_type, int shift); + +/* +// Drawing part filter boxes +// +// API +// int showPartFilterBoxes(const IplImage *image, + const filterObject *filter, + CvPoint *points, int *levels, int kPoints, + CvScalar color, int thickness, + int line_type, int shift); +// INPUT +// image - initial image +// filters - a set of part filters +// n - number of part filters +// partsDisplacement - a set of points +// levels - levels of feature pyramid +// kPoints - number of foot filter positions +// color - line color for each box +// thickness - line thickness +// line_type - line type +// shift - shift +// OUTPUT +// window contained initial image and filter boxes +// RESULT +// Error status +*/ +int showPartFilterBoxes(IplImage *image, + const filterObject **filters, + int n, CvPoint **partsDisplacement, + int *levels, int kPoints, + CvScalar color, int thickness, + int line_type, int shift); + +/* +// Drawing boxes +// +// API +// int showBoxes(const IplImage *img, + const CvPoint *points, const CvPoint *oppositePoints, int kPoints, + CvScalar color, int thickness, int line_type, int shift); +// INPUT +// img - initial image +// points - top left corner coordinates +// oppositePoints - right bottom corner coordinates +// kPoints - points number +// color - line color for each box +// thickness - line thickness +// line_type - line type +// shift - shift +// OUTPUT +// RESULT +// Error status +*/ +int showBoxes(IplImage *img, + const CvPoint *points, const CvPoint *oppositePoints, int kPoints, + CvScalar color, int thickness, int line_type, int shift); + +#endif \ No newline at end of file diff --git a/modules/objdetect/src/_lsvmparser.h b/modules/objdetect/src/_lsvmparser.h new file mode 100644 index 0000000000..3d5cf56ffc --- /dev/null +++ b/modules/objdetect/src/_lsvmparser.h @@ -0,0 +1,66 @@ +#ifndef LSVM_PARSER +#define LSVM_PARSER + +#include "precomp.hpp" +#include "_types.h" + +#define MODEL 1 +#define P 2 +#define COMP 3 +#define SCORE 4 +#define RFILTER 100 +#define PFILTERs 101 +#define PFILTER 200 +#define SIZEX 150 +#define SIZEY 151 +#define WEIGHTS 152 +#define TAGV 300 +#define Vx 350 +#define Vy 351 +#define TAGD 400 +#define Dx 451 +#define Dy 452 +#define Dxx 453 +#define Dyy 454 +#define BTAG 500 + +#define STEP_END 1000 + +#define EMODEL (STEP_END + MODEL) +#define EP (STEP_END + P) +#define ECOMP (STEP_END + COMP) +#define ESCORE (STEP_END + SCORE) +#define ERFILTER (STEP_END + RFILTER) +#define EPFILTERs (STEP_END + PFILTERs) +#define EPFILTER (STEP_END + PFILTER) +#define ESIZEX (STEP_END + SIZEX) +#define ESIZEY (STEP_END + SIZEY) +#define EWEIGHTS (STEP_END + WEIGHTS) +#define ETAGV (STEP_END + TAGV) +#define EVx (STEP_END + Vx) +#define EVy (STEP_END + Vy) +#define ETAGD (STEP_END + TAGD) +#define EDx (STEP_END + Dx) +#define EDy (STEP_END + Dy) +#define EDxx (STEP_END + Dxx) +#define EDyy (STEP_END + Dyy) +#define EBTAG (STEP_END + BTAG) + +//extern "C" { + void LSVMparser(const char * filename, filterObject *** model, int *last, int *max, int **comp, float **b, int *count, float * score); +#ifdef __cplusplus +extern "C" +#endif + int loadModel( + // Входные параметры + const char *modelPath,// - путь до файла с моделью + + // Выходные параметры + filterObject ***filters,// - массив указателей на фильтры компонент + int *kFilters, //- общее количество фильтров во всех моделях + int *kComponents, //- количество компонент + int **kPartFilters, //- массив, содержащий количество точных фильтров в каждой компоненте + float **b, //- массив линейных членов в оценочной функции + float *scoreThreshold); //- порог для score) +//}; +#endif \ No newline at end of file diff --git a/modules/objdetect/src/_matching.h b/modules/objdetect/src/_matching.h new file mode 100644 index 0000000000..266ddf6232 --- /dev/null +++ b/modules/objdetect/src/_matching.h @@ -0,0 +1,396 @@ +/*****************************************************************************/ +/* Matching procedure API */ +/*****************************************************************************/ +// +#ifndef SVM_MATCHING +#define SVM_MATCHING + +#include "_latentsvm.h" +#include "_error.h" +#include "_distancetransform.h" +#include "_fft.h" +#include "_routine.h" + +//extern "C" { +/* +// Function for convolution computation +// +// API +// int convolution(const filterObject *Fi, const featureMap *map, float *f); +// INPUT +// Fi - filter object +// map - feature map +// OUTPUT +// f - the convolution +// RESULT +// Error status +*/ +int convolution(const filterObject *Fi, const featureMap *map, float *f); + +/* +// Computation multiplication of FFT images +// +// API +// int fftImagesMulti(float *fftImage1, float *fftImage2, int numRows, int numColls, + float *multi); +// INPUT +// fftImage1 - first fft image +// fftImage2 - second fft image +// (numRows, numColls) - image dimesions +// OUTPUT +// multi - multiplication +// RESULT +// Error status +*/ +int fftImagesMulti(float *fftImage1, float *fftImage2, int numRows, int numColls, + float *multi); + +/* +// Turnover filter matrix for the single feature +// +// API +// int rot2PI(float *filter, int dimX, int dimY, float *rot2PIFilter, + int p, int shift); +// INPUT +// filter - filter weight matrix +// (dimX, dimY) - dimension of filter matrix +// p - number of features +// shift - number of feature (or channel) +// OUTPUT +// rot2PIFilter - rotated matrix +// RESULT +// Error status +*/ +int rot2PI(float *filter, int dimX, int dimY, float *rot2PIFilter, + int p, int shift); + +/* +// Addition nullable bars to the dimension of feature map (single feature) +// +// API +// int addNullableBars(float *rot2PIFilter, int dimX, int dimY, + float *newFilter, int newDimX, int newDimY); +// INPUT +// rot2PIFilter - filter matrix for the single feature that was rotated +// (dimX, dimY) - dimension rot2PIFilter +// (newDimX, newDimY)- dimension of feature map for the single feature +// OUTPUT +// newFilter - filter matrix with nullable bars +// RESULT +// Error status +*/ +int addNullableBars(float *rot2PIFilter, int dimX, int dimY, + float *newFilter, int newDimX, int newDimY); + +/* +// Computation FFT image for filter object +// +// API +// int getFFTImageFilterObject(const filterObject *filter, + int mapDimX, int mapDimY, + fftImage **image); +// INPUT +// filter - filter object +// (mapDimX, mapDimY)- dimension of feature map +// OUTPUT +// image - fft image +// RESULT +// Error status +*/ +int getFFTImageFilterObject(const filterObject *filter, + int mapDimX, int mapDimY, + fftImage **image); + +/* +// Computation FFT image for feature map +// +// API +// int getFFTImageFeatureMap(const featureMap *map, fftImage **image); +// INPUT +// OUTPUT +// RESULT +// Error status +*/ +int getFFTImageFeatureMap(const featureMap *map, fftImage **image); + +/* +// Function for convolution computation using FFT +// +// API +// int convFFTConv2d(const fftImage *featMapImage, const fftImage *filterImage, + int filterDimX, int filterDimY, float **conv); +// INPUT +// featMapImage - feature map image +// filterImage - filter image +// (filterDimX,filterDimY) - filter dimension +// OUTPUT +// conv - the convolution +// RESULT +// Error status +*/ +int convFFTConv2d(const fftImage *featMapImage, const fftImage *filterImage, + int filterDimX, int filterDimY, float **conv); + +/* +// Computation objective function D according the original paper +// +// API +// int filterDispositionLevel(const filterObject *Fi, const featureMap *pyramid, + float **scoreFi, + int **pointsX, int **pointsY); +// INPUT +// Fi - filter object (weights and coefficients of penalty + function that are used in this routine) +// pyramid - feature map +// OUTPUT +// scoreFi - values of distance transform on the level at all positions +// (pointsX, pointsY)- positions that correspond to the maximum value + of distance transform at all grid nodes +// RESULT +// Error status +*/ +int filterDispositionLevel(const filterObject *Fi, const featureMap *pyramid, + float **scoreFi, + int **pointsX, int **pointsY); + +/* +// Computation objective function D according the original paper using FFT +// +// API +// int filterDispositionLevelFFT(const filterObject *Fi, const fftImage *featMapImage, + float **scoreFi, + int **pointsX, int **pointsY); +// INPUT +// Fi - filter object (weights and coefficients of penalty + function that are used in this routine) +// featMapImage - FFT image of feature map +// OUTPUT +// scoreFi - values of distance transform on the level at all positions +// (pointsX, pointsY)- positions that correspond to the maximum value + of distance transform at all grid nodes +// RESULT +// Error status +*/ +int filterDispositionLevelFFT(const filterObject *Fi, const fftImage *featMapImage, + float **scoreFi, + int **pointsX, int **pointsY); + +/* +// Computation border size for feature map +// +// API +// int computeBorderSize(int maxXBorder, int maxYBorder, int *bx, int *by); +// INPUT +// maxXBorder - the largest root filter size (X-direction) +// maxYBorder - the largest root filter size (Y-direction) +// OUTPUT +// bx - border size (X-direction) +// by - border size (Y-direction) +// RESULT +// Error status +*/ +int computeBorderSize(int maxXBorder, int maxYBorder, int *bx, int *by); + +/* +// Addition nullable border to the feature map +// +// API +// int addNullableBorder(featureMap *map, int bx, int by); +// INPUT +// map - feature map +// bx - border size (X-direction) +// by - border size (Y-direction) +// OUTPUT +// RESULT +// Error status +*/ +int addNullableBorder(featureMap *map, int bx, int by); + +/* +// Computation the maximum of the score function at the level +// +// API +// int maxFunctionalScoreFixedLevel(const filterObject **all_F, int n, + const featurePyramid *H, + int level, float b, + int maxXBorder, int maxYBorder, + float *score, CvPoint **points, int *kPoints, + CvPoint ***partsDisplacement); +// INPUT +// all_F - the set of filters (the first element is root filter, + the other - part filters) +// n - the number of part filters +// H - feature pyramid +// level - feature pyramid level for computation maximum score +// b - linear term of the score function +// maxXBorder - the largest root filter size (X-direction) +// maxYBorder - the largest root filter size (Y-direction) +// OUTPUT +// score - the maximum of the score function at the level +// points - the set of root filter positions (in the block space) +// levels - the set of levels +// kPoints - number of root filter positions +// partsDisplacement - displacement of part filters (in the block space) +// RESULT +// Error status +*/ +int maxFunctionalScoreFixedLevel(const filterObject **all_F, int n, + const featurePyramid *H, + int level, float b, + int maxXBorder, int maxYBorder, + float *score, CvPoint **points, int *kPoints, + CvPoint ***partsDisplacement); + +/* +// Computation score function at the level that exceed threshold +// +// API +// int thresholdFunctionalScoreFixedLevel(const filterObject **all_F, int n, + const featurePyramid *H, + int level, float b, + int maxXBorder, int maxYBorder, + float scoreThreshold, + float **score, CvPoint **points, int *kPoints, + CvPoint ***partsDisplacement); +// INPUT +// all_F - the set of filters (the first element is root filter, + the other - part filters) +// n - the number of part filters +// H - feature pyramid +// level - feature pyramid level for computation maximum score +// b - linear term of the score function +// maxXBorder - the largest root filter size (X-direction) +// maxYBorder - the largest root filter size (Y-direction) +// scoreThreshold - score threshold +// OUTPUT +// score - score function at the level that exceed threshold +// points - the set of root filter positions (in the block space) +// levels - the set of levels +// kPoints - number of root filter positions +// partsDisplacement - displacement of part filters (in the block space) +// RESULT +// Error status +*/ +int thresholdFunctionalScoreFixedLevel(const filterObject **all_F, int n, + const featurePyramid *H, + int level, float b, + int maxXBorder, int maxYBorder, + float scoreThreshold, + float **score, CvPoint **points, int *kPoints, + CvPoint ***partsDisplacement); + +/* +// Computation the maximum of the score function +// +// API +// int maxFunctionalScore(const filterObject **all_F, int n, + const featurePyramid *H, float b, + int maxXBorder, int maxYBorder, + float *score, + CvPoint **points, int **levels, int *kPoints, + CvPoint ***partsDisplacement); +// INPUT +// all_F - the set of filters (the first element is root filter, + the other - part filters) +// n - the number of part filters +// H - feature pyramid +// b - linear term of the score function +// maxXBorder - the largest root filter size (X-direction) +// maxYBorder - the largest root filter size (Y-direction) +// OUTPUT +// score - the maximum of the score function +// points - the set of root filter positions (in the block space) +// levels - the set of levels +// kPoints - number of root filter positions +// partsDisplacement - displacement of part filters (in the block space) +// RESULT +// Error status +*/ +int maxFunctionalScore(const filterObject **all_F, int n, + const featurePyramid *H, float b, + int maxXBorder, int maxYBorder, + float *score, + CvPoint **points, int **levels, int *kPoints, + CvPoint ***partsDisplacement); + +/* +// Computation score function that exceed threshold +// +// API +// int thresholdFunctionalScore(const filterObject **all_F, int n, + const featurePyramid *H, + float b, + int maxXBorder, int maxYBorder, + float scoreThreshold, + float **score, + CvPoint **points, int **levels, int *kPoints, + CvPoint ***partsDisplacement); +// INPUT +// all_F - the set of filters (the first element is root filter, + the other - part filters) +// n - the number of part filters +// H - feature pyramid +// b - linear term of the score function +// maxXBorder - the largest root filter size (X-direction) +// maxYBorder - the largest root filter size (Y-direction) +// scoreThreshold - score threshold +// OUTPUT +// score - score function values that exceed threshold +// points - the set of root filter positions (in the block space) +// levels - the set of levels +// kPoints - number of root filter positions +// partsDisplacement - displacement of part filters (in the block space) +// RESULT +// Error status +*/ +int thresholdFunctionalScore(const filterObject **all_F, int n, + const featurePyramid *H, + float b, + int maxXBorder, int maxYBorder, + float scoreThreshold, + float **score, + CvPoint **points, int **levels, int *kPoints, + CvPoint ***partsDisplacement); + + +/* +// Perform non-maximum suppression algorithm (described in original paper) +// to remove "similar" bounding boxes +// +// API +// int nonMaximumSuppression(int numBoxes, const CvPoint *points, + const CvPoint *oppositePoints, const float *score, + float overlapThreshold, + int *numBoxesout, CvPoint **pointsOut, + CvPoint **oppositePointsOut, float **scoreOut); +// INPUT +// numBoxes - number of bounding boxes +// points - array of left top corner coordinates +// oppositePoints - array of right bottom corner coordinates +// score - array of detection scores +// overlapThreshold - threshold: bounding box is removed if overlap part + is greater than passed value +// OUTPUT +// numBoxesOut - the number of bounding boxes algorithm returns +// pointsOut - array of left top corner coordinates +// oppositePointsOut - array of right bottom corner coordinates +// scoreOut - array of detection scores +// RESULT +// Error status +*/ +#ifdef __cplusplus +extern "C" +#endif +int nonMaximumSuppression(int numBoxes, const CvPoint *points, + const CvPoint *oppositePoints, const float *score, + float overlapThreshold, + int *numBoxesOut, CvPoint **pointsOut, + CvPoint **oppositePointsOut, float **scoreOut); +#ifdef __cplusplus +extern "C" +#endif +int getMaxFilterDims(const filterObject **filters, int kComponents, + const int *kPartFilters, + unsigned int *maxXBorder, unsigned int *maxYBorder); +//} +#endif diff --git a/modules/objdetect/src/_resizeimg.h b/modules/objdetect/src/_resizeimg.h new file mode 100644 index 0000000000..b9dcd4c04f --- /dev/null +++ b/modules/objdetect/src/_resizeimg.h @@ -0,0 +1,11 @@ +#ifndef RESIZEIMG +#define RESIZEIMG + +#include "precomp.hpp" +#include "_types.h" + +IplImage * resize_opencv (IplImage * img, float scale); +IplImage * resize_article_dp1(IplImage * img, float scale, const int k); +IplImage * resize_article_dp(IplImage * img, float scale, const int k); + +#endif \ No newline at end of file diff --git a/modules/objdetect/src/_routine.h b/modules/objdetect/src/_routine.h new file mode 100644 index 0000000000..866c359099 --- /dev/null +++ b/modules/objdetect/src/_routine.h @@ -0,0 +1,36 @@ +#ifndef _ROUTINE_H +#define _ROUTINE_H + +#include "precomp.hpp" +#include "_types.h" +#include "_error.h" + + +////////////////////////////////////////////////////////////// +// Memory management routines +// All paramaters names correspond to previous data structures description +// All "alloc" functions return allocated memory for 1 object +// with all fields including arrays +// Error status is return value +////////////////////////////////////////////////////////////// +int allocFilterObject(filterObject **obj, const int sizeX, const int sizeY, + const int p, const int xp); +int freeFilterObject (filterObject **obj); + +int allocFeatureMapObject(featureMap **obj, const int sizeX, const int sizeY, + const int p, const int xp); +int freeFeatureMapObject (featureMap **obj); + +#ifdef __cplusplus +extern "C" +#endif +int allocFeaturePyramidObject(featurePyramid **obj, + const int lambda, const int countLevel); + +#ifdef __cplusplus +extern "C" +#endif +int freeFeaturePyramidObject (featurePyramid **obj); +int allocFFTImage(fftImage **image, int p, int dimX, int dimY); +int freeFFTImage(fftImage **image); +#endif \ No newline at end of file diff --git a/modules/objdetect/src/_types.h b/modules/objdetect/src/_types.h new file mode 100644 index 0000000000..156d7d0e69 --- /dev/null +++ b/modules/objdetect/src/_types.h @@ -0,0 +1,93 @@ +#ifndef SVM_TYPE +#define SVM_TYPE + +//#include "opencv2/core/core.hpp" +//#include "opencv2/highgui/highgui.hpp" +#include "precomp.hpp" + +//#define FFT_CONV + +// Значение числа PI +#define PI 3.1415926535897932384626433832795 + +// Точность сравнения пары вещественных чисел +#define EPS 0.000001 + +// Минимальное и максимальное значение для вещественного типа данных +#define F_MAX 3.402823466e+38 +#define F_MIN -3.402823465e+38 + +// The number of elements in bin +// The number of sectors in gradient histogram building +#define CNTPARTION 9 + +// The number of levels in image resize procedure +// We need Lambda levels to resize image twice +#define LAMBDA 10 + +// Block size. Used in feature pyramid building procedure +#define SIDE_LENGTH 8 + +////////////////////////////////////////////////////////////// +// main data structures // +////////////////////////////////////////////////////////////// + +// DataType: STRUCT featureMap +// FEATURE MAP DESCRIPTION +// Rectangular map (sizeX x sizeY), +// every cell stores feature vector (dimension = p) +// H - matrix of feature vectors +// to set and get feature vectors (i,j) +// used formula Map[(j * sizeX + i) * p + k], where +// k - component of feature vector in cell (i, j) +// END OF FEATURE MAP DESCRIPTION +// xp - auxillary parameter for internal use +// size of row in feature vectors +// (yp = (int) (p / xp); p = xp * yp) +typedef struct{ + int sizeX; + int sizeY; + int p; + int xp; + float *Map; +} featureMap; + +// DataType: STRUCT featurePyramid +// +// countLevel - number of levels in the feature pyramid +// lambda - resize scale coefficient +// pyramid - array of pointers to feature map at different levels +typedef struct{ + int countLevel; + int lambda; + featureMap **pyramid; +} featurePyramid; + +// DataType: STRUCT filterDisposition +// The structure stores preliminary results in optimization process +// with objective function D +// +// x - array with X coordinates of optimization problems solutions +// y - array with Y coordinates of optimization problems solutions +// score - array with optimal objective values +typedef struct{ + float *score; + int *x; + int *y; +} filterDisposition; + +// DataType: STRUCT fftImage +// The structure stores FFT image +// +// p - number of channels +// x - array of FFT images for 2d signals +// n - number of rows +// m - number of collums +typedef struct{ + unsigned int p; + unsigned int dimX; + unsigned int dimY; + float **channels; +} fftImage; + +#endif diff --git a/modules/objdetect/src/distancetransform.cpp b/modules/objdetect/src/distancetransform.cpp new file mode 100644 index 0000000000..89c5749514 --- /dev/null +++ b/modules/objdetect/src/distancetransform.cpp @@ -0,0 +1,395 @@ +#include "_distancetransform.h" + +/* +// Computation the point of intersection functions +// (parabolas on the variable y) +// a(y - q1) + b(q1 - y)(q1 - y) + f[q1] +// a(y - q2) + b(q2 - y)(q2 - y) + f[q2] +// +// API +// int GetPointOfIntersection(const float *f, + const float a, const float b, + int q1, int q2, float *point); +// INPUT +// f - function on the regular grid +// a - coefficient of the function +// b - coefficient of the function +// q1 - parameter of the function +// q2 - parameter of the function +// OUTPUT +// point - point of intersection +// RESULT +// Error status +*/ +int GetPointOfIntersection(const float *f, + const float a, const float b, + int q1, int q2, float *point) +{ + if (q1 == q2) + { + return DISTANCE_TRANSFORM_EQUAL_POINTS; + } /* if (q1 == q2) */ + (*point) = ( (f[q2] - a * q2 + b *q2 * q2) - + (f[q1] - a * q1 + b * q1 * q1) ) / (2 * b * (q2 - q1)); + return DISTANCE_TRANSFORM_OK; +} + +/* +// Decision of one dimensional problem generalized distance transform +// on the regular grid at all points +// min (a(y' - y) + b(y' - y)(y' - y) + f(y')) (on y') +// +// API +// int DistanceTransformOneDimensionalProblem(const float *f, const int n, + const float a, const float b, + float *distanceTransform, + int *points); +// INPUT +// f - function on the regular grid +// n - grid dimension +// a - coefficient of optimizable function +// b - coefficient of optimizable function +// OUTPUT +// distanceTransform - values of generalized distance transform +// points - arguments that corresponds to the optimal value of function +// RESULT +// Error status +*/ +int DistanceTransformOneDimensionalProblem(const float *f, const int n, + const float a, const float b, + float *distanceTransform, + int *points) +{ + int i, k; + int tmp; + int diff; + float pointIntersection; + int *v; + float *z; + k = 0; + + // Allocation memory (must be free in this function) + v = (int *)malloc (sizeof(int) * n); + z = (float *)malloc (sizeof(float) * (n + 1)); + + v[0] = 0; + z[0] = (float)F_MIN; // left border of envelope + z[1] = (float)F_MAX; // right border of envelope + + for (i = 1; i < n; i++) + { + tmp = GetPointOfIntersection(f, a, b, v[k], i, &pointIntersection); + if (tmp != DISTANCE_TRANSFORM_OK) + { + free(v); + free(z); + return DISTANCE_TRANSFORM_GET_INTERSECTION_ERROR; + } /* if (tmp != DISTANCE_TRANSFORM_OK) */ + if (pointIntersection <= z[k]) + { + // Envelope doesn't contain current parabola + do + { + k--; + tmp = GetPointOfIntersection(f, a, b, v[k], i, &pointIntersection); + if (tmp != DISTANCE_TRANSFORM_OK) + { + free(v); + free(z); + return DISTANCE_TRANSFORM_GET_INTERSECTION_ERROR; + } /* if (tmp != DISTANCE_TRANSFORM_OK) */ + }while (pointIntersection <= z[k]); + // Addition parabola to the envelope + k++; + v[k] = i; + z[k] = pointIntersection; + z[k + 1] = (float)F_MAX; + } + else + { + // Addition parabola to the envelope + k++; + v[k] = i; + z[k] = pointIntersection; + z[k + 1] = (float)F_MAX; + } /* if (pointIntersection <= z[k]) */ + } + + // Computation values of generalized distance transform at all grid points + k = 0; + for (i = 0; i < n; i++) + { + while (z[k + 1] < i) + { + k++; + } + points[i] = v[k]; + diff = i - v[k]; + distanceTransform[i] = a * diff + b * diff * diff + f[v[k]]; + } + + // Release allocated memory + free(v); + free(z); + return DISTANCE_TRANSFORM_OK; +} + +/* +// Computation next cycle element +// +// API +// int GetNextCycleElement(int k, int n, int q); +// INPUT +// k - index of the previous cycle element +// n - number of matrix rows +// q - parameter that equal + (number_of_rows * number_of_columns - 1) +// OUTPUT +// None +// RESULT +// Next cycle element +*/ +int GetNextCycleElement(int k, int n, int q) +{ + return ((k * n) % q); +} + +/* +// Transpose cycle elements +// +// API +// void TransposeCycleElements(float *a, int *cycle, int cycle_len) +// INPUT +// a - initial matrix +// cycle - indeces array of cycle +// cycle_len - number of elements in the cycle +// OUTPUT +// a - matrix with transposed elements +// RESULT +// Error status +*/ +void TransposeCycleElements(float *a, int *cycle, int cycle_len) +{ + int i; + float buf; + for (i = cycle_len - 1; i > 0 ; i--) + { + buf = a[ cycle[i] ]; + a[ cycle[i] ] = a[ cycle[i - 1] ]; + a[ cycle[i - 1] ] = buf; + } +} + +/* +// Transpose cycle elements +// +// API +// void TransposeCycleElements(int *a, int *cycle, int cycle_len) +// INPUT +// a - initial matrix +// cycle - indeces array of cycle +// cycle_len - number of elements in the cycle +// OUTPUT +// a - matrix with transposed elements +// RESULT +// Error status +*/ +void TransposeCycleElements_int(int *a, int *cycle, int cycle_len) +{ + int i; + int buf; + for (i = cycle_len - 1; i > 0 ; i--) + { + buf = a[ cycle[i] ]; + a[ cycle[i] ] = a[ cycle[i - 1] ]; + a[ cycle[i - 1] ] = buf; + } +} + +/* +// Getting transposed matrix +// +// API +// void Transpose(float *a, int n, int m); +// INPUT +// a - initial matrix +// n - number of rows +// m - number of columns +// OUTPUT +// a - transposed matrix +// RESULT +// None +*/ +void Transpose(float *a, int n, int m) +{ + int *cycle; + int i, k, q, cycle_len; + int max_cycle_len; + + max_cycle_len = n * m; + + // Allocation memory (must be free in this function) + cycle = (int *)malloc(sizeof(int) * max_cycle_len); + + cycle_len = 0; + q = n * m - 1; + for (i = 1; i < q; i++) + { + k = GetNextCycleElement(i, n, q); + cycle[cycle_len] = i; + cycle_len++; + + while (k > i) + { + cycle[cycle_len] = k; + cycle_len++; + k = GetNextCycleElement(k, n, q); + } + if (k == i) + { + TransposeCycleElements(a, cycle, cycle_len); + } /* if (k == i) */ + cycle_len = 0; + } + + // Release allocated memory + free(cycle); +} + +/* +// Getting transposed matrix +// +// API +// void Transpose_int(int *a, int n, int m); +// INPUT +// a - initial matrix +// n - number of rows +// m - number of columns +// OUTPUT +// a - transposed matrix +// RESULT +// None +*/ +void Transpose_int(int *a, int n, int m) +{ + int *cycle; + int i, k, q, cycle_len; + int max_cycle_len; + + max_cycle_len = n * m; + + // Allocation memory (must be free in this function) + cycle = (int *)malloc(sizeof(int) * max_cycle_len); + + cycle_len = 0; + q = n * m - 1; + for (i = 1; i < q; i++) + { + k = GetNextCycleElement(i, n, q); + cycle[cycle_len] = i; + cycle_len++; + + while (k > i) + { + cycle[cycle_len] = k; + cycle_len++; + k = GetNextCycleElement(k, n, q); + } + if (k == i) + { + TransposeCycleElements_int(a, cycle, cycle_len); + } /* if (k == i) */ + cycle_len = 0; + } + + // Release allocated memory + free(cycle); +} + +/* +// Decision of two dimensional problem generalized distance transform +// on the regular grid at all points +// min{d2(y' - y) + d4(y' - y)(y' - y) + + min(d1(x' - x) + d3(x' - x)(x' - x) + f(x',y'))} (on x', y') +// +// API +// int DistanceTransformTwoDimensionalProblem(const float *f, + const int n, const int m, + const float coeff[4], + float *distanceTransform, + int *pointsX, int *pointsY); +// INPUT +// f - function on the regular grid +// n - number of rows +// m - number of columns +// coeff - coefficients of optimizable function + coeff[0] = d1, coeff[1] = d2, + coeff[2] = d3, coeff[3] = d4 +// OUTPUT +// distanceTransform - values of generalized distance transform +// pointsX - arguments x' that correspond to the optimal value +// pointsY - arguments y' that correspond to the optimal value +// RESULT +// Error status +*/ +int DistanceTransformTwoDimensionalProblem(const float *f, + const int n, const int m, + const float coeff[4], + float *distanceTransform, + int *pointsX, int *pointsY) +{ + int i, j, tmp; + int resOneDimProblem; + float *internalDistTrans; + int *internalPointsX; + int size = n * m; + + // Allocation memory (must be free in this function) + internalDistTrans = (float *)malloc(sizeof(float) * size); + internalPointsX = (int *)malloc(sizeof(int) * size); + + + for (i = 0; i < n; i++) + { + resOneDimProblem = DistanceTransformOneDimensionalProblem( + f + i * m, m, + coeff[0], coeff[2], + internalDistTrans + i * m, + internalPointsX + i * m); + if (resOneDimProblem != DISTANCE_TRANSFORM_OK) + { + free(internalDistTrans); + return DISTANCE_TRANSFORM_ERROR; + } /* if (resOneDimProblem != DISTANCE_TRANSFORM_OK) */ + } + Transpose(internalDistTrans, n, m); + for (j = 0; j < m; j++) + { + resOneDimProblem = DistanceTransformOneDimensionalProblem( + internalDistTrans + j * n, n, + coeff[1], coeff[3], + distanceTransform + j * n, + pointsY + j * n); + if (resOneDimProblem != DISTANCE_TRANSFORM_OK) + { + free(internalDistTrans); + return DISTANCE_TRANSFORM_ERROR; + } /* if (resOneDimProblem != DISTANCE_TRANSFORM_OK) */ + } + Transpose(distanceTransform, m, n); + Transpose_int(pointsY, m, n); + + for (i = 0; i < n; i++) + { + for (j = 0; j < m; j++) + { + tmp = pointsY[i * m + j]; + pointsX[i * m + j] = internalPointsX[tmp * m + j]; + } + } + + // Release allocated memory + free(internalDistTrans); + free(internalPointsX); + return DISTANCE_TRANSFORM_OK; +} \ No newline at end of file diff --git a/modules/objdetect/src/featurepyramid.cpp b/modules/objdetect/src/featurepyramid.cpp new file mode 100644 index 0000000000..217944701e --- /dev/null +++ b/modules/objdetect/src/featurepyramid.cpp @@ -0,0 +1,576 @@ +#include "_latentsvm.h" +#include "_resizeimg.h" + +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif + +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +int sign(float r){ + if(r > 0.0001f) return 1; + if(r < -0.0001f) return -1; + return 0; +} + +/* +// Getting feature map for the selected subimage +// +// API +// int getFeatureMaps(const IplImage * image, const int k, featureMap **map); +// INPUT +// image - selected subimage +// k - size of cells +// OUTPUT +// map - feature map +// RESULT +// Error status +*/ +int getFeatureMaps_dp(const IplImage * image,const int k, featureMap **map) +{ + int sizeX, sizeY; + int p, px, strsz; + int height, width, channels; + int i, j, kk, c, ii, jj, d; + float * datadx, * datady; + float tmp, x, y, tx, ty; + IplImage * dx, * dy; + int *nearest_x, *nearest_y; + float *w, a_x, b_x; + + float kernel[3] = {-1.f, 0.f, 1.f}; + CvMat kernel_dx = cvMat(1, 3, CV_32F, kernel); + CvMat kernel_dy = cvMat(3, 1, CV_32F, kernel); + + float * r; + int * alfa; + + float boundary_x[CNTPARTION+1]; + float boundary_y[CNTPARTION+1]; + float max, tmp_scal; + int maxi; + + height = image->height; + width = image->width ; + + channels = image->nChannels; + + dx = cvCreateImage(cvSize(image->width , image->height) , IPL_DEPTH_32F , 3); + dy = cvCreateImage(cvSize(image->width , image->height) , IPL_DEPTH_32F , 3); + + sizeX = width / k; + sizeY = height / k; + px = CNTPARTION + 2 * CNTPARTION; // контрастное и не контрастное изображение + p = px; + strsz = sizeX * p; + allocFeatureMapObject(map, sizeX, sizeY, p, px); + + cvFilter2D(image, dx, &kernel_dx, cvPoint(-1, 0)); + cvFilter2D(image, dy, &kernel_dy, cvPoint(0, -1)); + + for(i = 0; i <= CNTPARTION; i++) + { + boundary_x[i] = cosf((((float)i) * (((float)PI) / (float) (CNTPARTION)))); + boundary_y[i] = sinf((((float)i) * (((float)PI) / (float) (CNTPARTION)))); + }/*for(i = 0; i <= CNTPARTION; i++) */ + + r = (float *)malloc( sizeof(float) * (width * height)); + alfa = (int *)malloc( sizeof(int ) * (width * height * 2)); + + for(j = 1; j < height-1; j++) + { + datadx = (float*)(dx->imageData + dx->widthStep *j); + datady = (float*)(dy->imageData + dy->widthStep *j); + for(i = 1; i < width-1; i++) + { + c = 0; + x = (datadx[i*channels+c]); + y = (datady[i*channels+c]); + + r[j * width + i] =sqrtf(x*x + y*y); + for(kk = 1; kk < channels; kk++) + { + tx = (datadx[i*channels+kk]); + ty = (datady[i*channels+kk]); + tmp =sqrtf(tx*tx + ty*ty); + if(tmp > r[j * width + i]) + { + r[j * width + i] = tmp; + c = kk; + x = tx; + y = ty; + } + }/*for(kk = 1; kk < channels; kk++)*/ + + + + max = boundary_x[0]*x + boundary_y[0]*y; + maxi = 0; + for (kk = 0; kk < CNTPARTION; kk++) { + tmp_scal = boundary_x[kk]*x + boundary_y[kk]*y; + if (tmp_scal> max) { + max = tmp_scal; + maxi = kk; + }else if (-tmp_scal> max) { + max = -tmp_scal; + maxi = kk + CNTPARTION; + } + } + alfa[j * width * 2 + i * 2 ] = maxi % CNTPARTION; + alfa[j * width * 2 + i * 2 + 1] = maxi; + }/*for(i = 0; i < width; i++)*/ + }/*for(j = 0; j < height; j++)*/ + + //подсчет весов и смещений + nearest_x = (int *)malloc(sizeof(int) * k); + nearest_y = (int *)malloc(sizeof(int) * k); + w = (float*)malloc(sizeof(float) * (k * 2)); + + for(i = 0; i < k / 2; i++) + { + nearest_x[i] = -1; + nearest_y[i] = -1; + }/*for(i = 0; i < k / 2; i++)*/ + for(i = k / 2; i < k; i++) + { + nearest_x[i] = 1; + nearest_y[i] = 1; + }/*for(i = k / 2; i < k; i++)*/ + + for(j = 0; j < k / 2; j++) + { + b_x = k / 2 + j + 0.5f; + a_x = k / 2 - j - 0.5f; + w[j * 2 ] = 1.0f/a_x * ((a_x * b_x) / ( a_x + b_x)); + w[j * 2 + 1] = 1.0f/b_x * ((a_x * b_x) / ( a_x + b_x)); + }/*for(j = 0; j < k / 2; j++)*/ + for(j = k / 2; j < k; j++) + { + a_x = j - k / 2 + 0.5f; + b_x =-j + k / 2 - 0.5f + k; + w[j * 2 ] = 1.0f/a_x * ((a_x * b_x) / ( a_x + b_x)); + w[j * 2 + 1] = 1.0f/b_x * ((a_x * b_x) / ( a_x + b_x)); + }/*for(j = k / 2; j < k; j++)*/ + + + //интерполяция + for(i = 0; i < sizeY; i++) + { + for(j = 0; j < sizeX; j++) + { + for(ii = 0; ii < k; ii++) + { + for(jj = 0; jj < k; jj++) + { + if ((i * k + ii > 0) && (i * k + ii < height - 1) && (j * k + jj > 0) && (j * k + jj < width - 1)) + { + d = (k*i + ii)* width + (j*k + jj); + (*map)->Map[(i ) * strsz + (j ) * (*map)->p + alfa[d * 2 ] ] += + r[d] * w[ii * 2 ] * w[jj * 2 ]; + (*map)->Map[(i ) * strsz + (j ) * (*map)->p + alfa[d * 2 + 1] + CNTPARTION] += + r[d] * w[ii * 2 ] * w[jj * 2 ]; + if ((i + nearest_y[ii] >= 0) && (i + nearest_y[ii] <= sizeY - 1)) + { + (*map)->Map[(i + nearest_y[ii]) * strsz + (j ) * (*map)->p + alfa[d * 2 ] ] += + r[d] * w[ii * 2 + 1] * w[jj * 2 ]; + (*map)->Map[(i + nearest_y[ii]) * strsz + (j ) * (*map)->p + alfa[d * 2 + 1] + CNTPARTION] += + r[d] * w[ii * 2 + 1] * w[jj * 2 ]; + } + if ((j + nearest_x[jj] >= 0) && (j + nearest_x[jj] <= sizeX - 1)) + { + (*map)->Map[(i ) * strsz + (j + nearest_x[jj]) * (*map)->p + alfa[d * 2 ] ] += + r[d] * w[ii * 2 ] * w[jj * 2 + 1]; + (*map)->Map[(i ) * strsz + (j + nearest_x[jj]) * (*map)->p + alfa[d * 2 + 1] + CNTPARTION] += + r[d] * w[ii * 2 ] * w[jj * 2 + 1]; + } + if ((i + nearest_y[ii] >= 0) && (i + nearest_y[ii] <= sizeY - 1) && (j + nearest_x[jj] >= 0) && (j + nearest_x[jj] <= sizeX - 1)) + { + (*map)->Map[(i + nearest_y[ii]) * strsz + (j + nearest_x[jj]) * (*map)->p + alfa[d * 2 ] ] += + r[d] * w[ii * 2 + 1] * w[jj * 2 + 1]; + (*map)->Map[(i + nearest_y[ii]) * strsz + (j + nearest_x[jj]) * (*map)->p + alfa[d * 2 + 1] + CNTPARTION] += + r[d] * w[ii * 2 + 1] * w[jj * 2 + 1]; + } + } + }/*for(jj = 0; jj < k; jj++)*/ + }/*for(ii = 0; ii < k; ii++)*/ + }/*for(j = 1; j < sizeX - 1; j++)*/ + }/*for(i = 1; i < sizeY - 1; i++)*/ + + cvReleaseImage(&dx); + cvReleaseImage(&dy); + + + free(w); + free(nearest_x); + free(nearest_y); + + free(r); + free(alfa); + + return LATENT_SVM_OK; +} + +/* +// Feature map Normalization and Truncation +// +// API +// int normalizationAndTruncationFeatureMaps(featureMap *map, const float alfa); +// INPUT +// map - feature map +// alfa - truncation threshold +// OUTPUT +// map - truncated and normalized feature map +// RESULT +// Error status +*/ +int normalizationAndTruncationFeatureMaps(featureMap *map, const float alfa) +{ + int i,j, ii; + int sizeX, sizeY, p, pos, pp, xp, pos1, pos2; + float * part_noma; // norm of C(i, j) + float * new_data; + float norm_val; + + sizeX = map->sizeX; + sizeY = map->sizeY; + part_noma = (float *)malloc (sizeof(float) * (sizeX * sizeY)); + + p = map->xp / 3; + + for(i = 0; i < sizeX * sizeY; i++) + { + norm_val = 0.0; + pos = i * map->p; + for(j = 0; j < p; j++) + { + norm_val += map->Map[pos + j] * map->Map[pos + j]; + }/*for(j = 0; j < p; j++)*/ + part_noma[i] = norm_val; + }/*for(i = 0; i < sizeX * sizeY; i++)*/ + + xp = map->xp; + pp = xp * 4; + sizeX -= 2; + sizeY -= 2; + + new_data = (float *)malloc (sizeof(float) * (sizeX * sizeY * pp)); +//normalization + for(i = 1; i <= sizeY; i++) + { + for(j = 1; j <= sizeX; j++) + { + norm_val = sqrtf( + part_noma[(i )*(sizeX + 2) + (j )] + + part_noma[(i )*(sizeX + 2) + (j + 1)] + + part_noma[(i + 1)*(sizeX + 2) + (j )] + + part_noma[(i + 1)*(sizeX + 2) + (j + 1)]); + pos1 = (i ) * (sizeX + 2) * xp + (j ) * xp; + pos2 = (i-1) * (sizeX ) * pp + (j-1) * pp; + for(ii = 0; ii < p; ii++) + { + new_data[pos2 + ii ] = map->Map[pos1 + ii ] / norm_val; + }/*for(ii = 0; ii < p; ii++)*/ + for(ii = 0; ii < 2 * p; ii++) + { + new_data[pos2 + ii + p * 4] = map->Map[pos1 + ii + p] / norm_val; + }/*for(ii = 0; ii < 2 * p; ii++)*/ + norm_val = sqrtf( + part_noma[(i )*(sizeX + 2) + (j )] + + part_noma[(i )*(sizeX + 2) + (j + 1)] + + part_noma[(i - 1)*(sizeX + 2) + (j )] + + part_noma[(i - 1)*(sizeX + 2) + (j + 1)]); + for(ii = 0; ii < p; ii++) + { + new_data[pos2 + ii + p ] = map->Map[pos1 + ii ] / norm_val; + }/*for(ii = 0; ii < p; ii++)*/ + for(ii = 0; ii < 2 * p; ii++) + { + new_data[pos2 + ii + p * 6] = map->Map[pos1 + ii + p] / norm_val; + }/*for(ii = 0; ii < 2 * p; ii++)*/ + norm_val = sqrtf( + part_noma[(i )*(sizeX + 2) + (j )] + + part_noma[(i )*(sizeX + 2) + (j - 1)] + + part_noma[(i + 1)*(sizeX + 2) + (j )] + + part_noma[(i + 1)*(sizeX + 2) + (j - 1)]); + for(ii = 0; ii < p; ii++) + { + new_data[pos2 + ii + p * 2] = map->Map[pos1 + ii ] / norm_val; + }/*for(ii = 0; ii < p; ii++)*/ + for(ii = 0; ii < 2 * p; ii++) + { + new_data[pos2 + ii + p * 8] = map->Map[pos1 + ii + p] / norm_val; + }/*for(ii = 0; ii < 2 * p; ii++)*/ + norm_val = sqrtf( + part_noma[(i )*(sizeX + 2) + (j )] + + part_noma[(i )*(sizeX + 2) + (j - 1)] + + part_noma[(i - 1)*(sizeX + 2) + (j )] + + part_noma[(i - 1)*(sizeX + 2) + (j - 1)]); + for(ii = 0; ii < p; ii++) + { + new_data[pos2 + ii + p * 3 ] = map->Map[pos1 + ii ] / norm_val; + }/*for(ii = 0; ii < p; ii++)*/ + for(ii = 0; ii < 2 * p; ii++) + { + new_data[pos2 + ii + p * 10] = map->Map[pos1 + ii + p] / norm_val; + }/*for(ii = 0; ii < 2 * p; ii++)*/ + }/*for(j = 1; j <= sizeX; j++)*/ + }/*for(i = 1; i <= sizeY; i++)*/ +//truncation + for(i = 0; i < sizeX * sizeY * pp; i++) + { + if(new_data [i] > alfa) new_data [i] = alfa; + }/*for(i = 0; i < sizeX * sizeY * pp; i++)*/ +//swop data + + map->p = pp; + map->xp = xp; + map->sizeX = sizeX; + map->sizeY = sizeY; + + free (map->Map); + free (part_noma); + + map->Map = new_data; + + return LATENT_SVM_OK; +} +/* +// Feature map reduction +// In each cell we reduce dimension of the feature vector +// according to original paper special procedure +// +// API +// int PCAFeatureMaps(featureMap *map) +// INPUT +// map - feature map +// OUTPUT +// map - feature map +// RESULT +// Error status +*/ +int PCAFeatureMaps(featureMap *map) +{ + int i,j, ii, jj, k; + int sizeX, sizeY, p, pp, xp, yp, pos1, pos2; + float * new_data; + float val; + float nx, ny; + + sizeX = map->sizeX; + sizeY = map->sizeY; + p = map->p; + pp = map->xp + 4; + yp = 4; + xp = (map->xp / 3); + + nx = 1.0f / sqrtf((float)(xp * 2)); + ny = 1.0f / sqrtf((float)(yp )); + + new_data = (float *)malloc (sizeof(float) * (sizeX * sizeY * pp)); + + for(i = 0; i < sizeY; i++) + { + for(j = 0; j < sizeX; j++) + { + pos1 = ((i)*sizeX + j)*p; + pos2 = ((i)*sizeX + j)*pp; + k = 0; + for(jj = 0; jj < xp * 2; jj++) + { + val = 0; + for(ii = 0; ii < yp; ii++) + { + val += map->Map[pos1 + yp * xp + ii * xp * 2 + jj]; + }/*for(ii = 0; ii < yp; ii++)*/ + new_data[pos2 + k] = val * ny; + k++; + }/*for(jj = 0; jj < xp * 2; jj++)*/ + for(jj = 0; jj < xp; jj++) + { + val = 0; + for(ii = 0; ii < yp; ii++) + { + val += map->Map[pos1 + ii * xp + jj]; + }/*for(ii = 0; ii < yp; ii++)*/ + new_data[pos2 + k] = val * ny; + k++; + }/*for(jj = 0; jj < xp; jj++)*/ + for(ii = 0; ii < yp; ii++) + { + val = 0; + for(jj = 0; jj < 2 * xp; jj++) + { + val += map->Map[pos1 + yp * xp + ii * xp * 2 + jj]; + }/*for(jj = 0; jj < xp; jj++)*/ + new_data[pos2 + k] = val * nx; + k++; + } /*for(ii = 0; ii < yp; ii++)*/ + }/*for(j = 0; j < sizeX; j++)*/ + }/*for(i = 0; i < sizeY; i++)*/ +//swop data + + map->p = pp; + map->xp = pp; + + free (map->Map); + + map->Map = new_data; + + return LATENT_SVM_OK; +} + +/* +// Getting feature pyramid +// +// API +// int getFeaturePyramid(IplImage * image, const filterObject **all_F, + const int n_f, + const int lambda, const int k, + const int startX, const int startY, + const int W, const int H, featurePyramid **maps); +// INPUT +// image - image +// lambda - resize scale +// k - size of cells +// startX - X coordinate of the image rectangle to search +// startY - Y coordinate of the image rectangle to search +// W - width of the image rectangle to search +// H - height of the image rectangle to search +// OUTPUT +// maps - feature maps for all levels +// RESULT +// Error status +*/ +int getFeaturePyramid(IplImage * image, + const int lambda, const int k, + const int startX, const int startY, + const int W, const int H, featurePyramid **maps) +{ + IplImage *img2, *imgTmp, *imgResize; + float step, tmp; + int cntStep; + int maxcall; + int i; + int err; + featureMap *map; + + //geting subimage + cvSetImageROI(image, cvRect(startX, startY, W, H)); + img2 = cvCreateImage(cvGetSize(image), image->depth, image->nChannels); + cvCopy(image, img2, NULL); + cvResetImageROI(image); + + if(img2->depth != IPL_DEPTH_32F) + { + imgResize = cvCreateImage(cvSize(img2->width , img2->height) , IPL_DEPTH_32F , 3); + cvConvert(img2, imgResize); + } + else + { + imgResize = img2; + } + + step = powf(2.0f, 1.0f/ ((float)lambda)); + maxcall = W/k; + if( maxcall > H/k ) + { + maxcall = H/k; + } + cntStep = (int)(logf((float)maxcall/(5.0f))/logf(step)) + 1; + //printf("Count step: %f %d\n", step, cntStep); + + allocFeaturePyramidObject(maps, lambda, cntStep + lambda); + + for(i = 0; i < lambda; i++) + { + tmp = 1.0f / powf(step, (float)i); + imgTmp = resize_opencv (imgResize, tmp); + //imgTmp = resize_article_dp(img2, tmp, 4); + err = getFeatureMaps_dp(imgTmp, 4, &map); + err = normalizationAndTruncationFeatureMaps(map, 0.2f); + err = PCAFeatureMaps(map); + (*maps)->pyramid[i] = map; + //printf("%d, %d\n", map->sizeY, map->sizeX); + cvReleaseImage(&imgTmp); + } + + /**********************************one**************/ + for(i = 0; i < cntStep; i++) + { + tmp = 1.0f / powf(step, (float)i); + imgTmp = resize_opencv (imgResize, tmp); + //imgTmp = resize_article_dp(imgResize, tmp, 8); + err = getFeatureMaps_dp(imgTmp, 8, &map); + err = normalizationAndTruncationFeatureMaps(map, 0.2f); + err = PCAFeatureMaps(map); + (*maps)->pyramid[i + lambda] = map; + //printf("%d, %d\n", map->sizeY, map->sizeX); + cvReleaseImage(&imgTmp); + }/*for(i = 0; i < cntStep; i++)*/ + + if(img2->depth != IPL_DEPTH_32F) + { + cvReleaseImage(&imgResize); + } + + cvReleaseImage(&img2); + return LATENT_SVM_OK; +} + +/* +// add zero border to feature map +// +// API +// int addBordersToFeatureMaps(featureMap *map, const int bX, const int bY); +// INPUT +// map - feature map +// bX - border size in x +// bY - border size in y +// OUTPUT +// map - feature map +// RESULT +// Error status +*/ +int addBordersToFeatureMaps(featureMap *map, const int bX, const int bY){ + int i,j, jj; + int sizeX, sizeY, p, pos1, pos2; + float * new_data; + + sizeX = map->sizeX; + sizeY = map->sizeY; + p = map->p; + + new_data = (float *)malloc (sizeof(float) * ((sizeX + 2 * bX) * (sizeY + 2 * bY) * p)); + + for(i = 0; i < ((sizeX + 2 * bX) * (sizeY + 2 * bY) * p); i++) + { + new_data[i] = (float)0; + }/*for(i = 0; i < ((sizeX + 2 * bX) * (sizeY + 2 * bY) * p); i++)*/ + + for(i = 0; i < sizeY; i++) + { + for(j = 0; j < sizeX; j++) + { + + pos1 = ((i )*sizeX + (j )) * p; + pos2 = ((i + bY)*(sizeX + 2 * bX) + (j + bX)) * p; + + for(jj = 0; jj < p; jj++) + { + new_data[pos2 + jj] = map->Map[pos1 + jj]; + }/*for(jj = 0; jj < p; jj++)*/ + }/*for(j = 0; j < sizeX; j++)*/ + }/*for(i = 0; i < sizeY; i++)*/ + //swop data + + map->sizeX = sizeX + 2 * bX; + map->sizeY = sizeY + 2 * bY; + + free (map->Map); + + map->Map = new_data; + + return LATENT_SVM_OK; +} \ No newline at end of file diff --git a/modules/objdetect/src/fft.cpp b/modules/objdetect/src/fft.cpp new file mode 100644 index 0000000000..e4d974a8c9 --- /dev/null +++ b/modules/objdetect/src/fft.cpp @@ -0,0 +1,246 @@ +#include "_fft.h" + +int getEntireRes(int number, int divisor, int *entire, int *res) +{ + *entire = number / divisor; + *res = number % divisor; + return FFT_OK; +} + +int getMultipliers(int n, int *n1, int *n2) +{ + int multiplier, i; + if (n == 1) + { + *n1 = 1; + *n2 = 1; + return FFT_ERROR; // n = 1 + } + multiplier = n / 2; + for (i = multiplier; i >= 2; i--) + { + if (n % i == 0) + { + *n1 = i; + *n2 = n / i; + return FFT_OK; // n = n1 * n2 + } + } + *n1 = 1; + *n2 = n; + return FFT_ERROR; // n - prime number +} + +/* +// 1-dimensional FFT +// +// API +// int fft(float *x_in, float *x_out, int n, int shift); +// INPUT +// x_in - input signal +// n - number of elements for searching Fourier image +// shift - shift between input elements +// OUTPUT +// x_out - output signal (contains 2n elements in order + Re(x_in[0]), Im(x_in[0]), Re(x_in[1]), Im(x_in[1]) and etc.) +// RESULT +// Error status +*/ +int fft(float *x_in, float *x_out, int n, int shift) +{ + int n1, n2, res, k1, k2, m1, m2, index, idx; + float alpha, beta, gamma, angle, cosAngle, sinAngle; + float tmpGamma, tmpAlpha, tmpBeta; + float tmpRe, tmpIm, phaseRe, phaseIm; + res = getMultipliers(n, &n1, &n2); + if (res == FFT_OK) + { + fft(x_in, x_out, n1, shift); + fft(x_in, x_out, n2, shift); + } + alpha = (float)(2.0 * PI / ((float)n)); + beta = (float)(2.0 * PI / ((float)n1)); + gamma = (float)(2.0 * PI / ((float)n2)); + for (k1 = 0; k1 < n1; k1++) + { + tmpBeta = beta * k1; + for (k2 = 0; k2 < n2; k2++) + { + idx = shift * (n2 * k1 + k2); + x_out[idx] = 0.0; + x_out[idx + 1] = 0.0; + tmpGamma = gamma * k2; + tmpAlpha = alpha * k2; + for (m1 = 0; m1 < n1; m1++) + { + tmpRe = 0.0; + tmpIm = 0.0; + for (m2 = 0; m2 < n2; m2++) + { + angle = tmpGamma * m2; + index = shift * (n1 * m2 + m1); + cosAngle = cosf(angle); + sinAngle = sinf(angle); + tmpRe += x_in[index] * cosAngle + x_in[index + 1] * sinAngle; + tmpIm += x_in[index + 1] * cosAngle - x_in[index] * sinAngle; + } + angle = tmpAlpha * m1; + cosAngle = cosf(angle); + sinAngle = sinf(angle); + phaseRe = cosAngle * tmpRe + sinAngle * tmpIm; + phaseIm = cosAngle * tmpIm - sinAngle * tmpRe; + angle = tmpBeta * m1; + cosAngle = cosf(angle); + sinAngle = sinf(angle); + x_out[idx] += (cosAngle * phaseRe + sinAngle * phaseIm); + x_out[idx + 1] += (cosAngle * phaseIm - sinAngle * phaseRe); + } + } + } + return FFT_OK; +} + +/* +// Inverse 1-dimensional FFT +// +// API +// int fftInverse(float *x_in, float *x_out, int n, int shift); +// INPUT +// x_in - Fourier image of 1d input signal(contains 2n elements + in order Re(x_in[0]), Im(x_in[0]), + Re(x_in[1]), Im(x_in[1]) and etc.) +// n - number of elements for searching counter FFT image +// shift - shift between input elements +// OUTPUT +// x_in - input signal (contains n elements) +// RESULT +// Error status +*/ +int fftInverse(float *x_in, float *x_out, int n, int shift) +{ + int n1, n2, res, k1, k2, m1, m2, index, idx; + float alpha, beta, gamma, angle, cosAngle, sinAngle; + float tmpRe, tmpIm, phaseRe, phaseIm; + res = getMultipliers(n, &n1, &n2); + if (res == FFT_OK) + { + fftInverse(x_in, x_out, n1, shift); + fftInverse(x_in, x_out, n2, shift); + } + alpha = (float)(2.0f * PI / ((float)n)); + beta = (float)(2.0f * PI / ((float)n1)); + gamma = (float)(2.0f * PI / ((float)n2)); + for (m1 = 0; m1 < n1; m1++) + { + for (m2 = 0; m2 < n2; m2++) + { + idx = (n1 * m2 + m1) * shift; + x_out[idx] = 0.0; + x_out[idx + 1] = 0.0; + for (k2 = 0; k2 < n2; k2++) + { + tmpRe = 0.0; + tmpIm = 0.0; + for (k1 = 0; k1 < n1; k1++) + { + angle = beta * k1 * m1; + index = shift *(n2 * k1 + k2); + sinAngle = sinf(angle); + cosAngle = cosf(angle); + tmpRe += x_in[index] * cosAngle - x_in[index + 1] * sinAngle; + tmpIm += x_in[index] * sinAngle + x_in[index + 1] * cosAngle; + } + angle = alpha * m1 * k2; + sinAngle = sinf(angle); + cosAngle = cosf(angle); + phaseRe = cosAngle * tmpRe - sinAngle * tmpIm; + phaseIm = cosAngle * tmpIm + sinAngle * tmpRe; + angle = gamma * k2 * m2; + sinAngle = sinf(angle); + cosAngle = cosf(angle); + x_out[idx] += cosAngle * phaseRe - sinAngle * phaseIm; + x_out[idx + 1] += cosAngle * phaseIm + sinAngle * phaseRe; + } + x_out[idx] /= n; + x_out[idx + 1] /= n; + } + } + return FFT_OK; +} + +/* +// 2-dimensional FFT +// +// API +// int fft2d(float *x_in, float *x_out, int numRows, int numColls); +// INPUT +// x_in - input signal (matrix, launched by rows) +// numRows - number of rows +// numColls - number of collumns +// OUTPUT +// x_out - output signal (contains (2 * numRows * numColls) elements + in order Re(x_in[0][0]), Im(x_in[0][0]), + Re(x_in[0][1]), Im(x_in[0][1]) and etc.) +// RESULT +// Error status +*/ +int fft2d(float *x_in, float *x_out, int numRows, int numColls) +{ + int i, size; + float *x_outTmp; + size = numRows * numColls; + x_outTmp = (float *)malloc(sizeof(float) * (2 * size)); + for (i = 0; i < numRows; i++) + { + fft(x_in + i * 2 * numColls, + x_outTmp + i * 2 * numColls, + numColls, 2); + } + for (i = 0; i < numColls; i++) + { + fft(x_outTmp + 2 * i, + x_out + 2 * i, + numRows, 2 * numColls); + } + free(x_outTmp); + return FFT_OK; +} + +/* +// Inverse 2-dimensional FFT +// +// API +// int fftInverse2d(float *x_in, float *x_out, int numRows, int numColls); +// INPUT +// x_in - Fourier image of matrix (contains (2 * numRows * numColls) + elements in order Re(x_in[0][0]), Im(x_in[0][0]), + Re(x_in[0][1]), Im(x_in[0][1]) and etc.) +// numRows - number of rows +// numColls - number of collumns +// OUTPUT +// x_out - initial signal (matrix, launched by rows) +// RESULT +// Error status +*/ +int fftInverse2d(float *x_in, float *x_out, int numRows, int numColls) +{ + int i, size; + float *x_outTmp; + size = numRows * numColls; + x_outTmp = (float *)malloc(sizeof(float) * (2 * size)); + for (i = 0; i < numRows; i++) + { + fftInverse(x_in + i * 2 * numColls, + x_outTmp + i * 2 * numColls, + numColls, 2); + } + for (i = 0; i < numColls; i++) + { + fftInverse(x_outTmp + 2 * i, + x_out + 2 * i, + numRows, 2 * numColls); + } + free(x_outTmp); + return FFT_OK; +} + diff --git a/modules/objdetect/src/latentsvm.cpp b/modules/objdetect/src/latentsvm.cpp new file mode 100644 index 0000000000..2a811f97ca --- /dev/null +++ b/modules/objdetect/src/latentsvm.cpp @@ -0,0 +1,611 @@ +#include "_latentsvm.h" +#include "_matching.h" + +/* +// Transformation filter displacement from the block space +// to the space of pixels at the initial image +// +// API +// int convertPoints(int countLevel, CvPoint *points, int *levels, + CvPoint **partsDisplacement, int kPoints, int n); +// INPUT +// countLevel - the number of levels in the feature pyramid +// points - the set of root filter positions (in the block space) +// levels - the set of levels +// partsDisplacement - displacement of part filters (in the block space) +// kPoints - number of root filter positions +// n - number of part filters +// initialImageLevel - level that contains features for initial image +// maxXBorder - the largest root filter size (X-direction) +// maxYBorder - the largest root filter size (Y-direction) +// OUTPUT +// points - the set of root filter positions (in the space of pixels) +// partsDisplacement - displacement of part filters (in the space of pixels) +// RESULT +// Error status +*/ +int convertPoints(int countLevel, int lambda, + int initialImageLevel, + CvPoint *points, int *levels, + CvPoint **partsDisplacement, int kPoints, int n, + int maxXBorder, + int maxYBorder) +{ + int i, j, bx, by; + float step, scale; + step = powf( 2.0f, 1.0f / ((float)lambda) ); + + computeBorderSize(maxXBorder, maxYBorder, &bx, &by); + + for (i = 0; i < kPoints; i++) + { + // scaling factor for root filter + scale = SIDE_LENGTH * powf(step, (float)(levels[i] - initialImageLevel)); + points[i].x = (int)((points[i].x - bx + 1) * scale); + points[i].y = (int)((points[i].y - by + 1) * scale); + + // scaling factor for part filters + scale = SIDE_LENGTH * powf(step, (float)(levels[i] - lambda - initialImageLevel)); + for (j = 0; j < n; j++) + { + partsDisplacement[i][j].x = (int)((partsDisplacement[i][j].x - + 2 * bx + 1) * scale); + partsDisplacement[i][j].y = (int)((partsDisplacement[i][j].y - + 2 * by + 1) * scale); + } + } + return LATENT_SVM_OK; +} + +/* +// Elimination boxes that are outside the image boudaries +// +// API +// int clippingBoxes(int width, int height, + CvPoint *points, int kPoints); +// INPUT +// width - image wediht +// height - image heigth +// points - a set of points (coordinates of top left or + bottom right corners) +// kPoints - points number +// OUTPUT +// points - updated points (if coordinates less than zero then + set zero coordinate, if coordinates more than image + size then set coordinates equal image size) +// RESULT +// Error status +*/ +int clippingBoxes(int width, int height, + CvPoint *points, int kPoints) +{ + int i; + for (i = 0; i < kPoints; i++) + { + if (points[i].x > width - 1) + { + points[i].x = width - 1; + } + if (points[i].x < 0) + { + points[i].x = 0; + } + if (points[i].y > height - 1) + { + points[i].y = height - 1; + } + if (points[i].y < 0) + { + points[i].y = 0; + } + } + return LATENT_SVM_OK; +} + +/* +// Creation feature pyramid with nullable border +// +// API +// featurePyramid* createFeaturePyramidWithBorder(const IplImage *image, + int maxXBorder, int maxYBorder); + +// INPUT +// image - initial image +// maxXBorder - the largest root filter size (X-direction) +// maxYBorder - the largest root filter size (Y-direction) +// OUTPUT +// RESULT +// Feature pyramid with nullable border +*/ +featurePyramid* createFeaturePyramidWithBorder(IplImage *image, + int maxXBorder, int maxYBorder) +{ + int opResult; + int bx, by; + int level; + featurePyramid *H; + + // Obtaining feature pyramid + opResult = getFeaturePyramid(image, LAMBDA, SIDE_LENGTH, 0, 0, + image->width, image->height, &H); + + if (opResult != LATENT_SVM_OK) + { + freeFeaturePyramidObject(&H); + return NULL; + } /* if (opResult != LATENT_SVM_OK) */ + + // Addition nullable border for each feature map + // the size of the border for root filters + computeBorderSize(maxXBorder, maxYBorder, &bx, &by); + for (level = 0; level < H->countLevel; level++) + { + addNullableBorder(H->pyramid[level], bx, by); + } + return H; +} + +/* +// Computation of the root filter displacement and values of score function +// +// API +// int searchObject(const featurePyramid *H, const filterObject **all_F, int n, + float b, + int maxXBorder, + int maxYBorder, + CvPoint **points, int **levels, int *kPoints, float *score, + CvPoint ***partsDisplacement); +// INPUT +// image - initial image for searhing object +// all_F - the set of filters (the first element is root filter, + other elements - part filters) +// n - the number of part filters +// b - linear term of the score function +// maxXBorder - the largest root filter size (X-direction) +// maxYBorder - the largest root filter size (Y-direction) +// OUTPUT +// points - positions (x, y) of the upper-left corner + of root filter frame +// levels - levels that correspond to each position +// kPoints - number of positions +// score - value of the score function +// partsDisplacement - part filters displacement for each position + of the root filter +// RESULT +// Error status +*/ +int searchObject(const featurePyramid *H, const filterObject **all_F, + int n, float b, + int maxXBorder, + int maxYBorder, + CvPoint **points, int **levels, int *kPoints, float *score, + CvPoint ***partsDisplacement) +{ + int opResult; + + // Matching + opResult = maxFunctionalScore(all_F, n, H, b, maxXBorder, maxYBorder, + score, points, levels, + kPoints, partsDisplacement); + if (opResult != LATENT_SVM_OK) + { + return LATENT_SVM_SEARCH_OBJECT_FAILED; + } + + // Transformation filter displacement from the block space + // to the space of pixels at the initial image + // that settles at the level number LAMBDA + convertPoints(H->countLevel, H->lambda, LAMBDA, (*points), + (*levels), (*partsDisplacement), (*kPoints), n, + maxXBorder, maxYBorder); + + return LATENT_SVM_OK; +} + +/* +// Computation right bottom corners coordinates of bounding boxes +// +// API +// int estimateBoxes(CvPoint *points, int *levels, int kPoints, + int sizeX, int sizeY, CvPoint **oppositePoints); +// INPUT +// points - left top corners coordinates of bounding boxes +// levels - levels of feature pyramid where points were found +// (sizeX, sizeY) - size of root filter +// OUTPUT +// oppositePoins - right bottom corners coordinates of bounding boxes +// RESULT +// Error status +*/ +int estimateBoxes(CvPoint *points, int *levels, int kPoints, + int sizeX, int sizeY, CvPoint **oppositePoints) +{ + int i; + float step; + + step = powf( 2.0f, 1.0f / ((float)(LAMBDA))); + + *oppositePoints = (CvPoint *)malloc(sizeof(CvPoint) * kPoints); + for (i = 0; i < kPoints; i++) + { + getOppositePoint(points[i], sizeX, sizeY, step, levels[i] - LAMBDA, &((*oppositePoints)[i])); + } + return LATENT_SVM_OK; +} + +/* +// Computation of the root filter displacement and values of score function +// +// API +// int searchObjectThreshold(const featurePyramid *H, + const filterObject **all_F, int n, + float b, + int maxXBorder, int maxYBorder, + float scoreThreshold, + CvPoint **points, int **levels, int *kPoints, + float **score, CvPoint ***partsDisplacement); +// INPUT +// H - feature pyramid +// all_F - the set of filters (the first element is root filter, + other elements - part filters) +// n - the number of part filters +// b - linear term of the score function +// maxXBorder - the largest root filter size (X-direction) +// maxYBorder - the largest root filter size (Y-direction) +// scoreThreshold - score threshold +// OUTPUT +// points - positions (x, y) of the upper-left corner + of root filter frame +// levels - levels that correspond to each position +// kPoints - number of positions +// score - values of the score function +// partsDisplacement - part filters displacement for each position + of the root filter +// RESULT +// Error status +*/ +int searchObjectThreshold(const featurePyramid *H, + const filterObject **all_F, int n, + float b, + int maxXBorder, int maxYBorder, + float scoreThreshold, + CvPoint **points, int **levels, int *kPoints, + float **score, CvPoint ***partsDisplacement) +{ + int opResult; + + + // Matching + opResult = thresholdFunctionalScore(all_F, n, H, b, + maxXBorder, maxYBorder, + scoreThreshold, + score, points, levels, + kPoints, partsDisplacement); + if (opResult != LATENT_SVM_OK) + { + return LATENT_SVM_SEARCH_OBJECT_FAILED; + } + + // Transformation filter displacement from the block space + // to the space of pixels at the initial image + // that settles at the level number LAMBDA + convertPoints(H->countLevel, H->lambda, LAMBDA, (*points), + (*levels), (*partsDisplacement), (*kPoints), n, + maxXBorder, maxYBorder); + + return LATENT_SVM_OK; +} + +/* +// Compute opposite point for filter box +// +// API +// int getOppositePoint(CvPoint point, + int sizeX, int sizeY, + float step, int degree, + CvPoint *oppositePoint); + +// INPUT +// point - coordinates of filter top left corner + (in the space of pixels) +// (sizeX, sizeY) - filter dimension in the block space +// step - scaling factor +// degree - degree of the scaling factor +// OUTPUT +// oppositePoint - coordinates of filter bottom corner + (in the space of pixels) +// RESULT +// Error status +*/ +int getOppositePoint(CvPoint point, + int sizeX, int sizeY, + float step, int degree, + CvPoint *oppositePoint) +{ + float scale; + scale = SIDE_LENGTH * powf(step, (float)degree); + oppositePoint->x = (int)(point.x + sizeX * scale); + oppositePoint->y = (int)(point.y + sizeY * scale); + return LATENT_SVM_OK; +} + + +/* +// Drawing root filter boxes +// +// API +// int showRootFilterBoxes(const IplImage *image, + const filterObject *filter, + CvPoint *points, int *levels, int kPoints, + CvScalar color, int thickness, + int line_type, int shift); +// INPUT +// image - initial image +// filter - root filter object +// points - a set of points +// levels - levels of feature pyramid +// kPoints - number of points +// color - line color for each box +// thickness - line thickness +// line_type - line type +// shift - shift +// OUTPUT +// window contained initial image and filter boxes +// RESULT +// Error status +*/ +int showRootFilterBoxes(IplImage *image, + const filterObject *filter, + CvPoint *points, int *levels, int kPoints, + CvScalar color, int thickness, + int line_type, int shift) +{ + int i; + float step; + CvPoint oppositePoint; + step = powf( 2.0f, 1.0f / ((float)LAMBDA)); + + for (i = 0; i < kPoints; i++) + { + // Drawing rectangle for filter + getOppositePoint(points[i], filter->sizeX, filter->sizeY, + step, levels[i] - LAMBDA, &oppositePoint); + cvRectangle(image, points[i], oppositePoint, + color, thickness, line_type, shift); + } + cvShowImage("Initial image", image); + return LATENT_SVM_OK; +} + +/* +// Drawing part filter boxes +// +// API +// int showPartFilterBoxes(const IplImage *image, + const filterObject *filter, + CvPoint *points, int *levels, int kPoints, + CvScalar color, int thickness, + int line_type, int shift); +// INPUT +// image - initial image +// filters - a set of part filters +// n - number of part filters +// partsDisplacement - a set of points +// levels - levels of feature pyramid +// kPoints - number of foot filter positions +// color - line color for each box +// thickness - line thickness +// line_type - line type +// shift - shift +// OUTPUT +// window contained initial image and filter boxes +// RESULT +// Error status +*/ +int showPartFilterBoxes(IplImage *image, + const filterObject **filters, + int n, CvPoint **partsDisplacement, + int *levels, int kPoints, + CvScalar color, int thickness, + int line_type, int shift) +{ + int i, j; + float step; + CvPoint oppositePoint; + + step = powf( 2.0f, 1.0f / ((float)LAMBDA)); + + for (i = 0; i < kPoints; i++) + { + for (j = 0; j < n; j++) + { + // Drawing rectangles for part filters + getOppositePoint(partsDisplacement[i][j], + filters[j + 1]->sizeX, filters[j + 1]->sizeY, + step, levels[i] - 2 * LAMBDA, &oppositePoint); + cvRectangle(image, partsDisplacement[i][j], oppositePoint, + color, thickness, line_type, shift); + } + } + cvShowImage("Initial image", image); + return LATENT_SVM_OK; +} + +/* +// Drawing boxes +// +// API +// int showBoxes(const IplImage *img, + const CvPoint *points, const CvPoint *oppositePoints, int kPoints, + CvScalar color, int thickness, int line_type, int shift); +// INPUT +// img - initial image +// points - top left corner coordinates +// oppositePoints - right bottom corner coordinates +// kPoints - points number +// color - line color for each box +// thickness - line thickness +// line_type - line type +// shift - shift +// OUTPUT +// RESULT +// Error status +*/ +int showBoxes(IplImage *img, + const CvPoint *points, const CvPoint *oppositePoints, int kPoints, + CvScalar color, int thickness, int line_type, int shift) +{ + int i; + for (i = 0; i < kPoints; i++) + { + cvRectangle(img, points[i], oppositePoints[i], + color, thickness, line_type, shift); + } + cvShowImage("Initial image", img); + return LATENT_SVM_OK; +} + +/* +// Computation maximum filter size for each dimension +// +// API +// int getMaxFilterDims(const filterObject **filters, int kComponents, + const int *kPartFilters, + unsigned int *maxXBorder, unsigned int *maxYBorder); +// INPUT +// filters - a set of filters (at first root filter, then part filters + and etc. for all components) +// kComponents - number of components +// kPartFilters - number of part filters for each component +// OUTPUT +// maxXBorder - maximum of filter size at the horizontal dimension +// maxYBorder - maximum of filter size at the vertical dimension +// RESULT +// Error status +*/ +int getMaxFilterDims(const filterObject **filters, int kComponents, + const int *kPartFilters, + unsigned int *maxXBorder, unsigned int *maxYBorder) +{ + int i, componentIndex; + *maxXBorder = filters[0]->sizeX; + *maxYBorder = filters[0]->sizeY; + componentIndex = kPartFilters[0] + 1; + for (i = 1; i < kComponents; i++) + { + if (filters[componentIndex]->sizeX > *maxXBorder) + { + *maxXBorder = filters[componentIndex]->sizeX; + } + if (filters[componentIndex]->sizeY > *maxYBorder) + { + *maxYBorder = filters[componentIndex]->sizeY; + } + componentIndex += (kPartFilters[i] + 1); + } + return LATENT_SVM_OK; +} + +/* +// Computation root filters displacement and values of score function +// +// API +// int searchObjectThresholdSomeComponents(const featurePyramid *H, + const filterObject **filters, + int kComponents, const int *kPartFilters, + const float *b, float scoreThreshold, + CvPoint **points, CvPoint **oppPoints, + float **score, int *kPoints); +// INPUT +// H - feature pyramid +// filters - filters (root filter then it's part filters, etc.) +// kComponents - root filters number +// kPartFilters - array of part filters number for each component +// b - array of linear terms +// scoreThreshold - score threshold +// OUTPUT +// points - root filters displacement (top left corners) +// oppPoints - root filters displacement (bottom right corners) +// score - array of score values +// kPoints - number of boxes +// RESULT +// Error status +*/ +int searchObjectThresholdSomeComponents(const featurePyramid *H, + const filterObject **filters, + int kComponents, const int *kPartFilters, + const float *b, float scoreThreshold, + CvPoint **points, CvPoint **oppPoints, + float **score, int *kPoints) +{ + int error = 0; + int i, j, s, f, componentIndex; + unsigned int maxXBorder, maxYBorder; + CvPoint **pointsArr, **oppPointsArr, ***partsDisplacementArr; + float **scoreArr; + int *kPointsArr, **levelsArr; + + // Allocation memory + pointsArr = (CvPoint **)malloc(sizeof(CvPoint *) * kComponents); + oppPointsArr = (CvPoint **)malloc(sizeof(CvPoint *) * kComponents); + scoreArr = (float **)malloc(sizeof(float *) * kComponents); + kPointsArr = (int *)malloc(sizeof(int) * kComponents); + levelsArr = (int **)malloc(sizeof(int *) * kComponents); + partsDisplacementArr = (CvPoint ***)malloc(sizeof(CvPoint **) * kComponents); + + // Getting maximum filter dimensions + error = getMaxFilterDims(filters, kComponents, kPartFilters, &maxXBorder, &maxYBorder); + componentIndex = 0; + *kPoints = 0; + // For each component perform searching + for (i = 0; i < kComponents; i++) + { + searchObjectThreshold(H, &(filters[componentIndex]), kPartFilters[i], + b[i], maxXBorder, maxYBorder, scoreThreshold, + &(pointsArr[i]), &(levelsArr[i]), &(kPointsArr[i]), + &(scoreArr[i]), &(partsDisplacementArr[i])); + estimateBoxes(pointsArr[i], levelsArr[i], kPointsArr[i], + filters[componentIndex]->sizeX, filters[componentIndex]->sizeY, &(oppPointsArr[i])); + componentIndex += (kPartFilters[i] + 1); + *kPoints += kPointsArr[i]; + } + + *points = (CvPoint *)malloc(sizeof(CvPoint) * (*kPoints)); + *oppPoints = (CvPoint *)malloc(sizeof(CvPoint) * (*kPoints)); + *score = (float *)malloc(sizeof(float) * (*kPoints)); + s = 0; + for (i = 0; i < kComponents; i++) + { + f = s + kPointsArr[i]; + for (j = s; j < f; j++) + { + (*points)[j].x = pointsArr[i][j - s].x; + (*points)[j].y = pointsArr[i][j - s].y; + (*oppPoints)[j].x = oppPointsArr[i][j - s].x; + (*oppPoints)[j].y = oppPointsArr[i][j - s].y; + (*score)[j] = scoreArr[i][j - s]; + } + s = f; + } + + // Release allocated memory + for (i = 0; i < kComponents; i++) + { + free(pointsArr[i]); + free(oppPointsArr[i]); + free(scoreArr[i]); + free(levelsArr[i]); + for (j = 0; j < kPointsArr[i]; j++) + { + free(partsDisplacementArr[i][j]); + } + free(partsDisplacementArr[i]); + } + free(pointsArr); + free(oppPointsArr); + free(scoreArr); + free(kPointsArr); + free(levelsArr); + free(partsDisplacementArr); + return LATENT_SVM_OK; +} diff --git a/modules/objdetect/src/latentsvmdetector.cpp b/modules/objdetect/src/latentsvmdetector.cpp new file mode 100644 index 0000000000..f7dcd8fa5d --- /dev/null +++ b/modules/objdetect/src/latentsvmdetector.cpp @@ -0,0 +1,134 @@ +#include "precomp.hpp" +#include "_lsvmparser.h" +#include "_matching.h" + +/* +// load trained detector from a file +// +// API +// CvLatentSvmDetector* cvLoadLatentSvmDetector(const char* filename); +// INPUT +// filename - path to the file containing the parameters of + - trained Latent SVM detector +// OUTPUT +// trained Latent SVM detector in internal representation +*/ +CvLatentSvmDetector* cvLoadLatentSvmDetector(const char* filename) +{ + CvLatentSvmDetector* detector = 0; + filterObject** filters = 0; + int kFilters = 0; + int kComponents = 0; + int* kPartFilters = 0; + float* b = 0; + float scoreThreshold = 0.f; + + loadModel(filename, &filters, &kFilters, &kComponents, &kPartFilters, &b, &scoreThreshold); + + detector = (CvLatentSvmDetector*)malloc(sizeof(CvLatentSvmDetector)); + detector->filters = filters; + detector->b = b; + detector->num_components = kComponents; + detector->num_filters = kFilters; + detector->num_part_filters = kPartFilters; + detector->score_threshold = scoreThreshold; + + return detector; +} + +/* +// release memory allocated for CvLatentSvmDetector structure +// +// API +// void cvReleaseLatentSvmDetector(CvLatentSvmDetector** detector); +// INPUT +// detector - CvLatentSvmDetector structure to be released +// OUTPUT +*/ +void cvReleaseLatentSvmDetector(CvLatentSvmDetector** detector) +{ + free((*detector)->b); + free((*detector)->num_part_filters); + for (int i = 0; i < (*detector)->num_filters; i++) + { + free((*detector)->filters[i]->H); + free((*detector)->filters[i]); + } + free((*detector)->filters); + free((*detector)); + *detector = 0; +} + +/* +// find rectangular regions in the given image that are likely +// to contain objects and corresponding confidence levels +// +// API +// CvSeq* cvLatentSvmDetectObjects(const IplImage* image, +// CvLatentSvmDetector* detector, +// CvMemStorage* storage, +// float overlap_threshold = 0.5f); +// INPUT +// image - image to detect objects in +// detector - Latent SVM detector in internal representation +// storage - memory storage to store the resultant sequence +// of the object candidate rectangles +// overlap_threshold - threshold for the non-maximum suppression algorithm [here will be the reference to original paper] +// OUTPUT +// sequence of detected objects (bounding boxes and confidence levels stored in CvObjectDetection structures) +*/ +CvSeq* cvLatentSvmDetectObjects(IplImage* image, + CvLatentSvmDetector* detector, + CvMemStorage* storage, + float overlap_threshold) +{ + featurePyramid *H = 0; + CvPoint *points = 0, *oppPoints = 0; + int kPoints = 0; + float *score = 0; + unsigned int maxXBorder = 0, maxYBorder = 0; + int numBoxesOut = 0; + CvPoint *pointsOut = 0; + CvPoint *oppPointsOut = 0; + float *scoreOut = 0; + CvSeq* result_seq = 0; + + cvConvertImage(image, image, CV_CVTIMG_SWAP_RB); + // Getting maximum filter dimensions + getMaxFilterDims((const filterObject**)(detector->filters), detector->num_components, detector->num_part_filters, &maxXBorder, &maxYBorder); + // Create feature pyramid with nullable border + H = createFeaturePyramidWithBorder(image, maxXBorder, maxYBorder); + // Search object + searchObjectThresholdSomeComponents(H, (const filterObject**)(detector->filters), detector->num_components, + detector->num_part_filters, detector->b, detector->score_threshold, + &points, &oppPoints, &score, &kPoints); + // Clipping boxes + clippingBoxes(image->width, image->height, points, kPoints); + clippingBoxes(image->width, image->height, oppPoints, kPoints); + // NMS procedure + nonMaximumSuppression(kPoints, points, oppPoints, score, overlap_threshold, + &numBoxesOut, &pointsOut, &oppPointsOut, &scoreOut); + + result_seq = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvObjectDetection), storage ); + + for (int i = 0; i < numBoxesOut; i++) + { + CvObjectDetection detection = {{0, 0, 0, 0}, 0}; + detection.score = scoreOut[i]; + CvRect bounding_box = {0, 0, 0, 0}; + bounding_box.x = pointsOut[i].x; + bounding_box.y = pointsOut[i].y; + bounding_box.width = oppPointsOut[i].x - pointsOut[i].x; + bounding_box.height = oppPointsOut[i].y - pointsOut[i].y; + detection.rect = bounding_box; + cvSeqPush(result_seq, &detection); + } + cvConvertImage(image, image, CV_CVTIMG_SWAP_RB); + + freeFeaturePyramidObject(&H); + free(points); + free(oppPoints); + free(score); + + return result_seq; +} \ No newline at end of file diff --git a/modules/objdetect/src/lsvmparser.cpp b/modules/objdetect/src/lsvmparser.cpp new file mode 100644 index 0000000000..a9eeac6f46 --- /dev/null +++ b/modules/objdetect/src/lsvmparser.cpp @@ -0,0 +1,800 @@ +#include +#include "string.h" +#include "_lsvmparser.h" + +int isMODEL (char *str){ + char stag [] = ""; + char etag [] = ""; + if(strcmp(stag, str) == 0)return MODEL; + if(strcmp(etag, str) == 0)return EMODEL; + return 0; +} +int isP (char *str){ + char stag [] = "

"; + char etag [] = "

"; + if(strcmp(stag, str) == 0)return P; + if(strcmp(etag, str) == 0)return EP; + return 0; +} +int isSCORE (char *str){ + char stag [] = ""; + char etag [] = ""; + if(strcmp(stag, str) == 0)return SCORE; + if(strcmp(etag, str) == 0)return ESCORE; + return 0; +} +int isCOMP (char *str){ + char stag [] = ""; + char etag [] = ""; + if(strcmp(stag, str) == 0)return COMP; + if(strcmp(etag, str) == 0)return ECOMP; + return 0; +} +int isRFILTER (char *str){ + char stag [] = ""; + char etag [] = ""; + if(strcmp(stag, str) == 0)return RFILTER; + if(strcmp(etag, str) == 0)return ERFILTER; + return 0; +} +int isPFILTERs (char *str){ + char stag [] = ""; + char etag [] = ""; + if(strcmp(stag, str) == 0)return PFILTERs; + if(strcmp(etag, str) == 0)return EPFILTERs; + return 0; +} +int isPFILTER (char *str){ + char stag [] = ""; + char etag [] = ""; + if(strcmp(stag, str) == 0)return PFILTER; + if(strcmp(etag, str) == 0)return EPFILTER; + return 0; +} +int isSIZEX (char *str){ + char stag [] = ""; + char etag [] = ""; + if(strcmp(stag, str) == 0)return SIZEX; + if(strcmp(etag, str) == 0)return ESIZEX; + return 0; +} +int isSIZEY (char *str){ + char stag [] = ""; + char etag [] = ""; + if(strcmp(stag, str) == 0)return SIZEY; + if(strcmp(etag, str) == 0)return ESIZEY; + return 0; +} +int isWEIGHTS (char *str){ + char stag [] = ""; + char etag [] = ""; + if(strcmp(stag, str) == 0)return WEIGHTS; + if(strcmp(etag, str) == 0)return EWEIGHTS; + return 0; +} +int isV (char *str){ + char stag [] = ""; + char etag [] = ""; + if(strcmp(stag, str) == 0)return TAGV; + if(strcmp(etag, str) == 0)return ETAGV; + return 0; +} +int isVx (char *str){ + char stag [] = ""; + char etag [] = ""; + if(strcmp(stag, str) == 0)return Vx; + if(strcmp(etag, str) == 0)return EVx; + return 0; +} +int isVy (char *str){ + char stag [] = ""; + char etag [] = ""; + if(strcmp(stag, str) == 0)return Vy; + if(strcmp(etag, str) == 0)return EVy; + return 0; +} +int isD (char *str){ + char stag [] = ""; + char etag [] = ""; + if(strcmp(stag, str) == 0)return TAGD; + if(strcmp(etag, str) == 0)return ETAGD; + return 0; +} +int isDx (char *str){ + char stag [] = ""; + char etag [] = ""; + if(strcmp(stag, str) == 0)return Dx; + if(strcmp(etag, str) == 0)return EDx; + return 0; +} +int isDy (char *str){ + char stag [] = ""; + char etag [] = ""; + if(strcmp(stag, str) == 0)return Dy; + if(strcmp(etag, str) == 0)return EDy; + return 0; +} +int isDxx (char *str){ + char stag [] = ""; + char etag [] = ""; + if(strcmp(stag, str) == 0)return Dxx; + if(strcmp(etag, str) == 0)return EDxx; + return 0; +} +int isDyy (char *str){ + char stag [] = ""; + char etag [] = ""; + if(strcmp(stag, str) == 0)return Dyy; + if(strcmp(etag, str) == 0)return EDyy; + return 0; +} +int isB (char *str){ + char stag [] = ""; + char etag [] = ""; + if(strcmp(stag, str) == 0)return BTAG; + if(strcmp(etag, str) == 0)return EBTAG; + return 0; +} + +int getTeg(char *str){ + int sum = 0; + sum = isMODEL (str)+ + isP (str)+ + isSCORE (str)+ + isCOMP (str)+ + isRFILTER (str)+ + isPFILTERs (str)+ + isPFILTER (str)+ + isSIZEX (str)+ + isSIZEY (str)+ + isWEIGHTS (str)+ + isV (str)+ + isVx (str)+ + isVy (str)+ + isD (str)+ + isDx (str)+ + isDy (str)+ + isDxx (str)+ + isDyy (str)+ + isB (str); + + return sum; +} + +void addFilter(filterObject *** model, int *last, int *max){ + filterObject ** nmodel; + int i; + (*last) ++; + if((*last) >= (*max)){ + (*max) += 10; + nmodel = (filterObject **)malloc(sizeof(filterObject *) * (*max)); + for(i = 0; i < *last; i++){ + nmodel[i] = (* model)[i]; + } + free(* model); + (*model) = nmodel; + } + (*model) [(*last)] = (filterObject *)malloc(sizeof(filterObject)); +} + +void parserRFilter (FILE * xmlf, int p, filterObject * model, float *b){ + int st = 0; + int sizeX, sizeY; + int tag; + int tagVal; + char ch; + int i,j,ii; + char buf[1024]; + char tagBuf[1024]; + double *data; + //printf("\n"); + + model->V.x = 0; + model->V.y = 0; + model->V.l = 0; + model->fineFunction[0] = 0.0; + model->fineFunction[1] = 0.0; + model->fineFunction[2] = 0.0; + model->fineFunction[3] = 0.0; + + i = 0; + j = 0; + st = 0; + tag = 0; + while(!feof(xmlf)){ + ch = fgetc( xmlf ); + if(ch == '<'){ + tag = 1; + j = 1; + tagBuf[j - 1] = ch; + }else { + if(ch == '>'){ + tagBuf[j ] = ch; + tagBuf[j + 1] = '\0'; + + tagVal = getTeg(tagBuf); + + if(tagVal == ERFILTER){ + //printf("\n"); + return; + } + if(tagVal == SIZEX){ + st = 1; + i = 0; + } + if(tagVal == ESIZEX){ + st = 0; + buf[i] = '\0'; + sizeX = atoi(buf); + model->sizeX = sizeX; + //printf("%d\n", sizeX); + } + if(tagVal == SIZEY){ + st = 1; + i = 0; + } + if(tagVal == ESIZEY){ + st = 0; + buf[i] = '\0'; + sizeY = atoi(buf); + model->sizeY = sizeY; + //printf("%d\n", sizeY); + } + if(tagVal == WEIGHTS){ + data = (double *)malloc( sizeof(double) * p * sizeX * sizeY); + fread(data, sizeof(double), p * sizeX * sizeY, xmlf); + model->H = (float *)malloc(sizeof(float)* p * sizeX * sizeY); + for(ii = 0; ii < p * sizeX * sizeY; ii++){ + model->H[ii] = (float)data[ii]; + } + free(data); + } + if(tagVal == EWEIGHTS){ + //printf("WEIGHTS OK\n"); + } + if(tagVal == BTAG){ + st = 1; + i = 0; + } + if(tagVal == EBTAG){ + st = 0; + buf[i] = '\0'; + *b =(float) atof(buf); + //printf("%f\n", *b); + } + + tag = 0; + i = 0; + }else{ + if((tag == 0)&& (st == 1)){ + buf[i] = ch; i++; + }else{ + tagBuf[j] = ch; j++; + } + } + } + } +} + +void parserV (FILE * xmlf, int p, filterObject * model){ + int st = 0; + int tag; + int tagVal; + char ch; + int i,j; + char buf[1024]; + char tagBuf[1024]; + //printf(" \n"); + + i = 0; + j = 0; + st = 0; + tag = 0; + while(!feof(xmlf)){ + ch = fgetc( xmlf ); + if(ch == '<'){ + tag = 1; + j = 1; + tagBuf[j - 1] = ch; + }else { + if(ch == '>'){ + tagBuf[j ] = ch; + tagBuf[j + 1] = '\0'; + + tagVal = getTeg(tagBuf); + + if(tagVal == ETAGV){ + //printf(" \n"); + return; + } + if(tagVal == Vx){ + st = 1; + i = 0; + } + if(tagVal == EVx){ + st = 0; + buf[i] = '\0'; + model->V.x = atoi(buf); + //printf(" %d\n", model->V.x); + } + if(tagVal == Vy){ + st = 1; + i = 0; + } + if(tagVal == EVy){ + st = 0; + buf[i] = '\0'; + model->V.y = atoi(buf); + //printf(" %d\n", model->V.y); + } + tag = 0; + i = 0; + }else{ + if((tag == 0)&& (st == 1)){ + buf[i] = ch; i++; + }else{ + tagBuf[j] = ch; j++; + } + } + } + } +} +void parserD (FILE * xmlf, int p, filterObject * model){ + int st = 0; + int tag; + int tagVal; + char ch; + int i,j; + char buf[1024]; + char tagBuf[1024]; + //printf(" \n"); + + i = 0; + j = 0; + st = 0; + tag = 0; + while(!feof(xmlf)){ + ch = fgetc( xmlf ); + if(ch == '<'){ + tag = 1; + j = 1; + tagBuf[j - 1] = ch; + }else { + if(ch == '>'){ + tagBuf[j ] = ch; + tagBuf[j + 1] = '\0'; + + tagVal = getTeg(tagBuf); + + if(tagVal == ETAGD){ + //printf(" \n"); + return; + } + if(tagVal == Dx){ + st = 1; + i = 0; + } + if(tagVal == EDx){ + st = 0; + buf[i] = '\0'; + + model->fineFunction[0] = (float)atof(buf); + //printf(" %f\n", model->fineFunction[0]); + } + if(tagVal == Dy){ + st = 1; + i = 0; + } + if(tagVal == EDy){ + st = 0; + buf[i] = '\0'; + + model->fineFunction[1] = (float)atof(buf); + //printf(" %f\n", model->fineFunction[1]); + } + if(tagVal == Dxx){ + st = 1; + i = 0; + } + if(tagVal == EDxx){ + st = 0; + buf[i] = '\0'; + + model->fineFunction[2] = (float)atof(buf); + //printf(" %f\n", model->fineFunction[2]); + } + if(tagVal == Dyy){ + st = 1; + i = 0; + } + if(tagVal == EDyy){ + st = 0; + buf[i] = '\0'; + + model->fineFunction[3] = (float)atof(buf); + //printf(" %f\n", model->fineFunction[3]); + } + + tag = 0; + i = 0; + }else{ + if((tag == 0)&& (st == 1)){ + buf[i] = ch; i++; + }else{ + tagBuf[j] = ch; j++; + } + } + } + } +} + +void parserPFilter (FILE * xmlf, int p, int N_path, filterObject * model){ + int st = 0; + int sizeX, sizeY; + int tag; + int tagVal; + char ch; + int i,j, ii; + char buf[1024]; + char tagBuf[1024]; + double *data; + //printf(" (%d)\n", N_path); + + model->V.x = 0; + model->V.y = 0; + model->V.l = 0; + model->fineFunction[0] = 0.0f; + model->fineFunction[1] = 0.0f; + model->fineFunction[2] = 0.0f; + model->fineFunction[3] = 0.0f; + + i = 0; + j = 0; + st = 0; + tag = 0; + while(!feof(xmlf)){ + ch = fgetc( xmlf ); + if(ch == '<'){ + tag = 1; + j = 1; + tagBuf[j - 1] = ch; + }else { + if(ch == '>'){ + tagBuf[j ] = ch; + tagBuf[j + 1] = '\0'; + + tagVal = getTeg(tagBuf); + + if(tagVal == EPFILTER){ + //printf("\n"); + return; + } + + if(tagVal == TAGV){ + parserV(xmlf, p, model); + } + if(tagVal == TAGD){ + parserD(xmlf, p, model); + } + if(tagVal == SIZEX){ + st = 1; + i = 0; + } + if(tagVal == ESIZEX){ + st = 0; + buf[i] = '\0'; + sizeX = atoi(buf); + model->sizeX = sizeX; + //printf("%d\n", sizeX); + } + if(tagVal == SIZEY){ + st = 1; + i = 0; + } + if(tagVal == ESIZEY){ + st = 0; + buf[i] = '\0'; + sizeY = atoi(buf); + model->sizeY = sizeY; + //printf("%d\n", sizeY); + } + if(tagVal == WEIGHTS){ + data = (double *)malloc( sizeof(double) * p * sizeX * sizeY); + fread(data, sizeof(double), p * sizeX * sizeY, xmlf); + model->H = (float *)malloc(sizeof(float)* p * sizeX * sizeY); + for(ii = 0; ii < p * sizeX * sizeY; ii++){ + model->H[ii] = (float)data[ii]; + } + free(data); + } + if(tagVal == EWEIGHTS){ + //printf("WEIGHTS OK\n"); + } + tag = 0; + i = 0; + }else{ + if((tag == 0)&& (st == 1)){ + buf[i] = ch; i++; + }else{ + tagBuf[j] = ch; j++; + } + } + } + } +} +void parserPFilterS (FILE * xmlf, int p, filterObject *** model, int *last, int *max){ + int st = 0; + int N_path = 0; + int tag; + int tagVal; + char ch; + int i,j; + char buf[1024]; + char tagBuf[1024]; + //printf("\n"); + + i = 0; + j = 0; + st = 0; + tag = 0; + while(!feof(xmlf)){ + ch = fgetc( xmlf ); + if(ch == '<'){ + tag = 1; + j = 1; + tagBuf[j - 1] = ch; + }else { + if(ch == '>'){ + tagBuf[j ] = ch; + tagBuf[j + 1] = '\0'; + + tagVal = getTeg(tagBuf); + + if(tagVal == EPFILTERs){ + //printf("\n"); + return; + } + if(tagVal == PFILTER){ + addFilter(model, last, max); + parserPFilter (xmlf, p, N_path, (*model)[*last]); + N_path++; + } + tag = 0; + i = 0; + }else{ + if((tag == 0)&& (st == 1)){ + buf[i] = ch; i++; + }else{ + tagBuf[j] = ch; j++; + } + } + } + } +} +void parserComp (FILE * xmlf, int p, int *N_comp, filterObject *** model, float *b, int *last, int *max){ + int st = 0; + int tag; + int tagVal; + char ch; + int i,j; + char buf[1024]; + char tagBuf[1024]; + //printf(" %d\n", *N_comp); + + i = 0; + j = 0; + st = 0; + tag = 0; + while(!feof(xmlf)){ + ch = fgetc( xmlf ); + if(ch == '<'){ + tag = 1; + j = 1; + tagBuf[j - 1] = ch; + }else { + if(ch == '>'){ + tagBuf[j ] = ch; + tagBuf[j + 1] = '\0'; + + tagVal = getTeg(tagBuf); + + if(tagVal == ECOMP){ + (*N_comp) ++; + return; + } + if(tagVal == RFILTER){ + addFilter(model, last, max); + parserRFilter (xmlf, p, (*model)[*last],b); + } + if(tagVal == PFILTERs){ + parserPFilterS (xmlf, p, model, last, max); + } + tag = 0; + i = 0; + }else{ + if((tag == 0)&& (st == 1)){ + buf[i] = ch; i++; + }else{ + tagBuf[j] = ch; j++; + } + } + } + } +} +void parserModel(FILE * xmlf, filterObject *** model, int *last, int *max, int **comp, float **b, int *count, float * score){ + int p = 0; + int N_comp = 0; + int * cmp; + float *bb; + int st = 0; + int tag; + int tagVal; + char ch; + int i,j, ii = 0; + char buf[1024]; + char tagBuf[1024]; + + //printf("\n"); + + i = 0; + j = 0; + st = 0; + tag = 0; + while(!feof(xmlf)){ + ch = fgetc( xmlf ); + if(ch == '<'){ + tag = 1; + j = 1; + tagBuf[j - 1] = ch; + }else { + if(ch == '>'){ + tagBuf[j ] = ch; + tagBuf[j + 1] = '\0'; + + tagVal = getTeg(tagBuf); + + if(tagVal == EMODEL){ + //printf("\n"); + for(ii = 0; ii <= *last; ii++){ + (*model)[ii]->p = p; + (*model)[ii]->xp = 9; + } + * count = N_comp; + return; + } + if(tagVal == COMP){ + if(N_comp == 0){ + cmp = (int *)malloc(sizeof(int)); + bb = (float *)malloc(sizeof(float)); + * comp = cmp; + * b = bb; + * count = N_comp + 1; + } else { + cmp = (int *)malloc(sizeof(int) * (N_comp + 1)); + bb = (float *)malloc(sizeof(float) * (N_comp + 1)); + for(ii = 0; ii < N_comp; ii++){ + cmp[i] = (* comp)[ii]; + bb [i] = (* b )[ii]; + } + free(* comp); + free(* b ); + * comp = cmp; + * b = bb; + * count = N_comp + 1; + } + parserComp(xmlf, p, &N_comp, model, &((*b)[N_comp]), last, max); + cmp[N_comp - 1] = *last; + } + if(tagVal == P){ + st = 1; + i = 0; + } + if(tagVal == EP){ + st = 0; + buf[i] = '\0'; + p = atoi(buf); + //printf("

%d

\n", p); + } + if(tagVal == SCORE){ + st = 1; + i = 0; + } + if(tagVal == ESCORE){ + st = 0; + buf[i] = '\0'; + *score = (float)atof(buf); + //printf("%f\n", score); + } + tag = 0; + i = 0; + }else{ + if((tag == 0)&& (st == 1)){ + buf[i] = ch; i++; + }else{ + tagBuf[j] = ch; j++; + } + } + } + } +} + +void LSVMparser(const char * filename, filterObject *** model, int *last, int *max, int **comp, float **b, int *count, float * score){ + int st = 0; + int tag; + char ch; + int i,j; + FILE *xmlf; + char buf[1024]; + char tagBuf[1024]; + + (*max) = 10; + (*last) = -1; + (*model) = (filterObject ** )malloc((sizeof(filterObject * )) * (*max)); + + //printf("parse : %s\n", filename); + xmlf = fopen(filename, "rb"); + + i = 0; + j = 0; + st = 0; + tag = 0; + while(!feof(xmlf)){ + ch = fgetc( xmlf ); + if(ch == '<'){ + tag = 1; + j = 1; + tagBuf[j - 1] = ch; + }else { + if(ch == '>'){ + tag = 0; + i = 0; + tagBuf[j ] = ch; + tagBuf[j + 1] = '\0'; + if(getTeg(tagBuf) == MODEL){ + parserModel(xmlf, model, last, max, comp, b, count, score); + } + }else{ + if(tag == 0){ + buf[i] = ch; i++; + }else{ + tagBuf[j] = ch; j++; + } + } + } + } +} + +int loadModel( + // Входные параметры + const char *modelPath,// - путь до файла с моделью + + // Выходные параметры + filterObject ***filters,// - массив указателей на фильтры компонент + int *kFilters, //- общее количество фильтров во всех моделях + int *kComponents, //- количество компонент + int **kPartFilters, //- массив, содержащий количество точных фильтров в каждой компоненте + float **b, //- массив линейных членов в оценочной функции + float *scoreThreshold){ //- порог для score) + int last; + int max; + int *comp; + int count; + int i; + float score; + //printf("start_parse\n\n"); + + LSVMparser(modelPath, filters, &last, &max, &comp, b, &count, &score); + (*kFilters) = last + 1; + (*kComponents) = count; + (*scoreThreshold) = (float) score; + + (*kPartFilters) = (int *)malloc(sizeof(int) * count); + + for(i = 1; i < count;i++){ + (*kPartFilters)[i] = (comp[i] - comp[i - 1]) - 1; + } + (*kPartFilters)[0] = comp[0]; + + //printf("end_parse\n"); + return 0; +} \ No newline at end of file diff --git a/modules/objdetect/src/matching.cpp b/modules/objdetect/src/matching.cpp new file mode 100644 index 0000000000..c8634abda8 --- /dev/null +++ b/modules/objdetect/src/matching.cpp @@ -0,0 +1,1462 @@ +#include "_matching.h" +#include + +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif + +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +/* +// Function for convolution computation +// +// INPUT +// Fi - filter object +// map - feature map +// OUTPUT +// f - the convolution +// RESULT +// Error status +*/ +int convolution(const filterObject *Fi, const featureMap *map, float *f) +{ + int n1, m1, n2, m2, p, size, diff1, diff2; + int i1, i2, j1, j2, k; + + n1 = map->sizeY; + m1 = map->sizeX; + n2 = Fi->sizeY; + m2 = Fi->sizeX; + p = map->p; + if (n1 < n2 || m1 < m2) + { + return FILTER_OUT_OF_BOUNDARIES; + } + + // Computation number of positions for the filter + diff1 = n1 - n2 + 1; + diff2 = m1 - m2 + 1; + size = diff1 * diff2; + + for (i1 = 0; i1 < diff1; i1++) + { + for (j1 = 0; j1 < diff2; j1++) + { + f[i1 * diff2 + j1] = 0.0; + for (i2 = 0; i2 < n2; i2++) + { + for (j2 = 0; j2 < m2; j2++) + { + for (k = 0; k < p; k++) + { + f[i1 * diff2 + j1] += map->Map[(i1 + i2) * m1 * p + + (j1 + j2) * p + k] * + Fi->H[(i2 * m2 + j2) * p + k]; + } + } + } + } + } + return LATENT_SVM_OK; +} + +/* +// Computation multiplication of FFT images +// +// API +// int fftImagesMulti(float *fftImage1, float *fftImage2, int numRows, int numColls, + float *multi); +// INPUT +// fftImage1 - first fft image +// fftImage2 - second fft image +// (numRows, numColls) - image dimesions +// OUTPUT +// multi - multiplication +// RESULT +// Error status +*/ +int fftImagesMulti(float *fftImage1, float *fftImage2, int numRows, int numColls, + float *multi) +{ + int i, index, size; + size = numRows * numColls; + for (i = 0; i < size; i++) + { + index = 2 * i; + multi[index] = fftImage1[index] * fftImage2[index] - + fftImage1[index + 1] * fftImage2[index + 1]; + multi[index + 1] = fftImage1[index] * fftImage2[index + 1] + + fftImage1[index + 1] * fftImage2[index]; + } + return LATENT_SVM_OK; +} + +/* +// Turnover filter matrix for the single feature +// +// API +// int rot2PI(float *filter, int dimX, int dimY, float *rot2PIFilter, + int p, int shift); +// INPUT +// filter - filter weight matrix +// (dimX, dimY) - dimension of filter matrix +// p - number of features +// shift - number of feature (or channel) +// OUTPUT +// rot2PIFilter - rotated matrix +// RESULT +// Error status +*/ +int rot2PI(float *filter, int dimX, int dimY, float *rot2PIFilter, + int p, int shift) +{ + int i, size; + size = dimX * dimY; + for (i = 0; i < size; i++) + { + rot2PIFilter[i] = filter[(size - i - 1) * p + shift]; + } + return LATENT_SVM_OK; +} + +/* +// Addition nullable bars to the dimension of feature map (single feature) +// +// API +// int addNullableBars(float *rot2PIFilter, int dimX, int dimY, + float *newFilter, int newDimX, int newDimY); +// INPUT +// rot2PIFilter - filter matrix for the single feature that was rotated +// (dimX, dimY) - dimension rot2PIFilter +// (newDimX, newDimY)- dimension of feature map for the single feature +// OUTPUT +// newFilter - filter matrix with nullable bars +// RESULT +// Error status +*/ +int addNullableBars(float *rot2PIFilter, int dimX, int dimY, + float *newFilter, int newDimX, int newDimY) +{ + int size, i, j; + size = newDimX * newDimY; + for (i = 0; i < size; i++) + { + newFilter[2 * i] = 0.0; + newFilter[2 * i + 1] = 0.0; + } + for (i = 0; i < dimY; i++) + { + for (j = 0; j < dimX; j++) + { + newFilter[2 * (i * newDimX + j)] = rot2PIFilter[i * dimX + j]; + } + } + return LATENT_SVM_OK; +} + +/* +// Computation FFT image for filter object +// +// API +// int getFFTImageFilterObject(const filterObject *filter, + int mapDimX, int mapDimY, + fftImage **image); +// INPUT +// filter - filter object +// (mapDimX, mapDimY)- dimension of feature map +// OUTPUT +// image - fft image +// RESULT +// Error status +*/ +int getFFTImageFilterObject(const filterObject *filter, + int mapDimX, int mapDimY, + fftImage **image) +{ + unsigned int i, mapSize, filterSize; + int res; + float *newFilter, *rot2PIFilter; + + filterSize = filter->sizeX * filter->sizeY; + mapSize = mapDimX * mapDimY; + newFilter = (float *)malloc(sizeof(float) * (2 * mapSize)); + rot2PIFilter = (float *)malloc(sizeof(float) * filterSize); + res = allocFFTImage(image, filter->p, mapDimX, mapDimY); + if (res != LATENT_SVM_OK) + { + return res; + } + for (i = 0; i < filter->p; i++) + { + rot2PI(filter->H, filter->sizeX, filter->sizeY, rot2PIFilter, filter->p, i); + addNullableBars(rot2PIFilter, filter->sizeX, filter->sizeY, + newFilter, mapDimX, mapDimY); + fft2d(newFilter, (*image)->channels[i], mapDimY, mapDimX); + } + free(newFilter); + free(rot2PIFilter); + return LATENT_SVM_OK; +} + +/* +// Computation FFT image for feature map +// +// API +// int getFFTImageFeatureMap(const featureMap *map, fftImage **image); +// INPUT +// OUTPUT +// RESULT +// Error status +*/ +int getFFTImageFeatureMap(const featureMap *map, fftImage **image) +{ + int i, j, size; + float *buf; + allocFFTImage(image, map->p, map->sizeX, map->sizeY); + size = map->sizeX * map->sizeY; + buf = (float *)malloc(sizeof(float) * (2 * size)); + for (i = 0; i < map->p; i++) + { + for (j = 0; j < size; j++) + { + buf[2 * j] = map->Map[j * map->p + i]; + buf[2 * j + 1] = 0.0; + } + fft2d(buf, (*image)->channels[i], map->sizeY, map->sizeX); + } + free(buf); + return LATENT_SVM_OK; +} + +/* +// Function for convolution computation using FFT +// +// API +// int convFFTConv2d(const fftImage *featMapImage, const fftImage *filterImage, + int filterDimX, int filterDimY, float **conv); +// INPUT +// featMapImage - feature map image +// filterImage - filter image +// (filterDimX,filterDimY) - filter dimension +// OUTPUT +// conv - the convolution +// RESULT +// Error status +*/ +int convFFTConv2d(const fftImage *featMapImage, const fftImage *filterImage, + int filterDimX, int filterDimY, float **conv) +{ + int i, j, size, diffX, diffY, index; + float *imagesMult, *imagesMultRes, *fconv; + size = 2 * featMapImage->dimX * featMapImage->dimY; + imagesMult = (float *)malloc(sizeof(float) * size); + imagesMultRes = (float *)malloc(sizeof(float) * size); + fftImagesMulti(featMapImage->channels[0], filterImage->channels[0], + featMapImage->dimY, featMapImage->dimX, imagesMultRes); + for (i = 1; (i < featMapImage->p) && (i < filterImage->p); i++) + { + fftImagesMulti(featMapImage->channels[i],filterImage->channels[i], + featMapImage->dimY, featMapImage->dimX, imagesMult); + for (j = 0; j < size; j++) + { + imagesMultRes[j] += imagesMult[j]; + } + } + fconv = (float *)malloc(sizeof(float) * size); + fftInverse2d(imagesMultRes, fconv, featMapImage->dimY, featMapImage->dimX); + diffX = featMapImage->dimX - filterDimX + 1; + diffY = featMapImage->dimY - filterDimY + 1; + *conv = (float *)malloc(sizeof(float) * (diffX * diffY)); + for (i = 0; i < diffY; i++) + { + for (j = 0; j < diffX; j++) + { + index = (i + filterDimY - 1) * featMapImage->dimX + + (j + filterDimX - 1); + (*conv)[i * diffX + j] = fconv[2 * index]; + } + } + free(imagesMult); + free(imagesMultRes); + free(fconv); + return LATENT_SVM_OK; +} + +/* +// Computation objective function D according the original paper +// +// API +// int filterDispositionLevel(const filterObject *Fi, const featurePyramid *H, + int level, float **scoreFi, + int **pointsX, int **pointsY); +// INPUT +// Fi - filter object (weights and coefficients of penalty + function that are used in this routine) +// H - feature pyramid +// level - level number +// OUTPUT +// scoreFi - values of distance transform on the level at all positions +// (pointsX, pointsY)- positions that correspond to the maximum value + of distance transform at all grid nodes +// RESULT +// Error status +*/ +int filterDispositionLevel(const filterObject *Fi, const featureMap *pyramid, + float **scoreFi, + int **pointsX, int **pointsY) +{ + int n1, m1, n2, m2, p, size, diff1, diff2; + float *f; + int i1, j1; + int res; + + n1 = pyramid->sizeY; + m1 = pyramid->sizeX; + n2 = Fi->sizeY; + m2 = Fi->sizeX; + p = pyramid->p; + (*scoreFi) = NULL; + (*pointsX) = NULL; + (*pointsY) = NULL; + + // Processing the situation when part filter goes + // beyond the boundaries of the block set + if (n1 < n2 || m1 < m2) + { + return FILTER_OUT_OF_BOUNDARIES; + } /* if (n1 < n2 || m1 < m2) */ + + // Computation number of positions for the filter + diff1 = n1 - n2 + 1; + diff2 = m1 - m2 + 1; + size = diff1 * diff2; + + // Allocation memory for additional array (must be free in this function) + f = (float *)malloc(sizeof(float) * size); + // Allocation memory for arrays for saving decisions + (*scoreFi) = (float *)malloc(sizeof(float) * size); + (*pointsX) = (int *)malloc(sizeof(int) * size); + (*pointsY) = (int *)malloc(sizeof(int) * size); + + // Consruction values of the array f + // (a dot product vectors of feature map and weights of the filter) + res = convolution(Fi, pyramid, f); + if (res != LATENT_SVM_OK) + { + free(f); + free(*scoreFi); + free(*pointsX); + free(*pointsY); + return res; + } + + // TODO: necessary to change + for (i1 = 0; i1 < diff1; i1++) + { + for (j1 = 0; j1 < diff2; j1++) + { + f[i1 * diff2 + j1] *= (-1); + } + } + + // Decision of the general distance transform task + DistanceTransformTwoDimensionalProblem(f, diff1, diff2, Fi->fineFunction, + (*scoreFi), (*pointsX), (*pointsY)); + + // Release allocated memory + free(f); + return LATENT_SVM_OK; +} + +/* +// Computation objective function D according the original paper using FFT +// +// API +// int filterDispositionLevelFFT(const filterObject *Fi, const fftImage *featMapImage, + float **scoreFi, + int **pointsX, int **pointsY); +// INPUT +// Fi - filter object (weights and coefficients of penalty + function that are used in this routine) +// featMapImage - FFT image of feature map +// OUTPUT +// scoreFi - values of distance transform on the level at all positions +// (pointsX, pointsY)- positions that correspond to the maximum value + of distance transform at all grid nodes +// RESULT +// Error status +*/ +int filterDispositionLevelFFT(const filterObject *Fi, const fftImage *featMapImage, + float **scoreFi, + int **pointsX, int **pointsY) +{ + int n1, m1, n2, m2, p, size, diff1, diff2; + float *f; + int i1, j1; + int res; + fftImage *filterImage; + + n1 = featMapImage->dimY; + m1 = featMapImage->dimX; + n2 = Fi->sizeY; + m2 = Fi->sizeX; + p = featMapImage->p; + (*scoreFi) = NULL; + (*pointsX) = NULL; + (*pointsY) = NULL; + + // Processing the situation when part filter goes + // beyond the boundaries of the block set + if (n1 < n2 || m1 < m2) + { + return FILTER_OUT_OF_BOUNDARIES; + } /* if (n1 < n2 || m1 < m2) */ + + // Computation number of positions for the filter + diff1 = n1 - n2 + 1; + diff2 = m1 - m2 + 1; + size = diff1 * diff2; + + // Allocation memory for arrays for saving decisions + (*scoreFi) = (float *)malloc(sizeof(float) * size); + (*pointsX) = (int *)malloc(sizeof(int) * size); + (*pointsY) = (int *)malloc(sizeof(int) * size); + + // create filter image + getFFTImageFilterObject(Fi, featMapImage->dimX, featMapImage->dimY, &filterImage); + + // Consruction values of the array f + // (a dot product vectors of feature map and weights of the filter) + res = convFFTConv2d(featMapImage, filterImage, Fi->sizeX, Fi->sizeY, &f); + if (res != LATENT_SVM_OK) + { + free(f); + free(*scoreFi); + free(*pointsX); + free(*pointsY); + return res; + } + + // TODO: necessary to change + for (i1 = 0; i1 < diff1; i1++) + { + for (j1 = 0; j1 < diff2; j1++) + { + f[i1 * diff2 + j1] *= (-1); + } + } + + // Decision of the general distance transform task + DistanceTransformTwoDimensionalProblem(f, diff1, diff2, Fi->fineFunction, + (*scoreFi), (*pointsX), (*pointsY)); + + // Release allocated memory + free(f); + freeFFTImage(&filterImage); + return LATENT_SVM_OK; +} + +/* +// Computation border size for feature map +// +// API +// int computeBorderSize(int maxXBorder, int maxYBorder, int *bx, int *by); +// INPUT +// maxXBorder - the largest root filter size (X-direction) +// maxYBorder - the largest root filter size (Y-direction) +// OUTPUT +// bx - border size (X-direction) +// by - border size (Y-direction) +// RESULT +// Error status +*/ +int computeBorderSize(int maxXBorder, int maxYBorder, int *bx, int *by) +{ + *bx = (int)ceilf(((float) maxXBorder) / 2.0f + 1.0f); + *by = (int)ceilf(((float) maxYBorder) / 2.0f + 1.0f); + return LATENT_SVM_OK; +} + +/* +// Addition nullable border to the feature map +// +// API +// int addNullableBorder(featureMap *map, int bx, int by); +// INPUT +// map - feature map +// bx - border size (X-direction) +// by - border size (Y-direction) +// OUTPUT +// RESULT +// Error status +*/ +int addNullableBorder(featureMap *map, int bx, int by) +{ + int sizeX, sizeY, i, j, k; + float *new_map; + sizeX = map->sizeX + 2 * bx; + sizeY = map->sizeY + 2 * by; + new_map = (float *)malloc(sizeof(float) * sizeX * sizeY * map->p); + for (i = 0; i < sizeX * sizeY * map->p; i++) + { + new_map[i] = 0.0; + } + for (i = by; i < map->sizeY + by; i++) + { + for (j = bx; j < map->sizeX + bx; j++) + { + for (k = 0; k < map->p; k++) + { + new_map[(i * sizeX + j) * map->p + k] = + map->Map[((i - by) * map->sizeX + j - bx) * map->p + k]; + } + } + } + map->sizeX = sizeX; + map->sizeY = sizeY; + free(map->Map); + map->Map = new_map; + return LATENT_SVM_OK; +} + +featureMap* featureMapBorderPartFilter(featureMap *map, + int maxXBorder, int maxYBorder) +{ + int bx, by; + int sizeX, sizeY, i, j, k; + featureMap *new_map; + + computeBorderSize(maxXBorder, maxYBorder, &bx, &by); + sizeX = map->sizeX + 2 * bx; + sizeY = map->sizeY + 2 * by; + allocFeatureMapObject(&new_map, sizeX, sizeY, map->p, map->xp); + for (i = 0; i < sizeX * sizeY * map->p; i++) + { + new_map->Map[i] = 0.0; + } + for (i = by; i < map->sizeY + by; i++) + { + for (j = bx; j < map->sizeX + bx; j++) + { + for (k = 0; k < map->p; k++) + { + new_map->Map[(i * sizeX + j) * map->p + k] = + map->Map[((i - by) * map->sizeX + j - bx) * map->p + k]; + } + } + } + return new_map; +} + +/* +// Computation the maximum of the score function at the level +// +// API +// int maxFunctionalScoreFixedLevel(const filterObject **all_F, int n, + const featurePyramid *H, + int level, float b, + int maxXBorder, int maxYBorder, + float *score, CvPoint **points, int *kPoints, + CvPoint ***partsDisplacement); +// INPUT +// all_F - the set of filters (the first element is root filter, + the other - part filters) +// n - the number of part filters +// H - feature pyramid +// level - feature pyramid level for computation maximum score +// b - linear term of the score function +// maxXBorder - the largest root filter size (X-direction) +// maxYBorder - the largest root filter size (Y-direction) +// OUTPUT +// score - the maximum of the score function at the level +// points - the set of root filter positions (in the block space) +// levels - the set of levels +// kPoints - number of root filter positions +// partsDisplacement - displacement of part filters (in the block space) +// RESULT +// Error status +*/ +int maxFunctionalScoreFixedLevel(const filterObject **all_F, int n, + const featurePyramid *H, + int level, float b, + int maxXBorder, int maxYBorder, + float *score, CvPoint **points, + int *kPoints, CvPoint ***partsDisplacement) +{ + int i, j, k, dimX, dimY, nF0, mF0, p; + int diff1, diff2, index, last, partsLevel; + filterDisposition **disposition; + float *f; + float *scores; + float sumScorePartDisposition, maxScore; + int res; + featureMap *map; +#ifdef FFT_CONV + fftImage *rootFilterImage, *mapImage; +#else +#endif + + /* + // DEBUG variables + FILE *file; + char *tmp; + char buf[40] = "..\\Data\\score\\score", buf1[10] = ".csv"; + tmp = (char *)malloc(sizeof(char) * 80); + itoa(level, tmp, 10); + strcat(tmp, buf1); + //*/ + + // Feature map matrix dimension on the level + dimX = H->pyramid[level]->sizeX; + dimY = H->pyramid[level]->sizeY; + + // Number of features + p = H->pyramid[level]->p; + + // Getting dimension of root filter + nF0 = all_F[0]->sizeY; + mF0 = all_F[0]->sizeX; + // Processing the situation when root filter goes + // beyond the boundaries of the block set + if (nF0 > dimY || mF0 > dimX) + { + return LATENT_SVM_FAILED_SUPERPOSITION; + } + + diff1 = dimY - nF0 + 1; + diff2 = dimX - mF0 + 1; + + // Allocation memory for saving values of function D + // on the level for each part filter + disposition = (filterDisposition **)malloc(sizeof(filterDisposition *) * n); + for (i = 0; i < n; i++) + { + disposition[i] = (filterDisposition *)malloc(sizeof(filterDisposition)); + } + + // Allocation memory for values of score function for each block on the level + scores = (float *)malloc(sizeof(float) * (diff1 * diff2)); + + // A dot product vectors of feature map and weights of root filter +#ifdef FFT_CONV + getFFTImageFeatureMap(H->pyramid[level], &mapImage); + getFFTImageFilterObject(all_F[0], H->pyramid[level]->sizeX, H->pyramid[level]->sizeY, &rootFilterImage); + res = convFFTConv2d(mapImage, rootFilterImage, all_F[0]->sizeX, all_F[0]->sizeY, &f); + freeFFTImage(&mapImage); + freeFFTImage(&rootFilterImage); +#else + // Allocation memory for saving a dot product vectors of feature map and + // weights of root filter + f = (float *)malloc(sizeof(float) * (diff1 * diff2)); + // A dot product vectors of feature map and weights of root filter + res = convolution(all_F[0], H->pyramid[level], f); +#endif + if (res != LATENT_SVM_OK) + { + free(f); + free(scores); + for (i = 0; i < n; i++) + { + free(disposition[i]); + } + free(disposition); + return res; + } + + // Computation values of function D for each part filter + // on the level (level - LAMBDA) + partsLevel = level - LAMBDA; + // For feature map at the level 'partsLevel' add nullable border + map = featureMapBorderPartFilter(H->pyramid[partsLevel], + maxXBorder, maxYBorder); + + // Computation the maximum of score function + sumScorePartDisposition = 0.0; +#ifdef FFT_CONV + getFFTImageFeatureMap(map, &mapImage); + for (k = 1; k <= n; k++) + { + filterDispositionLevelFFT(all_F[k], mapImage, + &(disposition[k - 1]->score), + &(disposition[k - 1]->x), + &(disposition[k - 1]->y)); + } + freeFFTImage(&mapImage); +#else + for (k = 1; k <= n; k++) + { + filterDispositionLevel(all_F[k], map, + &(disposition[k - 1]->score), + &(disposition[k - 1]->x), + &(disposition[k - 1]->y)); + } +#endif + scores[0] = f[0] - sumScorePartDisposition + b; + maxScore = scores[0]; + (*kPoints) = 0; + for (i = 0; i < diff1; i++) + { + for (j = 0; j < diff2; j++) + { + sumScorePartDisposition = 0.0; + for (k = 1; k <= n; k++) + { + // This condition takes on a value true + // when filter goes beyond the boundaries of block set + if ((2 * i + all_F[k]->V.y < + map->sizeY - all_F[k]->sizeY + 1) && + (2 * j + all_F[k]->V.x < + map->sizeX - all_F[k]->sizeX + 1)) + { + index = (2 * i + all_F[k]->V.y) * + (map->sizeX - all_F[k]->sizeX + 1) + + (2 * j + all_F[k]->V.x); + sumScorePartDisposition += disposition[k - 1]->score[index]; + } + } + scores[i * diff2 + j] = f[i * diff2 + j] - sumScorePartDisposition + b; + if (maxScore < scores[i * diff2 + j]) + { + maxScore = scores[i * diff2 + j]; + (*kPoints) = 1; + } + else if ((scores[i * diff2 + j] - maxScore) * + (scores[i * diff2 + j] - maxScore) <= EPS) + { + (*kPoints)++; + } /* if (maxScore < scores[i * diff2 + j]) */ + } + } + + // Allocation memory for saving positions of root filter and part filters + (*points) = (CvPoint *)malloc(sizeof(CvPoint) * (*kPoints)); + (*partsDisplacement) = (CvPoint **)malloc(sizeof(CvPoint *) * (*kPoints)); + for (i = 0; i < (*kPoints); i++) + { + (*partsDisplacement)[i] = (CvPoint *)malloc(sizeof(CvPoint) * n); + } + + /*// DEBUG + strcat(buf, tmp); + file = fopen(buf, "w+"); + //*/ + // Construction of the set of positions for root filter + // that correspond the maximum of score function on the level + (*score) = maxScore; + last = 0; + for (i = 0; i < diff1; i++) + { + for (j = 0; j < diff2; j++) + { + if ((scores[i * diff2 + j] - maxScore) * + (scores[i * diff2 + j] - maxScore) <= EPS) + { + (*points)[last].y = i; + (*points)[last].x = j; + for (k = 1; k <= n; k++) + { + if ((2 * i + all_F[k]->V.y < + map->sizeY - all_F[k]->sizeY + 1) && + (2 * j + all_F[k]->V.x < + map->sizeX - all_F[k]->sizeX + 1)) + { + index = (2 * i + all_F[k]->V.y) * + (map->sizeX - all_F[k]->sizeX + 1) + + (2 * j + all_F[k]->V.x); + (*partsDisplacement)[last][k - 1].x = + disposition[k - 1]->x[index]; + (*partsDisplacement)[last][k - 1].y = + disposition[k - 1]->y[index]; + } + } + last++; + } /* if ((scores[i * diff2 + j] - maxScore) * + (scores[i * diff2 + j] - maxScore) <= EPS) */ + //fprintf(file, "%lf;", scores[i * diff2 + j]); + } + //fprintf(file, "\n"); + } + //fclose(file); + //free(tmp); + + // Release allocated memory + for (i = 0; i < n ; i++) + { + free(disposition[i]->score); + free(disposition[i]->x); + free(disposition[i]->y); + free(disposition[i]); + } + free(disposition); + free(f); + free(scores); + freeFeatureMapObject(&map); + return LATENT_SVM_OK; +} + +/* +// Computation score function at the level that exceed threshold +// +// API +// int thresholdFunctionalScoreFixedLevel(const filterObject **all_F, int n, + const featurePyramid *H, + int level, float b, + int maxXBorder, int maxYBorder, + float scoreThreshold, + float **score, CvPoint **points, int *kPoints, + CvPoint ***partsDisplacement); +// INPUT +// all_F - the set of filters (the first element is root filter, + the other - part filters) +// n - the number of part filters +// H - feature pyramid +// level - feature pyramid level for computation maximum score +// b - linear term of the score function +// maxXBorder - the largest root filter size (X-direction) +// maxYBorder - the largest root filter size (Y-direction) +// scoreThreshold - score threshold +// OUTPUT +// score - score function at the level that exceed threshold +// points - the set of root filter positions (in the block space) +// levels - the set of levels +// kPoints - number of root filter positions +// partsDisplacement - displacement of part filters (in the block space) +// RESULT +// Error status +*/ +int thresholdFunctionalScoreFixedLevel(const filterObject **all_F, int n, + const featurePyramid *H, + int level, float b, + int maxXBorder, int maxYBorder, + float scoreThreshold, + float **score, CvPoint **points, int *kPoints, + CvPoint ***partsDisplacement) +{ + int i, j, k, dimX, dimY, nF0, mF0, p; + int diff1, diff2, index, last, partsLevel; + filterDisposition **disposition; + float *f; + float *scores; + float sumScorePartDisposition; + int res; + featureMap *map; +#ifdef FFT_CONV + fftImage *rootFilterImage, *mapImage; +#else +#endif + /* + // DEBUG variables + FILE *file; + char *tmp; + char buf[40] = "..\\Data\\score\\score", buf1[10] = ".csv"; + tmp = (char *)malloc(sizeof(char) * 80); + itoa(level, tmp, 10); + strcat(tmp, buf1); + //*/ + + // Feature map matrix dimension on the level + dimX = H->pyramid[level]->sizeX; + dimY = H->pyramid[level]->sizeY; + + // Number of features + p = H->pyramid[level]->p; + + // Getting dimension of root filter + nF0 = all_F[0]->sizeY; + mF0 = all_F[0]->sizeX; + // Processing the situation when root filter goes + // beyond the boundaries of the block set + if (nF0 > dimY || mF0 > dimX) + { + return LATENT_SVM_FAILED_SUPERPOSITION; + } + + diff1 = dimY - nF0 + 1; + diff2 = dimX - mF0 + 1; + + // Allocation memory for saving values of function D + // on the level for each part filter + disposition = (filterDisposition **)malloc(sizeof(filterDisposition *) * n); + for (i = 0; i < n; i++) + { + disposition[i] = (filterDisposition *)malloc(sizeof(filterDisposition)); + } + + // Allocation memory for values of score function for each block on the level + scores = (float *)malloc(sizeof(float) * (diff1 * diff2)); + // A dot product vectors of feature map and weights of root filter +#ifdef FFT_CONV + getFFTImageFeatureMap(H->pyramid[level], &mapImage); + getFFTImageFilterObject(all_F[0], H->pyramid[level]->sizeX, H->pyramid[level]->sizeY, &rootFilterImage); + res = convFFTConv2d(mapImage, rootFilterImage, all_F[0]->sizeX, all_F[0]->sizeY, &f); + freeFFTImage(&mapImage); + freeFFTImage(&rootFilterImage); +#else + // Allocation memory for saving a dot product vectors of feature map and + // weights of root filter + f = (float *)malloc(sizeof(float) * (diff1 * diff2)); + res = convolution(all_F[0], H->pyramid[level], f); +#endif + if (res != LATENT_SVM_OK) + { + free(f); + free(scores); + for (i = 0; i < n; i++) + { + free(disposition[i]); + } + free(disposition); + return res; + } + + // Computation values of function D for each part filter + // on the level (level - LAMBDA) + partsLevel = level - LAMBDA; + // For feature map at the level 'partsLevel' add nullable border + map = featureMapBorderPartFilter(H->pyramid[partsLevel], + maxXBorder, maxYBorder); + + // Computation the maximum of score function + sumScorePartDisposition = 0.0; +#ifdef FFT_CONV + getFFTImageFeatureMap(map, &mapImage); + for (k = 1; k <= n; k++) + { + filterDispositionLevelFFT(all_F[k], mapImage, + &(disposition[k - 1]->score), + &(disposition[k - 1]->x), + &(disposition[k - 1]->y)); + } + freeFFTImage(&mapImage); +#else + for (k = 1; k <= n; k++) + { + filterDispositionLevel(all_F[k], map, + &(disposition[k - 1]->score), + &(disposition[k - 1]->x), + &(disposition[k - 1]->y)); + } +#endif + (*kPoints) = 0; + for (i = 0; i < diff1; i++) + { + for (j = 0; j < diff2; j++) + { + sumScorePartDisposition = 0.0; + for (k = 1; k <= n; k++) + { + // This condition takes on a value true + // when filter goes beyond the boundaries of block set + if ((2 * i + all_F[k]->V.y < + map->sizeY - all_F[k]->sizeY + 1) && + (2 * j + all_F[k]->V.x < + map->sizeX - all_F[k]->sizeX + 1)) + { + index = (2 * i + all_F[k]->V.y) * + (map->sizeX - all_F[k]->sizeX + 1) + + (2 * j + all_F[k]->V.x); + sumScorePartDisposition += disposition[k - 1]->score[index]; + } + } + scores[i * diff2 + j] = f[i * diff2 + j] - sumScorePartDisposition + b; + if (scores[i * diff2 + j] > scoreThreshold) + { + (*kPoints)++; + } + } + } + + // Allocation memory for saving positions of root filter and part filters + (*points) = (CvPoint *)malloc(sizeof(CvPoint) * (*kPoints)); + (*partsDisplacement) = (CvPoint **)malloc(sizeof(CvPoint *) * (*kPoints)); + for (i = 0; i < (*kPoints); i++) + { + (*partsDisplacement)[i] = (CvPoint *)malloc(sizeof(CvPoint) * n); + } + + /*// DEBUG + strcat(buf, tmp); + file = fopen(buf, "w+"); + //*/ + // Construction of the set of positions for root filter + // that correspond score function on the level that exceed threshold + (*score) = (float *)malloc(sizeof(float) * (*kPoints)); + last = 0; + for (i = 0; i < diff1; i++) + { + for (j = 0; j < diff2; j++) + { + if (scores[i * diff2 + j] > scoreThreshold) + { + (*score)[last] = scores[i * diff2 + j]; + (*points)[last].y = i; + (*points)[last].x = j; + for (k = 1; k <= n; k++) + { + if ((2 * i + all_F[k]->V.y < + map->sizeY - all_F[k]->sizeY + 1) && + (2 * j + all_F[k]->V.x < + map->sizeX - all_F[k]->sizeX + 1)) + { + index = (2 * i + all_F[k]->V.y) * + (map->sizeX - all_F[k]->sizeX + 1) + + (2 * j + all_F[k]->V.x); + (*partsDisplacement)[last][k - 1].x = + disposition[k - 1]->x[index]; + (*partsDisplacement)[last][k - 1].y = + disposition[k - 1]->y[index]; + } + } + last++; + } + //fprintf(file, "%lf;", scores[i * diff2 + j]); + } + //fprintf(file, "\n"); + } + //fclose(file); + //free(tmp); + + // Release allocated memory + for (i = 0; i < n ; i++) + { + free(disposition[i]->score); + free(disposition[i]->x); + free(disposition[i]->y); + free(disposition[i]); + } + free(disposition); + free(f); + free(scores); + freeFeatureMapObject(&map); + return LATENT_SVM_OK; +} + +/* +// Computation the maximum of the score function +// +// API +// int maxFunctionalScore(const filterObject **all_F, int n, + const featurePyramid *H, float b, + int maxXBorder, int maxYBorder, + float *score, + CvPoint **points, int **levels, int *kPoints, + CvPoint ***partsDisplacement); +// INPUT +// all_F - the set of filters (the first element is root filter, + the other - part filters) +// n - the number of part filters +// H - feature pyramid +// b - linear term of the score function +// maxXBorder - the largest root filter size (X-direction) +// maxYBorder - the largest root filter size (Y-direction) +// OUTPUT +// score - the maximum of the score function +// points - the set of root filter positions (in the block space) +// levels - the set of levels +// kPoints - number of root filter positions +// partsDisplacement - displacement of part filters (in the block space) +// RESULT +// Error status +*/ +int maxFunctionalScore(const filterObject **all_F, int n, + const featurePyramid *H, float b, + int maxXBorder, int maxYBorder, + float *score, + CvPoint **points, int **levels, int *kPoints, + CvPoint ***partsDisplacement) +{ + int l, i, j, k, s, f, level, numLevels; + float *tmpScore; + CvPoint ***tmpPoints; + CvPoint ****tmpPartsDisplacement; + int *tmpKPoints; + float maxScore; + int res; + + /* DEBUG + FILE *file; + //*/ + + // Computation the number of levels for seaching object, + // first lambda-levels are used for computation values + // of score function for each position of root filter + numLevels = H->countLevel - H->lambda; + + // Allocation memory for maximum value of score function for each level + tmpScore = (float *)malloc(sizeof(float) * numLevels); + // Allocation memory for the set of points that corresponds + // to the maximum of score function + tmpPoints = (CvPoint ***)malloc(sizeof(CvPoint **) * numLevels); + for (i = 0; i < numLevels; i++) + { + tmpPoints[i] = (CvPoint **)malloc(sizeof(CvPoint *)); + } + // Allocation memory for memory for saving parts displacement on each level + tmpPartsDisplacement = (CvPoint ****)malloc(sizeof(CvPoint ***) * numLevels); + for (i = 0; i < numLevels; i++) + { + tmpPartsDisplacement[i] = (CvPoint ***)malloc(sizeof(CvPoint **)); + } + // Number of points that corresponds to the maximum + // of score function on each level + tmpKPoints = (int *)malloc(sizeof(int) * numLevels); + for (i = 0; i < numLevels; i++) + { + tmpKPoints[i] = 0; + } + + // Set current value of the maximum of score function + res = maxFunctionalScoreFixedLevel(all_F, n, H, H->lambda, b, + maxXBorder, maxYBorder, + &(tmpScore[0]), + tmpPoints[0], + &(tmpKPoints[0]), + tmpPartsDisplacement[0]); + maxScore = tmpScore[0]; + (*kPoints) = tmpKPoints[0]; + + // Computation maxima of score function on each level + // and getting the maximum on all levels + /* DEBUG: maxScore + file = fopen("maxScore.csv", "w+"); + fprintf(file, "%i;%lf;\n", H->lambda, tmpScore[0]); + //*/ + for (l = H->lambda + 1; l < H->countLevel; l++) + { + k = l - H->lambda; + res = maxFunctionalScoreFixedLevel(all_F, n, H, l, b, + maxXBorder, maxYBorder, + &(tmpScore[k]), + tmpPoints[k], + &(tmpKPoints[k]), + tmpPartsDisplacement[k]); + //fprintf(file, "%i;%lf;\n", l, tmpScore[k]); + if (res != LATENT_SVM_OK) + { + continue; + } + if (maxScore < tmpScore[k]) + { + maxScore = tmpScore[k]; + (*kPoints) = tmpKPoints[k]; + } + else if ((maxScore - tmpScore[k]) * (maxScore - tmpScore[k]) <= EPS) + { + (*kPoints) += tmpKPoints[k]; + } /* if (maxScore < tmpScore[k]) else if (...)*/ + } + //fclose(file); + + // Allocation memory for levels + (*levels) = (int *)malloc(sizeof(int) * (*kPoints)); + // Allocation memory for the set of points + (*points) = (CvPoint *)malloc(sizeof(CvPoint) * (*kPoints)); + // Allocation memory for parts displacement + (*partsDisplacement) = (CvPoint **)malloc(sizeof(CvPoint *) * (*kPoints)); + + // Filling the set of points, levels and parts displacement + s = 0; + f = 0; + for (i = 0; i < numLevels; i++) + { + if ((tmpScore[i] - maxScore) * (tmpScore[i] - maxScore) <= EPS) + { + // Computation the number of level + level = i + H->lambda; + + // Addition a set of points + f += tmpKPoints[i]; + for (j = s; j < f; j++) + { + (*levels)[j] = level; + (*points)[j] = (*tmpPoints[i])[j - s]; + (*partsDisplacement)[j] = (*(tmpPartsDisplacement[i]))[j - s]; + } + s = f; + } /* if ((tmpScore[i] - maxScore) * (tmpScore[i] - maxScore) <= EPS) */ + } + (*score) = maxScore; + + // Release allocated memory + for (i = 0; i < numLevels; i++) + { + free(tmpPoints[i]); + free(tmpPartsDisplacement[i]); + } + free(tmpPoints); + free(tmpScore); + free(tmpKPoints); + + return LATENT_SVM_OK; +} + +/* +// Computation score function that exceed threshold +// +// API +// int thresholdFunctionalScore(const filterObject **all_F, int n, + const featurePyramid *H, + float b, + int maxXBorder, int maxYBorder, + float scoreThreshold, + float **score, + CvPoint **points, int **levels, int *kPoints, + CvPoint ***partsDisplacement); +// INPUT +// all_F - the set of filters (the first element is root filter, + the other - part filters) +// n - the number of part filters +// H - feature pyramid +// b - linear term of the score function +// maxXBorder - the largest root filter size (X-direction) +// maxYBorder - the largest root filter size (Y-direction) +// scoreThreshold - score threshold +// OUTPUT +// score - score function values that exceed threshold +// points - the set of root filter positions (in the block space) +// levels - the set of levels +// kPoints - number of root filter positions +// partsDisplacement - displacement of part filters (in the block space) +// RESULT +// Error status +*/ +int thresholdFunctionalScore(const filterObject **all_F, int n, + const featurePyramid *H, + float b, + int maxXBorder, int maxYBorder, + float scoreThreshold, + float **score, + CvPoint **points, int **levels, int *kPoints, + CvPoint ***partsDisplacement) +{ + int l, i, j, k, s, f, level, numLevels; + float **tmpScore; + CvPoint ***tmpPoints; + CvPoint ****tmpPartsDisplacement; + int *tmpKPoints; + int res; + + /* DEBUG + FILE *file; + //*/ + + // Computation the number of levels for seaching object, + // first lambda-levels are used for computation values + // of score function for each position of root filter + numLevels = H->countLevel - H->lambda; + + // Allocation memory for values of score function for each level + // that exceed threshold + tmpScore = (float **)malloc(sizeof(float*) * numLevels); + // Allocation memory for the set of points that corresponds + // to the maximum of score function + tmpPoints = (CvPoint ***)malloc(sizeof(CvPoint **) * numLevels); + for (i = 0; i < numLevels; i++) + { + tmpPoints[i] = (CvPoint **)malloc(sizeof(CvPoint *)); + } + // Allocation memory for memory for saving parts displacement on each level + tmpPartsDisplacement = (CvPoint ****)malloc(sizeof(CvPoint ***) * numLevels); + for (i = 0; i < numLevels; i++) + { + tmpPartsDisplacement[i] = (CvPoint ***)malloc(sizeof(CvPoint **)); + } + // Number of points that corresponds to the maximum + // of score function on each level + tmpKPoints = (int *)malloc(sizeof(int) * numLevels); + for (i = 0; i < numLevels; i++) + { + tmpKPoints[i] = 0; + } + + // Computation maxima of score function on each level + // and getting the maximum on all levels + /* DEBUG: maxScore + file = fopen("maxScore.csv", "w+"); + fprintf(file, "%i;%lf;\n", H->lambda, tmpScore[0]); + //*/ + (*kPoints) = 0; + for (l = H->lambda; l < H->countLevel; l++) + { + k = l - H->lambda; + //printf("Score at the level %i\n", l); + res = thresholdFunctionalScoreFixedLevel(all_F, n, H, l, b, + maxXBorder, maxYBorder, scoreThreshold, + &(tmpScore[k]), + tmpPoints[k], + &(tmpKPoints[k]), + tmpPartsDisplacement[k]); + //fprintf(file, "%i;%lf;\n", l, tmpScore[k]); + if (res != LATENT_SVM_OK) + { + continue; + } + (*kPoints) += tmpKPoints[k]; + } + //fclose(file); + + // Allocation memory for levels + (*levels) = (int *)malloc(sizeof(int) * (*kPoints)); + // Allocation memory for the set of points + (*points) = (CvPoint *)malloc(sizeof(CvPoint) * (*kPoints)); + // Allocation memory for parts displacement + (*partsDisplacement) = (CvPoint **)malloc(sizeof(CvPoint *) * (*kPoints)); + // Allocation memory for score function values + (*score) = (float *)malloc(sizeof(float) * (*kPoints)); + + // Filling the set of points, levels and parts displacement + s = 0; + f = 0; + for (i = 0; i < numLevels; i++) + { + // Computation the number of level + level = i + H->lambda; + + // Addition a set of points + f += tmpKPoints[i]; + for (j = s; j < f; j++) + { + (*levels)[j] = level; + (*points)[j] = (*tmpPoints[i])[j - s]; + (*score)[j] = tmpScore[i][j - s]; + (*partsDisplacement)[j] = (*(tmpPartsDisplacement[i]))[j - s]; + } + s = f; + } + + // Release allocated memory + for (i = 0; i < numLevels; i++) + { + free(tmpPoints[i]); + free(tmpPartsDisplacement[i]); + } + free(tmpPoints); + free(tmpScore); + free(tmpKPoints); + free(tmpPartsDisplacement); + + return LATENT_SVM_OK; +} + +void sort(int n, const float* x, int* indices) +{ + int i, j; + for (i = 0; i < n; i++) + for (j = i + 1; j < n; j++) + { + if (x[indices[j]] > x[indices[i]]) + { + //float x_tmp = x[i]; + int index_tmp = indices[i]; + //x[i] = x[j]; + indices[i] = indices[j]; + //x[j] = x_tmp; + indices[j] = index_tmp; + } + } +} + +/* +// Perform non-maximum suppression algorithm (described in original paper) +// to remove "similar" bounding boxes +// +// API +// int nonMaximumSuppression(int numBoxes, const CvPoint *points, + const CvPoint *oppositePoints, const float *score, + float overlapThreshold, + int *numBoxesOut, CvPoint **pointsOut, + CvPoint **oppositePointsOut, float **scoreOut); +// INPUT +// numBoxes - number of bounding boxes +// points - array of left top corner coordinates +// oppositePoints - array of right bottom corner coordinates +// score - array of detection scores +// overlapThreshold - threshold: bounding box is removed if overlap part + is greater than passed value +// OUTPUT +// numBoxesOut - the number of bounding boxes algorithm returns +// pointsOut - array of left top corner coordinates +// oppositePointsOut - array of right bottom corner coordinates +// scoreOut - array of detection scores +// RESULT +// Error status +*/ +int nonMaximumSuppression(int numBoxes, const CvPoint *points, + const CvPoint *oppositePoints, const float *score, + float overlapThreshold, + int *numBoxesOut, CvPoint **pointsOut, + CvPoint **oppositePointsOut, float **scoreOut) +{ + int i, j, index; + float* box_area = (float*)malloc(numBoxes * sizeof(float)); + int* indices = (int*)malloc(numBoxes * sizeof(int)); + int* is_suppressed = (int*)malloc(numBoxes * sizeof(int)); + + for (i = 0; i < numBoxes; i++) + { + indices[i] = i; + is_suppressed[i] = 0; + box_area[i] = (float)( (oppositePoints[i].x - points[i].x + 1) * + (oppositePoints[i].y - points[i].y + 1)); + } + + sort(numBoxes, score, indices); + for (i = 0; i < numBoxes; i++) + { + if (!is_suppressed[indices[i]]) + { + for (j = i + 1; j < numBoxes; j++) + { + if (!is_suppressed[indices[j]]) + { + int x1max = max(points[indices[i]].x, points[indices[j]].x); + int x2min = min(oppositePoints[indices[i]].x, oppositePoints[indices[j]].x); + int y1max = max(points[indices[i]].y, points[indices[j]].y); + int y2min = min(oppositePoints[indices[i]].y, oppositePoints[indices[j]].y); + int overlapWidth = x2min - x1max + 1; + int overlapHeight = y2min - y1max + 1; + if (overlapWidth > 0 && overlapHeight > 0) + { + float overlapPart = (overlapWidth * overlapHeight) / box_area[indices[j]]; + if (overlapPart > overlapThreshold) + { + is_suppressed[indices[j]] = 1; + } + } + } + } + } + } + + *numBoxesOut = 0; + for (i = 0; i < numBoxes; i++) + { + if (!is_suppressed[i]) (*numBoxesOut)++; + } + + *pointsOut = (CvPoint *)malloc((*numBoxesOut) * sizeof(CvPoint)); + *oppositePointsOut = (CvPoint *)malloc((*numBoxesOut) * sizeof(CvPoint)); + *scoreOut = (float *)malloc((*numBoxesOut) * sizeof(float)); + index = 0; + for (i = 0; i < numBoxes; i++) + { + if (!is_suppressed[indices[i]]) + { + (*pointsOut)[index].x = points[indices[i]].x; + (*pointsOut)[index].y = points[indices[i]].y; + (*oppositePointsOut)[index].x = oppositePoints[indices[i]].x; + (*oppositePointsOut)[index].y = oppositePoints[indices[i]].y; + (*scoreOut)[index] = score[indices[i]]; + index++; + } + + } + + free(indices); + free(box_area); + free(is_suppressed); + + return LATENT_SVM_OK; +} \ No newline at end of file diff --git a/modules/objdetect/src/precomp.hpp b/modules/objdetect/src/precomp.hpp index e4c912beaf..c0ac7c1161 100644 --- a/modules/objdetect/src/precomp.hpp +++ b/modules/objdetect/src/precomp.hpp @@ -54,6 +54,8 @@ #include "opencv2/objdetect/objdetect.hpp" #include "opencv2/imgproc/imgproc.hpp" #include "opencv2/imgproc/imgproc_c.h" +#include "opencv2/core/core_c.h" +#include "opencv2/highgui/highgui_c.h" #include "opencv2/core/internal.hpp" #endif diff --git a/modules/objdetect/src/resizeimg.cpp b/modules/objdetect/src/resizeimg.cpp new file mode 100644 index 0000000000..404509b17a --- /dev/null +++ b/modules/objdetect/src/resizeimg.cpp @@ -0,0 +1,244 @@ +#include "_resizeimg.h" +#include +#include +#include + + + +IplImage * resize_opencv (IplImage * img, float scale){ + IplImage * imgTmp; + + int W, H, tW, tH; + + W = img->width; + H = img->height; + + tW = (int)(((float)W) * scale + 0.5); + tH = (int)(((float)H) * scale + 0.5); + + imgTmp = cvCreateImage(cvSize(tW , tH), img->depth, img->nChannels); + cvResize( + img, + imgTmp, + CV_INTER_AREA + ); + + return imgTmp; +} + +// +///* +// * Fast image subsampling. +// * This is used to construct the feature pyramid. +// */ +// +//// struct used for caching interpolation values +//typedef struct { +// int si, di; +// float alpha; +//}alphainfo; +// +//// copy src into dst using pre-computed interpolation values +//void alphacopy(float *src, float *dst, alphainfo *ofs, int n) { +// int i; +// for(i = 0; i < n; i++){ +// dst[ofs[i].di] += ofs[i].alpha * src[ofs[i].si]; +// } +//} +// +//int round(float val){ +// return (int)(val + 0.5); +//} +//void bzero(float * arr, int cnt){ +// int i; +// for(i = 0; i < cnt; i++){ +// arr[i] = 0.0f; +// } +//} +//// resize along each column +//// result is transposed, so we can apply it twice for a complete resize +//void resize1dtran(float *src, int sheight, float *dst, int dheight, +// int width, int chan) { +// alphainfo *ofs; +// float scale = (float)dheight/(float)sheight; +// float invscale = (float)sheight/(float)dheight; +// +// // we cache the interpolation values since they can be +// // shared among different columns +// int len = (int)ceilf(dheight*invscale) + 2*dheight; +// int k = 0; +// int dy; +// float fsy1; +// float fsy2; +// int sy1; +// int sy2; +// int sy; +// int c, x; +// float *s, *d; +// +// ofs = (alphainfo *) malloc (sizeof(alphainfo) * len); +// for (dy = 0; dy < dheight; dy++) { +// fsy1 = dy * invscale; +// fsy2 = fsy1 + invscale; +// sy1 = (int)ceilf(fsy1); +// sy2 = (int)floorf(fsy2); +// +// if (sy1 - fsy1 > 1e-3) { +// assert(k < len); +// assert(sy1 - 1 >= 0); +// ofs[k].di = dy*width; +// ofs[k].si = sy1-1; +// ofs[k++].alpha = (sy1 - fsy1) * scale; +// } +// +// for (sy = sy1; sy < sy2; sy++) { +// assert(k < len); +// assert(sy < sheight); +// ofs[k].di = dy*width; +// ofs[k].si = sy; +// ofs[k++].alpha = scale; +// } +// +// if (fsy2 - sy2 > 1e-3) { +// assert(k < len); +// assert(sy2 < sheight); +// ofs[k].di = dy*width; +// ofs[k].si = sy2; +// ofs[k++].alpha = (fsy2 - sy2) * scale; +// } +// } +// +// // resize each column of each color channel +// bzero(dst, chan*width*dheight); +// for (c = 0; c < chan; c++) { +// for (x = 0; x < width; x++) { +// s = src + c*width*sheight + x*sheight; +// d = dst + c*width*dheight + x; +// alphacopy(s, d, ofs, k); +// } +// } +// free(ofs); +//} +// +//IplImage * resize_article_dp(IplImage * img, float scale, const int k){ +// IplImage * imgTmp; +// float W, H; +// unsigned char *dataSrc; +// float * dataf; +// float *src, *dst, *tmp; +// int i, j, kk, channels; +// int index; +// int widthStep; +// int tW, tH; +// +// W = (float)img->width; +// H = (float)img->height; +// channels = img->nChannels; +// widthStep = img->widthStep; +// +// tW = (int)(((float)W) * scale + 0.5f); +// tH = (int)(((float)H) * scale + 0.5f); +// +// src = (float *)malloc(sizeof(float) * (int)(W * H * 3)); +// +// dataSrc = (unsigned char*)(img->imageData); +// index = 0; +// for (kk = 0; kk < channels; kk++) +// { +// for (i = 0; i < W; i++) +// { +// for (j = 0; j < H; j++) +// { +// src[index++] = (float)dataSrc[j * widthStep + i * channels + kk]; +// } +// } +// } +// +// imgTmp = cvCreateImage(cvSize(tW , tH), IPL_DEPTH_32F, channels); +// +// dst = (float *)malloc(sizeof(float) * (int)(tH * tW) * channels); +// tmp = (float *)malloc(sizeof(float) * (int)(tH * W) * channels); +// +// resize1dtran(src, (int)H, tmp, (int)tH, (int)W , 3); +// +// resize1dtran(tmp, (int)W, dst, (int)tW, (int)tH, 3); +// +// index = 0; +// //dataf = (float*)imgTmp->imageData; +// for (kk = 0; kk < channels; kk++) +// { +// for (i = 0; i < tW; i++) +// { +// for (j = 0; j < tH; j++) +// { +// dataf = (float*)(imgTmp->imageData + j * imgTmp->widthStep); +// dataf[ i * channels + kk] = dst[index++]; +// } +// } +// } +// +// free(src); +// free(dst); +// free(tmp); +// return imgTmp; +//} +// +//IplImage * resize_article_dp1(IplImage * img, float scale, const int k){ +// IplImage * imgTmp; +// float W, H; +// float * dataf; +// float *src, *dst, *tmp; +// int i, j, kk, channels; +// int index; +// int widthStep; +// int tW, tH; +// +// W = (float)img->width; +// H = (float)img->height; +// channels = img->nChannels; +// widthStep = img->widthStep; +// +// tW = (int)(((float)W) * scale + 0.5f); +// tH = (int)(((float)H) * scale + 0.5f); +// +// src = (float *)malloc(sizeof(float) * (int)(W * H) * 3); +// +// index = 0; +// for (kk = 0; kk < channels; kk++) +// { +// for (i = 0; i < W; i++) +// { +// for (j = 0; j < H; j++) +// { +// src[index++] = (float)(*( (float *)(img->imageData + j * widthStep) + i * channels + kk)); +// } +// } +// } +// +// imgTmp = cvCreateImage(cvSize(tW , tH), IPL_DEPTH_32F, channels); +// +// dst = (float *)malloc(sizeof(float) * (int)(tH * tW) * channels); +// tmp = (float *)malloc(sizeof(float) * (int)(tH * W) * channels); +// +// resize1dtran(src, (int)H, tmp, (int)tH, (int)W , 3); +// +// resize1dtran(tmp, (int)W, dst, (int)tW, (int)tH, 3); +// +// index = 0; +// for (kk = 0; kk < channels; kk++) +// { +// for (i = 0; i < tW; i++) +// { +// for (j = 0; j < tH; j++) +// { +// dataf = (float *)(imgTmp->imageData + j * imgTmp->widthStep); +// dataf[ i * channels + kk] = dst[index++]; +// } +// } +// } +// +// free(src); +// free(dst); +// free(tmp); +// return imgTmp; +//} \ No newline at end of file diff --git a/modules/objdetect/src/routine.cpp b/modules/objdetect/src/routine.cpp new file mode 100644 index 0000000000..b55e9f185b --- /dev/null +++ b/modules/objdetect/src/routine.cpp @@ -0,0 +1,103 @@ +#include "_routine.h" + +int allocFilterObject(filterObject **obj, const int sizeX, const int sizeY, const int p, const int xp){ + int i; + (*obj) = (filterObject *)malloc(sizeof(filterObject)); + (*obj)->sizeX = sizeX; + (*obj)->sizeY = sizeY; + (*obj)->p = p ; + (*obj)->xp = xp ; + (*obj)->fineFunction[0] = 0.0f; + (*obj)->fineFunction[1] = 0.0f; + (*obj)->fineFunction[2] = 0.0f; + (*obj)->fineFunction[3] = 0.0f; + (*obj)->V.x = 0; + (*obj)->V.y = 0; + (*obj)->V.l = 0; + (*obj)->H = (float *) malloc(sizeof (float) * (sizeX * sizeY * p)); + for(i = 0; i < sizeX * sizeY * p; i++){ + (*obj)->H[i] = 0.0f; + } + return LATENT_SVM_OK; +} +int freeFilterObject (filterObject **obj){ + if(*obj == NULL) return 0; + free((*obj)->H); + free(*obj); + (*obj) = NULL; + return LATENT_SVM_OK; +} + +int allocFeatureMapObject(featureMap **obj, const int sizeX, const int sizeY, const int p, const int xp){ + int i; + (*obj) = (featureMap *)malloc(sizeof(featureMap)); + (*obj)->sizeX = sizeX; + (*obj)->sizeY = sizeY; + (*obj)->p = p ; + (*obj)->xp = xp ; + (*obj)->Map = (float *) malloc(sizeof (float) * (sizeX * sizeY * p)); + for(i = 0; i < sizeX * sizeY * p; i++){ + (*obj)->Map[i] = 0.0; + } + return LATENT_SVM_OK; +} +int freeFeatureMapObject (featureMap **obj){ + if(*obj == NULL) return 0; + free((*obj)->Map); + free(*obj); + (*obj) = NULL; + return LATENT_SVM_OK; +} + +int allocFeaturePyramidObject(featurePyramid **obj, const int lambda, const int countLevel){ + (*obj) = (featurePyramid *)malloc(sizeof(featurePyramid)); + (*obj)->countLevel = countLevel; + (*obj)->pyramid = (featureMap **)malloc(sizeof(featureMap *) * countLevel); + (*obj)->lambda = lambda; + return LATENT_SVM_OK; +} + +int freeFeaturePyramidObject (featurePyramid **obj){ + int i; + if(*obj == NULL) return 0; + for(i = 0; i < (*obj)->countLevel; i++) + freeFeatureMapObject(&((*obj)->pyramid[i])); + free((*obj)->pyramid); + free(*obj); + (*obj) = NULL; + return LATENT_SVM_OK; +} + +int allocFFTImage(fftImage **image, int p, int dimX, int dimY) +{ + int i, j, size; + *image = (fftImage *)malloc(sizeof(fftImage)); + (*image)->p = p; + (*image)->dimX = dimX; + (*image)->dimY = dimY; + (*image)->channels = (float **)malloc(sizeof(float *) * p); + size = 2 * dimX * dimY; + for (i = 0; i < p; i++) + { + (*image)->channels[i] = (float *)malloc(sizeof(float) * size); + for (j = 0; j < size; j++) + { + (*image)->channels[i][j] = 0.0; + } + } + return LATENT_SVM_OK; +} + +int freeFFTImage(fftImage **image) +{ + unsigned int i; + if (*image == NULL) return LATENT_SVM_OK; + for (i = 0; i < (*image)->p; i++) + { + free((*image)->channels[i]); + (*image)->channels[i] = NULL; + } + free((*image)->channels); + (*image)->channels = NULL; + return LATENT_SVM_OK; +} \ No newline at end of file diff --git a/samples/c/000028.jpg b/samples/c/000028.jpg new file mode 100644 index 0000000000000000000000000000000000000000..df4a907e5fd1e40bbfd0d3bb7ad633d6d7211b4f GIT binary patch literal 65555 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

$u*kpa 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