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):
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):
drawContours
just draws into theMat
you've provided it -- it won't change the channel count. - Dan MašekdrawContours
into thefor
loop, and have it draw one contour at a time into a blank image, and visualize it using HighGUI (or just save the images to disk instead). - Dan Mašekcontours.remove(i);
inside the loop that iterates overcontours
. You increment the index, even when you remove, so that you end up skipping some entries. Instead of removing undesireable ones from the original contours array, add the desired ones into a newArrayList
and then draw that. - Dan Mašek