📄 ms.cpp
字号:
// $masm\ms.cpp 1.5 milbo$ facial feature search using a pre-built model// Warning: this is raw research code -- expect it to be quite messy.// milbo Petaluma jun 05//-----------------------------------------------------------------------------// This program is free software; you can redistribute it and/or modify// it under the terms of the GNU General Public License as published by// the Free Software Foundation; either version 2 of the License, or// (at your option) any later version.//// This program is distributed in the hope that it will be useful,// but WITHOUT ANY WARRANTY; without even the implied warranty of// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the// GNU General Public License for more details.//// A copy of the GNU General Public License is available at// http://www.r-project.org/Licenses///-----------------------------------------------------------------------------#include "all.hpp"#define CONF_MsLogFilename "ms.log"static char sgImageOut[SLEN]; // name of image file minus path and minus extensionstatic char sgModel[SLEN]; // -s flag: model string prepended to printouts and appended to filenamesstatic bool fgBrief; // -fb flag: print brief output (as opposed to more verbose output)static clock_t gTime;static double gImagePreScale; // if input face is too big or too small we scale it by this amountunsigned gImageBits = IM_FinalImageOnly; // -xi flag: see IM_ defs in imagebits.hppconst static char sgUsage[] = "ms [FLAGS] IMAGES\n" "\n" "Writes face feature coordinates to %s\n" "\n" "Optional FLAGS Range Default\n" "\n" "-fa F append to log " CONF_MsLogFilename " 0..1 0: create new log file " CONF_MsLogFilename "\n" "-fb F brief messages 0..1 0: full progress messages\n" "\n" "-xi N image output in hex (default 1: FinalImageOnly)\n" " GlobalDetector 80 Whiskers 8\n" " WhiskersRef 40 Annotate 4\n" " DoubleScale 200 AnnotateRef 20 AllImages 2\n" " FullScale 100 ShowRefShape 10 EndImageOnly 1 (AllImages supercedes this)\n" "\n" "-m FILE.asm select model file (specify multiple models with multiple -m flags)\n" "\n" "-s STRING model name: use STRING as a prefix/suffix for progress prints and filenames (default: none)\n" " If specified, as a side effect append results to " CONF_sMsDatFilename "\n" "\n" "-r REF.shape align base shape to nn shape in REF.shape and print stats using ref shape in REF.shape\n" " Default: do NN search to align base shape, don't print stats\n" "\n" "-p PATTERN get input images matching regex PAT in REF.shape specified by -r (use masm -? for details)\n" "-P Mask1 Mask2 additional filter for -p shapes [default 0 0 for all]\n" "-ni N max number of images [default 0 for all matching -p and -P specs]\n" "-nr N 0 means no random selection, any other val select randomly with rand seed=N [default 1]\n" " (only applies when -ni N not equal to 0)\n" "\n" "-v use Viola Jones global face detector (instead of Rowley neural net detector)\n" "-ve use Viola Jones global face detector with Rowley eye positions\n" " (gives worse results than plain -v)\n" "\n" "-G generate feature data for post analysis\n" "-S skip image with a warning if global detector fails (else if -r then synthesize from ref shape)";#define CONF_fShowFitVersusFaceSize 0#define CONF_fSelfEvaluate 0 //TODO experimental code to try to predict how good the fit is#if CONF_fSelfEvaluatestatic double gLastFit;static int ngLastQualifyingDisplacements;static int ngStartLev;static double gDiffAll;static double gDiff;static int giIter;#endif#define CONF_fPrintShapeSize 0 //TODO print the final size and fitstatic int ngModels; //TODO must remove this global variable// following externs are listed here because they can't be defined in masmconf.hpp because of .h file order dependenciesvoid InitAsmsForSearch(tAsmModel Model[], // out, except sFile must be initialized on entry int nModels, unsigned ImageBits); // invoid CombineModelSearchResults(SHAPE &CombinedShape, // io: best shape formed by combining all searches int iModel, const SHAPE &Shape); // in: results of current search//-----------------------------------------------------------------------------// Return best fit value and the offsets ixBest and iyBest// Do this by getting an image profile at a range of offsets from iPoint// and matching each profile against the Modelstatic double FindBestMatchingProf (int &ixBest, int &iyBest, // out: for 1D profiles: ix is along whisker, iy is perp to whisker int iPoint, const SHAPE& Shape, const tLand Lands[], // in const SHAPE &AlignedMeanShape, int nPixSearch, // in tSearchImages &SearchImgs, const tAsmLev &Model, // in except SearchImgs.ProgressImg is modified int nTrimCovar){int nxMinOffset = -nPixSearch;int nxMaxOffset = nPixSearch;int nyMaxOffset;unsigned ProfSpec = Model.ProfSpecs[iPoint];if (ProfSpec & PROF_2d) // two dimensional profile? nyMaxOffset = CONF_nyMaxOffset(nPixSearch); // if so, we need to offset in y direction as wellelse { PrepareProf1D(SearchImgs.Img, Shape, ProfSpec, Lands, AlignedMeanShape, iPoint, nGetProfWidthFromModel(iPoint, 0, Model) + 2 * nPixSearch, 0, 0); nyMaxOffset = 0; // no y offset for 2D profiles }double BestFit = DBL_MAX;ixBest = 0, iyBest = 0;for (int iy = -nyMaxOffset; iy <= nyMaxOffset; iy++) // 1D; no offset 2D: offset vertically for (int ix = nxMinOffset; ix <= nxMaxOffset; ix++) // 1D: offset along whisker 2D: offset horizontally { // call model-specific fitness routine double Fit = GetProfileFit(SearchImgs, iPoint, ix, iy, Model, Shape, Lands, ProfSpec, nTrimCovar); // Test for a new best fit // We test against ix<=0 and iy<=0 so if there are exact matches then ixBest=0 (no change) if ((ix <= 0) && (iy <= 0)? Fit <= BestFit: Fit < BestFit) { ixBest = ix; iyBest = iy; BestFit = Fit; } }return BestFit;}//-----------------------------------------------------------------------------static inline bool fPassQualifyingDisplacementCriterion (const int ixBest, const int iyBest, const int nPixSearch){#if CONF_nQualifyingDisplacementMethod == 1 return ABS(ixBest) + ABS(iyBest) <= nPixSearch; // manhattan distance#elif CONF_nQualifyingDisplacementMethod == 2 return ABS(ixBest) <= nPixSearch/2; // original code#elif CONF_nQualifyingDisplacementMethod == 3 return sqrt(SQR(ixBest) + SQR(iyBest)) <= nPixSearch/2; // euclidean distance#elif CONF_nQualifyingDisplacementMethod == 4 return ABS(ixBest) <= (nPixSearch-1)/2;#elif CONF_nQualifyingDisplacementMethod == 5 return ABS(ixBest) + ABS(iyBest) <= (nPixSearch-1);#elif CONF_nQualifyingDisplacementMethod == 6 return sqrt(SQR(ixBest) + SQR(iyBest)) <= (nPixSearch-1)/2;#else SysErr("CONF_nQualifyingDisplacementMethod");#endif}//-----------------------------------------------------------------------------// Updates NewShape to suggested new value, and returns number of pixels within// the central 50% of the profiletemplate<typename ModelT>static int GetSuggestedShape (SHAPE& SuggestedShape, Vec &Weights, // out const SHAPE& Shape, int nPixSearch, tSearchImages &SearchImgs, // in except SearchImgs.ProgressImg is modified const ModelT &Model, const double Momentum, // in const tLand Lands[], const SHAPE &AlignedMeanShape, // in int nTrimCovar){int nWithinFifyPercent = 0;int ixBest, iyBest;int nPoints = SuggestedShape.nrows();for (int iPoint = 0; iPoint < nPoints; iPoint++) if (fPointUsed(Shape, iPoint)) { double Fit = FindBestMatchingProf(ixBest, iyBest, iPoint, Shape, Lands, AlignedMeanShape, nPixSearch, SearchImgs, Model, nTrimCovar);#if CONF_fSelfEvaluate gLastFit += Fit;#endif // set SuggestedShape(iPoint) to best offset from current position if (Model.ProfSpecs[iPoint] & PROF_2d) // two dimensional profile? { SuggestedShape(iPoint, VX) = Shape(iPoint, VX) - ixBest; // x,y orthogonal to image sides (not to whisker) SuggestedShape(iPoint, VY) = Shape(iPoint, VY) - iyBest; if (fPassQualifyingDisplacementCriterion(ixBest, iyBest, nPixSearch)) nWithinFifyPercent++; } else // must move point along the whisker { double DeltaX = 0, DeltaY = 0; if (ixBest || iyBest) GetProfStepSize(&DeltaX, &DeltaY, Shape, iPoint, 0, Lands, AlignedMeanShape);#if CONF_nSearchMomentumLev <= CONF_nMaxLevs SuggestedShape(iPoint, VX) = Shape(iPoint, VX) + Momentum * ((ixBest * DeltaX) + (iyBest * DeltaY)); SuggestedShape(iPoint, VY) = Shape(iPoint, VY) + Momentum * ((ixBest * DeltaY) - (iyBest * DeltaX)); if (Momemtum * ABS(ixBest) <= nPixSearch/2) nWithinFifyPercent++;#else // ignore momentum SuggestedShape(iPoint, VX) = GetX(Shape(iPoint, VX), ixBest, iyBest, DeltaX, DeltaY); SuggestedShape(iPoint, VY) = GetY(Shape(iPoint, VY), ixBest, iyBest, DeltaX, DeltaY); if (ABS(ixBest) <= nPixSearch/2) nWithinFifyPercent++;#endif } if (CONF_fWeightedAlign) { if (Fit < .001) // prevent div by zero and also limit too high weight for spurious good match Fit = .001; // magic nbr determined by basic testing, could perhaps be improved TODO Weights(iPoint) = 1 / Fit; // a better fit has a bigger weight TODO should adjust for Prof.nrows() } else Weights(iPoint) = 1; }return nWithinFifyPercent;}//-----------------------------------------------------------------------------// Show shape as search progresses, bitmap format. Gaudy colors, but useful for debugging.static void ShowSearchShape (const SHAPE &Shape, int iter, // in RgbImage &ProgressImg, // out bool fLastPass, double ScaleOut, // in int nMaxSearchIters, // in const tLand Lands[], const SHAPE &AlignedMeanShape) // in{unsigned Color;if (iter == 0) // 1st pass? Color = C_DBLUE;else if (fLastPass) // last pass Color = C_RED;else if (iter == 1) // 2nd pass? Color = C_GREEN;else // intermediate pass, use shades of gray, darker for higher generations { Color = 200 * (nMaxSearchIters - iter) / nMaxSearchIters; Color |= Color << 16; // convert to cyan }bool fWhiskers = false; // (gImageBits & IM_Whiskers) && iter == 0;DrawShape(ProgressImg, Shape, Color, ScaleOut, CONF_fConnectDots, fWhiskers, fWhiskers, 0, IM_TRANSPARENT, Lands, &AlignedMeanShape);}//-----------------------------------------------------------------------------const Image &GetDisplayImage (unsigned ImageBits, const Image &ScaledImg, const Image &FullImg, const Image &DoubleImg){if (ImageBits & IM_DoubleScale) return DoubleImg;else if (ImageBits & IM_FullScale) return FullImg;else return ScaledImg;}//-----------------------------------------------------------------------------static void ShowSearchProgress (RgbImage &ProgressImg, // io int iter, int nWithinFifyPercent, SHAPE Shape, // in int nMaxSearchIters, // in const tLand Lands[], const SHAPE &AlignedMeanShape, int iLev) // in{if (gImageBits & IM_AllImages) { double ScaleOut = GetScaleOut(gImageBits, iLev); ShowSearchShape(Shape, iter, ProgressImg, false, ScaleOut, nMaxSearchIters, Lands, AlignedMeanShape); }bprintf(fgBrief, "%2.2d ", nWithinFifyPercent);if ((iter + 1) % 16 == 0) // time to print a newline? bprintf(fgBrief, "\n%s ", sgModel); }//-----------------------------------------------------------------------------// This returns the inter-eye distance, or 1 if we aren't normalizing to the inter-eye distance.// The results is used (later) as follows: we divide the fitness by inter-eye distance // to normalize the fitness for printingstatic double GetNormDist (const SHAPE &Shape, const SHAPE &RefShape){#if CONF_nMethodNormSearchResults==NormSearchResults_Noreturn 1;#elif CONF_nMethodNormSearchResults==NormSearchResults_FittedEyeToEyereturn PointDist(Shape, MLEye, MREye);#elif CONF_nMethodNormSearchResults==NormSearchResults_RefEyeToEyereturn PointDist(RefShape, MLEye, MREye);#elseSysErr("GetNormDist");#endif}//-----------------------------------------------------------------------------template<typename ModelT>static void ShapeSearch (SHAPE &Shape, // io tSearchImages &SearchImgs, // in except SearchImgs.ProgressImg is modified const ModelT &Model, // in int iLev, const tLand Lands[], SHAPE *pRefShape, // in const char sImageBase[], int nStartLev) // in{SHAPE ProfShape(Shape); // Shape after profile matchingint iter = 0, nWithinFifyPercent = 0;// if fgExplicitPrevNext is true:// AlignedMeanShape is used to figure out prev and next landmarks, but only if // those landmarks are missing (i.e. have value 0,0) in Shape. So if Shape has a full // complement of landmarks, AlignedMeanShape isn't used during the search// if fgExplicitPrevNext is false: AlignedMeanShape isn't usedSHAPE AlignedMeanShape(Shape);// The shape params, initialized to 0// The original formulation called for this to be set to 0 each time we run the model (search// for "Model.EigVecs * b" below). I find we get slightly better results if we remember the// shape params from the previous run. Thus this is outside the loop.Vec b(Model.EigVecs.nrows()); Vec Weights(Shape.nrows());bprintf(fgBrief, "Search ");int nUsedPoints = nGetNbrUsedPoints(Shape, Shape.nrows());#if CONF_fSelfEvaluategLastFit = 0;ngLastQualifyingDisplacements = 0;gDiffAll = 0;gDiff = 0;#endifint nMaxSearchIters = Model.nMaxSearchIters;#if CONF_fUseBestSoFarMatView BestShape(Shape); // best shape seen so far (ie highest nBest)int nBest = 0;#endifwhile ((iter < nMaxSearchIters) && (nWithinFifyPercent <= (Model.nQualifyingDisplacments * nUsedPoints)/100)) { if (fgExplicitPrevNext && (nUsedPoints != Model.nPoints)) // AlignedMeanShape is only needed if don't have all landmarks AlignShape(AlignedMeanShape, Shape); // Estimate the best ProfShape by profile matching the landmarks in Shape. Also initialize Weights. double Momentum = 1.0;#if CONF_nSearchMomentumLev <= CONF_nMaxLevs if (iLev >= CONF_nSearchMomentumLev && iter == 0) Momentum = CONF_SearchMomentum;#endif nWithinFifyPercent = GetSuggestedShape(ProfShape, Weights, Shape, Model.nPixSearch, SearchImgs, Model.AsmLevs[iLev], Momentum, Lands, AlignedMeanShape, Model.nTrimCovar);#if CONF_fSelfEvaluate ngLastQualifyingDisplacements += nWithinFifyPercent;#endif ShowSearchProgress(SearchImgs.ProgressImg, iter, nWithinFifyPercent, Shape, nMaxSearchIters, Lands, AlignedMeanShape, iLev); if (CONF_fPrintAllInDatFile && pRefShape) AppendDistancesToDatFile(iLev, ProfShape, *pRefShape, GetNormDist(ProfShape, *pRefShape), sImageBase, sgModel, "bProf", -1); // align ProfShape to the shape model, put result in Shape if (iter < CONF_nItersBeforeModelFit) // CONF_nItersBeforeModelFit is actually always 0 unless changed for debugging Shape = ProfShape; // don't conform ProfShape to model i.e. use profile matches as is else { bool fShapeModelFinalIter = (iter >= nMaxSearchIters-1); // true if reached max iters and
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -