├── .gitignore ├── COPYING ├── Makefile ├── README.rst ├── features_pedro_py.cc ├── numpymacros.h ├── pyhog.py └── pyhog_example.ipynb /.gitignore: -------------------------------------------------------------------------------- 1 | src/output/ 2 | 3 | *.aux 4 | *.log 5 | *.out 6 | *.blg 7 | *.bbl 8 | *.toc 9 | *.snm 10 | *.nav 11 | 12 | *.o 13 | *.so 14 | 15 | *.pyc 16 | *.pyo 17 | 18 | .sconsign.dblite 19 | 20 | .*.swp 21 | *~ 22 | 23 | tags 24 | 25 | core 26 | core.* 27 | 28 | *.i 29 | 30 | *.class 31 | 32 | *.exe 33 | *.dll 34 | *.mdb 35 | *.pidb 36 | *.csproj.user 37 | *.suo 38 | *.cache 39 | *.usertasks 40 | *.userprefs 41 | 42 | *.bak 43 | 44 | bin 45 | obj 46 | 47 | Thumbs.db 48 | 49 | .R.Rout 50 | .RData 51 | .Rhistory 52 | 53 | .deps 54 | 55 | changelog.txt 56 | 57 | cscope.* 58 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (C) 2008, 2009, 2010 Pedro Felzenszwalb, Ross Girshick 2 | Copyright (C) 2007 Pedro Felzenszwalb, Deva Ramanan 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NUMPY=`python -c 'import numpy; print numpy.get_include()'` 2 | PYROOT=`python -c 'import sys; print sys.prefix'` 3 | VER=`python -c "import sys; print('%s.%s'%(sys.version_info[0],sys.version_info[1]))"` 4 | CC=g++ 5 | LIBS= 6 | #FLAGS= -Wall -DNUMPYCHECK -fPIC 7 | #FLAGS = -Wall -DNDEBUG -O2 -ffast-math -pipe -msse -msse2 -mmmx -mfpmath=sse -fomit-frame-pointer 8 | #FLAGS = -Wall -DNDEBUG -O2 -ffast-math -fPIC 9 | FLAGS = -DNUMPYCHECK -DNDEBUG -O2 -ffast-math -msse2 -fPIC 10 | 11 | .PHONY: all 12 | all: features_pedro_py.so 13 | 14 | features_pedro_py.so: features_pedro_py.o 15 | g++ $^ -shared -o $@ $(LIBS) 16 | 17 | features_pedro_py.o: features_pedro_py.cc numpymacros.h 18 | g++ -c $< $(FLAGS) -I$(NUMPY) -I$(PYROOT)/include/python$(VER) -I../src/ -o $@ 19 | 20 | .PHONY: clean 21 | clean: 22 | rm -f *.o *.so 23 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | 2 | pyhog 3 | ---------- 4 | 5 | The Pascal VOC Toolkit comes with a Matlab/C implementation of HOG features by 6 | Pedro Felzenszwalb, Deva Ramanan and presumably others. Since I'm not very fond 7 | of Matlab I replaced the Matlab-specific parts for their Numpy equivalents. It 8 | works, but it's not very efficient because it copies the array into a 9 | Fortran-ordered version. That should be easy to fix. 10 | 11 | See an example of here: http://nbviewer.ipython.org/github/dimatura/pyhog/blob/master/pyhog_example.ipynb 12 | 13 | Daniel Maturana - dimatura@cmu.edu 14 | 2012 15 | -------------------------------------------------------------------------------- /features_pedro_py.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "numpymacros.h" 9 | 10 | // small value, used to avoid division by zero 11 | #define eps 0.0001 12 | 13 | // unit vectors used to compute gradient orientation 14 | double uu[9] = {1.0000, 15 | 0.9397, 16 | 0.7660, 17 | 0.500, 18 | 0.1736, 19 | -0.1736, 20 | -0.5000, 21 | -0.7660, 22 | -0.9397}; 23 | double vv[9] = {0.0000, 24 | 0.3420, 25 | 0.6428, 26 | 0.8660, 27 | 0.9848, 28 | 0.9848, 29 | 0.8660, 30 | 0.6428, 31 | 0.3420}; 32 | 33 | static inline double min(double x, double y) { return (x <= y ? x : y); } 34 | static inline double max(double x, double y) { return (x <= y ? y : x); } 35 | 36 | static inline int min(int x, int y) { return (x <= y ? x : y); } 37 | static inline int max(int x, int y) { return (x <= y ? y : x); } 38 | 39 | // main function: 40 | // takes a double color image and a bin size 41 | // returns HOG features 42 | static PyObject *process(PyObject *self, PyObject *args) { 43 | // in 44 | PyArrayObject *mximage; 45 | int sbin; 46 | 47 | // out 48 | PyArrayObject *mxfeat; 49 | 50 | if (!PyArg_ParseTuple(args, "O!i", 51 | &PyArray_Type, &mximage, 52 | &sbin 53 | )) { 54 | return NULL; 55 | } 56 | 57 | //TODO fix warnings 58 | FARRAY_CHECK(mximage); 59 | NDIM_CHECK(mximage, 3); 60 | DIM_CHECK(mximage, 2, 3); 61 | TYPE_CHECK(mximage, NPY_FLOAT64); 62 | 63 | double *im = (double *)PyArray_DATA(mximage); 64 | npy_intp dims[3]; 65 | dims[0] = PyArray_DIM(mximage, 0); 66 | dims[1] = PyArray_DIM(mximage, 1); 67 | dims[2] = PyArray_DIM(mximage, 2); 68 | //printf("%d %d %d\n",(int)dims[0],(int)dims[1],(int)dims[2]); 69 | 70 | // memory for caching orientation histograms & their norms 71 | int blocks[2]; 72 | blocks[0] = (int)round((double)dims[0]/(double)sbin); 73 | blocks[1] = (int)round((double)dims[1]/(double)sbin); 74 | 75 | double *hist = (double *)calloc(blocks[0]*blocks[1]*18, sizeof(double)); 76 | double *norm = (double *)calloc(blocks[0]*blocks[1], sizeof(double)); 77 | 78 | // memory for HOG features 79 | // TODO there's a way to do this in one call 80 | npy_intp out[3]; 81 | out[0] = max(blocks[0]-2, 0); 82 | out[1] = max(blocks[1]-2, 0); 83 | out[2] = 27+4; 84 | 85 | //mxfeat = mxCreateNumericArray(3, out, mxDOUBLE_CLASS, mxREAL); 86 | mxfeat = (PyArrayObject*) PyArray_NewFromDescr( 87 | &PyArray_Type, PyArray_DescrFromType(NPY_FLOAT64), 88 | 3, out, NULL, NULL, NPY_ARRAY_F_CONTIGUOUS, NULL); 89 | //(PyArrayObject *)PyArray_SimpleNew(3, out, NPY_FLOAT64); 90 | 91 | double *feat = (double *)PyArray_DATA(mxfeat); 92 | 93 | int visible[2]; 94 | visible[0] = blocks[0]*sbin; 95 | visible[1] = blocks[1]*sbin; 96 | 97 | for (int x = 1; x < visible[1]-1; x++) { 98 | for (int y = 1; y < visible[0]-1; y++) { 99 | // first color channel 100 | double *s = im + min(x, dims[1]-2)*dims[0] + min(y, dims[0]-2); 101 | double dy = *(s+1) - *(s-1); 102 | double dx = *(s+dims[0]) - *(s-dims[0]); 103 | double v = dx*dx + dy*dy; 104 | 105 | // second color channel 106 | s += dims[0]*dims[1]; 107 | double dy2 = *(s+1) - *(s-1); 108 | double dx2 = *(s+dims[0]) - *(s-dims[0]); 109 | double v2 = dx2*dx2 + dy2*dy2; 110 | 111 | // third color channel 112 | s += dims[0]*dims[1]; 113 | double dy3 = *(s+1) - *(s-1); 114 | double dx3 = *(s+dims[0]) - *(s-dims[0]); 115 | double v3 = dx3*dx3 + dy3*dy3; 116 | 117 | // pick channel with strongest gradient 118 | if (v2 > v) { 119 | v = v2; 120 | dx = dx2; 121 | dy = dy2; 122 | } 123 | if (v3 > v) { 124 | v = v3; 125 | dx = dx3; 126 | dy = dy3; 127 | } 128 | 129 | // snap to one of 18 orientations 130 | double best_dot = 0; 131 | int best_o = 0; 132 | for (int o = 0; o < 9; o++) { 133 | double dot = uu[o]*dx + vv[o]*dy; 134 | if (dot > best_dot) { 135 | best_dot = dot; 136 | best_o = o; 137 | } else if (-dot > best_dot) { 138 | best_dot = -dot; 139 | best_o = o+9; 140 | } 141 | } 142 | 143 | // add to 4 histograms around pixel using linear interpolation 144 | double xp = ((double)x+0.5)/(double)sbin - 0.5; 145 | double yp = ((double)y+0.5)/(double)sbin - 0.5; 146 | int ixp = (int)floor(xp); 147 | int iyp = (int)floor(yp); 148 | double vx0 = xp-ixp; 149 | double vy0 = yp-iyp; 150 | double vx1 = 1.0-vx0; 151 | double vy1 = 1.0-vy0; 152 | v = sqrt(v); 153 | 154 | if (ixp >= 0 && iyp >= 0) { 155 | *(hist + ixp*blocks[0] + iyp + best_o*blocks[0]*blocks[1]) += 156 | vx1*vy1*v; 157 | } 158 | 159 | if (ixp+1 < blocks[1] && iyp >= 0) { 160 | *(hist + (ixp+1)*blocks[0] + iyp + best_o*blocks[0]*blocks[1]) += 161 | vx0*vy1*v; 162 | } 163 | 164 | if (ixp >= 0 && iyp+1 < blocks[0]) { 165 | *(hist + ixp*blocks[0] + (iyp+1) + best_o*blocks[0]*blocks[1]) += 166 | vx1*vy0*v; 167 | } 168 | 169 | if (ixp+1 < blocks[1] && iyp+1 < blocks[0]) { 170 | *(hist + (ixp+1)*blocks[0] + (iyp+1) + best_o*blocks[0]*blocks[1]) += 171 | vx0*vy0*v; 172 | } 173 | } 174 | } 175 | 176 | // compute energy in each block by summing over orientations 177 | for (int o = 0; o < 9; o++) { 178 | double *src1 = hist + o*blocks[0]*blocks[1]; 179 | double *src2 = hist + (o+9)*blocks[0]*blocks[1]; 180 | double *dst = norm; 181 | double *end = norm + blocks[1]*blocks[0]; 182 | while (dst < end) { 183 | *(dst++) += (*src1 + *src2) * (*src1 + *src2); 184 | src1++; 185 | src2++; 186 | } 187 | } 188 | 189 | // compute features 190 | for (int x = 0; x < out[1]; x++) { 191 | for (int y = 0; y < out[0]; y++) { 192 | double *dst = feat + x*out[0] + y; 193 | double *src, *p, n1, n2, n3, n4; 194 | 195 | p = norm + (x+1)*blocks[0] + y+1; 196 | n1 = 1.0 / sqrt(*p + *(p+1) + *(p+blocks[0]) + *(p+blocks[0]+1) + eps); 197 | p = norm + (x+1)*blocks[0] + y; 198 | n2 = 1.0 / sqrt(*p + *(p+1) + *(p+blocks[0]) + *(p+blocks[0]+1) + eps); 199 | p = norm + x*blocks[0] + y+1; 200 | n3 = 1.0 / sqrt(*p + *(p+1) + *(p+blocks[0]) + *(p+blocks[0]+1) + eps); 201 | p = norm + x*blocks[0] + y; 202 | n4 = 1.0 / sqrt(*p + *(p+1) + *(p+blocks[0]) + *(p+blocks[0]+1) + eps); 203 | 204 | double t1 = 0; 205 | double t2 = 0; 206 | double t3 = 0; 207 | double t4 = 0; 208 | 209 | // contrast-sensitive features 210 | src = hist + (x+1)*blocks[0] + (y+1); 211 | for (int o = 0; o < 18; o++) { 212 | double h1 = min(*src * n1, 0.2); 213 | double h2 = min(*src * n2, 0.2); 214 | double h3 = min(*src * n3, 0.2); 215 | double h4 = min(*src * n4, 0.2); 216 | *dst = 0.5 * (h1 + h2 + h3 + h4); 217 | t1 += h1; 218 | t2 += h2; 219 | t3 += h3; 220 | t4 += h4; 221 | dst += out[0]*out[1]; 222 | src += blocks[0]*blocks[1]; 223 | } 224 | 225 | // contrast-insensitive features 226 | src = hist + (x+1)*blocks[0] + (y+1); 227 | for (int o = 0; o < 9; o++) { 228 | double sum = *src + *(src + 9*blocks[0]*blocks[1]); 229 | double h1 = min(sum * n1, 0.2); 230 | double h2 = min(sum * n2, 0.2); 231 | double h3 = min(sum * n3, 0.2); 232 | double h4 = min(sum * n4, 0.2); 233 | *dst = 0.5 * (h1 + h2 + h3 + h4); 234 | dst += out[0]*out[1]; 235 | src += blocks[0]*blocks[1]; 236 | } 237 | 238 | // texture features 239 | *dst = 0.2357 * t1; 240 | dst += out[0]*out[1]; 241 | *dst = 0.2357 * t2; 242 | dst += out[0]*out[1]; 243 | *dst = 0.2357 * t3; 244 | dst += out[0]*out[1]; 245 | *dst = 0.2357 * t4; 246 | } 247 | } 248 | 249 | // hack 250 | //PyArray_FLAGS(mxfeat) |= NPY_F_CONTIGUOUS; 251 | //PyArray_FLAGS(mxfeat) &= ~NPY_C_CONTIGUOUS; 252 | 253 | free(hist); 254 | free(norm); 255 | 256 | return PyArray_Return(mxfeat);//Py_BuildValue("N", mxfeat); 257 | } 258 | 259 | static PyMethodDef features_pedro_py_methods[] = { 260 | {"process", 261 | process, 262 | METH_VARARGS, 263 | "process"}, 264 | {NULL, NULL, 0, NULL} /* sentinel*/ 265 | }; 266 | 267 | PyMODINIT_FUNC initfeatures_pedro_py() { 268 | Py_InitModule("features_pedro_py", features_pedro_py_methods); 269 | import_array(); 270 | } 271 | 272 | -------------------------------------------------------------------------------- /numpymacros.h: -------------------------------------------------------------------------------- 1 | #ifndef NUMPY_MACROS__H 2 | #define NUMPY_MACROS__H 3 | 4 | /* 5 | This is from the book 'Python Scripting for Computational Science' 6 | by Hans Petter Langtangen, with some modifications. 7 | */ 8 | 9 | #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION 10 | #include 11 | 12 | /* define some macros for array dimension/type check 13 | and subscripting */ 14 | #define QUOTE(s) # s /* turn s into string "s" */ 15 | #define NDIM_CHECK(a, expected_ndim) \ 16 | if (PyArray_NDIM(a) != expected_ndim) { \ 17 | PyErr_Format(PyExc_ValueError, \ 18 | "%s array is %d-dimensional, but expected to be %d-dimensional",\ 19 | QUOTE(a), PyArray_NDIM(a), expected_ndim); \ 20 | return NULL; \ 21 | } 22 | #define DIM_CHECK(a, dim, expected_length) \ 23 | if (dim > PyArray_NDIM(a)) { \ 24 | PyErr_Format(PyExc_ValueError, \ 25 | "%s array has no %d dimension (max dim. is %d)", \ 26 | QUOTE(a), dim, PyArray_NDIM(a)); \ 27 | return NULL; \ 28 | } \ 29 | if (PyArray_DIM(a, dim) != expected_length) { \ 30 | PyErr_Format(PyExc_ValueError, \ 31 | "%s array has wrong %d-dimension=%ld (expected %d)", \ 32 | QUOTE(a), dim, PyArray_DIM(a, dim), expected_length); \ 33 | return NULL; \ 34 | } 35 | #define TYPE_CHECK(a, tp) \ 36 | if (PyArray_TYPE(a) != tp) { \ 37 | PyErr_Format(PyExc_TypeError, \ 38 | "%s array is not of correct type (%d)", QUOTE(a), tp); \ 39 | return NULL; \ 40 | } 41 | #define CALLABLE_CHECK(func) \ 42 | if (!PyCallable_Check(func)) { \ 43 | PyErr_Format(PyExc_TypeError, \ 44 | "%s is not a callable function", QUOTE(func)); \ 45 | return NULL; \ 46 | } 47 | #define CARRAY_CHECK(a) \ 48 | if (!(PyArray_ISCONTIGUOUS(a) && PyArray_ISCARRAY(a))) { \ 49 | PyErr_Format(PyExc_TypeError, \ 50 | "%s is not a contiguous c-array", QUOTE(a)); \ 51 | return NULL; \ 52 | } 53 | #define FARRAY_CHECK(a) \ 54 | if (!(PyArray_ISFARRAY(a))) { \ 55 | PyErr_Format(PyExc_TypeError, \ 56 | "%s is not a contiguous f-array", QUOTE(a)); \ 57 | return NULL; \ 58 | } 59 | #define CHECK(assertion, message) \ 60 | if (!(assertion)) { \ 61 | PyErr_Format(PyExc_ValueError, message); \ 62 | return NULL; \ 63 | } 64 | 65 | #define DIND1(a, i) *((npy_float64 *) PyArray_GETPTR1(a, i)) 66 | #define DIND2(a, i, j) \ 67 | *((npy_float64 *) PyArray_GETPTR2(a, i, j)) 68 | #define DIND3(a, i, j, k) \ 69 | *((npy_float64 *) Py_Array_GETPTR3(a, i, j, k)) 70 | 71 | #define FIND1(a, i) *((npy_float32 *) PyArray_GETPTR1(a, i)) 72 | #define FIND2(a, i, j) \ 73 | *((npy_float32 *) PyArray_GETPTR2(a, i, j)) 74 | #define FIND3(a, i, j, k) \ 75 | *((npy_float32 *) Py_Array_GETPTR3(a, i, j, k)) 76 | 77 | #define IIND1(a, i) *((npy_int *) PyArray_GETPTR1(a, i)) 78 | #define IIND2(a, i, j) \ 79 | *((npy_int *) PyArray_GETPTR2(a, i, j)) 80 | #define IIND3(a, i, j, k) \ 81 | *((npy_int *) Py_Array_GETPTR3(a, i, j, k)) 82 | 83 | #define U8IND2(a, i, j) \ 84 | *((npy_uint8 *) PyArray_GETPTR2(a, i, j)) 85 | 86 | #define U8IND3(a, i, j, k) \ 87 | *((npy_uint8 *) PyArray_GETPTR3(a, i, j, k)) 88 | 89 | #endif 90 | -------------------------------------------------------------------------------- /pyhog.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | import features_pedro_py 4 | 5 | try: 6 | from scipy.misc import imrotate 7 | imrotate_available = True 8 | except ImportError: 9 | imrotate_available = False 10 | 11 | def features_pedro(img, sbin): 12 | imgf = img.copy('F') 13 | hogf = features_pedro_py.process(imgf, sbin) 14 | return hogf 15 | 16 | def hog_picture(w, bs=20): 17 | """ Visualize positive HOG weights. 18 | ported to numpy from https://github.com/CSAILVision/ihog/blob/master/showHOG.m 19 | """ 20 | if not imrotate_available: 21 | raise RuntimeError('This function requires scipy') 22 | bim1 = np.zeros((bs, bs)) 23 | bim1[:,round(bs/2)-1:round(bs/2)] = 1 24 | bim = np.zeros((9,)+bim1.shape) 25 | for i in xrange(9): 26 | bim[i] = imrotate(bim1, -i*20)/255.0 27 | s = w.shape 28 | w = w.copy() 29 | w[w < 0] = 0 30 | im = np.zeros((bs*s[0], bs*s[1])) 31 | for i in xrange(s[0]): 32 | iis = slice( i*bs, (i+1)*bs ) 33 | for j in xrange(s[1]): 34 | jjs = slice( j*bs, (j+1)*bs ) 35 | for k in xrange(9): 36 | im[iis,jjs] += bim[k] * w[i,j,k+18] 37 | return im/np.max(w) 38 | --------------------------------------------------------------------------------