Scaling Image in Python makes it darker - python

Good day,
I have an image which I generate through a deep learning process. The image is RGB, and the values of the image range from -0.28 to 1.25. Typically I would rescale the image so that the values are floating point between 0 and 1, or integers between 0 and 255. However I have found that in my current experiment doing this has made my images much darker. The image type is np.array (float64).
If I plot the image using matplotlib.pyplot then the values of the original image get clipped, but the image is not darkened.
The problem with this is that I am unable to save this version of the image. plt.imsave('image.png', art) gives an error.
When I scale the image I get the below output which is dark. This image can be saved using plt.imsave().
Here is my scaling function:
def scale(img):
return((img - img.min())/(img.max() - img.min()) * 255)
My questions:
1) Why I am I not able to save my image in the first (bright) image? If scaling is the problem, then:
2) Why does scaling make the image dark.
Help is much appreciated.

1) Why am I not able to save my image in the first (bright) image?
It's hard to answer this without seeing the specific error you're getting, but my guess is it might have to do with the range of values in your image. Maybe negative values are an issue, or the fact that you have both negative floats and floats larger than 1.
If I create some fake RGB image data in the range [-0.28, 1.25] and try to save it with plt.imsave(), I get the following error:
ValueError: Floating point image RGB values must be in the 0..1 range.
2) Why does scaling make the image dark?
Scaling your image's pixel values will likely change the appearance.
Imagine you had a light image, such that the values in the image ranged from [200, 255]. When you scale the values, you spread the values from [0, 255] and now you have pixels that were previously bright (around 200) being mapped to black (0). If you have a generally bright image, it will seem darker after scaling. This seems to be the case for you.
As a side note: I would suggest using Pillow or OpenCV rather than Matplotlib if you're doing lots of image-related work :)
EDIT
As #alkasm pointed out in a comment, when you use plt.imshow() to display the image, the values are clipped. This means that the first image will have all negative values mapped to 0, and all values greater than 1 mapped to 1. The first image is clipped and saturated to make it appear that there are more dark and bright pixels than there should be.
So it's not that the second image is darker, it's that the first image isn't displayed properly.

Related

Find darkest color in image with python

I was just wondering you could find the darkest colour in a greyscale image with python. I would prefer using pillow but OpenCV would be fine.
I found this but couldn't make sense of it.
If this is simple just say.
Thanks
** edit **
The issue that I have is that with the rest of the script the darkest colour is very unlikely to be black.
So in the image, after you read using OpenCV or Pillow, and because it is grayscale, the darkest "color" should be 0, which is black. The range of pixel values is from 0 and 255. If you want to find the darkest value, you can just use the minimal function.
import cv2
img = cv2.imread("2.png", 0)
print(min(img.flatten()))
"For grayscale images, the pixel value is a single number that represents the brightness of the pixel. The most common pixel format is the byte image, where this number is stored as an 8-bit integer giving a range of possible values from 0 to 255. Typically zero is taken to be black, and 255 is taken to be white."
Reference: https://homepages.inf.ed.ac.uk/rbf/HIPR2/value.htm

Python subpixel-accurate image crop

I need to crop an image with subpixel accuracy. For example, I might need to create an interpolated rectangular crop with corners (108.5, 350.9) and (368.3, 230.1) out of an image with dimensions 640x480. How can I achieve this?
Edit: It's a reasonable concession to stretch the cropped area to fit it into a data matrix. However you can't just change the borders of the crop to integer coordinates.
Well I'm not sure if I can call this an answer because I don't really know what your question is but I try to shed some light on you.
So I guess your problem arises from some misconception.
First of all DPI, PPI or whatever you want to use is nothing but a factor that tells you how many dots, points, pixels you have per inch. That factor allows you to determin print sizes or convert between pixel dimensions and inch dimensions.
That's by no means related to cropping an image.
Cropping a rectangular region is a very common task.
Also having ROIs with sub-pixel coordinates is pretty common as their coordinates often arise from calculations that yield non-integer values.
Usually you simply round coordinates to integers so your problem vanishes.
If you want to get intensity values for sub-pixel coordinates you can interpolate between neigbouring pixels. But as images cannot have half pixels you will have to store that information in an image that has more or less pixels.
So here's what I would do if I didn't want to use rounded coordinates.
If my coordinate is >= x.5 I'd add a column or row, otherwise I'd skip the pixel.
If I would add a column or row I would interpolate it's values.
But to be honest I don't see any use case for this and I never had to do anything but using integer coordinates for cropping in my career.
You cannot print fractions of pixels and you cannot display them either, so what's the point?
The solution seems to require that you calculate the center of the rectangle that you want to crop out of the image, and the height and width of the rectangle as well. Then just scale up the entire image until the desired rectangle has integer dimensions, then do a usual crop. You will have to scale the horizontal and vertical dimensions by separate amounts, so this will slightly distort the cropped portion and you will have to adjust for the distortion in the image encoding format you use.

how to extract the relative colour intensity in a black and white image in python?

