19

I use OpenCV and Python and I want to remove the small connected object from my image.

I have the following binary image as input:

Input binary image

The image is the result of this code:

dilation = cv2.dilate(dst,kernel,iterations = 2)
erosion = cv2.erode(dilation,kernel,iterations = 3)

I want to remove the objects highlighted in red:

enter image description here

How can I achieve this using OpenCV?

Dan Mašek
  • 17,852
  • 6
  • 57
  • 85
Zahra
  • 369
  • 1
  • 2
  • 9
  • 7
    What criteria did you use to determine which objects to highlight? Why those particular 7 and any of the other similarly sized or smaller objects? – Dan Mašek Mar 15 '17 at 00:18
  • 1
    nn i want to remove all smalls object and this 7 its just example for object how i want to remove it –  Zahra Mar 15 '17 at 08:23
  • 1
    @DanMašek I want to use the surface of the object as a criteria –  Zahra Mar 15 '17 at 08:36
  • @Zahra: What about `findContours`, filter the contours by `contourArea`, and `drawContours` on the small ones filling them with black. – Dan Mašek Mar 15 '17 at 13:33
  • 1
    @DanMašek i know this method but i need to use a method without contour :( –  Zahra Mar 15 '17 at 15:36
  • 1
    @Zahra Then why didn't you mention that fact (and specify that requirement) that in your question? – Dan Mašek Mar 15 '17 at 15:52
  • @DanMašek but my question about connected objects and also with abinary image It's not sufficient? –  Zahra Mar 15 '17 at 16:32
  • You could get contours and discard small ones by area. Or you can use connected components and discard small ones by area – fmw42 Jan 17 '23 at 19:41

4 Answers4

53

How about with connectedComponentsWithStats (doc):

# find all of the connected components (white blobs in your image).
# im_with_separated_blobs is an image where each detected blob has a different pixel value ranging from 1 to nb_blobs - 1.
nb_blobs, im_with_separated_blobs, stats, _ = cv2.connectedComponentsWithStats(im)
# stats (and the silenced output centroids) gives some information about the blobs. See the docs for more information. 
# here, we're interested only in the size of the blobs, contained in the last column of stats.
sizes = stats[:, -1]
# the following lines result in taking out the background which is also considered a component, which I find for most applications to not be the expected output.
# you may also keep the results as they are by commenting out the following lines. You'll have to update the ranges in the for loop below. 
sizes = sizes[1:]
nb_blobs -= 1

# minimum size of particles we want to keep (number of pixels).
# here, it's a fixed value, but you can set it as you want, eg the mean of the sizes or whatever.
min_size = 150  

# output image with only the kept components
im_result = np.zeros_like(im_with_separated_blobs)
# for every component in the image, keep it only if it's above min_size
for blob in range(nb_blobs):
    if sizes[blob] >= min_size:
        # see description of im_with_separated_blobs above
        im_result[im_with_separated_blobs == blob + 1] = 255

Output : enter image description here

Soltius
  • 2,162
  • 1
  • 16
  • 28
  • 2
    @sotius thanks ...I try your solution but it gives me an error :Build\OpenCV\opencv-3.2.0\modules\imgproc\src\connectedcomponents.cpp:1664: error: (-215) iDepth == CV_8U || iDepth == CV_8S in function cv::connectedComponents_sub1 –  Zahra Mar 15 '17 at 15:34
  • 3
    @Zahra , input image of the function needs to be 8bit. Convert using img=img.astype(numpy.uint8) – Soltius Mar 15 '17 at 16:04
  • 2
    Note that the line `im_result = np.zeros(im.shape)` always creates an array of dtype float64 instead of adapting the dtype of the input image. Changing the dtype of an image array in the midst of a more complicated image processing chain may cause unexpected errors, which is why I suggest changing the line to `im_result = np.zeros_like(im)`. – Thomas Fritz Jan 17 '23 at 12:08
  • 1
    @ThomasFritz you're right, I've edited the code to take this into account. – Soltius Jan 17 '23 at 13:53
1

In order to remove objects automatically you need to locate them in the image. From the image you provided I see nothing that distinguishes the 7 highlighted items from others. You have to tell your computer how to recognize objects you don't want. If they look the same, this is not possible.

If you have multiple images where the objects always look like that you could use template matching techniques.

Also the closing operation doesn't make much sense to me.

Piglet
  • 27,501
  • 3
  • 20
  • 43
0

#For isolated or unconnected blobs: Try this (you can set noise_removal_threshold to whatever you like and make it relative to the largest contour for example or a nominal value like 100 or 25).

    mask = np.zeros_like(img)
    for contour in contours:
      area = cv2.contourArea(contour)
      if area > noise_removal_threshold:
        cv2.fillPoly(mask, [contour], 255)
  • Answers should be complete: (add this to your answer) ``` contours, hierarchy = cv2.findContours(edged, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) ``` – Priyank Pathak Jul 26 '23 at 07:14
0

Removing small connected components by area is called area opening. OpenCV does not have this as a function, it can be implemented as shown in other answers. But most other image processing packages will have an area opening function.

For example using scikit-image:

import skimage
import imageio.v3 as iio

img = iio.imread('cQMZm.png')[:,:,0]

out = skimage.morphology.area_opening(img, area_threshold=150, connectivity=2)

For example using DIPlib:

import diplib as dip

out = dip.AreaOpening(img, filterSize=150, connectivity=2)

PS: The DIPlib implementation is noticeably faster. Disclaimer: I'm an author of DIPlib.

Cris Luengo
  • 55,762
  • 10
  • 62
  • 120