In OpenCV when I convert JPG image (8 bit per channel) to gray scale, either using cv.cvtColor(img, cv.COLOR_BGR2GRAY) or simply reading it as a grayscale immediately: cv.imread(path + 'image.JPG', cv.IMREAD_GRAYSCALE), the grayscale image is only an 8 bit image.
Is there a way of getting a 16 bit gray scale image?
I know I can square the values of the grayscale image and get 16 bits that way, but I want a true 16 bit of colour information (not 8 bits scaled up).
What you could do is create your custom function to convert BGR in uint16 to a GRAYSCALE in uint16. Fore example as follows:
def bgr2gray(img):
weights = [0.11, 0.59, 0.3]
return np.uint16(np.dot(img, weight))
Where the weights are the standard weights used to convert from RGB/BGR to grayscale (https://www.tutorialspoint.com/dip/grayscale_to_rgb_conversion.htm#:~:text=Since%20its%20an%20RGB%20image,Its%20done%20in%20this%20way.&text=If%20you%20have%20an%20color,into%20grayscale%20using%20average%20method.).
Then you would apply this function to the BGR image that you previously converted to uint16. However, this should in general not give you more information than converting the 8-bit BGR image to a 8-bit grayscale image. It would be different if the original BGR image was 16-bit.
To convert from uint8 to uint16, you should use the following formula:
img16 = np.uint16(img8)*256
You can convert you 8 bit/pixel gray image to a 16 bits per pixel but you have to note that the 16 bpp image will not transport more information than the original one.
If you want to keep the original dynamic (0->255) do:
img16 = np.uint16(img8)
If you want to extend the dynamic (make sense for further processing that require more than 8 bpp prevision ) do:
cv.convertScaleAbs(img8,img16,alpha=(65535/255))
Scale the BGR coefficients by 256 before applying them to the image:
import numpy as np
import cv2
# Create a small BGR image with unique pixel values
test_img = np.array([[(b, g, r)
for b in range(0, 256, 8)
for g in range(2, 256, 8)
for r in range(4, 256, 8)]], dtype=np.uint8)
coefficients = np.uint16(256 * np.array((.114, .587, .299)))
test_img.dot(coefficients)
This does preserve additional information:
>>> len(np.unique(cv2.cvtColor(test_img, cv2.COLOR_BGR2GRAY)))
249
>>> len(np.unique(test_img.dot(coefficients)))
7034
Related
When enlarging or shrinking with the cv2.resize function, 16-bit grayscale images are increased to 24 bits. Is there a way to resize them without changing the depth?
img=cv2.imread("16bitgray.tiff")
img512 = cv2.resize(img, (512,512), interpolation=cv2.INTER_NEAREST)
If you read a 16-bit greyscale TIFF with:
img=cv2.imread("16bitgray.tiff")
you'll get an RGB888 TIFF because that is the default. You need:
img=cv2.imread("16bitgray.tiff", cv2.IMREAD_UNCHANGED)
I am reading an image from a camera that comes in cv2.COLOR_RGB2BGR format. Below is a temporary work around for what I am trying to achieve:
import cv2
from skimage import transform, io
...
_, img = cam.read()
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
cv2.imwrite("temp.png", img)
img = io.imread("temp.png", as_gray=True)
img = transform.resize(img, (320, 240), mode='symmetric', preserve_range=True)
I found one way to do this conversion from this post, however, it seems that the image data is not the same than if I read the same image from a path?
I've also found from this documentation that I can use img_as_float(cv2_img), but this conversion does not produce the same result as what is returned by io.imread("temp.png", as_gray=True)
What is the proper way to do this conversion efficiently? Should I first convert the image back to RGB then use img_as_float()?
I guess, the basic problem you encounter, are the different luma calculations used by OpenCV and scikit-image:
OpenCV uses:
Y = 0.299 * R + 0.587 * G + 0.114 * B
scikit-image uses:
Y = 0.2125 * R + 0.7154 * G + 0.0721 * B
Let's have some tests – using the following image for example:
import cv2
import numpy as np
from skimage import io
# Assuming we have some kind of "OpenCV image", i.e. BGR color ordering
cv2_bgr = cv2.imread('paddington.png')
# Convert to grayscale
cv2_gray = cv2.cvtColor(cv2_bgr, cv2.COLOR_BGR2GRAY)
# Save BGR image
cv2.imwrite('cv2_bgr.png', cv2_bgr)
# Save grayscale image
cv2.imwrite('cv2_gray.png', cv2_gray)
# Convert to grayscale with custom luma
cv2_custom_luma = np.uint8(0.2125 * cv2_bgr[..., 2] + 0.7154 * cv2_bgr[..., 1] + 0.0721 * cv2_bgr[..., 0])
# Load BGR saved image using scikit-image with as_gray; becomes np.float64
sc_bgr_w = io.imread('cv2_bgr.png', as_gray=True)
# Load grayscale saved image using scikit-image without as_gray; remains np.uint8
sc_gray_wo = io.imread('cv2_gray.png')
# Load grayscale saved image using scikit-image with as_gray; remains np.uint8
sc_gray_w = io.imread('cv2_gray.png', as_gray=True)
# OpenCV grayscale = scikit-image grayscale loaded image without as_gray? Yes.
print('Pixel mismatches:', cv2.countNonZero(cv2.absdiff(cv2_gray, sc_gray_wo)))
# Pixel mismatches: 0
# OpenCV grayscale = scikit-image grayscale loaded image with as_gray? Yes.
print('Pixel mismatches:', cv2.countNonZero(cv2.absdiff(cv2_gray, sc_gray_w)))
# Pixel mismatches: 0
# OpenCV grayscale = scikit-image BGR loaded (and scaled) image with as_gray? No.
print('Pixel mismatches:', cv2.countNonZero(cv2.absdiff(cv2_gray, np.uint8(sc_bgr_w * 255))))
# Pixel mismatches: 131244
# OpenCV grayscale with custom luma = scikit-image BGR loaded (and scaled) image with as_gray? Almost.
print('Pixel mismatches:', cv2.countNonZero(cv2.absdiff(cv2_custom_luma, np.uint8(sc_bgr_w * 255))))
# Pixel mismatches: 1
You see:
When opening the grayscale image, scikit-image simply uses the np.uint8 values, regardless of using as_gray=True or not.
When opening the color image with as_gray=True, scikit-image applies rgb2gray, scales all values to 0.0 ... 1.0, thus uses np.float64. Even scaling back to 0 ... 255 and np.uint8 yields a lot of pixel mismatches between this image and the OpenCV grayscale image – due to the different luma calculations.
When calculating the luma manually and accordingly to rgb2gray, the OpenCV grayscale image is almost identical. The one pixel mismatch might be due to floating point inaccuracies.
----------------------------------------
System information
----------------------------------------
Platform: Windows-10-10.0.16299-SP0
Python: 3.9.1
NumPy: 1.20.1
OpenCV: 4.5.1
scikit-image: 0.18.1
----------------------------------------
Example:
1st image: the original image.
2nd, 3rd and 4th images: the outputs I
want.
I know PIL has the method PIL.ImageOps.grayscale(image) that returns the 4th image, but it doesn't have parameters to produce the 2nd and 3rd ones (partial grayscale).
When you convert an image to greyscale, you are essentially desaturating it to remove saturated colours. So, in order to achieve your desired effect, you probably want to convert to HSV mode, reduce the saturation and convert back to RGB mode.
from PIL import Image
# Open input image
im = Image.open('potato.png')
# Convert to HSV mode and separate the channels
H, S, V = im.convert('HSV').split()
# Halve the saturation - you might consider 2/3 and 1/3 saturation
S = S.point(lambda p: p//2)
# Recombine channels
HSV = Image.merge('HSV', (H,S,V))
# Convert to RGB and save
result = HSV.convert('RGB')
result.save('result.png')
If you prefer to do your image processing in Numpy rather than PIL, you can achieve the same result as above with this code:
from PIL import Image
import numpy as np
# Open input image
im = Image.open('potato.png')
# Convert to HSV and go to Numpy
HSV = np.array(im.convert('HSV'))
# Halve the saturation with Numpy. Hue will be channel 0, Saturation is channel 1, Value is channel 2
HSV[..., 1] = HSV[..., 1] // 2
# Go back to "PIL Image", go back to RGB and save
Image.fromarray(HSV, mode="HSV").convert('RGB').save('result.png')
Of course, set the entire Saturation channel to zero for full greyscale.
from PIL import ImageEnhance
# value: float between 0.0 (grayscale) and 1.0 (original)
ImageEnhance.Color(image).enhance(value)
P.S.: Mark's solution works, but it seems to be increasing the exposure.
I am working with OpenCV to convert an image from RGB to HSV. Ultimately, I have a Matlab routine that I am trying to implement in Python, so I am double checking my conversions against the values obtained in Matlab. I've got some questions about the conversion of RGB to HSV using OpenCV. For example, take these two different ways of converting the image.
img = cv2.imread(img_path)
hsv1 = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
hsv2 = cv2.cvtColor(np.float32(img), cv2.COLOR_BGR2HSV)
Using Matlab, the max value of the hue matrix is found as:
>> I = imread(img_path);
>> I_hsv = rgb2hsv(I);
>> max(max(I_hsv(:,:,1)))
ans =
0.9979
Using the HSV matrices obtained using OpenCV:
In [1]: cv2.minMaxLoc(hsv1[:,:,0]/180.)
Out[1]: (0.0, 0.9944444444444445, (72, 0), (1013, 399))
In [2]: cv2.minMaxLoc(hsv2[:,:,0]/360.)
Out[2]: (0.0, 0.9979166388511658, (72, 0), (456, 768))
So I have two questions.
1) Why when I convert to float32 does the hue matrix have a maximum value of 360, instead of 180?
2) Why does the conversion to float32 make the value of the hue matrix closer to that computed using Matlab?
#fmw42 is correct in that the opencv spec calls for H to be divided by 2 when the output type is uint8 providing a range of H from 0 to 180. The range of H will be 0 to 360 if the output type is float32.
This stuffing of values is not ideal for consistency or comparing results when data may be in different formats, but at least it is documented.
From color_conversions v4.7.0:
RGB ↔ HSV
...
On output 0≤V≤1, 0≤S≤1, 0≤H≤360 .
The values are then converted to the destination data type:
8-bit images: V←255V,S←255S,H←H/2(to fit to 0 to 255)
16-bit images: (currently not supported) V<−65535V,S<−65535S,H<−H
32-bit images: H, S, and V are left as is
Observe the following image:
Observe the following Python code:
import cv2
img = cv2.imread("rainbow.png", cv2.IMREAD_COLOR)
img = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # convert it to hsv
img = cv2.cvtColor(img, cv2.COLOR_HSV2BGR) # convert back to BGR
cv2.imwrite("out.png", img)
Here's the output image:
If you can't see it, there's a clear loss of visual fidelity in the image here. For comparison's sake, here's the original next to the output image zoomed in around the yellows:
What's going on here? Is there any way to prevent these blocky artifacts from appearing? I need to convert to the HSL color space to rotate the hue, but I can't do that if I'm going to get these kinds of artifacts.
As a note, the output image does not have the artifacts when I don't do the two conversions; the conversions themselves are indeed the cause.
Back at a computer now - try like this:
#!/usr/bin/env python3
import numpy as np
import cv2
img = cv2.imread("rainbow.png", cv2.IMREAD_COLOR)
img = img.astype(np.float32)/255 # go to 32-bit float on 0..1
img = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # convert it to hsv
img = cv2.cvtColor(img, cv2.COLOR_HSV2BGR) # convert back to BGR
cv2.imwrite("output.png", (img*255).astype(np.uint8))
I think the problem is that when you use unsigned 8-bit representation, the Hue gets "squished" from a range of 0..360 into a range of 0..180, in 2 degree increments in order to stay within 8-bit unsigned range of 0..255 causing steps between nearby values. A solution is to move to 32-bit floats and scale to the range 0..1.