Implementing Lazy Snapping using OpenCV's grabCut function

Tweet


Source code

Here, we introduce how to implement Lazy Snapping using OpenCV 2.4.11. OpenCV's grabCut function is used. GUI is not implemented here. The strokes which represents the foreground or the background is assumed to be given.

// 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 files


[Input] Target image image.bmp


[Input] User stroke (Red: foregound, blue: background) mask.bmp


Mixture of the target image and the strokes (This image is for visualization purpose) stroke.bmp


[Output] Region segmentation result (Red: foreground, blue: background) region.bmp


[Output] Mixture of the target image and the segmentation result composite.bmp


[Ouput] Target image with its pixel which is judged as background filled with white color foreground.bmp


Explanation

This page explains the example of using OpenCV's grabCut function. Explanation of GrabCut and Lazy Snapping is omitted. If you want to know how to use OpenCV's grabCut function as GrabCut, please read other webpages. This page  explains how to use OpenCV's grabCut function as Lazy Snapping. grabCut function is as follows.

void cv::grabCut( InputArray _img, InputOutputArray _mask, Rect rect, InputOutputArray _bgdModel, InputOutputArray _fgdModel, int iterCount, int mode )

Set the target input image to _img. This variable is 3-channel 8-bit cv::Mat type variable.
_mask is explained later.
As for rect, just use cv::Rect variable when you use as Lazy Snapping.
As for _bgdModel and _fgdModel, just set cv::Mat variable.
iterCount is the number of iteration. Set a value, for example, from 1 to 10.
As for mode, set cv::GC_INIT_WITH_MASK when you use as Lazy Snapping.

_mask is a 1-channel 8-bit cv::Mat variable. Size is the same as the target input image. For each pixel, the value of one of the followings are set.
cv::GC_BGD // This value is 0. The user has set this pixel as background.
cv::GC_FGD // This value is 1. The user has set this pixel as foreground.
cv::GC_PR_BGD // This value is 2. This pixel is possibly background.
cv::GC_PR_FGD // This value is 3. This pixel is possibly foreground.

The pixel which is set as GC_BGD or GC_FGD would not change during the further computation. grabCut function adequately assigns either GC_PR_BGD or GC_PR_FGD for the pixels where GC_PR_BGD or GC_PR_FGD is set beforehand.

Be careful not to use _mask which is initialized by zero. 0 is GC_BGD, so such pixel remain unchanged when grabCut function is called. If you want to let grabCut function compute whether the pixel is foreground or background, you have to set GC_PR_BGD or GC_PR_FGD.

Before calling grabCut function, set initival value for each pixel of _mask. You have to set either GC_PR_BGD or GC_PR_FGD for the pixel except for GC_BGD or GC_FGD. Set GC_PR_BGD if the probability to be background is high, ore set GC_PR_FGD if the probability to be foregound is high.

The region segmentation result is overwritten to _mask after calling grabCut function. The pixel where GC_BGD or GC_FGD is set remains unchanged. Other pixels are GC_PR_BGD or GC_PR_FGD. The pixel which is GC_FGD or GC_PR_FGD is foreground. The pixel which is GC_BGD or GC_PR_BGD is backround. This is the region segmentation result.


Back