00001 /*!@file Features/HistogramOfGradients.C */ 00002 00003 00004 // //////////////////////////////////////////////////////////////////// // 00005 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2000-2005 // 00006 // by the University of Southern California (USC) and the iLab at USC. // 00007 // See http://iLab.usc.edu for information about this project. // 00008 // //////////////////////////////////////////////////////////////////// // 00009 // Major portions of the iLab Neuromorphic Vision Toolkit are protected // 00010 // under the U.S. patent ``Computation of Intrinsic Perceptual Saliency // 00011 // in Visual Environments, and Applications'' by Christof Koch and // 00012 // Laurent Itti, California Institute of Technology, 2001 (patent // 00013 // pending; application number 09/912,225 filed July 23, 2001; see // 00014 // http://pair.uspto.gov/cgi-bin/final/home.pl for current status). // 00015 // //////////////////////////////////////////////////////////////////// // 00016 // This file is part of the iLab Neuromorphic Vision C++ Toolkit. // 00017 // // 00018 // The iLab Neuromorphic Vision C++ Toolkit is free software; you can // 00019 // redistribute it and/or modify it under the terms of the GNU General // 00020 // Public License as published by the Free Software Foundation; either // 00021 // version 2 of the License, or (at your option) any later version. // 00022 // // 00023 // The iLab Neuromorphic Vision C++ Toolkit is distributed in the hope // 00024 // that it will be useful, but WITHOUT ANY WARRANTY; without even the // 00025 // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // 00026 // PURPOSE. See the GNU General Public License for more details. // 00027 // // 00028 // You should have received a copy of the GNU General Public License // 00029 // along with the iLab Neuromorphic Vision C++ Toolkit; if not, write // 00030 // to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, // 00031 // Boston, MA 02111-1307 USA. // 00032 // //////////////////////////////////////////////////////////////////// // 00033 // 00034 // Primary maintainer for this file: Daniel Parks <danielfp@usc.edu> 00035 // $HeadURL$ 00036 // $Id$ 00037 // 00038 00039 #include "Features/HistogramOfGradients.H" 00040 #include "Image/DrawOps.H" 00041 #include "Image/MathOps.H" 00042 #include "Image/Kernels.H" 00043 #include "Image/CutPaste.H" 00044 #include "Image/ColorOps.H" 00045 #include "Image/FilterOps.H" 00046 #include "Image/ShapeOps.H" 00047 00048 00049 HistogramOfGradients::HistogramOfGradients(bool normalize, Dims cellDims, bool fixedCells, int numOrientations, bool signedOrient) : 00050 itsNormalize(normalize), 00051 itsCellDims(cellDims), 00052 itsFixedDims(fixedCells), 00053 itsOriBins(numOrientations), 00054 itsOriSigned(signedOrient), 00055 itsEpsilon(0.0001F) 00056 { 00057 00058 } 00059 00060 HistogramOfGradients::~HistogramOfGradients() 00061 { 00062 } 00063 00064 std::vector<float> HistogramOfGradients::createHistogram(const Image<float>& img, const Image<float>& img2, const Image<float>& img3) 00065 { 00066 Image<float> gradmag, gradang; 00067 calculateGradient(img,img2,img3,gradmag,gradang); 00068 return createHistogramFromGradient(gradmag,gradang); 00069 } 00070 00071 00072 void HistogramOfGradients::calculateGradient(const Image<float>& img, const Image<float>& img2, const Image<float>& img3, Image<float>& gradmag, Image<float>&gradang) 00073 { 00074 00075 gradientSobel(img, gradmag, gradang); 00076 00077 if(img2.initialized()) 00078 { 00079 Image<float> gradmag2, gradang2; 00080 // Calculate Sobel for image 2 00081 gradientSobel(img2,gradmag2,gradang2); 00082 // Take element-wise max of magnitudes and use that to choose angles, store into last arguments 00083 takeLinkedMax(gradmag,gradmag2,gradang,gradang2,gradmag,gradang); 00084 } 00085 00086 if(img3.initialized()) 00087 { 00088 Image<float> gradmag3, gradang3; 00089 // Calculate Sobel for image 2 00090 gradientSobel(img3,gradmag3,gradang3); 00091 // Determine maximum filter per orientation 00092 takeLinkedMax(gradmag,gradmag3,gradang,gradang3,gradmag,gradang); 00093 } 00094 00095 } 00096 00097 std::vector<float> HistogramOfGradients::createHistogramFromGradient(const Image<float>& gradmag, const Image<float>& gradang) 00098 { 00099 // Determine the number of cells 00100 00101 Dims cells; 00102 if(itsFixedDims) 00103 cells = itsCellDims; 00104 else 00105 cells = Dims(int(round(float(gradmag.getWidth())/itsCellDims.w())),int(round(float(gradmag.getHeight())/itsCellDims.h()))); 00106 00107 FeatureVector fv = FeatureVector(cells.w(),cells.h(),itsOriBins); 00108 00109 // Scan image and we will cumulate local samples into a "cells" sized grid 00110 // of bins, with interpolation. 00111 for (int rx=1; rx<gradmag.getWidth()-1; rx++) 00112 for (int ry=1; ry<gradmag.getHeight()-1; ry++) 00113 { 00114 if(!gradmag.coordsOk(rx,ry)) // outside image 00115 continue; 00116 // Get bin fractions 00117 const float xf = float(rx)/float(gradmag.getWidth())*cells.w(); 00118 const float yf = float(ry)/float(gradmag.getHeight())*cells.h(); 00119 00120 float gradMag = gradmag.getValInterp(rx, ry); 00121 float gradAng = gradang.getValInterp(rx, ry); 00122 00123 // will be interpolated into cells.w x cells.h x itsOriBins: 00124 addToBin(xf,yf,gradAng,gradMag,itsOriSigned,fv); 00125 00126 } 00127 00128 std::vector<float> returnVec; 00129 // normalize bins 00130 if(itsNormalize) 00131 { 00132 LINFO("Normalizing feature vector"); 00133 returnVec = normalizeFeatureVector(fv); 00134 } 00135 else 00136 returnVec = fv.getFeatureVector(); 00137 LINFO("Calculated HOG of size %Zu",returnVec.size()); 00138 return returnVec; 00139 00140 } 00141 00142 std::vector<float> HistogramOfGradients::normalizeFeatureVector(FeatureVector featureVector) 00143 { 00144 std::vector<float> fv = featureVector.getFeatureVector(); 00145 const int xsize = featureVector.getXSize(); 00146 const int ysize = featureVector.getYSize(); 00147 const int zsize = featureVector.getZSize(); 00148 // Output is shrunk by number of cells in the block used in normalization 00149 const int newxsize = xsize - 2; 00150 const int newysize = ysize - 2; 00151 // If we have too small of a histogram, we can't normalize 00152 if(newxsize < 1 || newysize < 1) 00153 return fv; 00154 std::vector<float>::const_iterator ofv=fv.begin(); 00155 std::vector<float> distfv=std::vector<float>(xsize*ysize); 00156 std::vector<float>::iterator dfv=distfv.begin(); 00157 // Calculate L2 Norm ||v||^2 which will be used to update vector v like so: v=sqrt(||v||^2+epsilon) 00158 for(int x=0;x<xsize;x++) 00159 for(int y=0;y<ysize;y++) 00160 { 00161 float dSq=0; 00162 for(int z=0;z<zsize;z++) 00163 { 00164 // Increment original vector 00165 dSq += *ofv * *ofv; 00166 ofv++; 00167 } 00168 *(dfv++)=dSq; 00169 } 00170 dfv=distfv.begin(); 00171 ofv=fv.begin(); 00172 std::vector<float> newfv=std::vector<float>(newxsize*newysize*zsize*4); 00173 std::vector<float>::iterator nfv=newfv.begin(); 00174 const int h=newysize; 00175 for(int x=0;x<xsize;x++) 00176 for(int y=0;y<ysize;y++) 00177 { 00178 if(x<newxsize && y<newysize) 00179 { 00180 // Upper left normalization block 00181 float n1 = sqrt(*dfv + *(dfv+h) + *(dfv+1) + *(dfv+h+1) + itsEpsilon); 00182 // Upper right normalization block 00183 float n2 = sqrt(*(dfv+h) + *(dfv+2*h) + *(dfv+h+1) + *(dfv+2*h+1) + itsEpsilon); 00184 // Lower left normalization block 00185 float n3 = sqrt(*(dfv+1) + *(dfv+h+1) + *(dfv+2) + *(dfv+h+2) + itsEpsilon); 00186 //Lower right normalization block 00187 float n4 = sqrt(*(dfv+h+1) + *(dfv+2*h+1) + *(dfv+h+2) + *(dfv+2*h+2) + itsEpsilon); 00188 00189 for(int z=0;z<zsize;z++) 00190 { 00191 //Increment new vector 00192 *(nfv++)=*ofv/n1; 00193 *(nfv++)=*ofv/n2; 00194 *(nfv++)=*ofv/n3; 00195 *(nfv++)=*ofv/n4; 00196 ofv++; 00197 } 00198 // Increment distance vector 00199 dfv++; 00200 } 00201 else 00202 { 00203 ofv+=zsize; 00204 dfv++; 00205 } 00206 } 00207 00208 return newfv; 00209 } 00210 00211 void HistogramOfGradients::addToBin(const float xf, const float yf, const float ang_in, const float mag, const bool oriSigned, FeatureVector &fv) 00212 { 00213 float ang=ang_in; 00214 float oriBin; 00215 int numBins = fv.getZSize(); 00216 if(oriSigned) 00217 { 00218 // ensure that angle is within -2*M_PI to 2*M_PI 00219 ang=fmod(ang,2*M_PI); 00220 // convert from -2*M_PI:2*M_PI to 0:2*M_PI 00221 if (ang < 0.0) 00222 ang += 2*M_PI; 00223 oriBin = ang / 2.0 / M_PI * float(numBins); 00224 } 00225 else 00226 { 00227 // ensure that angle is within -M_PI to M_PI 00228 ang=fmod(ang,M_PI); 00229 // convert from -M_PI:M_PI to 0:M_PI 00230 if (ang < 0.0) 00231 ang += M_PI; 00232 oriBin = ang / M_PI * float(numBins); 00233 } 00234 fv.addValue(xf, yf, oriBin, mag); 00235 } 00236 00237 00238 // ###################################################################### 00239 /* So things look consistent in everyone's emacs... */ 00240 /* Local Variables: */ 00241 /* indent-tabs-mode: nil */ 00242 /* End: */