|
@@ -0,0 +1,407 @@
|
|
|
+#include <math.h>
|
|
|
+#include "mex.h"
|
|
|
+
|
|
|
+// small value, used to avoid division by zero
|
|
|
+#define eps 0.0001
|
|
|
+
|
|
|
+// unit vectors used to compute gradient orientation
|
|
|
+double uu[9] = {1.0000,
|
|
|
+ 0.9397,
|
|
|
+ 0.7660,
|
|
|
+ 0.500,
|
|
|
+ 0.1736,
|
|
|
+ -0.1736,
|
|
|
+ -0.5000,
|
|
|
+ -0.7660,
|
|
|
+ -0.9397
|
|
|
+ };
|
|
|
+
|
|
|
+double vv[9] = {0.0000,
|
|
|
+ 0.3420,
|
|
|
+ 0.6428,
|
|
|
+ 0.8660,
|
|
|
+ 0.9848,
|
|
|
+ 0.9848,
|
|
|
+ 0.8660,
|
|
|
+ 0.6428,
|
|
|
+ 0.3420
|
|
|
+ };
|
|
|
+
|
|
|
+static inline double min(double x, double y) { return (x <= y ? x : y); }
|
|
|
+static inline double max(double x, double y) { return (x <= y ? y : x); }
|
|
|
+
|
|
|
+static inline int min(int x, int y) { return (x <= y ? x : y); }
|
|
|
+static inline int max(int x, int y) { return (x <= y ? y : x); }
|
|
|
+
|
|
|
+// main function:
|
|
|
+// takes a double color image and a bin size
|
|
|
+// returns HOG features
|
|
|
+mxArray *process(const mxArray *mximage, const mxArray *mxsbin)
|
|
|
+{
|
|
|
+ double *im = (double *)mxGetPr(mximage);
|
|
|
+ const int *dims = mxGetDimensions(mximage);
|
|
|
+ if (mxGetNumberOfDimensions(mximage) != 3 ||
|
|
|
+ dims[2] != 3 ||
|
|
|
+ mxGetClassID(mximage) != mxDOUBLE_CLASS)
|
|
|
+ mexErrMsgTxt("Invalid input");
|
|
|
+
|
|
|
+ // size of a cell in pixel, used for both dimensions
|
|
|
+ int sbin = (int)mxGetScalar(mxsbin);
|
|
|
+
|
|
|
+ // memory for caching orientation histograms & their norms
|
|
|
+ int blocks[2];
|
|
|
+ // compute number of cells fitting into current image given speficied cell size in pixel
|
|
|
+ // use floor to prevent running over image borders
|
|
|
+ blocks[0] = (int)floor( (double)dims[0] / (double)sbin );
|
|
|
+ blocks[1] = (int)floor( (double)dims[1] / (double)sbin );
|
|
|
+
|
|
|
+ // pre-allocate memory
|
|
|
+ double *hist = (double *)mxCalloc(blocks[0]*blocks[1]*18, sizeof(double));
|
|
|
+ double *norm = (double *)mxCalloc(blocks[0]*blocks[1], sizeof(double));
|
|
|
+
|
|
|
+ // memory for HOG features
|
|
|
+ int out[3];
|
|
|
+ // we compute as many blocks as possible given the specified cell size and the current image
|
|
|
+ out[0] = max(blocks[0], 0);
|
|
|
+ out[1] = max(blocks[1], 0);
|
|
|
+ // note: previously, a subtraction of 2 was done to guarantee identical bilinear interpolation behaviour in all cells
|
|
|
+ // however, are more interested in obtaining a correct number of returned cells, rather than in slightly more consistent interpolation results...
|
|
|
+ // apart from that, the only cells affected are the ones on the top and left border of the cell array
|
|
|
+
|
|
|
+ out[2] = 27+4+1;
|
|
|
+ mxArray *mxfeat = mxCreateNumericArray(3, out, mxDOUBLE_CLASS, mxREAL);
|
|
|
+ double *feat = (double *)mxGetPr(mxfeat);
|
|
|
+
|
|
|
+ int visible[2];
|
|
|
+ visible[0] = blocks[0]*sbin;
|
|
|
+ visible[1] = blocks[1]*sbin;
|
|
|
+
|
|
|
+ // If you want to be 100% save, init output values here.
|
|
|
+ // Note that this should not be needed, since every value will be set lateron explicitely.
|
|
|
+ //
|
|
|
+ //double *dst = feat;
|
|
|
+ //for ( int tmp = 0; tmp < out[0]*out[1]*out[2]; tmp++, dst++ )
|
|
|
+ //{
|
|
|
+ // *dst = 0.0;
|
|
|
+ //}
|
|
|
+
|
|
|
+ // start by 1 and end by -1 to ensure safe gradient calculations on boundaries
|
|
|
+ for (int x = 1; x < visible[1]-1; x++)
|
|
|
+ {
|
|
|
+ for (int y = 1; y < visible[0]-1; y++)
|
|
|
+ {
|
|
|
+ // first color channel
|
|
|
+ // NOTE: why minimum check? boundary safety is given by for loop ends
|
|
|
+ // col offset current row position
|
|
|
+ double *s = im + min(x, dims[1]-1)*dims[0] + min(y, dims[0]-1);
|
|
|
+ //gradient in y direction
|
|
|
+ double dy = *(s+1) - *(s-1);
|
|
|
+ // gradient in x direction
|
|
|
+ double dx = *(s+dims[0]) - *(s-dims[0]);
|
|
|
+ // squared gradient strength on current pixel
|
|
|
+ double v = dx*dx + dy*dy;
|
|
|
+
|
|
|
+ // second color channel
|
|
|
+ s += dims[0]*dims[1];
|
|
|
+ //gradient in y direction
|
|
|
+ double dy2 = *(s+1) - *(s-1);
|
|
|
+ // gradient in x direction
|
|
|
+ double dx2 = *(s+dims[0]) - *(s-dims[0]);
|
|
|
+ // squared gradient strength on current pixel
|
|
|
+ double v2 = dx2*dx2 + dy2*dy2;
|
|
|
+
|
|
|
+ // third color channel
|
|
|
+ s += dims[0]*dims[1];
|
|
|
+ //gradient in y direction
|
|
|
+ double dy3 = *(s+1) - *(s-1);
|
|
|
+ // gradient in x direction
|
|
|
+ double dx3 = *(s+dims[0]) - *(s-dims[0]);
|
|
|
+ // squared gradient strength on current pixel
|
|
|
+ double v3 = dx3*dx3 + dy3*dy3;
|
|
|
+
|
|
|
+ // pick channel with strongest gradient
|
|
|
+ if (v2 > v)
|
|
|
+ {
|
|
|
+ v = v2;
|
|
|
+ dx = dx2;
|
|
|
+ dy = dy2;
|
|
|
+ }
|
|
|
+ if (v3 > v)
|
|
|
+ {
|
|
|
+ v = v3;
|
|
|
+ dx = dx3;
|
|
|
+ dy = dy3;
|
|
|
+ }
|
|
|
+
|
|
|
+ //
|
|
|
+ // now, discretize gradient orientation into one of 18 possible (oriented) bins
|
|
|
+ //
|
|
|
+
|
|
|
+ // strength of strongest orientation in this pixel
|
|
|
+ double best_dot = 0;
|
|
|
+ // index of strongest orientation in this pixel
|
|
|
+ int best_o = 0;
|
|
|
+
|
|
|
+ for (int o = 0; o < 9; o++)
|
|
|
+ {
|
|
|
+ double dot = uu[o]*dx + vv[o]*dy;
|
|
|
+ if (dot > best_dot)
|
|
|
+ {
|
|
|
+ best_dot = dot;
|
|
|
+ best_o = o;
|
|
|
+ }
|
|
|
+ else if (-dot > best_dot)
|
|
|
+ {
|
|
|
+ best_dot = -dot;
|
|
|
+ best_o = o+9;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // add to 4 histograms around pixel using linear interpolation
|
|
|
+
|
|
|
+ // current position normalized to cell scale, e.g. xp = 1.4 -> located in the left part of second cell
|
|
|
+ // subtraction of 0.5 to move relative to cell center
|
|
|
+ double xp = ((double)x+0.5)/(double)sbin - 0.5;
|
|
|
+ double yp = ((double)y+0.5)/(double)sbin - 0.5;
|
|
|
+
|
|
|
+ // that's the index of the cell, whose center is directly left of current position in x direction
|
|
|
+ int ixp = (int)floor(xp);
|
|
|
+ // that's the index of the cell, whose center is directly on top of current position in y direction
|
|
|
+ int iyp = (int)floor(yp);
|
|
|
+
|
|
|
+ // distance to left, used for weighting the gradient strength by 1-distance, guaranteed to be in [0,1]
|
|
|
+ double vx0 = xp-ixp;
|
|
|
+ // distance to top
|
|
|
+ double vy0 = yp-iyp;
|
|
|
+ // distance to right
|
|
|
+ double vx1 = 1.0-vx0;
|
|
|
+ // distance to bottom
|
|
|
+ double vy1 = 1.0-vy0;
|
|
|
+
|
|
|
+ // normalized gradient strength on current pixel
|
|
|
+ v = sqrt(v);
|
|
|
+
|
|
|
+ // if left upper cell exists
|
|
|
+ if ( (ixp >= 0) && (iyp >= 0) )
|
|
|
+ {
|
|
|
+ *(hist + ixp*blocks[0] + iyp + best_o*blocks[0]*blocks[1]) +=
|
|
|
+ vx1*vy1*v; // i.e., (1-distX0)*(1-distY0)*v
|
|
|
+ }
|
|
|
+
|
|
|
+ // if right upper cell exists
|
|
|
+ if ( (ixp+2 < blocks[1]) && (iyp >= 0) )
|
|
|
+ {
|
|
|
+ *(hist + (ixp+1)*blocks[0] + iyp + best_o*blocks[0]*blocks[1]) +=
|
|
|
+ vx0*vy1*v;
|
|
|
+ }
|
|
|
+
|
|
|
+ // if left lower cell exists
|
|
|
+ if ( (ixp >= 0) && (iyp+2 < blocks[0]) )
|
|
|
+ {
|
|
|
+ *(hist + ixp*blocks[0] + (iyp+1) + best_o*blocks[0]*blocks[1]) +=
|
|
|
+ vx1*vy0*v;
|
|
|
+ }
|
|
|
+
|
|
|
+ // if right lower cell exists
|
|
|
+ if ( (ixp+2 < blocks[1]) && (iyp+2 < blocks[0]) )
|
|
|
+ {
|
|
|
+ *(hist + (ixp+1)*blocks[0] + (iyp+1) + best_o*blocks[0]*blocks[1]) +=
|
|
|
+ vx0*vy0*v;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // compute energy in each block by summing over orientations
|
|
|
+ for (int o = 0; o < 9; o++)
|
|
|
+ {
|
|
|
+ double *src1 = hist + o*blocks[0]*blocks[1];
|
|
|
+ double *src2 = hist + (o+9)*blocks[0]*blocks[1];
|
|
|
+ double *dst = norm;
|
|
|
+ double *end = norm + blocks[1]*blocks[0];
|
|
|
+
|
|
|
+ while (dst < end)
|
|
|
+ {
|
|
|
+ *(dst++) += (*src1 + *src2) * (*src1 + *src2);
|
|
|
+ src1++;
|
|
|
+ src2++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // compute features
|
|
|
+ for (int x = 0; x < out[1]; x++)
|
|
|
+ {
|
|
|
+ for (int y = 0; y < out[0]; y++)
|
|
|
+ {
|
|
|
+ double *dst = feat + x*out[0] + y;
|
|
|
+ double *src, *p, n1, n2, n3, n4;
|
|
|
+
|
|
|
+ // compute normalization factors for all 4 possible blocks of 2x2 cells
|
|
|
+
|
|
|
+
|
|
|
+ // block with current, right, lower, and lower right cell
|
|
|
+ if ( ( (x+1) < out[1] ) && ( (y+1) < out[0] ) )
|
|
|
+ {
|
|
|
+ p = norm + x*blocks[0] + y;
|
|
|
+ n1 = 1.0 / sqrt(*p + *(p+1) + *(p+blocks[0]) + *(p+blocks[0]+1) + eps);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ n1 = 1.0 / sqrt(eps);
|
|
|
+
|
|
|
+ // block with current, upper, right, and upper right cell
|
|
|
+ if ( ( (x+1) < out[1] ) && ( (y-1) >= 0 ) )
|
|
|
+ {
|
|
|
+ p = norm + x*blocks[0] + (y-1);
|
|
|
+ n2 = 1.0 / sqrt(*p + *(p+1) + *(p+blocks[0]) + *(p+blocks[0]+1) + eps);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ n2 = 1.0 / sqrt(eps);
|
|
|
+
|
|
|
+ // block with current, lower, left, and lower left cell
|
|
|
+ if ( ( (x-1) >= 0 ) && ( (y+1) < out[0] ) )
|
|
|
+ {
|
|
|
+ p = norm + (x-1)*blocks[0] + y;
|
|
|
+ n3 = 1.0 / sqrt(*p + *(p+1) + *(p+blocks[0]) + *(p+blocks[0]+1) + eps);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ n3 = 1.0 / sqrt(eps);
|
|
|
+
|
|
|
+ // block with current, upper, left, and upper left cell
|
|
|
+ if ( ( (x-1) >= 0 ) && ( (y-1) >= 0 ) )
|
|
|
+ {
|
|
|
+ p = norm + (x-1)*blocks[0] + (y-1);
|
|
|
+ n4 = 1.0 / sqrt(*p + *(p+1) + *(p+blocks[0]) + *(p+blocks[0]+1) + eps);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ n4 = 1.0 / sqrt(eps);
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ // copy normalization factors for blocks on boundaries
|
|
|
+ // -----------------
|
|
|
+ // | n4 | | n2 |
|
|
|
+ // -----------------
|
|
|
+ // | | x,y | |
|
|
|
+ // -----------------
|
|
|
+ // | n3 | | n1 |
|
|
|
+ // -----------------
|
|
|
+ if ( (x) == 0 ) // left boundary
|
|
|
+ {
|
|
|
+ if ( (y) == 0 ) // left top corner
|
|
|
+ {
|
|
|
+ n4 = n1; n3 = n1; n2 = n1;
|
|
|
+ }
|
|
|
+ else if ( (y) == (out[0]-1) ) // left lower corner
|
|
|
+ {
|
|
|
+ n4 = n2; n3 = n2; n1 = n2;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ n4 = n2; n3 = n1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if ( (x) == (out[1]-1) ) // right boundary
|
|
|
+ {
|
|
|
+ if ( (y) == 0 ) // right top corner
|
|
|
+ {
|
|
|
+ n4 = n3; n2 = n3; n1 = n3;
|
|
|
+ }
|
|
|
+ else if ( (y) == (out[0]-1) ) // right lower corner
|
|
|
+ {
|
|
|
+ n3 = n4; n2 = n4; n1 = n4;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ n2 = n4; n1 = n3;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if ( (y) == 0 ) // upper boundary ( corners already tackled)
|
|
|
+ {
|
|
|
+ if ( ( x > 0 ) && ( x < (out[1]-1) ) )
|
|
|
+ {
|
|
|
+ n4 = n3; n2 = n1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if ( (y) == (out[0]-1) ) // lower boundary ( corners already tackled)
|
|
|
+ {
|
|
|
+ if ( ( x > 0 ) && ( x < (out[1]-1) ) )
|
|
|
+ {
|
|
|
+ n3 = n4; n1 = n2;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ double t1 = 0;
|
|
|
+ double t2 = 0;
|
|
|
+ double t3 = 0;
|
|
|
+ double t4 = 0;
|
|
|
+
|
|
|
+ // contrast-sensitive features
|
|
|
+ src = hist + (x)*blocks[0] + (y);
|
|
|
+ for (int o = 0; o < 18; o++)
|
|
|
+ {
|
|
|
+ double h1 = min(*src * n1, 0.2);
|
|
|
+ double h2 = min(*src * n2, 0.2);
|
|
|
+ double h3 = min(*src * n3, 0.2);
|
|
|
+ double h4 = min(*src * n4, 0.2);
|
|
|
+ *dst = 0.5 * (h1 + h2 + h3 + h4);
|
|
|
+ t1 += h1;
|
|
|
+ t2 += h2;
|
|
|
+ t3 += h3;
|
|
|
+ t4 += h4;
|
|
|
+ // move pointers to next position
|
|
|
+ dst += out[0]*out[1];
|
|
|
+ src += blocks[0]*blocks[1];
|
|
|
+ }
|
|
|
+
|
|
|
+ // contrast-insensitive features
|
|
|
+ src = hist + (x)*blocks[0] + (y);
|
|
|
+ for (int o = 0; o < 9; o++)
|
|
|
+ {
|
|
|
+ double sum = *src + *(src + 9*blocks[0]*blocks[1]);
|
|
|
+ double h1 = min(sum * n1, 0.2);
|
|
|
+ double h2 = min(sum * n2, 0.2);
|
|
|
+ double h3 = min(sum * n3, 0.2);
|
|
|
+ double h4 = min(sum * n4, 0.2);
|
|
|
+ *dst = 0.5 * (h1 + h2 + h3 + h4);
|
|
|
+ dst += out[0]*out[1];
|
|
|
+ src += blocks[0]*blocks[1];
|
|
|
+ }
|
|
|
+
|
|
|
+ // texture features
|
|
|
+ //to be complicable to FFLD code
|
|
|
+ *dst = 0.2357 * t4;
|
|
|
+ //*dst = 0.2357 * t1;
|
|
|
+ dst += out[0]*out[1];
|
|
|
+ *dst = 0.2357 * t2;
|
|
|
+ dst += out[0]*out[1];
|
|
|
+ *dst = 0.2357 * t3;
|
|
|
+ dst += out[0]*out[1];
|
|
|
+ //to be complicable to FFLD code
|
|
|
+ *dst = 0.2357 * t1;
|
|
|
+ //*dst = 0.2357 * t4;
|
|
|
+
|
|
|
+ // truncation feature
|
|
|
+ dst += out[0]*out[1];
|
|
|
+ *dst = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ mxFree(hist);
|
|
|
+ mxFree(norm);
|
|
|
+ return mxfeat;
|
|
|
+}
|
|
|
+
|
|
|
+// matlab entry point
|
|
|
+// F = features(image, bin)
|
|
|
+// image should be color with double values
|
|
|
+void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
|
|
|
+{
|
|
|
+ if (nrhs != 2)
|
|
|
+ mexErrMsgTxt("Wrong number of inputs");
|
|
|
+ if (nlhs != 1)
|
|
|
+ mexErrMsgTxt("Wrong number of outputs");
|
|
|
+ plhs[0] = process(prhs[0], prhs[1]);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|