Lazy SnappingをOpenCV 2.4.11で実装する方法を紹介します.OpenCVのgrabCut関数を利用します.GUIは実装しません.前景と背景を表すストロークはあらかじめ与えられているものとします.
// Include directory C:\OpenCV2.4.11\build\include
// Library directory (x86) 
C:\OpenCV2.4.11\build\x86\vc12\lib
// Library directory (x64) 
C:\OpenCV2.4.11\build\x64\vc12\lib
// PATH (x86) C:\OpenCV2.4.11\build\x86\vc12\bin
// PATH (x64) C:\OpenCV2.4.11\build\x64\vc12\bin
#if _DEBUG
#pragma comment(lib,
"opencv_core2411d.lib")
#pragma comment(lib,
"opencv_highgui2411d.lib")
#pragma comment(lib,
"opencv_imgproc2411d.lib")
#else
#pragma comment(lib,
"opencv_core2411.lib")
#pragma comment(lib,
"opencv_highgui2411.lib")
#pragma comment(lib,
"opencv_imgproc2411.lib")
#endif
#include
<opencv2/opencv.hpp>
int main()
{
  // Input
  cv::Mat 
inputImage = cv::imread("image.bmp", 1);
  cv::Mat 
inputMask = cv::imread("mask.bmp", 1);
  if 
(inputImage.data == NULL || inputMask.data ==
NULL) return 
1;
  if 
(inputImage.size() != 
inputMask.size()) 
return 1;
  cv::Mat 
strokeImage = inputImage.clone();
  cv::Mat 
processMask = cv::Mat(inputMask.rows, 
inputMask.cols, CV_8UC1);
  cv::Mat 
regionImage = inputMask.clone();
  cv::Mat 
compositeImage = inputImage.clone();
  cv::Mat 
fgImage = inputImage.clone();
  // Mask
  for (int 
y = 0; y < inputMask.rows; y++) {
    for 
(int x = 0; x < inputMask.cols; x++) {
      cv::Vec<unsigned 
char, 3> color = inputMask.at<cv::Vec<unsigned 
char, 3>>(y, x);
      if (color[0] 
== 0 && color[1] 
== 0 && color[2] 
== 255) {
        
processMask.at<unsigned char>(y, x) = 
cv::GC_FGD;
        
strokeImage.at<cv::Vec<unsigned 
char, 3>>(y, x)[0] 
= 0;
        
strokeImage.at<cv::Vec<unsigned 
char, 3>>(y, x)[1] 
= 0;
        
strokeImage.at<cv::Vec<unsigned 
char, 3>>(y, x)[2] 
= 255;
      }
     
else if (color[0] 
== 255 && color[1] 
== 0 && color[2] 
== 0) {
        
processMask.at<unsigned char>(y, x) = 
cv::GC_BGD;
        
strokeImage.at<cv::Vec<unsigned 
char, 3>>(y, x)[0] 
= 255;
        
strokeImage.at<cv::Vec<unsigned 
char, 3>>(y, x)[1] 
= 0;
        
strokeImage.at<cv::Vec<unsigned 
char, 3>>(y, x)[2] 
= 0;
      }
     
else {
        
processMask.at<unsigned char>(y, x) = (x + y) % 
2 == 0 ? cv::GC_PR_FGD : cv::GC_PR_BGD;
      }
    }
  }
  // LazySnapping
  cv::Mat bgdModel;
  cv::Mat fgdModel;
  cv::Rect rect;
  const int 
iterCount = 4;
  cv::grabCut(inputImage, processMask, rect, 
bgdModel, fgdModel, iterCount, cv::GC_INIT_WITH_MASK);
  // GrabCut
  // rect = cv::Rect(13, 32, 302, 155);
  // cv::grabCut(inputImage, processMask, rect, 
bgdModel, fgdModel, iterCount, cv::GC_INIT_WITH_RECT);
  // Foreground
  for (int 
y = 0; y < inputMask.rows; y++) {
    for 
(int x = 0; x < inputMask.cols; x++) {
      compositeImage.at<cv::Vec<unsigned 
char, 3>>(y, x)[0] 
/= 2;
      compositeImage.at<cv::Vec<unsigned 
char, 3>>(y, x)[1] 
/= 2;
      compositeImage.at<cv::Vec<unsigned 
char, 3>>(y, x)[2] 
/= 2;
     
int category = processMask.at<unsigned 
char>(y, x);
     
if (category == cv::GC_FGD || category == 
cv::GC_PR_FGD) {
        
regionImage.at<cv::Vec<unsigned 
char, 3>>(y, x)[0] 
= 0;
        
regionImage.at<cv::Vec<unsigned 
char, 3>>(y, x)[1] 
= 0;
        
regionImage.at<cv::Vec<unsigned 
char, 3>>(y, x)[2] 
= (category == cv::GC_FGD ? 255 : 127);
        
compositeImage.at<cv::Vec<unsigned 
char, 3>>(y, x)[0] 
+= 0;
        
compositeImage.at<cv::Vec<unsigned 
char, 3>>(y, x)[1] 
+= 0;
        
compositeImage.at<cv::Vec<unsigned 
char, 3>>(y, x)[2] 
+= 128;
      }
     
else if (category == cv::GC_BGD || category == 
cv::GC_PR_BGD) {
        
regionImage.at<cv::Vec<unsigned 
char, 3>>(y, x)[0] 
= (category == cv::GC_BGD ? 255 : 127);
        
regionImage.at<cv::Vec<unsigned 
char, 3>>(y, x)[1] 
= 0;
        
regionImage.at<cv::Vec<unsigned 
char, 3>>(y, x)[2] 
= 0;
        
compositeImage.at<cv::Vec<unsigned 
char, 3>>(y, x)[0] 
+= 128;
        
compositeImage.at<cv::Vec<unsigned 
char, 3>>(y, x)[1] 
+= 0;
        
compositeImage.at<cv::Vec<unsigned 
char, 3>>(y, x)[2] 
+= 0;
        
fgImage.at<cv::Vec<unsigned 
char, 3>>(y, x)[0] 
= 255;
        
fgImage.at<cv::Vec<unsigned 
char, 3>>(y, x)[1] 
= 255;
        
fgImage.at<cv::Vec<unsigned 
char, 3>>(y, x)[2] 
= 255;
      }
    }
  }
  // Output
  cv::imwrite("stroke.bmp", 
strokeImage);
  cv::imwrite("region.bmp", 
regionImage);
  cv::imwrite("composite.bmp", 
compositeImage);
  cv::imwrite("foreground.bmp", 
fgImage);
  return 0;
}

