Image Mask

画像からマスク画像を作成して、目標とする果物を抽出して描画します。

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

画像からマスクを作成

  1. マスク画像を使って果物の周辺をマスクして、目標とする果物だけを描画します。
    マスク画像は白黒2値の画像で果物の輪郭を捉えているので画像の解析にも使えます。
    DibStruct Dib; で定義されている hBmp と hMask の二枚の領域を初期化する InitDib() 関数です。
    //★ BMP 画像に合わせて 24bit Dib を初期化する
    void  IMAGEAI::InitDib()
    {   HDC      hDC;
        LPBITMAPINFO    Info;   //BITMAP 構造体のヘッダー
    
        hDC = GetDC(hWnd);
        Info = (LPBITMAPINFO)GlobalAlloc(GPTR, sizeof(BITMAPINFO));
        Info->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
        Info->bmiHeader.biWidth = m_Width;
        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);
        Dib.hMask = CreateDIBSection(hDC,Info,0,(void**)&Dib.Mask,NULL,0);
        Dib.hMaskDC = CreateCompatibleDC(hDC);
        SelectObject(Dib.hMaskDC,Dib.hMask);
    
        GlobalFree(Info);
        ReleaseDC(hWnd,hDC);
    }
    
  2. 画像全体から閾値を指定して果物の色以外を削除する方法では、うまく抽出できない場合があります。
    そこでペイントソフトの選択ツールと同じように「隣接するピクセルを比較して」果物を抽出します。
    hBmpDC に転送された元の画像を参照して、hMaskDC に白黒2値のマスク画像を作成します。
    hBmpDC の画像は Set_Mask() を実行しても、壊れずにそのまま残っています。
    x, y はマウスの座標を取得する型に合わせて WORD で受け取っています。
    BMP 画像は上下が逆になっているので yw = m_Height - y; で座標を計算します。
    Pixcel(xw,yw) が隣接するピクセルを比較してマスク画像を作成する関数です。
    // Dib.Data の果物を抽出して Mask を設定
    void  IMAGEAI::Set_Mask(WORD xp, WORD yp, WORD wd)
    {   int     x, y, cp, xw, yw;
        xw = xp;
        yw = m_Height - yp;
        for(y=0; y<m_Height; y++)
            for(x=0; x<m_Width; x++)
                for(cp=0; cp<3; cp++)
                {   MASK(y, x, cp) = 0;  }
        Show(Dib.hBmpDC, 0, 0);
        SetLimit(xw, yw, wd);
    VarMsg(hWnd,"WORD WD:",wd);
        Pixcel(xw,yw);  
    }
    
  3. 隣接するピクセルを比較してマスク画像を作成する Pixcel(x,y) 関数です。
    閾値の範囲に治まる座標のマスクを白(255,255,255)に設定します。
    Dib.Data[y,x] から始めて、上下左右の隣接するピクセルを調べます。
    // 隣接するピクセルを再帰でテスト 
    void  IMAGEAI::Pixcel(int x, int y)
    {
        if (OutPic(x,y)==false) return;
        MASK(y, x, 0) = 255;
        MASK(y, x, 1) = 255;
        MASK(y, x, 2) = 255;
        Pixcel(x+1,y);
        Pixcel(x,y+1);
        Pixcel(x-1,y);
        Pixcel(x,y-1);
    }
    
  4. Dib.Data[y,x] のピクセルを調べる OutPic(x,y) 関数です。
    y,x が画像範囲を超えると false を返します。
    y,x のマスクが既に白に設定されていれば false を返します。
    閾値の範囲に治まるかを調べて false または true を返します。
    // Dib.Data[X,Y] が low[3]~high[3] の範囲外のとき false 
    bool  IMAGEAI::OutPic(int x, int y)
    {
        if (x<0 || y<0) return false;
        if (x>=m_Width || y>=m_Height)  return false;
        if (Dib.Mask[(y*m_Width+x)*3]==255) return false;
        if (MAP(y,x,0)<low[0])  return false;
        if (MAP(y,x,0)>high[0]) return false;
        if (MAP(y,x,1)<low[1])  return false;
        if (MAP(y,x,1)>high[1]) return false;
        if (MAP(y,x,2)<low[2])  return false;
        if (MAP(y,x,2)>high[2]) return false;
        return true;
    }
    
  5. Pixcel() 関数は「再帰で多重に呼び出される」ので、スタック領域が不足するようです。
    [プロジェクト/Mainのプロパティ/リンカー/システム/スタックのサイズの設定] からサイズを 10000000 に設定して下さい。
    今回の BMP サイズであれば、スタックは 10000000(10MB) あれば足りるようです。
    ビルド/ソリューションのリビルドで再コンパイルしてテストして下さい。
    再起で抽出の詳細は「超初心者のプログラム入門(C/C++)」から「*で囲まれた中を塗りつぶす」を参照して下さい。
    この先はアイデアとアルゴリズムの勝負です。
    画像解析(画像認識)に必要な関数を工夫して追加して下さい。
  6. Main.cpp の AppInit() から OpenFile() で画像ファイルを選択します。
    InitDib() で DibStruct を初期化して下さい。
    WM_PAINT では入力した画像の上からマスクをかけて描画するので Dib.hMaskDC にもイメージを転送して下さい。
    // 画像をロードして、24bit DIB を作成
    LRESULT  AppInit(HWND hwnd)
    {
        ImageAI= new IMAGEAI(hwnd);
        if (ImageAI->OpenFile()==false) return false;
        ImageAI->InitDib();
        ImageAI->Show(ImageAI->Dib.hBmpDC, 0, 0);
        ImageAI->Show(ImageAI->Dib.hMaskDC, 0, 0);
        return true;
    }
    
  7. WM_PAINT では、入力した画像の上から AndMask() でマスク画像を重ねます。
            case WM_PAINT:          // Mask を使って画像を描画
                hdc = BeginPaint(hWnd,&ps);
                if (ImageAI)
                {   ImageAI->Show(hdc, 30, 20);
                    ImageAI->AndMask(hdc, 30, 20);
                }
                EndPaint(hWnd, &ps);
                break;
    
  8. WM_LBUTTONDOWN: では、閾値の領域を定義してマウスのクリックで値をアップします。
        WORD    wd= 10;             // 閾値(幅)
            ・・・
            case WM_LBUTTONDOWN:    // 左ボタンをクリック
                wd= wd+2;
                // Mask を作成
                ImageAI->Set_Mask(200,200,wd);
                InvalidateRect(hWnd,NULL,FALSE);
                break;
    
  9. 画像の上からマスク画像を重ねる AndMask() 関数です。
    ラスタオペレーションに SRCAND を使います。
    // 画像の上から Mask 画像を重ねる 
    HRESULT  IMAGEAI::AndMask(HDC hdc, long x, long y)
    {
        if (Dib.hMaskDC == NULL)    return FALSE;
        BitBlt(hdc, x, y, m_Width, m_Height, Dib.hMaskDC, 0, 0, SRCAND);
        return TRUE;
    }
    

[Next Chapter ↓] クリックで果物を抽出
[Previous Chapter ↑] Select Color

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