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:
Thanks
[...] 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:
.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()
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()
----------------------------------------
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
----------------------------------------