Gabe
Gabe

Reputation: 115

OpenCV Grabcut outputs only what is marked as GC_FGD

I am using JavaCV version 3.4.2 along with Android SDK 27. I am trying to use grabcut with a mask first.

Here is my input image (left) and the mask (right):

Note that here I am showing the mask with the shirt underlayed. The mask is actually all black, whith the green marks being = GC_FGD and red = GC_BGD.

Here is the result :(

The code that runs grabcut:

Mat mask = imageView.mask;

// Create ROI
Rect bounding_box = new Rect(0, 0, mask.cols(), mask.rows());

// extracted features for foreground & bg
Mat bgdModel = new Mat();
Mat fgdModel = new Mat();

// Get original image and convert from RGBA to RGB
Mat original_image = new Mat();
Utils.bitmapToMat(imageView.original_bitmap, original_image);
Imgproc.cvtColor(original_image, original_image, Imgproc.COLOR_RGBA2RGB);

// Do extraction
Imgproc.grabCut(original_image, mask, bounding_box, bgdModel, fgdModel,
        /*iterCount:*/1, Imgproc.GC_INIT_WITH_MASK);

Mat foreground = new Mat(original_image.size(), CvType.CV_8UC1,
        new Scalar(0, 0, 0));
Bitmap output_image = Bitmap.createBitmap(mask.cols(), mask.rows(),
        Bitmap.Config.ARGB_8888);
Mat source = new Mat(1, 1, CvType.CV_8U, new Scalar(0));

original_image.copyTo(foreground, mask);
Utils.matToBitmap(foreground, output_image);

Here is the code that crates the mask (literally drawing onto the mat with wither GC_BGD or GC_FGD depending on which color is selected on the UI. Green is fg and red is bg):

// I am thinking that the issue lies here. It is assuming all black is DEF bg. Idk how to circumvent this.
mask = new Mat (bmp.getHeight(), bmp.getWidth(), CvType.CV_8U, new Scalar(Imgproc.GC_BGD)); 
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        int action = event.getAction();
        byte[] buffer = new byte[3];
        switch (action)
        {
            case MotionEvent.ACTION_DOWN:
                downx = getPointerCoords(event)[0];//event.getX();
                downy = getPointerCoords(event)[1];//event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                upx = getPointerCoords(event)[0];//event.getX();
                upy = getPointerCoords(event)[1];//event.getY();
                canvas.drawLine(downx, downy, upx, upy, paint);
                Imgproc.line(mask, new Point(downx, downy), new Point(upx, upy),
                        new Scalar(
                                paint.getColor() == Color.parseColor("#1de9b6") ?
                                        (byte)Imgproc.GC_FGD : (byte)Imgproc.GC_BGD
                        ), 10);
                invalidate();
                downx = upx;
                downy = upy;
                break;
            case MotionEvent.ACTION_UP:
                upx = getPointerCoords(event)[0];//event.getX();
                upy = getPointerCoords(event)[1];//event.getY();
                canvas.drawLine(downx, downy, upx, upy, paint);
                Imgproc.line(mask, new Point(downx, downy), new Point(upx, upy),
                        new Scalar(
                                paint.getColor() == Color.parseColor("#1de9b6") ?
                                (byte)Imgproc.GC_FGD : (byte)Imgproc.GC_BGD
                        ), 10);
                invalidate();
                break;
            case MotionEvent.ACTION_CANCEL:
                break;
            default:
                break;
        }

Upvotes: 0

Views: 347

Answers (1)

Gabe
Gabe

Reputation: 115

I was able to fix my problem. There were multiple problems with the implementation. The reason that my output was just the selected GC_FDG line is that everything else in the mask was GC_BGD. I changed everything my mask to be filled with GC_PR_BGD instead and then annotated the parts with GC_FDG and GC_BGD.

That finally created a different mask, but the output was still the same. The mask that GrabCut generated was the GC_FGD I annotated, GC_PR_BGD that was automatically generated by grabcut (the prediction of what my image is), my selected GC_BGD, and everything else was GC_PR_BGD.

Because of this, I used Core.compare to generate the appropriate masks. My updated code is here:

Doing GrabCut:

// Get masked from the drawing we made
Mat mask = imageView.mask;

// Create ROI
Rect bounding_box = new Rect(10, 10, mask.cols()-10,
        mask.rows()-10);

// Get original image and convert from RGBA to RGB
Mat original_image = new Mat();
Utils.bitmapToMat(imageView.original_bitmap, original_image);
Imgproc.cvtColor(original_image, original_image, Imgproc.COLOR_RGBA2RGB);

// Do extraction
Imgproc.grabCut(original_image, mask, bounding_box, new Mat(), new Mat(),
        NUM_ITERATIONS, Imgproc.GC_INIT_WITH_MASK);

// New mask to hold ONLY what was marked GC_PR_FGD by grabcut on our mask.
Mat probable_fgd_mask = new Mat();
Core.compare(mask, new Scalar(Imgproc.GC_PR_FGD), probable_fgd_mask, Core.CMP_EQ);

// Reusing mask variable, store into mask only what was marked as GC_FGD
// inside of mask.
Core.compare(mask, new Scalar(Imgproc.GC_FGD), mask, Core.CMP_EQ);

// Combine both masks so that we have GC_FGD + GC_PR_FGD
Core.add(probable_fgd_mask, mask, mask);

// We will store the foreground into an all-black Mat,
Mat foreground = new Mat(original_image.size(), CvType.CV_8UC1,
        new Scalar(0, 0, 0));

// Copy the original image to 'foreground', but mask it with our newly created
// mask (GC_FGD + GC_PR_FGD)
original_image.copyTo(foreground, mask);

and the only change to the code that creates the mask is how we create the Mat:

FROM:

mask = new Mat (bmp.getHeight(), bmp.getWidth(), CvType.CV_8U, new Scalar(Imgproc.GC_BGD));

TO:

mask = new Mat (bmp.getHeight(), bmp.getWidth(), CvType.CV_8U, new Scalar(Imgproc.GC_PR_BGD));

Hopefully this helps someone else :) I was stuck on this for a day and a half!

Upvotes: 1

Related Questions