Suppose I have got a black an white image, how do I convert the colour intensity at each point into a numerical value that represents its relativity intensity?
I checked somewhere on the web and found the following:
Intensity = np.asarray(PIL.Image.open('test.jpg'))
What's the difference between asarray and array?
Besides, the shape of the array Intensity is '181L, 187L, 3L'. The size of the image test.jpg is 181x187, so what does the extra '3' represent?
And are there any other better ways of extracting the colour intensity of an image?
thank you.
The image is being opened as a color image, not as a black and white one. The shape is 181x187x3 because of that: the 3 is there because each pixel is an RGB value. Quite often images in black and white are actually stored in an RGB format. For an image image, if np.all(image[:,:,0]==image[:,:,1]) and so on, then you can just choose to use any of them (eg, image[:,:,0]). Alternatively, you could take the mean with np.mean(image,axis=2).
Note too that the range of values will depend on the format, and so depending upon what you mean by color intensity, you may need to normalize them. In the case of a jpeg, they are probably uint8s, so you may want image[:,:,0].astype('float')/255 or something similar.

opencv, BGR2HSV creates lots of artifacts

This image is just an example. Top right is the original image, top left is the hue, bottom left the saturation and bottom right is the value. As can be easily seen both H and S are filled with artifacts. I want to reduce the brightness so the result picks a lot of this artifacts.
What I am doing wrong?
My code is simply:
vc = cv2.VideoCapture( 0 )
# while true and checking ret
ret, frame = vc.read()
frame_hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
cv2.imshow("h", frame_hsv[:,:,0])
cv2.imshow("s", frame_hsv[:,:,1])
cv2.imshow("v", frame_hsv[:,:,2])
I feel there is a misunderstanding in your question. While the answer of Boyko Peranov is certainly true, there are no problems with the images you provided. The logic behind it is the following: your camera takes pictures in the RGB color space, which is by definition a cube. When you convert it to the HSV color space, all the pixels are mapped to the following cone:
The Hue (first channel of HSV) is the angle on the cone, the Saturation (second channel of HSV, called Chroma in the image) is the distance to the center of the cone and the Value (third channel of HSV) is the height on the cone.
The Hue channel is usually defined between 0-360 and starts with red at 0 (In the case of 8 bit images, OpenCV use the 0-180 range to fit a unsigned char as stated in the documentation). But the thing is, two pixels of value 0 and 359 are really really close together in color. It can be seen more easily when flattening the HSV cone by taking only the outer surface (when Saturation is maximal):
Even if these values are perceptually close (perfectly red at 0 and red with a little tiny bit of purple at 359), these two values are far apart. This is the cause of the "artifacts" you describe in the Hue channel. When OpenCV shows it to you in grayscale, it mapped black to 0 and white to 359. They are, in fact, really similar colors, but when mapped in grayscale, are displayed too far apart. There are two ways to circumvent this counter-intuitive fact: you can re-cast the H channel into RGB space with a fixed saturation and value, which will show a closer representation to our perception. You could also use another color space based on perception (such as the Lab color space) which won't give you these mathematical side-effects.
The reason why these artifact patches are square are explained by Boyko Peranov. The JPEG compression works by replacing pixels by bigger squares that approximates the patch it replaces. If you put the quality of the compression really low when you create the jpg, you can see these squares appears even in the RGB image. The lower the quality, the bigger and more visible are the squares. The mean value of these squares is a single value which, for tints of red, may end up being between 0 and 5 (displayed as black) or 355 and 359 (displayed as white). That explains why the "artifacts" are square-shaped.
We may also ask ourselves why are there more JPEG compression artifacts visible in the hue channel. This is because of chroma subsampling, where studies based on perception showed that our eyes are less prone to see rapid variations in color than rapid variations in intensity. So, when compression, JPEG deliberately loses chroma information because we won't notice it anyway.
The story is similar for the saturation (your bottom left image) white varying spots. You're describing pixels nearly black (on the tip of the cone). Hence, the Saturation value could vary much but won't affect the color of the pixel much: it will always be near black. This is also a side-effect of the HSV color space not being purely based on perception.
The conversion between RGB (or BGR for OpenCV) and HSV is (in theory) lossless. You can convince yourself of this: re-convert your HSV image into the RGB one, you get the exact same image as you began with, no artifacts added.
You are working with a lossy compressed image, hence the rectangular artifacts. With video you have low exposition time, can have bandwidth limitations, etc. So the overall picture quality degrades. You can:
Use a series of still shots by using Capture instead of VideoCapture or
Extract 5-10 video frames, and average them.

What does matplotlib `imshow(interpolation='nearest')` do?

I use imshow function with interpolation='nearest' on a grayscale image and get a nice color picture as a result, looks like it does some sort of color segmentation for me, what exactly is going on there?
I would also like to get something like this for image processing, is there some function on numpy arrays like interpolate('nearest') out there?
EDIT: Please correct me if I'm wrong, it looks like it does simple pixel clustering (clusters are colors of the corresponding colormap) and the word 'nearest' says that it takes the nearest colormap color (probably in the RGB space) to decide to which cluster the pixel belongs.
interpolation='nearest' simply displays an image without trying to interpolate between pixels if the display resolution is not the same as the image resolution (which is most often the case). It will result an image in which pixels are displayed as a square of multiple pixels.
There is no relation between interpolation='nearest' and the grayscale image being displayed in color. By default imshow uses the jet colormap to display an image. If you want it to be displayed in greyscale, call the gray() method to select the gray colormap.

Categories