简体   繁体   中英

Removing isolated pixels using OpenCV

I'm looking for a way to remove isolated white pixels from a binary image using OpenCV. A similar question ( OpenCV get rid of isolated pixels ) has a bunch of "answers" but none seem to work for me. I've tried various combinations of opening and closing without success as well.

The article here:

https://homepages.inf.ed.ac.uk/rbf/HIPR2/hitmiss.htm

Suggests I can use the hit-or-miss operation for exactly this purpose:

结构元素

1 is used to locate isolated points in a binary image

And that the reason why is that 0s are interpreted differently than when they are used with erosion/dilation directly (where 0s are interpreted as "don't care's" rather than "not white" which is basically what I'm after). However, using this kernel simply renders the original image.

My input image is this:

计算输入图像

You'll notice there's a few white pixels near the left-hand side of the image which I'd like to get rid of.

Here's the code:

kernel = np.array([ [0, 0, 0],
                    [0, 1, 0],
                    [0, 0, 0]],np.uint8)

hitormiss = cv2.morphologyEx(input_image, cv2.MORPH_HITMISS, kernel)

cv2.imshow('hitormiss', hitormiss)

What is the right way of removing isolated pixels like these?

Update : Alexander's answer works like a charm and is the fastest solution. The other answer provides a solution too, which is to use the cv2.connectedComponents function, but it is much more processor-intensive. Here's a function that uses this approach:

def remove_isolated_pixels(self, image):
    connectivity = 8

    output = cv2.connectedComponentsWithStats(image, connectivity, cv2.CV_32S)

    num_stats = output[0]
    labels = output[1]
    stats = output[2]

    new_image = image.copy()

    for label in range(num_stats):
        if stats[label,cv2.CC_STAT_AREA] == 1:
            new_image[labels == label] = 0

    return new_image

I believe the OpenCV implementation was broken. There was a related issue on OpenCV's GitHub which seems to have merged a pull request to fix; I think it was added to OpenCV 3.3-rc as referenced in the pull request so hopefully this should be fixed by the next time you update OpenCV. I'm not sure if the problem is caused by the same thing or not.

The creative solution from the selected answer is great, but I agree with you: there must be a better way , despite the broken implementation.

On the OpenCV Hit-or-miss tutorial they state:

Therefore, the hit-or-miss operation comprises three steps:

  1. Erode image A with structuring element B1 .
  2. Erode the complement of image A ( A_c ) with structuring element B2 .
  3. AND results from step 1 and step 2.

It then goes on to say that this can be accomplished with a single kernel in the hit-or-miss transform, but as we know, it is broken. So let's do those steps instead.

import cv2
import numpy as np

# load image, ensure binary, remove bar on the left
input_image = cv2.imread('calc.png', 0)
input_image = cv2.threshold(input_image, 254, 255, cv2.THRESH_BINARY)[1]
input_image_comp = cv2.bitwise_not(input_image)  # could just use 255-img

kernel1 = np.array([[0, 0, 0],
                    [0, 1, 0],
                    [0, 0, 0]], np.uint8)
kernel2 = np.array([[1, 1, 1],
                    [1, 0, 1],
                    [1, 1, 1]], np.uint8)

hitormiss1 = cv2.morphologyEx(input_image, cv2.MORPH_ERODE, kernel1)
hitormiss2 = cv2.morphologyEx(input_image_comp, cv2.MORPH_ERODE, kernel2)
hitormiss = cv2.bitwise_and(hitormiss1, hitormiss2)

cv2.imshow('isolated.png', hitormiss)
cv2.waitKey()

孤立的像素

And then to remove, it's as simple as inverting the hitormiss and using that as a mask in cv2.bitwise_and() with the input_image .

hitormiss_comp = cv2.bitwise_not(hitormiss)  # could just use 255-img
del_isolated = cv2.bitwise_and(input_image, input_image, mask=hitormiss_comp)
cv2.imshow('removed.png', del_isolated)
cv2.waitKey()

删除了孤立的像素


Note: as discussed in the comments, erosion with kernel1 in this specific case is identical to the input binary image, so there's no need to do this computation, and this introduces some other unnecessary steps as well in this specific case. However, you could have different kernels than just a single 1 in the middle, so I'm going to keep the code as-is to keep it general for any kernels.

  1. Run connected component labeling
  2. Calculate the number of pixels each component have
  3. For each component with less than a min number of pixels convert all the component pixels to zero.

This is how I solved it:

import cv2 as cv
import numpy as np

# let's say "image" is a thresholded image

kernel = np.array([ [-1, -1, -1],
                    [-1,  1, -1],
                    [-1, -1, -1] ], dtype="int")
single_pixels = cv.morphologyEx(image, cv.MORPH_HITMISS, kernel)
single_pixels_inv = cv.bitwise_not(single_pixels)
image = cv.bitwise_and(image, image, mask=single_pixels_inv)

# now "image" shouldn't have alone pixels

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM