removing pixels less than n size(noise) in an image - open CV python

15,550

Solution 1

Preprocessing

A good idea when you're filtering an image is to lowpass the image or blur it a bit; that way neighboring pixels become a little more uniform in color, so it will ease brighter and darker spots on the image and keep holes out of your mask.

img = cv2.imread('image.jpg')
blur = cv2.GaussianBlur(img, (15, 15), 2)
lower_green = np.array([50, 100, 0])
upper_green = np.array([120, 255, 120])
mask = cv2.inRange(blur, lower_green, upper_green)
masked_img = cv2.bitwise_and(img, img, mask=mask)
cv2.imshow('', masked_img)
cv2.waitKey()

Blurred filter

Colorspace

Currently, you're trying to contain an image by a range of colors with different brightness---you want green pixels, regardless of whether they are dark or light. This is much more easily accomplished in the HSV colorspace. Check out my answer here going in-depth on the HSV colorspace.

img = cv2.imread('image.jpg')
blur = cv2.GaussianBlur(img, (15, 15), 2)
hsv = cv2.cvtColor(blur, cv2.COLOR_BGR2HSV)
lower_green = np.array([37, 0, 0])
upper_green = np.array([179, 255, 255])
mask = cv2.inRange(hsv, lower_green, upper_green)
masked_img = cv2.bitwise_and(img, img, mask=mask)
cv2.imshow('', masked_img)
cv2.waitKey()

HSV filtering

Removing noise in a binary image/mask

The answer provided by ngalstyan shows how to do this nicely with morphology. What you want to do is called opening, which is the combined process of eroding (which more or less just removes everything within a certain radius) and then dilating (which adds back to any remaining objects however much was removed). In OpenCV, this is accomplished with cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel). The tutorials on that page show how it works nicely.

img = cv2.imread('image.jpg')
blur = cv2.GaussianBlur(img, (15, 15), 2)
hsv = cv2.cvtColor(blur, cv2.COLOR_BGR2HSV)
lower_green = np.array([37, 0, 0])
upper_green = np.array([179, 255, 255])
mask = cv2.inRange(hsv, lower_green, upper_green)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (15, 15))
opened_mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
masked_img = cv2.bitwise_and(img, img, mask=opened_mask)
cv2.imshow('', masked_img)
cv2.waitKey()

Opened mask

Filling in gaps

In the above, opening was shown as the method to remove small bits of white from your binary mask. Closing is the opposite operation---removing chunks of black from your image that are surrounded by white. You can do this with the same idea as above, but using cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel). This isn't even necessary after the above in your case, as the mask doesn't have any holes. But if it did, you could close them up with closing. You'll notice my opening step actually removed a small bit of the plant at the bottom. You could actually fill those gaps with closing first, and then opening to remove the spurious bits elsewhere, but it's probably not necessary for this image.

Trying out new values for thresholding

You might want to get more comfortable playing around with different colorspaces and threshold levels to get a feel for what will work best for a particular image. It's not complete yet and the interface is a bit wonky, but I have a tool you can use online to try out different thresholding values in different colorspaces; check it out here if you'd like. That's how I quickly found values for your image.

Solution 2

Although, the above problem is solved using cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel). But, if somebody wants to use morphology.remove_small_objects to remove area less than a specified size, for those this answer may be helpful.

Code I used to remove noise for above image is:

import numpy as np
import cv2
from skimage import morphology
# Load the image, convert it to grayscale, and blur it slightly
image = cv2.imread('im.jpg')
cv2.imshow("Image", image)
#cv2.imwrite("image.jpg", image)
greenLower = np.array([50, 100, 0], dtype = "uint8")
greenUpper = np.array([120, 255, 120], dtype = "uint8")
green = cv2.inRange(image, greenLower, greenUpper)
#green = cv2.GaussianBlur(green, (3, 3), 0)
cv2.imshow("green", green)
cv2.imwrite("green.jpg", green)
imglab = morphology.label(green) # create labels in segmented image
cleaned = morphology.remove_small_objects(imglab, min_size=64, connectivity=2)

img3 = np.zeros((cleaned.shape)) # create array of size cleaned
img3[cleaned > 0] = 255 
img3= np.uint8(img3)
cv2.imshow("cleaned", img3)
cv2.imwrite("cleaned.jpg", img3)
cv2.waitKey(0)

Cleaned image is shown below:

enter image description here

To use morphology.remove_small_objects, first labeling of blobs is essential. For that I use imglab = morphology.label(green). Labeling is done like, all pixels of 1st blob is numbered as 1. similarly, all pixels of 7th blob numbered as 7 and so on. So, after removing small area, remaining blob's pixels values should be set to 255 so that cv2.imshow() can show these blobs. For that I create an array img3 of the same size as of cleaned image. I used img3[cleaned > 0] = 255 line to convert all pixels which value is more than 0 to 255.

Share:
15,550

Related videos on Youtube

Jack Yeoh
Author by

Jack Yeoh

Updated on September 15, 2022

Comments

  • Jack Yeoh
    Jack Yeoh over 1 year

    i am trying to remove noise in an image less and am currently running this code

    import numpy as np
    import argparse
    import cv2
    from skimage import morphology
    
    # Construct the argument parser and parse the arguments
    ap = argparse.ArgumentParser()
    ap.add_argument("-i", "--image", required = True,
        help = "Path to the image")
    args = vars(ap.parse_args())
    
    # Load the image, convert it to grayscale, and blur it slightly
    image = cv2.imread(args["image"])
    
    cv2.imshow("Image", image)
    cv2.imwrite("image.jpg", image)
    
    greenLower = np.array([50, 100, 0], dtype = "uint8")
    greenUpper = np.array([120, 255, 120], dtype = "uint8")
    
    green = cv2.inRange(image, greenLower, greenUpper)
    #green = cv2.GaussianBlur(green, (3, 3), 0)
    
    cv2.imshow("green", green)
    cv2.imwrite("green.jpg", green)
    
    cleaned = morphology.remove_small_objects(green, min_size=64, connectivity=2)
    
    cv2.imshow("cleaned", cleaned)
    cv2.imwrite("cleaned.jpg", cleaned)
    
    
    
    cv2.waitKey(0)
    

    However, the image does not seem to have changed from "green" to "cleaned" despite using the remove_small_objects function. why is this and how do i clean the image up? Ideally i would like to isolate only the image of the cabbage.

    My thought process is after thresholding to remove pixels less than 100 in size, then smoothen the image with blur and fill up the black holes surrounded by white - that is what i did in matlab. If anybody could direct me to get the same results as my matlab implementation, that would be greatly appreciated. Thanks for your help.

    Edit: made a few mistakes when changing the code, updated to what it currently is now and display the 3 images

    image:

    enter image description here

    green:

    enter image description here

    clean:

    enter image description here

    my goal is to get somthing like this picture below from matlab implementation:

    enter image description here

  • alkasm
    alkasm over 6 years
    It should be noted that you should use an odd sized kernel (like you're using) if you want to erode and then dilate, otherwise if it's even you will end up shifting the region downwards and to the right. This process of eroding then dilating is called opening and you can do it directly with cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel).
  • alkasm
    alkasm over 6 years
    Great addition to show how it can be done in the library the OP was originally using. Also for inline code, you can use back-ticks around your code, like `variable` to produce variable.
  • Efe
    Efe over 6 years
    wonderfull !!!! After 48 hours of manipulating pixels in captcha images, you saved my life... This is the only cv2 code that worked like a charm for me. Thanks