23

I am working on an app that is expected to remove image backgrounds using opencv, at first I tried using grabcut but it was too slow and the results were not always accurate, then I tried using threshold, although the results are not yet close th grabcut, its very fast and looks like a better, So my code is first looking at the image hue and analying which portion of it appears more, that portion is taken in as the background, the issue is at times its getting the foreground as background below is my code:

private Bitmap backGrndErase()
{

    Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.skirt);
    Log.d(TAG, "bitmap: " + bitmap.getWidth() + "x" + bitmap.getHeight());


    bitmap = ResizeImage.getResizedBitmap(bitmap, calculatePercentage(40, bitmap.getWidth()), calculatePercentage(40, bitmap.getHeight()));

    Mat frame = new Mat();
    Utils.bitmapToMat(bitmap, frame);

    Mat hsvImg = new Mat();
    List<Mat> hsvPlanes = new ArrayList<>();
    Mat thresholdImg = new Mat();

    // int thresh_type = Imgproc.THRESH_BINARY_INV;
    //if (this.inverse.isSelected())
    int thresh_type = Imgproc.THRESH_BINARY;

    // threshold the image with the average hue value
    hsvImg.create(frame.size(), CvType.CV_8U);
    Imgproc.cvtColor(frame, hsvImg, Imgproc.COLOR_BGR2HSV);
    Core.split(hsvImg, hsvPlanes);

    // get the average hue value of the image
    double threshValue = this.getHistAverage(hsvImg, hsvPlanes.get(0));

    Imgproc.threshold(hsvPlanes.get(0), thresholdImg, threshValue, mThresholdValue, thresh_type);
   // Imgproc.adaptiveThreshold(hsvPlanes.get(0), thresholdImg, 255, Imgproc.ADAPTIVE_THRESH_MEAN_C, Imgproc.THRESH_BINARY, 11, 2);

    Imgproc.blur(thresholdImg, thresholdImg, new Size(5, 5));

    // dilate to fill gaps, erode to smooth edges
    Imgproc.dilate(thresholdImg, thresholdImg, new Mat(), new Point(-1, -1), 1);
    Imgproc.erode(thresholdImg, thresholdImg, new Mat(), new Point(-1, -1), 3);

    Imgproc.threshold(thresholdImg, thresholdImg, threshValue, mThresholdValue, Imgproc.THRESH_BINARY);
    //Imgproc.adaptiveThreshold(thresholdImg, thresholdImg, 255, Imgproc.ADAPTIVE_THRESH_MEAN_C, Imgproc.THRESH_BINARY, 11, 2);

    // create the new image
    Mat foreground = new Mat(frame.size(), CvType.CV_8UC3, new Scalar(255, 255, 255));
    frame.copyTo(foreground, thresholdImg);


    Utils.matToBitmap(foreground,bitmap);
    //return foreground;

    alreadyRun = true;
    return  bitmap;

}

the method responsible for Hue:

    private double getHistAverage(Mat hsvImg, Mat hueValues)
{
    // init
    double average = 0.0;
    Mat hist_hue = new Mat();
    // 0-180: range of Hue values
    MatOfInt histSize = new MatOfInt(180);
    List<Mat> hue = new ArrayList<>();
    hue.add(hueValues);

    // compute the histogram
    Imgproc.calcHist(hue, new MatOfInt(0), new Mat(), hist_hue, histSize, new MatOfFloat(0, 179));

    // get the average Hue value of the image
    // (sum(bin(h)*h))/(image-height*image-width)
    // -----------------
    // equivalent to get the hue of each pixel in the image, add them, and
    // divide for the image size (height and width)
    for (int h = 0; h < 180; h++)
    {
        // for each bin, get its value and multiply it for the corresponding
        // hue
        average += (hist_hue.get(h, 0)[0] * h);
    }

    // return the average hue of the image
    average = average / hsvImg.size().height / hsvImg.size().width;
    return average;
}

A sample of the input and output:[Input Image1] Output Image

Input Image 2 and Output: enter image description here enter image description here

Input Image 3 and Output: enter image description here enter image description here

life evader
  • 970
  • 8
  • 16
  • 1
    The border in the shirt example is probably due the fact that you're working with JPEG images. For other examples, this kind of tasks on non-trivial backgrounds is not easy at all :D. – Miki Dec 09 '15 at 15:51
  • I have been on it for 3 weeks, tomorrow will mark the 4th week still no much progress – life evader Dec 09 '15 at 16:37
  • I can suggest you this approach: create your "background mask" starting from a color-reduced version of your original image (e.g. consider a 256 color image). In this way, you will have less color bucket and thus (maybe) an higher tolerance near color edges – bonnyz Dec 09 '15 at 16:44
  • could you help me with a sample code? – life evader Dec 09 '15 at 16:47
  • 1
    Take a look here: http://stackoverflow.com/a/10179800/2760919 This is Python implementation which reduce the number of color of the image (actually, there are many similar methods in the page), you will need to adapt one of them to Java (but it may be really slow to execute) – bonnyz Dec 09 '15 at 17:00
  • what does "Number of runs" mean? – sturkmen Dec 09 '15 at 23:27
  • Indicator1 is showing my current value of `mThresholdValue` whilst runs is indicating how many times I have changed the value of `mThresholdValue` its basically there for `debugging` – life evader Dec 09 '15 at 23:31
  • @lifeevader probably you can work 5 years on it without reaching perfect results for general cases without prior assumptions that will hold. – Micka Dec 10 '15 at 09:02
  • @Micka am guessing u right, but do u have an idea as to why this is happening? – life evader Dec 10 '15 at 09:14
  • because thresholding is a very primitive method and vision is extremely difficult in general. – Micka Dec 10 '15 at 09:17
  • what would you suggest?basically all Images I will be working with will be like Image 3 – life evader Dec 10 '15 at 09:22

3 Answers3

3

Indeed, as others have said you are unlikely to get good results just with a threshold on hue. You can use something similar to GrabCut, but faster.

Under the hood, GrabCut calculates foreground and background histograms, then calculates the probability of each pixel being FG/BG based on these histograms, and then optimizes the resulting probability map using graph cut to obtain a segmentation.

Last step is most expensive, and it may be ignored depending on the application. Instead, you may apply the threshold to the probability map to obtain a segmentation. It may (and will) be worse than GrabCut, but will be better than your current approach.

There are some points to consider for this approach. The choice of histogram model would be very important here. You can either consider 2 channels in some space like YUV or HSV, consider 3 channels of RGB, or consider 2 channels of normalized RGB. You also have to select an appropriate bin size for those histograms. Too small bins would lead to 'overtraining', while too large will reduce the precision. The tradeoffs between those are a topic for a separate discussion, in brief - I would advice using RGB with 64 bins per channel for start and then see what changes are better for your data.

Also, you can get better results for coarse binning if you use interpolation to get values between bins. In past I have used trilinear interpolation and it was kind of good, compared to no interpolation at all.

But remember that there are no guarantees that your segmentation will be correct without prior knowledge on object shape, either with GrabCut, thresholding or this approach.

alexisrozhkov
  • 1,623
  • 12
  • 18
0

I would try again Grabcut, it is one of the best segmentation methods available. This is the result I get

cv::Mat bgModel,fgModel; // the models (internally used)
cv::grabCut(image,// input image
            object_mask,// segmentation result
            rectang,// rectangle containing foreground
            bgModel,fgModel, // models
            5,// number of iterations
            cv::GC_INIT_WITH_RECT); // use rectangle
// Get the pixels marked as likely foreground
cv::compare(object_mask,cv::GC_PR_FGD,object_mask,cv::CMP_EQ);
cv::threshold(object_mask, object_mask, 0,255, CV_THRESH_BINARY);  //ensure the mask is binary

The only problem of Grabcut is that you have to give as an input a rectangle containing the object you want to extract. Apart from that it works pretty well.

hoaphumanoid
  • 977
  • 1
  • 9
  • 25
  • to be honest thats very impressive,i tried grabcut, it was kind of slow but for such good results I can try using it again, but even when I was using it, it used to mark the right low corner of the image out just like it did in your result, how can i prevent that from happening – life evader Dec 11 '15 at 15:44
  • It is difficult to do that automatically, since it's the algorithm the one who iteratively selects what is foreground and what is background. Although you can give the algorithm a ground truth like GC_BGD or GC_PR_FGD values. More information here http://docs.opencv.org/2.4/modules/imgproc/doc/miscellaneous_transformations.html#grabcut and here http://docs.opencv.org/master/d8/d83/tutorial_py_grabcut.html#gsc.tab=0 – hoaphumanoid Dec 12 '15 at 06:55
  • A trick to speed up Grabcut is to reduce the image first, the apply GC and obtain the segmentation matrix, then amplify the matrix to the original size and multiply it by the original image. You might lose quality though – hoaphumanoid Dec 12 '15 at 07:00
0

Your method of finding average hue is WRONG! As you most probably know, hue is expressed as angle and takes value in [0,360] range. Therefore, a pixel with hue 360 essentially has same colour as a pixel with hue 0 (both are pure red). In the same way, a pixel with hue 350 is actually closer to a pixel with hue 10 than a pixel with hue, say for example, 300.

As for opencv, cvtColor function actually divides calculated hue value by 2 to fit it in 8 bit integer. Thus, in opencv, hue values wrap after 180. Now, consider we have two red(ish) pixels with hues 10 and 170. If we take their average, we will get 90 — hue of pure cyan, the exact opposite of red — which is not our desired value.

Therefore, to correctly find the average hue, you need to first find average pixel value in RGB colour space, then calculate the hue from this RGB value. You can create 1x1 matrix with average RGB pixel and convert it to HSV/HSL.

Following the same reasoning, applying threshold to hue image doesn't work flawlessly. It does not consider wrapping of hue values.

If I understand correctly, you want to find pixels with similar hue as the background. Assuming we know the colour of background, I would do this segmentation in RGB space. I would introduce some tolerance variable. I would use the background pixel value as centre and this tolerance as radius and thus define a sphere in RGB colour space. Now, rest is inspecting each pixel value, if it falls inside this sphere, then classify as background; otherwise, regard it as foreground pixel.

asif
  • 975
  • 8
  • 16