Search code examples
pythonimageopencvscikit-image

Why does cv2.bilateralFilter behave so differently for different data types?


As the title states, how come cv2.bilateralFilter behaves so differently for different dtypes? cv2.bilateralFilter only works for np.float32 or np.uint8 dtypes, the documentation says. But when I apply it on my image, the output looks vastly different for the two dtypes.

I am using the skimage functions img_as_x to convert between data types, as I have heard that one must never use .astype(x) when working with images. So my code looks like this:

Radargram = img_as_float32(cv2.imread(image)[:,:,::-1])
CropRadGram = (Radargram[700:2450,:,:])[:,:,0]
SigmaAll = 10
RadGramFilter = cv2.bilateralFilter(img_as_ubyte(CropRadGram), -1, SigmaAll, SigmaAll) #As uint8
RadGramFilter2 = cv2.bilateralFilter(CropRadGram, -1, SigmaAll, SigmaAll) #as float32
plt.figure(figsize=(20,20)); plt.imshow(RadGramFilter,cmap='gray')
plt.figure(figsize=(20,20)); plt.imshow(RadGramFilter2,cmap='gray')
RadGramFilterImg = Image.fromarray(img_as_ubyte(RadGramFilter)) #Transform to uint8 and construct image
RadGramFilterImg.save(f'Prepared_CropGrams/{IMG_nr}_RAD_prepared.png') #Save the image

And the output of RadGramFilter and RadGramFilter2 can be seen here:

RadGramFilter

RadGramFilter2

Thanks


Solution

  • [...] as I have heard that one must never use .astype(x) when working with images.

    That highly depends on the used library, and use case! In this very specific example, using img_as_float32 instead of .astype(np.float32) leads to the observed behaviour in the first place!

    Having a look at the documentation on cv2.bilateralFilter, we see that there's a parameter sigmaColor, which you set to 20, for using both with the np.uint8 and np.float32 image. But, sigmaColor = 20 only properly works for the np.uint8 case here, since its values are in the range of [0 ... 255]. Your np.float32 image on the other hand has values in the range of [0.0 ... 1.0], which explains the heavy blurring.

    So, there are two options to overcome that issue, both involve manually scaling:

    1. Use .astype(np.float32) in the beginning. Then, you can use sigmaColor = 20, but your resulting image will have values in the range 0.0 ... 255.0, such that you'd need to divide by 255, e.g. before plotting using matplotlib.
    import cv2
    import matplotlib.pyplot as plt
    import numpy as np
    from skimage import img_as_float32, img_as_ubyte
    
    # Use .astype(x) instead of img_as_x
    img = cv2.imread('path/to/your/image.jpg')[..., ::-1]
    filter_uint8 = cv2.bilateralFilter(img, 200, 200, 200)
    filter_float32 = cv2.bilateralFilter(img.astype(np.float32), 200, 200, 200)
    
    plt.figure(1, figsize=(14, 8))
    plt.subplot(2, 2, 1), plt.imshow(img), plt.title('Original image')
    plt.subplot(2, 2, 2), plt.imshow(filter_uint8), plt.title('uint8 filtered')
    plt.subplot(2, 2, 3), plt.imshow(filter_float32), plt.title('float32 filtered')
    plt.subplot(2, 2, 4), plt.imshow(filter_float32 / 255), plt.title('float32 filtered, corrected')
    plt.tight_layout()
    

    Option 1

    1. Divide sigmaColor by 255 in the cv2.bilateralFilter call. So, your result will straightforwardly have values in the range of 0.0 ... 1.0:
    import cv2
    import matplotlib.pyplot as plt
    from skimage import img_as_float32, img_as_ubyte
    
    # Scale sigmaColor w.r.t. to float value range of [0.0 ... 1.0]
    img = img_as_float32(cv2.imread('path/to/your/image.jpg')[..., ::-1])
    filter_uint8 = cv2.bilateralFilter(img_as_ubyte(img), 200, 200, 200)
    filter_float32 = cv2.bilateralFilter(img, 200, 200, 200)
    filter_float32_corr = cv2.bilateralFilter(img, 200, 200/255, 200)
    
    plt.figure(0, figsize=(14, 8))
    plt.subplot(2, 2, 1), plt.imshow(img), plt.title('Original image')
    plt.subplot(2, 2, 2), plt.imshow(filter_uint8), plt.title('uint8 filtered')
    plt.subplot(2, 2, 3), plt.imshow(filter_float32), plt.title('float32 filtered')
    plt.subplot(2, 2, 4), plt.imshow(filter_float32_corr), plt.title('float32 filtered, corrected')
    plt.tight_layout(), plt.show()
    

    Option 2

    ----------------------------------------
    System information
    ----------------------------------------
    Platform:      Windows-10-10.0.16299-SP0
    Python:        3.9.1
    Matplotlib:    3.4.0
    NumPy:         1.20.2
    OpenCV:        4.5.1
    scikit-image:  0.18.1
    ----------------------------------------