[入力] 対象画像 image.bmp

[入力] ユーザストローク(赤:前景,青:背景) mask.bmp

対象画像とストロークの合成画像(確認のための画像) stroke.bmp

[出力] 領域分割結果(赤:前景,青:背景) region.bmp

[出力] 領域分割結果と対象画像を重ね合わせた画像 composite.bmp

[出力] 入力画像に対して,背景の画素を白で表現した画像 foreground.bmp
このページでは,OpenCVのgrabCut関数の実装例を紹介しています.GrabCutやLazy Snappingの説明は省きます.OpenCVのgrabCut関数をGrabCutとして使う方法は他のウェブページでも解説があるので,このページではその説明は省略します.このページでは,OpenCVのgrabCut関数をLazy Snappingとして使う方法を説明します.grabCut関数は以下のようなものです.
void cv::grabCut( InputArray _img, InputOutputArray _mask, Rect rect, InputOutputArray _bgdModel, InputOutputArray _fgdModel, int iterCount, int mode )
まず,_imgですが,対象の入力画像を指定します.3チャンネル8bitのcv::Mat形式の変数を指定します.
続いて,_maskですが,後で詳しく説明します.
rectについては,Lazy Snappingとして使う場合は,cv::Rect形式の変数を指定するだけでOKです.
_bgdModelと_fgdModelですが,cv::Mat形式の変数を与えてやるだけでOKです.
iterCountは反復回数です.1~10ぐらいの値を指定します.
modeですが,Lazy Snappingとして使う場合は,cv::GC_INIT_WITH_MASKを指定します.
さて,_maskについて説明します.これは,1チャンネル8bitのcv::Mat形式の変数です.対象入力画像と同じサイズです.各画素には,以下の4つの値のいずれかを指定します.
cv::GC_BGD // 値は0.ユーザが背景だと指定した画素を表します.
cv::GC_FGD // 値は1.ユーザが前景だと指定した画素を表します.
cv::GC_PR_BGD // 値は2.背景である可能性のある画素を表します.
cv::GC_PR_FGD // 値は3.前景である可能性のある画素を表します.
GC_BGDやGC_FGDを指定された画素は,その後の計算で値が変化することはありません.GC_PR_BGDやGC_PR_FGDが指定された画素は,その後の計算でgrabCut関数が適切にGC_PR_BGDかGC_PR_FGDの値を割り当てます.
よくある間違いとしては,_maskの全ての画素を0で初期化したままにしてはいけない,というものがあります.0はGC_BGDですので,その後にいくらgrabCut関数を呼び出しても,その画素はずっと背景のまま変化しません.前景か背景かをgrabCut関数に計算してもらいたい画素に対しては必ずGC_PR_BGDかGC_PR_FGDの値を設定する必要があります.
grabCut関数を呼び出す前,_maskの各画素には初期値を設定します.GC_BGDとGC_FGD以外の画素にはGC_PR_BGDかGC_PR_FGDのどちらかの値を設定します.背景である可能性のほうが高ければGC_PR_BGDを,前景である可能性のほうが高ければGC_PR_FGDを設定します.
grabCut関数を呼び出した後,領域分割の結果が_maskに上書きされます.元々GC_BGDやGC_FGDだった画素は変化しません.それ以外の画素にはGC_PR_BGDかGC_PR_FGDが入っています.ある画素がもしGC_FGDもしくはGC_PR_FGDであれば,その画素は前景です.ある画素がもしGC_BGDもしくはGC_PR_BGDであれば,その画素は背景です.これが領域分割結果となります.