📄 masm.cpp
字号:
// $masm\masm.cpp 1.5 milbo$ build active shape model from shapes and associated images// Warning: this is raw research code -- expect it to be quite messy.// milbo Petaluma jun05//-----------------------------------------------------------------------------// 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"typedef struct tShapes // information on all shapes, initialized from shape file by ReadMasmShapes { int nPoints; // number of points (i.e. landmarks) per shape int nShapes; // number of shapes char sImageDirs[SLEN]; // image directories at head of shape file, separated by semicolons SHAPE AvShape; // average shape after aligning shapes ShapeVec OrgShapes; // array[nShapes] of SHAPE: original shapes from file, but all with same nbr of points as RefShape ShapeVec Shapes; // array[nShapes] of SHAPE: OrgShapes, but aligned StringVec TagStrings; // array[nShapes] of string: string tag preceding each shape in shape file IntVec TagInts; // array[nShapes] of unsigned: hex number at start of each of above tags IntVec nUsedLandmarks; // array[nShapes] of int: number of landmarks used in each shape BoolVec fExtraLEyeLandsUsed;// array[nShapes] of bool: true if MLEye0..MLEye7 landmark are not 0,0 TODO this is a hack used when synthesizing landmarks // this is needed because synthesized points are already 0,0 by the time we get to CollectProfs() BoolVec fExtraREyeLandsUsed;// array[nShapes] of bool: ditto but for MREye0..MREye7 const tLand *pLand; // landmark information for these shapes }tShapes;typedef cvec<MatVec> MatVecLev; // a vector of matrices for each point for each level in the image pyramidtypedef cvec<VecVec> VecVecLev; // a vector of one-dimensional matrices for each level in the image pyramidtypedef cvec<IntVec> IntVecLev;typedef cvec<MatVecLev> MatVecLevSub; // a vector of matrices for each sub-profile for each point for each level in the image pyramidtypedef cvec<VecVecLev> VecVecLevSub; // a vector of one-dimensional matrices for each sub-profile for each level in the image pyramidtypedef struct tStats // everything you ever wanted to know about the profiles for all shapes and images at all pyramid levels { IntVecLev nProfs; // array(nLevs][nPoints]: nbr of profiles for each point MatVecLevSub Covars; // array[nLevs][nPoints][nSubs] of covar mats: each is nPointsPerProf*nPointsPerProf VecVecLevSub AvProfs; // array[nLevs][nPoints][nSubs] of average profile vecs: each is 1 x nPointsPerProf MatVecLevSub Profs; // array[nLevs][nPoints][nSubs] of profile mats: each is nShapes x nPointsPerProf }tStats;static bool fgVerbose = false; // -fv command line flagstatic clock_t gStartTime = clock(); // time program startedstatic const char sgUsage[] = "Usage: masm [OPTIONS] INFILE.shape\n" "\n" "Creates OUTFILE.asm from INFILE.shape\n" "\n" "Options\n\n" "-o OUTFILE output file [default INFILE.asm]\n" "\n" "-p PATTERN\n" " Load only shapes in INFILE.shape with tags matching case-independent PATTERN.\n" " PATTERN is an egrep style pattern.\n" " Example: -p \"xyz\" loads filenames containing xyz\n" " Example: -p \" m000| m001\" loads filenames beginning with m000 or m001.\n" " Default: NULL i.e. match all shapes\n" "\n" "-P Mask1 Mask2\n" " Load only shapes which satisfy (Attr & Mask1) == Mask2.\n" " Attr is the hex number at start of the tag, Mask1 and Mask2 are hex.\n" " This filter is applied after -p PATTERN.\n" " Example: -P 2 2 matches faces with glasses (FA_Glasses=2, see atface.hpp).\n" " Example: -P 2 0 matches faces without glasses.\n" " Default: no filter (Mask1=Mask2=0) i.e. match all shapes\n" "\n" "-S FILE:FileType:iSet:-I:N\n" " Train on the given sample set (don't use with -P or -p flags)\n" " FILE is the sample file\n" " FileType is the file type (\"m\" or \"B\" or \"*\" for all)\n" " iSet is the column in iSamples (0 is test set, 1 is 1st cross-validation set, etc.)\n" " -I is a negative integer from 1 to N which specifies which part of the validation set to leave out\n" " N for N-fold cross validation\n" " Example: -S m2.sam:*:1:-3:10 uses shapes listed in 1st set of m2.sam\n" " and leaves out set 3 of 10 (i.e. step 3 of 10-fold cross validation).\n" "\n" "-fv F verbose [default 0]\n" "\n" "-ni N max number of images [default 0 for all]";//-----------------------------------------------------------------------------static char *sSecsElapsed (void) // return a string showing seconds elapsed since gStartTime{static char s[PLEN];sprintf(s, "[%.1f secs]", (double)(clock() - gStartTime) / CLOCKS_PER_SEC);return s;}//-----------------------------------------------------------------------------// Add Shape to NewShape on a point by point basis.// Same as NewShape += Shape, but with this difference: if a point isn't used in Shape,// we use the corresponding point in the alternative shape AltShape insteadstatic void SumShapes (SHAPE &NewShape, // io const SHAPE &Shape, const SHAPE &AltShape) // in{CheckSameDim(NewShape, Shape, "SumShapes");CheckSameDim(NewShape, AltShape, "SumShapes");for (int iPoint = 0; iPoint < Shape.nrows(); iPoint++) { if (fPointUsed(Shape, iPoint)) { NewShape(iPoint, VX) += Shape(iPoint, VX); NewShape(iPoint, VY) += Shape(iPoint, VY); } else { NewShape(iPoint, VX) += AltShape(iPoint, VX); NewShape(iPoint, VY) += AltShape(iPoint, VY); } }}//-----------------------------------------------------------------------------// Generate a bitmap with the centralized shapes drawn on it.// For debugging.#if CONF_fShowDebugImagesstatic void ShowCentralizedShapes (const ShapeVec &Shapes, const SHAPE &AvShape){char sPath[SLEN]; sprintf(sPath, "%s/centralized.bmp", CONF_sOutDir);lprintf(", generating %s", sPath);int nShapes = Shapes.size();int iShape;#if 0// get image width from shape sizesint xMax = -1000;int xMin = 1000;for (iShape = 0; iShape < nShapes; iShape++) { int xMin1 = Shapes[iShape].col(VX).minElem(); if (xMin1 < xMin) xMin = xMin1; int xMax1 = Shapes[iShape].col(VX).maxElem(); if (xMax1 < xMax) xMax = xMax1; }int nImageWidth = 2 * __max(-xMin + 10, xMax + 10);nImageWidth = (nImageWidth+4)/4; // make sure width is divisble by 4nImageWidth *= 4;int nImageHeight = 3 * nImageWidth / 2;#elseint nImageWidth = 500;int nImageHeight = 500;#endifRgbImage Img(nImageWidth, 3 * nImageWidth / 2);FillRgbImage(Img, 0xff, 0xff, 0xff);//BresenhamDrawLine(Img, 128, 128, 128, -Img.width/2, 0, Img.width/2, 0); // draw cross hair//BresenhamDrawLine(Img, 128, 128, 128, 0, -Img.height/2, 0, Img.height/2);BresenhamDrawLine(Img, 210, 210, 210, -Img.width/2, 0, Img.width/2, 0); // draw cross hairBresenhamDrawLine(Img, 210, 210, 210, 0, -Img.height/2, 0, Img.height/2);int nShapes1 = __min(20, nShapes); // print a max of 20 shapesint nBump = __max(1, (double)nShapes/nShapes1);#if 0 // just first and last shapeDrawShape(Img, Shapes[0], C_RED, 1, IM_NO_CONNECT_DOTS, IM_ANNOTATE, IM_NO_WHISKERS, 0, IM_NO_TRANSPARENT);DrawShape(Img, Shapes[nShapes-1], C_YELLOW, 1, IM_NO_CONNECT_DOTS, IM_ANNOTATE, IM_NO_WHISKERS, 0, IM_NO_TRANSPARENT);#elsefor (iShape = 0; iShape < nShapes; iShape += nBump) {#if 1 //char s[100]; sprintf(s, "iShape %d\n", iShape); //Shapes[iShape].print(s); DrawShape(Img, Shapes[iShape], C_BLACK, 1, IM_CONNECT_DOTS, IM_NO_ANNOTATE, IM_NO_WHISKERS, 0, IM_NO_TRANSPARENT);#else if (iShape == 0 || iShape >= nShapes - nBump) // first and last image in red DrawShape(Img, Shapes[iShape], C_RED, 1, IM_CONNECT_DOTS, IM_NO_ANNOTATE, IM_NO_WHISKERS, 0, IM_NO_TRANSPARENT); else DrawShape(Img, Shapes[iShape], C_YELLOW, 1, IM_CONNECT_DOTS, IM_NO_ANNOTATE, IM_NO_WHISKERS, 0, IM_NO_TRANSPARENT);#endif#endif }WriteBmp(Img, sPath);}#endif CONF_fShowDebugImages//-----------------------------------------------------------------------------static AlignImageToAlignedShape (Image &Img, const SHAPE &AlignedShape, const SHAPE &OrgShape){// align image with its aligned shape (use AlignedShapes versus OrgShapes to get needed translation)int nPoints = AlignedShape.nrows();SHAPE Shape(OrgShape);Mat Pose(AlignShape(Shape, AlignedShape));double xOffset = -Pose(0, 2);double yOffset = Pose(1, 2);double Scale = Pose(0, 0);double Theta = atan(Pose(1, 0) / Pose(0, 0));// Extend the image so when we extract we don't cut off part of the face// Needed because ExtractImage doesn't change the size of the image yet extracts a magnified part of the image// The face in the magnified part of the image could be bigger than the image if we didn't extend.//TODO could greatly increase the speed of this if we combined the following three func calls into one funcint xExtend = Scale * 2 * abs(xOffset);int yExtend = Scale * 2 * abs(yOffset);ExtendImage(Img, xExtend, xExtend, yExtend, yExtend);ExtractImage(Img, Img.width, Img.height, Img.width/2, Img.height/2, Scale, Theta, QUIET, IM_NEAREST_PIXEL, IM_EXTEND);MoveImage(Img, -xOffset, yOffset, QUIET, IM_EXTEND);}//-----------------------------------------------------------------------------// Generate a bitmap with all the aligned shapes drawn on it.// For debugging.#if CONF_fShowDebugImagesstatic void ShowAlignedShapes (const tShapes &Shapes, int iRefShape, const char sShapeFile[]) // in{#define IMAGE_SCALE 2Image Img;RgbImage RgbImg;lprintf("%s/aligned.bmp ", CONF_sOutDir);const char *sShapeName = &Shapes.TagStrings[iRefShape][FNAME_OFFSET];sLoadImageGivenDirs(Img, Shapes.sImageDirs, sShapeName);AlignImageToAlignedShape(Img, Shapes.Shapes[iRefShape], Shapes.OrgShapes[iRefShape]);ScaleImage(Img, IMAGE_SCALE * Img.width, IMAGE_SCALE * Img.height, QUIET, IM_NEAREST_PIXEL);ConvertGrayImageToRgb(RgbImg, Img);BresenhamDrawLine(RgbImg, 128, 128, 128, -RgbImg.width/2, 0, RgbImg.width/2, 0); // draw cross hairBresenhamDrawLine(RgbImg, 128, 128, 128, 0, -RgbImg.height/2, 0, RgbImg.height/2);for (int iShape = 0; iShape < Shapes.nShapes; iShape++) DrawShape(RgbImg, IMAGE_SCALE * Shapes.Shapes[iShape], C_YELLOW, 1.0, IM_CONNECT_DOTS, IM_NO_ANNOTATE, IM_NO_WHISKERS, 0, IM_TRANSPARENT);DrawShape(RgbImg, IMAGE_SCALE * Shapes.AvShape, C_RED, 1.0, IM_CONNECT_DOTS, IM_NO_ANNOTATE, IM_NO_WHISKERS, 0, IM_NO_TRANSPARENT, NULL, NULL, IM_DRAW_CIRCLES);CropImageToFace(RgbImg, Shapes.AvShape, IMAGE_SCALE, IM_WIDE_MARGINS);RgbPrintf(RgbImg, 8, 3, C_YELLOW, 120, "%s aligned shapes", sShapeFile);char sPath[SLEN];sprintf(sPath, "%s/aligned.bmp", CONF_sOutDir);WriteBmp(RgbImg, sPath);#undef IMAGE_SCALE}#endif CONF_fShowDebugImages//-----------------------------------------------------------------------------// Check that we have enough data for each landmark and issue a warning (but do nothing// else) if the amount of data is suspiciously low.// This is needed as a sanity check if we are experimenting with shapes files.// For example if some landmarks are missing by mistake or we are supposed be generating landmarks// artificially and don't.static void CountNbrSamplesForEachLandmark (const ShapeVec &Shapes, const SHAPE &RefShape){int iRow;int nShapes = Shapes.size();IntVec nUsed(CONF_nMaxLandmarks, 0);for (int iShape = 0; iShape < nShapes; iShape++) for (iRow = 0; iRow < RefShape.nrows(); iRow++) if (Shapes[iShape](iRow, VX) != 0 || Shapes[iShape](iRow, VY) != 0) // is landmark used? nUsed[iRow]++;for (iRow = 0; iRow < RefShape.nrows(); iRow++) { if (nUsed[iRow] < 5) // 5 is arbitrary SysErr("Only %d used samples in iRow %d", nUsed[iRow], iRow); if (nUsed[iRow] < 100) // 100 is arbitrary { Warn("Only %d used samples in iRow %d", nUsed[iRow], iRow); break; // only issue a max of one warning } }}//-----------------------------------------------------------------------------// This aligns the shapes using the method in CootesTaylor 2004 section 4.2// But we differ from that method as follows://// 1. we don't normalize AvShape len to 1 (so can put aligned shapes on top of// images directly for debugging visualization)//// 2. we don't use tangent space (TODO revisit but Stegmann notes that it doesn't make a practical difference)//// 3. We ignore missing (i.e. 0,0) landmarks i.e. we can deal with shapes with missing landmarks// This allows you to mix, say, XM2VTS and BioId or AR images during training.// The passed in RefShape must have all its landmarks, though.static void AlignShapes (ShapeVec &Shapes, SHAPE &AvShape, // io const SHAPE &RefShape) // in{#define CONF_nMaxAlignPasses 20int nShapes = Shapes.size();for (int iShape = 0; iShape < nShapes; iShape++)#if CONF_fStraightenBeforeAligning StraightenAndCentralizeShape(Shapes[iShape]);#else CentralizeShape(Shapes[iShape]);#endifCountNbrSamplesForEachLandmark(Shapes, RefShape); // check that we have enough data for each landmark// generate AvShape, the average shapeAvShape = RefShape;for (int iPass = 0; iPass < CONF_nMaxAlignPasses; iPass++) { SHAPE NewAvShape(AvShape.nrows(), AvShape.ncols()); SHAPE AltShape(AvShape); // alternate shape: see SumShapes for (iShape = 0; iShape < nShapes; iShape++) { AlignShape(Shapes[iShape], AvShape); //TODO was CentralizeShape(Shapes[iShape])
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -