📄 marki.cpp
字号:
// $util\marki.cpp 1.5 milbo$ manually landmark images////-----------------------------------------------------------------------------// Example:// The following command line//// marki -o new.shape -l 7 -p " m" -P 2 2 ../shape/68.shape//// will load all XM2VTS files with glasses in ../shape/68.shape into marki.// If you click on an image you will set the position of landmark 7 (which// is the tip of the chin). The results will be saved to new.shape, and also// file called "marked".//// You have to put the image files into the directories listed in the header// of marki.shape before using marki, and the images all have to be ".pgm"s.//// -l 7 specifies landmark 7 i.e. clicking on the image will update point 7//// -p " m" specifies files with tag strings matching the regular exprssion " m"// i.e. all XM2VTS files (look at 68.shape and 84.shape to see the tag strings)// -p " m[^r]" will show all except "reversed" i.e mirrored XM2VTS files//// -P 2 2 specifies tags with bit 0x2 set i.e. faces with glasses.// "-P 2 0" will show all faces _without_ glasses// (look at 84.shape and atface.hpp to learn about tags)//// You can "accept" or "reject" a file by clicking on the appropriate buttons.// This will create files "marki.sed" and "tagged" which you can use// for post processing. This is useful for making corrections to the tags// e.g. an image is marked as glasses when it is in fact not wearing glasses.//// Be a bit careful because marki doesn't warn you before overwriting an// output file (i.e. any of NEW.shape, marked, marki.tag, tagged)//// Marki moves automatically to the next image unless you change that// behaviour by clicking on the AutoNext button.// Right clicking moves to the next image without changing the landmark position.// Have a look at the WmCommand function below to see what commands are available// using the mouse.//// See the WmKeydown function below to see what you can do from the keyboard.//// Note that marki writes to the registry (to store page layout etc. information).////-----------------------------------------------------------------------------// TODO! The last time I tried this, it seems to only work on for XM2VTS// files -- the program crashes when you try to save the results for files// that are not XM2VTS files (but "marked' and "tagged" will be saved ok// before the crash).////-----------------------------------------------------------------------------// My windows knowledges comes from Petzold's books, so that's where// you want to look to understand this program, if necessary.// This files uses a form of Hungariain notatin -- the prefix "g" on// and indentifier means "global" for example.//// TODO: Bug: when moving focus back to main window (from dialog window)// it sets the current image to MARKED//// Warning: this is raw research code -- expect it to be quite messy.// Tab size is 4// milbo nov 05 durban//-----------------------------------------------------------------------------// 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"#include <commctrl.h>#include "marki.hpp"#define USE_SHAPE_FILE 1 // 1:read shape file and get images from it // 0: read images from directory "adj" (TODO still working?)static const char *sgVersion = "1.5"; const char *sgProgramName = "marki";static const char *sgMarkiLog = "marki.log";static const char *sgClass = "marki";static const char *sgRegistryKey = "Software\\milbo\\marki";static const char *sgRegistryName = "Config";static const char *sgTagged = "tagged";static const char *sgMarkedFile = "marked";static const char *sgUmSed = "marki.sed";static const char *sgDlg = "Dlg";static int igLandmark = MLEyeInner; // -l command line flagstatic char sgNewShapeFile[SLEN]; // -o command line flagstatic char sgTagRegExp[SLEN]; // -p command line flagstatic unsigned gAttrMask1, gAttrMask2; // -P command line flagstatic unsigned gRejectBit; // -R command line flagstatic bool fgOnlySelectedFiles; // -O command line flagstatic int xgDlg; // posn of dialog windowstatic int ygDlg;static HWND hgMainWnd; // main windowstatic HWND hgToolbar; // toolbarstatic HWND hgDlgWnd; // dialog windowstatic HBITMAP hgToolbarBmp; // toolbar bitmapstatic HINSTANCE hgAppInstance; // application instancestatic HANDLE hgProgramMutex; // this program's mutual exclusion handlestatic int ngMaiwidth, ngMaiheight; // main window dimensionsstatic clock_t gStartTime; // time program startedstatic RgbImage gImg; // current image#if USE_SHAPE_FILEstatic char sgShapeFile[SLEN];static ShapeVec gShapesOrg; // array[nShapes] of SHAPE, each SHAPE is nPoints x 2, original training shapesstatic ShapeVec gShapes; // above, but modified by user mouse clicksstatic int ngPoints; // number of landmark points in each training set shapestatic Mat gLand; // Lands structure#elsestatic const char *sgDefaultImageDir = "adj"; // directory holding images#endifstatic StringVec gTagStrings; // array[nShapes] of string preceding each shape in ASM file, i.e. image filenamesstatic int ngImages; // total number of imagesstatic int igImage; // index of current image in gTagStringsstatic char sgImageDirs[SLEN]; // director(ies) holding imagesint igTopCrop, igBottomCrop; // what we cropped off the image before displaying itint igLeftCrop, igRightCrop;static bool fgAutoNext; // toolbar button: auto next after markstatic bool fgEqualize;static bool fgConnectDots;#define CONF_nMaxCrop 1 // allow only two crop statesstatic int igCrop; // toolbar button: crop 0..CONF_nMaxCropstatic bool fgMarkedFilesUpToDate = true;static bool fgTaggedFilesUpToDate = true;typedef enum eImageStatus { ST_NONE=0, ST_REJECT, ST_ACCEPT } eImageStatus;static char *sgImageStatus[] = { "", "REJECT", "ACCEPT" }; // must match eImageStatusstatic eImageStatus gImageStatus[CONF_nMaxFiles]; // array of accept/reject status, same index as gTagStrings// "chugging", which is a fast slide show, is useful if you are checking that// all images marked as say wearing glasses are indeed wearing glasses.static bool fgChug; // chug mode: automatically cycle through imagesstatic const int IdTimer_Chug = 1;static const int igChugDelay = 250; // ms delay between images in chug modestatic TBBUTTON gToolbarButtons[] = { 6, IDM_Save, BUTTON_Standard, 7, IDM_Equalize, BUTTON_Standard, 8, IDM_ConnectDots, BUTTON_Standard, 2, IDM_AutoNext, BUTTON_Standard, 5, IDM_Crop, BUTTON_Standard, 0, IDM_Prev, BUTTON_Standard, 1, IDM_Next, BUTTON_Standard, 4, IDM_Reject, BUTTON_Standard, 3, IDM_Accept, BUTTON_Standard, 9, IDM_EraseLandmark, BUTTON_Standard, -1, // -1 terminates list };static char *sgTooltips[] = { "Auto next on reject/accept", // IDM_AutoNext "Equalize images before displaying", // IDM_Equalize "Connect the dots", // IDM_ConnectDots "Crop display", // IDM_Crop "Display previous image", // IDM_Prev "Display next image", // IDM_Next "Reject image", // IDM_Reject "Accept image", // IDM_Accept "Erase landmark", // IDM_EraseLandmark };#if USE_SHAPE_FILEstatic char sgUsage[] ="Usage: marki [-l LANDMARK] [-o OUTFILE] [-c CENTER_LANDMARK] [-p PATTERN] [-O] INFILE.shape\n""\n""-l LANDMARK\n""\t\tLandmark to be altered by left mouse click\n""\t\tDefault: 29 (inner left eye)\n""\n""-o OUTFILE.shape\n""\t\tOutput filename.\n""\t\tDefault: _INFILE.shape\n""\n""-p PATTERN\n""\t\tPATTERN is an egrep style pattern (not a file wildcard).\n""\t\tLoad only shapes in INFILE.shape with tags matching case-independent PATTERN.\n""\t\tExample: -p \"xyz\" loads filenames containing xyz\n""\t\tExample: -p \" m000| m001\" loads filenames beginning with m000 or m001.\n""\t\tDefault: all (except global detector shapes)\n""\n""-P Mask1 Mask2\n""\t\tLoad only shapes which satisfy (Attr & Mask1) == Mask2.\n""\t\tAttr is the hex number at start of the tag, Mask1 and Mask2 are hex.\n""\t\tThis filter is applied after -p PATTERN.\n""\t\tExample: -P 2 2 matches faces with glasses (FA_Glasses=2, see atface.hpp).\n""\t\tExample: -P 2 0 matches faces without glasses.\n""\t\tDefault: no filter (Mask1=Mask2=0).\n""\n""-O\n""\t\tWrite only the shapes selected by PATTERN when writing the new shape file OUTFILE.shape.\n""\t\tDefault: write all the shapes in INFILE.shape to OUTFILE.shape, updating those that have been marked.\n""\n""-R HexBit\n""\t\tSet the given bit in the tags in OUTFILE.shape for images marked \"REJECT\"\n""\t\tClear the given bit images marked \"ACCEPT\"\n""\t\tDefault: HexBit=0 i.e. don't change bits in OUTFILE.shape\n""\n""INFILE.shape\n""\t\tInput shape file. Default: marki.shape\n";#endif//-----------------------------------------------------------------------------static void StripQuotesAndBackQuote (char *sStripped, const char *sQuoted){// remove quotes at each end of string, if anystrcpy(sStripped, &sQuoted[sQuoted[0] == '"'? 1:0]); // discard initial "int iLen = strlen(sStripped)-1;if (iLen > 0 && sStripped[iLen] == '"') sStripped[iLen] = 0; // discard final "// replace backquote with spacefor (int i = 0; sStripped[i]; i++) if (sStripped[i] == '`') sStripped[i] = ' ';}//-----------------------------------------------------------------------------// See sgUsage#if USE_SHAPE_FILEstatic char *sParseCmdLine (LPSTR pCmdLine) // return error msg if failure, NULL on success{char *sWhiteSpace = " \t";char sStripped[SLEN];// A hack to deal with strtok: convert spaces inside quotes to backquote// This allows us to place spaces inside strings in quotes (for regular expression and filenames)int Len = strlen(pCmdLine);for (int i = 0; i < Len; i++) if (pCmdLine[i] == '"') { i++; while (pCmdLine[i] && pCmdLine[i] != '"') { if (pCmdLine[i] == ' ') pCmdLine[i] = '`'; i++; } }char *sToken = strtok(pCmdLine, sWhiteSpace);while (sToken != NULL) { if (sToken[0] == '-') { if (sToken[1] == 0) return sgUsage; switch (sToken[1]) { case 'l': sToken = strtok(NULL, sWhiteSpace); if (sToken == NULL) return sgUsage; igLandmark = -1; sscanf(sToken, "%d", &igLandmark); if (igLandmark < 0 || igLandmark > 200) return sgUsage; break; case 'o': sToken = strtok(NULL, sWhiteSpace); if (sToken == NULL) return sgUsage; StripQuotesAndBackQuote(sStripped, sToken); if (!sStripped || sStripped[0] == 0 || sStripped[0] == '-') return sgUsage; strcpy(sgNewShapeFile, sStripped); break; case 'p': sToken = strtok(NULL, sWhiteSpace); if (sToken == NULL) return sgUsage; StripQuotesAndBackQuote(sStripped, sToken); if (!sStripped || sStripped[0] == 0 || sStripped[0] == '-') return sgUsage; strcpy(sgTagRegExp, sStripped); break; case 'P': sToken = strtok(NULL, sWhiteSpace); if (sToken == NULL || 1 != sscanf(sToken, "%x", &gAttrMask1)) return "-P needs two hex number arguments. Use -? for help."; sToken = strtok(NULL, sWhiteSpace); if (sToken == NULL || 1 != sscanf(sToken, "%x", &gAttrMask2)) return "-P needs two hex number arguments. Use -? for help."; break; case 'R': sToken = strtok(NULL, sWhiteSpace); if (sToken == NULL || 1 != sscanf(sToken, "%x", &gRejectBit)) return "Expected a hex number after -R. Use -? for help."; break; case 'O': fgOnlySelectedFiles = true; break; default: // bad flag return sgUsage; } } else // assume a string without '-' is the INFILE { StripQuotesAndBackQuote(sgShapeFile, sToken); strcpy(sgShapeFile, sToken); } sToken = strtok(NULL, sWhiteSpace); if (sgShapeFile[0] && sToken) return "Flags not allowed after shape file"; }if (!sgShapeFile[0]) // no shape file specified return "\nYou must specify a shape file. Use marki -? for help.\n";if (sgNewShapeFile[0] == 0) { char sDrive[_MAX_DRIVE], sDir[_MAX_DIR], sFname[_MAX_FNAME], sExt[_MAX_EXT], sNewFname[_MAX_FNAME]; _splitpath(sgShapeFile, sDrive, sDir, sFname, sExt); sprintf(sNewFname, "_%s", sFname); _makepath(sgNewShapeFile, sDrive, sDir, sNewFname, ".shape"); }lprintf("ShapeFile %s ", sgShapeFile);lprintf("OutShapeFileFile %s\n", sgNewShapeFile);lprintf("Landmark %s\n", (igLandmark<sizeof(sgPointNames)/sizeof(sgPointNames[0])? sgPointNames[igLandmark]: "?"));lprintf("RegExp \"%s\"\n", sgTagRegExp);lprintf("Mask1 %x [%s] ", gAttrMask1, sGetAtFaceString(gAttrMask1));lprintf("Mask2 %x [%s]\n", gAttrMask2, sGetAtFaceString(gAttrMask2));lprintf("HexBit %x [%s]\n", gRejectBit, sGetAtFaceString(gRejectBit));return NULL; // success}#endif//-----------------------------------------------------------------------------static void GetStateFromRegistry (int *pxPos, int *pyPos, int *pxSize, int *pySize, int *piCrop, bool *pfAutoNext, bool *pfEqualize, bool *pfConnectDots, int *pxDlg, int *pyDlg){HKEY hKey;DWORD nBuf = SLEN;BYTE Buf[SLEN];int xPos, yPos, xSize, ySize, iCrop=-1, fAutoNext=-1, fEqualize=-1, fConnectDots=-1, xDlg=-1, yDlg=-1;// get values from registry and validate themif (ERROR_SUCCESS != RegOpenKeyEx(HKEY_CURRENT_USER, sgRegistryKey, 0, KEY_READ, &hKey) || ERROR_SUCCESS != RegQueryValueEx(hKey, sgRegistryName, NULL, NULL, Buf, &nBuf)) { // Can't get what we want from registry. Set big vals, and clip them below. lprintf("Can't read registry HKEY_CURRENT_USER Key \"%s\" Name \"%s\", using defaults\n", sgRegistryKey, sgRegistryName); xPos = yPos = 0; xSize = ySize = 100000; }else if (10 != sscanf((char *)Buf, "%d %d %d %d %d %d %d %d %d %d", &xPos, &yPos, &xSize, &ySize, &iCrop, &fAutoNext, &fEqualize, &fConnectDots, &xDlg, &yDlg) || xPos + xSize < 0 || yPos + ySize < 0 || xSize < 20 || ySize < 20 || // pos can be neg if window edges slightly off screen (fAutoNext != 1 && fAutoNext != 0) || (fEqualize != 1 && fEqualize != 0) || (fConnectDots != 1 && fConnectDots != 0) || (igCrop < 0 || igCrop > 2) || (xDlg < 0 || yDlg < 0)) { // Can't get what we want from registry. Set big vals, and clip them below. lprintf("Can't get values from registry HKEY_CURRENT_USER Key \"%s\" Name \"%s\" " "%dx%d %dx%d Crop %d Auto %d Eq %d Con %d, using defaults\n", sgRegistryKey, sgRegistryName, xPos, yPos, xSize, ySize, iCrop, fAutoNext, fEqualize, fConnectDots); xPos = yPos = 0; xSize = ySize = 100000; xDlg = yDlg = -1; }else { *piCrop = iCrop; *(int *)pfAutoNext = fAutoNext; *(int *)pfEqualize = fEqualize; *(int *)pfConnectDots = fConnectDots; }RegCloseKey(hKey);if (igCrop > CONF_nMaxCrop) igCrop = 0;// Validate registry entries against actual work area// If invalid, return biggest possible main window size.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -