Search code examples
pythonnumpyopencvimage-processingmasking

Otsu thresholding inside mask


I'm working with Python and trying to do Otsu thresholding on an image but only inside the mask (yes, I have an image and a mask image). It means less pixel on the image will be included in the histogram for calculating the Otsu threshold.

I'm currently using the cv2.threshold function without the mask image and have no idea how to do this kind of job.

ret, OtsuMat = cv2.threshold(GaborMat, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

Since this function also incorporates the pixels outside the mask, I think it will give a less accurate threshold.

This is the example of the image and its mask:

https://drive.google.com/drive/folders/1p8JMhncJs19oOWO9RdkWuEADVGqE-gzQ?usp=sharing

Hope there is a OpenCV or other lib function to do it easily (and also with fast computing), but any kind of help will be appreciated.


Solution

  • I had a try at this using the threshold_otsu() method from skimage and a Numpy masked array. I don't know if there are faster ways - the skimage is normally pretty well optimised. If anyone else wants to take my sample data and try other ideas on it, please feel free - although there is a service charge of one upvote ;-)

    #!/usr/bin/env python3
    
    import cv2
    import numpy as np
    import numpy.ma as ma
    from skimage.filters import threshold_otsu
    
    # Set up some repeatable test data, 4 blocks 100x100 pixels each of random normal np.uint8s centred on 32, 64, 160,192
    np.random.seed(42)
    a=np.random.normal(size = (100,100), loc = 32,scale=10).astype(np.uint8)
    b=np.random.normal(size = (100,100), loc = 64,scale=10).astype(np.uint8)
    c=np.random.normal(size = (100,100), loc = 160,scale=10).astype(np.uint8)
    d=np.random.normal(size = (100,100), loc = 192,scale=10).astype(np.uint8)
    # Stack (concatenate) the 4 squares horizontally across the page
    im = np.hstack((a,b,c,d))
    # Next line is just for debug
    cv2.imwrite('start.png',im)
    

    That gives us this:

    enter image description here

    # Now make a mask revealing only left half of image, centred on 32 and 64
    mask=np.zeros((100,400))
    mask[:,200:]=1
    masked = ma.masked_array(im,mask)
    print(threshold_otsu(masked.compressed()))       # Prints 47
    
    # Now do same revealing only right half of image, centred on 160 and 192
    masked = ma.masked_array(im,1-mask)
    print(threshold_otsu(masked.compressed()))       # Prints 175
    

    The histogram of the test data looks like this, x-axis is 0..255

    enter image description here


    Adapting to your own sample data, I get this:

    #!/usr/bin/env python3
    
    import cv2
    import numpy as np
    import numpy.ma as ma
    from skimage.filters import threshold_otsu
    
    # Load images
    im   = cv2.imread('eye.tif', cv2.IMREAD_UNCHANGED)
    mask = cv2.imread('mask.tif', cv2.IMREAD_UNCHANGED)
    
    # Calculate Otsu threshold on entire image
    print(threshold_otsu(im))                       # prints 130
    
    # Now do same for masked image
    masked = ma.masked_array(im,mask>0)
    print(threshold_otsu(masked.compressed())).     # prints 124