openCV Java | Remove blobs by area in a binary image

Viewed 647

I have a black&white image (single channel, 0's and 255's only) and I'd like to remove small blobs in it that are below a certain area threshold using openCV 3.4.2 in Java.

Now I already found the following thread: Removing blobs from a binary image, which is pretty much the same - but I need some help translating the answer into Java code. Also, since I'd like to remove black spots, I invert the image before and after processing. EDIT: thanks to some kind suggestions I managed to get a working code and modified it so now it's an MCVE.

My attempt so far:

import java.util.ArrayList;

import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.Scalar;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;

public class contourCheck {

public static void main(String[] args) {

    // initialises openCV   
    System.loadLibrary(Core.NATIVE_LIBRARY_NAME);   

    // reads original black&white image
    Mat binary_image =  Imgcodecs.imread("C:/Users/MyName/Desktop/TestImages/testpic.png", Imgcodecs.CV_LOAD_IMAGE_GRAYSCALE);

    // creates temporary Mat
    Mat temp_image = binary_image;

    // inverts image
    Core.bitwise_not(temp_image,temp_image);

    // finds all contours in the image
    ArrayList<MatOfPoint> contours = new ArrayList<MatOfPoint>();
    Imgproc.findContours(temp_image, contours, new Mat(), Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_NONE);

    // deletes contours above minArea to keep only the contours that are supposed to be removed from the image
    int minArea = 10;
    for (int i = 0; i < contours.size(); i++) {
        double area = Imgproc.contourArea(contours.get(i));
        if (area > minArea) {
            contours.remove(i);
        }   
    } 
    System.out.println("number of contours remaining: " + contours.size());  
    for (int j = 0; j < contours.size(); j++) {

        if (j > 0) { // apparently temp_image gets also inverted, therefore it gets inverted here once again
            Core.bitwise_not(temp_image,temp_image);
        }
        // fills in small (<= minArea) contours with 0's  
        Imgproc.drawContours(temp_image, contours,j, new Scalar(0),Core.FILLED);

        // inverts image once again to get the original state
        Core.bitwise_not(temp_image,binary_image);

        // writes image with filtered contours
        Imgcodecs.imwrite("C:/Users/MyName/Desktop/TestImages/test/testpic_filtered" + j + ".png", binary_image);
    }
}
}

Now here's an example picture where I'd like to remove all black spots that are below minArea(= 10):

example binary image ("testpic.png")

What surprises me now is that some of the very big components are removed (e.g. the huge circle with some little circles inside or the huge rectangle) while smaller ones are kept. Is there an error in my code or am I misunderstanding some concepts here? Also, why does bitwise_not also invert temp_image, the source Mat (Core.bitwise_not(temp_image,binary_image))?

Note: the code creates an image for every contour that is removed, meaning that 74 images are created.

Example output (last image after removing all the contours):

image with contours removed

1 Answers

The problem lies in the following loop:

for (int i = 0; i < contours.size(); i++) {
    double area = Imgproc.contourArea(contours.get(i));
    if (area > minArea) {
        contours.remove(i);
    }   
}

Notice, that you iterate over contours, and at the same time you sometimes remove elements. Remember that remove will shift all the subsequent elements forward by one position. Now, you increment the index on every iteration. This will cause you to skip a contour, for every one you've removed. Since your current goal is to retain only the small enough contours, the effect is that some of the bigger ones slip through.

My suggestion to solve this problem would be to take a slightly different approach -- rather than removing the undesireable contours, I'd create a new instance of ArrayList<MatOfPoint>, and populate it with the contours I'm interested in and use that in further processing.

Related