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であれば,その画素は背景です.これが領域分割結果となります.