Face Image

顔の画像をサイズを整えて保存します。

前田稔(Maeda Minoru)の超初心者のプログラム入門

Face Class

  1. テンプレートのプロジェクトは Windows10 でプログラムする から 02empty.zip で提供しています。
    顔の画像をサイズを整えて保存する専用の Image Size Class のヘッダーファイル ImgSize.h です。
    Image Size Class では「int m_Width //画像の幅」に加えて「int m_Wbyte //幅のバイト数」を定義しています。
    これは画像の幅をバウンダリに合わせてパディングされることを考慮した値です。
    //★ Image Size Class Header File  前田 稔
    #ifndef     _Face
    #define     _Face
    #include    <windows.h>
    #include    <math.h>
    #pragma     once
    #define     SAFE_DELDC(p)   { if (p) { DeleteDC (p);   (p)=NULL; } }
    #define     SAFE_DELOBJ(p)  { if (p) { DeleteObject(p); (p)=NULL; } }
    #define     MAP(y,x,c)  (Dib.Data[(y*m_Wbyte+x)*3+c])
    
    typedef struct
    {   HBITMAP         hBmp;
        HDC             hBmpDC;
        LPBYTE          Data;           // 24bit DIB の BITMAP DATA
        HBITMAP         hMask;
        HDC             hMaskDC;
        LPBYTE          GData;          // 24bit Mask の BITMAP DATA
    }   DibStruct;
    
    class IMGSIZE
    { protected:
        HWND        hWnd;
        HBITMAP     hBmp;               //画像の HBITMAP
        HDC         hBmpDC;             //画像の HDC
    
      public:
        int         m_Width;            //画像の幅
        int         m_Height;           //画像の高さ
        int         m_Wbyte;            //幅のバイト数
        DibStruct   Dib;                //RGB 画像解析の構造体
        char        szFile[MAX_PATH];   //オープンするファイル名(パス付き)
        int     vect[9][2] =            //Vector Table
        { {-1,-1}, {-1,0}, {-1,1}, {0,-1}, {0,0}, {0,1}, {1,-1}, {1,0}, {1,1} };
    
        IMGSIZE(HWND hWnd);             //Constructor
        virtual ~IMGSIZE();             //Destructor
        HRESULT     LoadBmp(LPSTR szBitmap);
        void        SaveBmp(LPSTR fname, HDC hdc, HBITMAP hbm);
    
        //入力画像を BitBlt() で描画(転送)する
        HRESULT     Show(HDC hdc,int x,int y,int w,int h,int xd,int yd);
        HRESULT     Show(HDC hdc,int x,int y)
                    {  return Show(hdc,x,y,m_Width,m_Height,0,0);  }
        HRESULT     Show(HDC hdc, int x, int y, int w, int h);
    
        //入力画像を StretchBlt() で転送する
        HRESULT     Show(HDC hdc,int x,int y,int w,int h,int sx,int sy,int sw,int sh);
        HRESULT     Show(HDC hdc,POINTS p0,POINTS p1);
    
        //Dib.hBmpDC を BitBlt() で転送する
        HRESULT     ShowDib(HDC hdc, int x, int y);
    
        //Dib Function
        void        InitDib();
        void        Mark(int xp, int yp);
    
        BOOL        OpenFile();
        void        Adjust(BOOL flag=TRUE);
    };
    
    #endif
    
  2. Image Size Class のプログラムファイル ImgSize.cpp のソースコードです。
    顔画像の認識は、鼻の頭を中心に 256*256 のサイズで切り出した画像を使います。
    hMaskDC が切り出す画像を格納する領域で、入力画像のサイズに関係なく 256*256 に設定されています。
    // Image Size Class Program File  前田 稔
    #include    "ImgSize.h"
    
    void  VarMsg(HWND hwnd, char *msg, WORD n)
    {   char    str[80];
        wsprintf(str,"%s=%d\n",msg,n);
        SetWindowText(hwnd,(LPCSTR)str);
    }
    void  VarMsg(HWND hwnd, int x, int y)
    {   char    str[80];
        wsprintf(str, "x:%d  y:%d", x, y);
        SetWindowText(hwnd, (LPCSTR)str);
    }
    
    // Face Object Class 関数
    // コンストラクタ(オブジェクトの初期化)
    IMGSIZE::IMGSIZE(HWND hwnd)
    {   hWnd= hwnd;
        hBmp= NULL;
        hBmpDC= NULL;
        Dib.hBmp = NULL;
        Dib.hBmpDC = NULL;
        Dib.hMask = NULL;
        Dib.hMaskDC = NULL;
        m_Width = m_Height = 0;
        szFile[0]= '\0';
    }
    
    // デストラクタ(オブジェクトの終了)
    IMGSIZE::~IMGSIZE()
    {   SAFE_DELDC(Dib.hBmpDC);
        SAFE_DELDC(Dib.hMaskDC);
        SAFE_DELDC(hBmpDC);
        SAFE_DELOBJ(Dib.hBmp);
        SAFE_DELOBJ(Dib.hMask);
        SAFE_DELOBJ(hBmp);
    }
    
    //★ BMP File のロード
    HRESULT  IMGSIZE::LoadBmp(LPSTR szBitmap)
    {   HDC         hdc;
        BITMAP      bmp;
    
        SAFE_DELDC(Dib.hBmpDC);
        SAFE_DELDC(Dib.hMaskDC);
        SAFE_DELDC(hBmpDC);
        SAFE_DELOBJ(Dib.hBmp);
        SAFE_DELOBJ(Dib.hMask);
        SAFE_DELOBJ(hBmp);
        m_Width = m_Height = 0;
    
        hBmp= (HBITMAP)LoadImage(NULL,szBitmap,IMAGE_BITMAP,0,0,
                                 LR_LOADFROMFILE|LR_CREATEDIBSECTION);
        if (hBmp==NULL)
        {   MessageBox(NULL,szBitmap,"Load BMP Error",MB_OK);
            return  FALSE;
        }
        hdc= GetDC(hWnd);
        hBmpDC= CreateCompatibleDC(hdc);
        SelectObject(hBmpDC, hBmp);
    
        // サイズを保存
        GetObject(hBmp,sizeof(BITMAP),&bmp);
        m_Width= bmp.bmWidth;
        m_Height= bmp.bmHeight;
        m_Wbyte = (m_Width + 7) & 0xfffffff8;
        return TRUE;
    }
    
    // Save BMP File
    void  IMGSIZE::SaveBmp(LPSTR fname, HDC hdc, HBITMAP hbm)
    {   HANDLE  hFile;
        DWORD   dwBytes, len;
        BITMAP  bm;
        int     ret;
        static  BITMAPFILEHEADER    g_Bf = { 0x4d42, 0, 0, 0, 0x36 };
        static  BITMAPINFOHEADER    g_Bi = { 0x28, 0, 0, 1, 24, 0, 0, 0, 0, 0, 0 };
        BYTE    *imgbuf;
    
        if (GetObject(hbm, sizeof(BITMAP), &bm) == 0)
        {   MessageBox(NULL, "GetObject() に失敗しました", "PutBmp", MB_OK);
            return;
        }
        hFile = CreateFile(fname, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
        if (hFile == NULL)    return;
    
        len = (bm.bmWidthBytes + 3) & 0xfffffffc;
        len *= bm.bmHeight;
        g_Bi.biWidth = bm.bmWidth;
        g_Bi.biHeight = bm.bmHeight;
        g_Bf.bfSize = g_Bf.bfOffBits + len;
    
        imgbuf= new BYTE[len];
        ret= GetDIBits(hdc,hbm,0,bm.bmHeight,imgbuf,(LPBITMAPINFO)&g_Bi,DIB_RGB_COLORS);
        if (ret==0)
        {   MessageBox(NULL,"ピクセルデータの取得に失敗しました", "PutBmp", MB_OK);
            CloseHandle(hFile);  return;
        }
        WriteFile(hFile, &g_Bf, sizeof(BITMAPFILEHEADER), &dwBytes, NULL);
        WriteFile(hFile, &g_Bi, sizeof(BITMAPINFOHEADER), &dwBytes, NULL);
        WriteFile(hFile, imgbuf, len, &dwBytes, NULL);
        CloseHandle(hFile);
        if (imgbuf) delete [] imgbuf;
    }
    
    //★ BMP 画像に合わせて 24bit Dib を初期化する
    void  IMGSIZE::InitDib()
    {   HDC      hDC;
        LPBITMAPINFO    Info;   //BITMAP 構造体のヘッダー
    
        hDC = GetDC(hWnd);
        Info = (LPBITMAPINFO)GlobalAlloc(GPTR, sizeof(BITMAPINFO));
        Info->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
        Info->bmiHeader.biWidth = m_Wbyte;
        Info->bmiHeader.biHeight = m_Height;
        Info->bmiHeader.biPlanes = 1;
        Info->bmiHeader.biBitCount = 24;        // 24 bit
        Info->bmiHeader.biCompression = BI_RGB; // 非圧縮
    
        Dib.hBmp = CreateDIBSection(hDC,Info,0,(void**)&Dib.Data,NULL,0);
        Dib.hBmpDC = CreateCompatibleDC(hDC);
        SelectObject(Dib.hBmpDC,Dib.hBmp);
    
        Info->bmiHeader.biWidth = 256;
        Info->bmiHeader.biHeight = 256;
        Dib.hMask = CreateDIBSection(hDC,Info,0,(void**)&Dib.GData,NULL,0);
        Dib.hMaskDC = CreateCompatibleDC(hDC);
        SelectObject(Dib.hMaskDC,Dib.hMask);
    
        GlobalFree(Info);
        ReleaseDC(hWnd,hDC);
    }
    
    //★ 入力画像を描画(転送)する
    HRESULT  IMGSIZE::Show(HDC hdc,int x,int y,int w,int h,int xd,int yd)
    {   if (hBmpDC==NULL)   return FALSE;
        BitBlt(hdc,x,y,w,h,hBmpDC,xd,yd,SRCCOPY);
        return TRUE;
    }
    HRESULT  IMGSIZE::Show(HDC hdc, int x, int y, int w, int h)
    {   if (Dib.hBmpDC == NULL)   return FALSE;
        BitBlt(hdc, x, y, w, h, Dib.hBmpDC, 0, 0, SRCCOPY);
        return TRUE;
    }
    
    //入力画像を StretchBlt() で転送する
    HRESULT  IMGSIZE::Show(HDC hdc,int x,int y,int w,int h,int sx,int sy,int sw,int sh)
    {   if (hBmpDC==NULL)   return FALSE;
        StretchBlt(hdc,x,y,w,h,hBmpDC,sx,sy,sw,sh,SRCCOPY);
        return TRUE;
    }
    HRESULT  IMGSIZE::Show(HDC hdc,POINTS p0,POINTS p1)
    {   int xp,yp,wk;
        if (hBmpDC==NULL)   return FALSE;
        wk= p0.y-p1.y;
        xp= p0.x-wk-wk;
        yp= p0.y-wk-wk;
        StretchBlt(hdc,0,0,256,256,hBmpDC,xp,yp,wk*4,wk*4,SRCCOPY);
        return TRUE;
    }
    
    // Dib.hBmpDC を BitBlt() で転送する
    HRESULT  IMGSIZE::ShowDib(HDC hdc, int x, int y)
    {   if (Dib.hBmpDC == NULL) return FALSE;
        BitBlt(hdc, x, y, m_Width, m_Height, Dib.hBmpDC, 0, 0, SRCCOPY);
        return TRUE;
    }
    
    // Dib.hBmp にマークを表示する(x,y: BMP 座標)
    void  IMGSIZE::Mark(int x, int y)
    {   int     xw,yw,i;
    
        if (x==0 || y==0)   return;
    VarMsg(hWnd,x,y);
        for(i=0; i<9; i++)
        {   xw= x+vect[i][0]*2;
            yw= y+vect[i][1]*2;
            MAP(yw,xw,0)= 0;
            MAP(yw,xw,1)= 0;
            MAP(yw,xw,2)= 0;
            xw += vect[i][0];
            yw += vect[i][1];
            MAP(yw,xw,0) = 0;
            MAP(yw,xw,1) = 0;
            MAP(yw,xw,2) = 255;
        }
    }
    
    // Window Size を画像に合わす
    void  IMGSIZE::Adjust(BOOL flag)
    {   RECT    rc;
        int     x,y;
    
        GetWindowRect(hWnd,&rc);
        x= rc.left;
        y= rc.top;
        rc.right= x + m_Width;
        rc.bottom= y + m_Height;
        AdjustWindowRect(&rc,WS_OVERLAPPEDWINDOW,flag);
        MoveWindow(hWnd,x,y,rc.right-rc.left,rc.bottom-rc.top,TRUE);
    }
    
    // OPENFILENAME 構造体で画像ファイルを Load する
    BOOL  IMGSIZE::OpenFile()
    {   OPENFILENAME    ofn;
    
        szFile[0]= '\0';
        memset(&ofn,0,sizeof(OPENFILENAME));
        ofn.lStructSize= sizeof(OPENFILENAME);
        ofn.hwndOwner= hWnd;
        ofn.lpstrFilter= "bmp(*.bmp)\0*.bmp\0All files(*.*)\0*.*\0\0";
        ofn.lpstrFile= szFile;
        ofn.lpstrFileTitle= NULL;
        ofn.nMaxFile= MAX_PATH;
        ofn.Flags= OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
        ofn.lpstrDefExt= "gif";
        ofn.lpstrTitle= "ファイルオープン!";
        if (GetOpenFileName(&ofn)!=TRUE)    return FALSE;
        LoadBmp(szFile);
        return TRUE;
    }
    
  3. サイズを整えて保存する Main.cpp のソースコードです。
    //★ 画像のサイズを整える    2018/04/25  前田 稔
    #define     NAME    "Face Check"
    #include    <windows.h>
    #include    "ImgSize.h"
    #pragma     once
    #pragma     comment(lib,"winmm.lib")
    #define     SAFE_DELETE(p)  { if (p) { delete (p);  (p)=NULL; } }
    
    HINSTANCE   g_hInst;
    HWND        g_hWnd;
    HDC         hdc;
    IMGSIZE     *ImgSize= NULL; // FACE Object Class
    POINTS      p0;             // Point 0
    POINTS      p1;             // Point 1
    
    // 画像をロードして、24bit DIB を作成
    LRESULT  AppInit(HWND hwnd)
    {
        ImgSize = new IMGSIZE(hwnd);
        if (ImgSize->OpenFile()==false) return false;
        ImgSize->Adjust(false);
        ImgSize->InitDib();
        ImgSize->Show(ImgSize->Dib.hBmpDC, 0, 0);
        p0.x=p1.x= 0;
        return true;
    }
    
    // プロシージャ
    LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
    {   PAINTSTRUCT ps;
        WORD    xp, yp;
        int     nowTime;
        char    str[80];
    
        switch(msg)
        {   case WM_CREATE:         // 初期化
                if (AppInit(hWnd)==FALSE)   PostQuitMessage(0);
                break;
            case WM_PAINT:
                hdc = BeginPaint(hWnd,&ps);
                if (ImgSize)
                {   ImgSize->ShowDib(hdc, 0, 0);  }
                EndPaint(hWnd, &ps);
                break;
            case WM_LBUTTONDOWN:    // 左ボタンをクリック
                xp = LOWORD(lp);
                yp = HIWORD(lp);
                if (p1.x!=0)
                {   p0.x=p1.x= 0;
                    ImgSize->Show(ImgSize->Dib.hBmpDC, 0, 0);
                    InvalidateRect(hWnd, NULL, FALSE);
                    break;
                }
                if (p0.x==0)
                {   p0.x= xp;
                    p0.y= yp;
                }
                else
                {   p1.x= xp;
                    p1.y= yp;
                }
                ImgSize->Mark(xp, (ImgSize->m_Height-1)-yp);
                InvalidateRect(hWnd, NULL, FALSE);
                break;
            case WM_RBUTTONDOWN:    // 右ボタンをクリック
                ImgSize->Show(ImgSize->Dib.hBmpDC, p0, p1);
                InvalidateRect(hWnd, NULL, FALSE);
                break;
            case WM_CLOSE:          // クローズ
                if (p1.x>0)
                {   nowTime= timeGetTime()%1000;
                    wsprintf(str, "c:\\tmp\\m%d.bmp", nowTime);
                    ImgSize->Show(ImgSize->Dib.hMaskDC, 0, 0, 256, 256);
                    ImgSize->SaveBmp(str,ImgSize->Dib.hMaskDC, ImgSize->Dib.hMask);
                }
                SAFE_DELETE(ImgSize);
                DestroyWindow(hWnd);
                break;
            case WM_DESTROY: 
                PostQuitMessage(0);
                break;
            default:
                return(DefWindowProc(hWnd, msg, wp, lp));
        }
        return (0L);
    }
    
    //★ Windows Main 関数
    int APIENTRY  WinMain(HINSTANCE hInst, HINSTANCE, LPSTR, int nCmdShow)
    {   MSG     msg;
    
        g_hInst= hInst;
        WNDCLASS wc = { CS_CLASSDC,WndProc,0L,0L,hInst,NULL,LoadCursor(NULL,IDC_ARROW),
                        (HBRUSH)GetStockObject(BLACK_BRUSH),NULL,NAME };
        if (RegisterClass(&wc)==0)    return FALSE;
        g_hWnd= CreateWindow(NAME,NAME,WS_OVERLAPPEDWINDOW,
                             CW_USEDEFAULT,CW_USEDEFAULT,500,500,
                             NULL,NULL,hInst,NULL);
        if (!g_hWnd)  return FALSE;
    
        ShowWindow(g_hWnd,nCmdShow);    //Window を表示
        UpdateWindow(g_hWnd);           //表示を初期化
        SetFocus(g_hWnd);               //フォーカスを設定
        while (GetMessage(&msg,NULL,0,0))
        {   TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        return S_OK;
    }
    

顔画像のサイズを整える

  1. 顔画像の識別は、果物のように色や幅と高さで識別する方法は使えません。
    影や日焼けや化粧によって色が変わるので、色で識別するには無理があります。
    顔の識別は、画像から目立つ特徴を抽出することで識別します。
    パーツの相対位置や大きさ、目や鼻やほお骨やあごの形を特徴として利用します。
  2. 最初のステップとして入力した顔の画像(カラー画像)を、鼻の頭を中心に 256×256 のサイズで保存します。
    Main.cpp の AppInit() から OpenFile() で画像ファイルを選択します。
    InitDib() で DibStruct を初期化して下さい。
    WM_PAINT では Dib.hBmpDC の画像を描画するので、入力した画像を Show() で Dib.hBmpDC に転送します。
    // 画像をロードして、24bit DIB を作成
    LRESULT  AppInit(HWND hwnd)
    {
        ImgSize = new IMGSIZE(hwnd);
        if (ImgSize->OpenFile()==false) return false;
        ImgSize->Adjust(false);
        ImgSize->InitDib();
        ImgSize->Show(ImgSize->Dib.hBmpDC, 0, 0);
        p0.x=p1.x= 0;
        return true;
    }
    
  3. WM_LBUTTONDOWN では、マウスの左クリックで鼻の頭(p0)と眉間(p1)をクリックします。
    三個目をクリックすると p0, p1 がリセットされます。
    ビュー座標と BMP 座標では上下が反転するので Mark() 関数に渡すときは、Y座標を反転させて下さい。
            case WM_LBUTTONDOWN:    // 左ボタンをクリック
                xp = LOWORD(lp);
                yp = HIWORD(lp);
                if (p1.x!=0)
                {   p0.x=p1.x= 0;
                    ImgSize->Show(ImgSize->Dib.hBmpDC, 0, 0);
                    InvalidateRect(hWnd, NULL, FALSE);
                    break;
                }
                if (p0.x==0)
                {   p0.x= xp;
                    p0.y= yp;
                }
                else
                {   p1.x= xp;
                    p1.y= yp;
                }
                ImgSize->Mark(xp, (ImgSize->m_Height-1)-yp);
                InvalidateRect(hWnd, NULL, FALSE);
                break;
    
  4. WM_RBUTTONDOWN: では、p0, p1 の座標から計算したサイズで画像を切り取って保存します。
    r= p0.y-p1.y とすると、切り取る画像の範囲は p0 を中心に r*4 が幅と高さになります。
    失敗したときは、三個目をクリックしてやり直して下さい。
            case WM_RBUTTONDOWN:    // 右ボタンをクリック
                ImgSize->Show(ImgSize->Dib.hBmpDC, p0, p1);
                InvalidateRect(hWnd, NULL, FALSE);
                break;
    
  5. WM_CLOSE で切り取った画像を c:\tmp\ のフォルダーに保存します。
    SaveBmp() 関数は24ビット BMP 形式でファイルに保存する関数です。
    画像ファイルの名前は重複しないように適当に付けられるので、必要に応じて名前を変更して下さい。
            case WM_CLOSE:          // クローズ
                if (p1.x>0)
                {
                    nowTime= timeGetTime()%1000;
                    wsprintf(str, "c:\\tmp\\m%d.bmp", nowTime);
                    ImgSize->Show(ImgSize->Dib.hMaskDC, 0, 0, 256, 256);
                    ImgSize->SaveBmp(str,ImgSize->Dib.hMaskDC, ImgSize->Dib.hMask);
                }
                SAFE_DELETE(ImgSize);
                DestroyWindow(hWnd);
                break;
    
  6. Mark() 関数は画像に Land Mark を表示する関数です。
    x,y は、左下を基点とする BMP 座標で指定して下さい。
    渡された座標をタイトルバーに表示しています。
    // Dib.hBmp にマークを表示する(x,y: BMP 座標)
    void  IMGSIZE::Mark(int x, int y)
    {   int     xw,yw,i;
    
        if (x==0 || y==0)   return;
    VarMsg(hWnd,x,y);
        for(i=0; i<9; i++)
        {   xw= x+vect[i][0]*2;
            yw= y+vect[i][1]*2;
            MAP(yw,xw,0)= 0;
            MAP(yw,xw,1)= 0;
            MAP(yw,xw,2)= 0;
            xw += vect[i][0];
            yw += vect[i][1];
            MAP(yw,xw,0) = 0;
            MAP(yw,xw,1) = 0;
            MAP(yw,xw,2) = 255;
        }
    }
    
  7. 同様のプログラムをC# Face Image でも作成しています。
    C#の方が簡単で解り易いので、合わせて参照して下さい。

[Next Chapter ↓] Binary Mode
[Previous Chapter ↑] 画像解析ガイド

超初心者のプログラム入門(Win32API C++)