Wednesday, November 25, 2009

bodytrack Perforated Loops

This benchmark uses an annealed particle filter to track the movement of specific parts of a human body (head, torso, arms, and legs) as the human moves through a scene. At each input video frame bodytrack starts with the probabilistic model of the location of the body parts from the previous frame. This model is represented as a set of particles. The goal is to compute a probabilistic model of the body part locations in the current frame.

The computation first uses a model of the dynamics of the human body to appropriately disperse the particles from the probabilistic particle model from the previous frame. The result is an initial model for the current frame. Each annealing step then uses two sets of image processing data from the current frame (an edge map representing the outline of the body and a binary image which separates the body in the foreground from the background of the scene) to refine the particle model. Specifically, it uses differences between the image processing data and the particle model to update the particle model to more accurately model the body part locations. The final particle model from the last annealing step is the probabilistic model for the frame.

The loop in the Update() function iterates over the annealing layers. Perforating this loop causes bodytrack to use fewer annealing layers. The result is a slightly less accurate final model.

// This function is defined in ParticleFilter.h
// in the bodytrack source code
bool ParticleFilter<T>::Update(fpType timeval) //weights have already been computed from previous step or initialization
{
if(!mInitialized) //check for proper initialization
{ std::cout << "Update Error : Particles not initialized" << std::endl;
return false;
}
if(!mModel->GetObservation(timeval))
{ std::cout << "Update Error : Model observation failed for time : " << timeval << std::endl;
return false;
}
// This loop is perforated, effectively changing k-- to k-=2
for(int k = (int)mModel->StdDevs().size() - 1; k >= 0 ; /*k--*/ k-=2) //loop over all annealing steps starting with highest
{
CalcCDF(mWeights, mCdf); //Monte Carlo re-sampling
Resample(mCdf, mBins, mSamples, mNParticles);
bool minValid = false;
while(!minValid)
{
int p = 0;
mNewParticles.resize(mNParticles);
for(uint i = 0; i < mBins.size(); i++) //distribute new particles randomly according to model stdDevs
for(uint j = 0; j < mBins[i]; j++)
{
mNewParticles[p] = mParticles[i]; //add new particle for each entry in each bin distributed randomly about duplicated particle
AddGaussianNoise(mNewParticles[p++], mModel->StdDevs()[k], mRnd);
}
CalcWeights(mNewParticles); //calculate particle weights and remove any invalid particles
minValid = (int)mNewParticles.size() >= mMinParticles; //repeat if not enough valid particles
if(!minValid) std::cout << "Not enough valid particles - Resampling!!!" << std::endl;
}
mParticles = mNewParticles; //save new particle set
}
return true;
}



The perforated loops in the ImageMeasurements class perform various parts of the difference calculations between the current particle model and the image processing data. Perforating these loops reduces the accuracy of the difference calculations, which in turn reduces the accuracy of the final particle model.

// This function is defined in ImageMeasurements.cpp in the bodytrack source code

void ImageMeasurements::InsideError(ProjectedCylinder &ProjCyl, BinaryImage &FGmap, int &error, int &samplePoints)
{
Point &p1 = ProjCyl.mPts[0], &p2 = ProjCyl.mPts[3];
Point s1 = ProjCyl.mPts[1] - p1, s2 = ProjCyl.mPts[2] - p2; //get direction vectors of the sides of the 2D cylinder projection
int n1 = max((int)(mag(s1) / mVstep + 0.5), 4); //compute number of points sampled on each side (sample at least 4)
float d = 1.0f / (float)n1++; //get fraction of side length per sample
mEdge12.resize(n1); mEdge43.resize(n1);
float delta = 0;
for(int i = 0; i < n1; i++) //generate sample points along each side of cylinder projection
{ mEdge12[i].Set(p1.x + delta * s1.x, p1.y + delta * s1.y);
mEdge43[i].Set(p2.x + delta * s2.x, p2.y + delta * s2.y);
delta += d;
}

int middle = n1 / 2;
Point p = mEdge43[middle] - mEdge12[middle]; //use the middle edge points to compute number of interior samples
int n2 = max((int)(mag(p) / mHstep + 0.5), 4); //compute number of interior points sampled (sample at least 4)
d = 1.0f / n2;
// This loop is perforated, changing i++ to i+=2 //get fraction of length per sample
for(int i = 0; i < n1; /*i++*/ i+=2) //for each set of edge points add n2 interior samples between them
{ p1 = mEdge12[i];
p = mEdge43[i] - p1;
delta = d;
// This loop is perforated, changing j++ to j+=2
for(int j = 1; j < n2; j++) //add equal spaced samples between each edge point
{ SampleInsidePoint(p1.x + delta * p.x, p1.y + delta * p.y, FGmap, error, samplePoints);
delta += d;
}
}

}



// This function is defined in ImageMeasurements.cpp in the bodytrack source code

float ImageMeasurements::ImageErrorInside(std::vector<BinaryImage> &ImageMaps, MultiCameraProjectedBody &ProjBodies)
{
int samples = 0;
int error = 0;
for(int i = 0; i < (int)ImageMaps.size(); i++) //for each camera, compute the edge map error term
{ int nParts = ProjBodies(i).Size();
// This loop is perforated, changing j++ to j+=2
for(int j = 0; j < nParts; j++) //accumulate edge error for each body part, counting samples
InsideError(ProjBodies(i)(j), ImageMaps[i], error, samples);
}
//cout << "Samples = " << samples << endl;
return (float)error / samples;
}



// This function is defined in ImageMeasurements.cpp in the bodytrack source code
float ImageMeasurements::ImageErrorEdge(std::vector<FlexImage8u> &ImageMaps, MultiCameraProjectedBody &ProjBodies)
{
int samples = 0;
float error = 0;
for(int i = 0; i < (int)ImageMaps.size(); i++) //for each camera, compute the edge map error term
{ int nParts = ProjBodies(i).Size();
// This loop is perforated, changing j++ to j+=2
for(int j = 0; j < nParts; /*j++*/ j+=2) //accumulate edge error for each body part, counting samples
EdgeError(ProjBodies(i)(j), ImageMaps[i], error, samples);
}
//cout << "Samples = " << samples << endl;
return (float)error / samples; //normalize to number of samples
}



// This function is defined in ImageMeasurements.cpp in the bodytrack source code
void ImageMeasurements::EdgeError(ProjectedCylinder &ProjCyl, FlexImage8u &EdgeMap, float &error, int &samplePoints)
{
int ErrorSSD = 0;
Point &p1 = ProjCyl.mPts[0];
Point s1 = ProjCyl.mPts[1] - p1; //get direction vector of side 1 of the 2D cylinder projection
int n1 = max((int)(mag(s1) / mStep + 0.5), 4); //compute number of points sampled (sample at least 4)
float d1 = 1.0f / (float)n1++; //get fraction of side length per sample

Point &p2 = ProjCyl.mPts[2];
Point s2 = ProjCyl.mPts[3] - p2; //repeat for side 2 of cylinder
int n2 = max((int)(mag(s2) / mStep + 0.5), 4);
float d2 = 1.0f / (float)n2++;

float delta = 0;
// This loop is perforated, changing i++ to i+=2
for(int i = 0; i < n1; /*i++*/ i+=2) //generate sample points along each side of cylinder projection
{ float x = p1.x + delta * s1.x;
float y = p1.y + delta * s1.y;
SampleEdgePoint(x, y, EdgeMap, ErrorSSD, samplePoints); //accumulate error at computed edge points on side 1
delta += d1;
}
delta = 0;
// This loop is perforated, changing i++ to i+=2
for(int i = 0; i < n2; /*i++*/ i+=2)
{ float x = p2.x + delta * s2.x;
float y = p2.y + delta * s2.y;
SampleEdgePoint(x, y, EdgeMap, ErrorSSD, samplePoints); //accumulate error at comptued edge points on side 2
delta += d2;
}
error += (float)ErrorSSD / (255.0f * 255.0f);
}



The final two loops are part of a Gaussian filter computation. Perforating these loops produces a new filter with a different frequency response. The overall effect on the accuracy is minimal.

// This function is defined in TrackingModelPthread.cpp in the bodytrack source code
template<class T, class T2>
inline void FlexFilterRowVPthread(int y, FlexImage<T,1> &src, FlexImage<T,1> &dst, T2 *kernel, int kernelSize, bool allocate = true)
{
int n = kernelSize / 2;

T *psrc = &src(n, y), *pdst = &dst(n, y);
// The following loop is perforated, changing x++ to x+=2
for(int x = n; x < src.Width() - n; /*x++*/ x+=2)
{ int k = 0;
T2 acc = 0;
// This loop is perforated, changing i++ to i+=2
for(int i = -n; i <= n; /*i++*/ i+=2)
acc += (T2)(psrc[i] * kernel[k++]);
*pdst = (T)acc;
pdst++;
psrc++;
}
}



Our evaluation of the source code indicates that perforating these loops leaves bodytrack able to process all inputs successfully (with, of course, some potential accuracy reductions).