open cv: add with numpy with maximum value [duplicate] - python

I am trying to increase brightness of a grayscale image. cv2.imread() returns a numpy array. I am adding integer value to every element of the array. Theoretically, this would increase each of them. After that I would be able to put upper threshold of 255 and get the image with the higher brightness.
Here is the code:
grey = cv2.imread(path+file,0)
print type(grey)
print grey[0]
new = grey + value
print new[0]
res = np.hstack((grey, new))
cv2.imshow('image', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
However, numpy addition apparently does something like that:
new_array = old_array % 256
Every pixel intensity value higher than 255 becomes a remainder of dividing by 256.
As a result, I am getting dark instead of completely white.
Here is the output:
<type 'numpy.ndarray'>
[115 114 121 ..., 170 169 167]
[215 214 221 ..., 14 13 11]
And here is the image:
How can I switch off this remainder mechanism? Is there any better way to increase brightness in OpenCV?

One idea would be to check before adding value whether the addition would result in an overflow by checking the difference between 255 and the current pixel value and checking if it's within value. If it does, we won't add value, we would directly set those at 255, otherwise we would do the addition. Now, this decision making could be eased up with a mask creation and would be -
mask = (255 - grey) < value
Then, feed this mask/boolean array to np.where to let it choose between 255 and grey+value based on the mask.
Thus, finally we would have the implementation as -
grey_new = np.where((255 - grey) < value,255,grey+value)
Sample run
Let's use a small representative example to demonstrate the steps.
In [340]: grey
Out[340]:
array([[125, 212, 104, 180, 244],
[105, 26, 132, 145, 157],
[126, 230, 225, 204, 91],
[226, 181, 43, 122, 125]], dtype=uint8)
In [341]: value = 100
In [342]: grey + 100 # Bad results (e.g. look at (0,1))
Out[342]:
array([[225, 56, 204, 24, 88],
[205, 126, 232, 245, 1],
[226, 74, 69, 48, 191],
[ 70, 25, 143, 222, 225]], dtype=uint8)
In [343]: np.where((255 - grey) < 100,255,grey+value) # Expected results
Out[343]:
array([[225, 255, 204, 255, 255],
[205, 126, 232, 245, 255],
[226, 255, 255, 255, 191],
[255, 255, 143, 222, 225]], dtype=uint8)
Testing on sample image
Using the sample image posted in the question to give us arr and using value as 50, we would have -

Here is another alternative:
# convert data type
gray = gray.astype('float32')
# shift pixel intensity by a constant
intensity_shift = 50
gray += intensity_shift
# another option is to use a factor value > 1:
# gray *= factor_intensity
# clip pixel intensity to be in range [0, 255]
gray = np.clip(gray, 0, 255)
# change type back to 'uint8'
gray = gray.astype('uint8)

Briefly, you should add 50 to each value, find maxBrightness, then thisPixel = int(255 * thisPixel / maxBrightness)
You have to run a check for an overflow for each pixel. The method suggested by Divakar is straightforward and fast. You actually might want to increment (by 50 in your case) each value and then normalize it to 255. This would preserve details in bright areas of your image.

Use OpenCV's functions. They implement "saturating" math.
new = cv.add(grey, value)
Documentation for cv.add
When you only write new = grey + value, that isn't OpenCV doing the work, that is numpy doing the work. And numpy does nothing special. Wrap-around for integers is standard behavior.

An alternate approach that worked efficiently for me is to "blend in" a white image to the original image using the blend function in the PIL>Image library.
from PIL import Image
correctionVal = 0.05 # fraction of white to add to the main image
img_file = Image.open(location_filename)
img_file_white = Image.new("RGB", (width, height), "white")
img_blended = Image.blend(img_file, img_file_white, correctionVal)
img_blended = img_file * (1 - correctionVal) + img_file_white * correctionVal
Hence, if correctionVal = 0, we get the original image, and if correctionVal = 1, we get pure white.
This function self-corrects for RGB values exceeding 255.
Blending in black (RGB 0, 0, 0) reduces brightness.

I ran into a similar issue, but instead of addition, it was scaling image pixels in non-uniform manner.
The 1-D version of this:
a=np.array([100,200,250,252,255],dtype=np.uint8)
scaling=array([ 1.1, 1.2, 1.4, 1.2, 1.1])
result=np.uint8(a*scaling)
This gets you the overflow issue, of course; the result:
array([110, 240, 94, 46, 24], dtype=uint8)
The np.where works:
result_lim=np.where(a*scaling<=255,a*scaling,255)
yields result_lim as:
array([ 110., 240., 255., 255., 255.])
I was wondering about timing, I did this test on a 4000 x 6000 image (instead of 1D array), and found the np.where(), at least for my conditions, took about 2.5x times as long. Didn't know if there was a better/faster way of doing this. The option of converting to float, doing the operation, and then clipping as noted above was a bit slower than the np.where() method.
Don't know if there are better methods for this.

Related

Increasing the speed of the color space conversion algorithm from HSV to RGB

I work on image pixels. I want to colorize my image in a specific formula and convert and save the image into RGB space after working with HSV. Opencv has functions to convert color spaces, But the color of the image changes my image. and I use the function to convert the pixel pixel, but my loop has a lot of time to run.
I wrote a piece of code in Python that contains a loop that takes a lot of time to execute.
What operational solutions do you have to reduce execution time?
I used the thread, but it is not implemented correctly and the time does not decrease.
Also, I used the np.apply_along_axis, but, this function increased the run time!
for i in range(row):
for j in range(col):
final[i][j][0], final[i][j][1], final[i][j][2] = colorir.HSV(final[i][j][0], final[i][j][1], final[i][j][2], max_sva=255).rgb()
When I convert the color with this code, my image is displayed in the correct color that I want, but when I use the following function, the image coloring is completely wrong:
final = cv2.cvtColor(final,cv2.COLOR_HSV2RGB)
or
final = matplotlib.colors.hsv_to_rgb(final)
Another question that occurred to me is that Is there a way to save an image that is in color space HSV without converting it to color space RGB? Without coloring it wrong? So that I don't have to use this snippet of runtime code above to convert?
Edit:
My image size is variable: for example 600x600
The execution time of these loops is about 15 seconds, which should be approximately less than 1 second.
I color the gray level image.
Below is my executable code:
import numpy as np
from numpy import newaxis
import cv2
from colorir import HSV, sRGB
zero1 = np.zeros((row, col), dtype=int)
new1 = np.dstack((zero1, zero1, num12))
new1 = np.where(num12 > 250, 0, num12)
newww = np.where(new1 < 0, 0, new1)
minus = np.subtract(num1, num2)
minus_2 = minus
minus = np.where(minus <=0, 33, minus)
minus = np.where(num2 >= np.multiply(num1 , 1.1), 33, minus)
minus = np.where(np.logical_and(num2 <= np.multiply(num1 , 1.1),num2 >= np.multiply(num1 , 1)), 107, minus)
minus = np.where(num2 < np.multiply(num1 , 1), 209, minus)
a_255 = np.full([row, col], 255, dtype=int)
final = np.dstack((minus, newww, a_255))
for i in range(row):
for j in range(col):
final[i][j][0], final[i][j][1], final[i][j][2] = HSV(final[i][j][0], final[i][j][1], final[i][j][2], max_sva=255).rgb()
My final image should only contain the green, blue, and orange colors I specified, but the image colored by functions 1 is pink and yellow, and unrelated colors.
A small example:
final = [[[105, 213, 235], [105, 213, 235], [105, 213, 235], [105, 213, 235], [105, 213, 235], [105, 213, 235]]]
final = np.asarray(final)
final = cv2.cvtColor(final,cv2.COLOR_HSV2BGR)
cv2.imshow("image", final)
cv2.waitKey(0)
When I run above sample code, with cv2.cvtColor(final,cv2.COLOR_HSV2BGR), I encounter:
error (Unsupported depth of input image: 'VDepth::contains(depth)' where 'depth' is 4 (CV_32S))
I have to use np.float32, but np.float32 color will spoil the final image.
There are two issues here:
OpenCV uses a range of 0-180 for the hue channel (because 360 doesn't fit in an 8-bit integer).
OpenCV defaults to BGR, but if you want RGB, you need to use cv2.COLOR_HSV2RGB, not cv2.COLOR_HSV2BGR.
Here is my version of your example at the end:
import numpy as np
import cv2
from colorir import HSV, sRGB
hsv = [[[105, 213, 235], [105, 213, 235], [105, 213, 235]]]
hsv = np.asarray(hsv)
rgb_colorir = np.zeros_like(hsv)
for i in range(hsv.shape[0]):
for j in range(hsv.shape[1]):
rgb_colorir[i][j][0], rgb_colorir[i][j][1], rgb_colorir[i][j][2] = HSV(hsv[i][j][0], hsv[i][j][1], hsv[i][j][2], max_sva=255).rgb()
hsv[:,:,0] //= 2 # OpenCV's different definition
hsv = hsv.astype(np.uint8) # OpenCV requires uint8
rgb_opencv = cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB)
Examining the converted images:
>>> rgb_colorir
array([[[ 88, 235, 39],
[ 88, 235, 39],
[ 88, 235, 39]]])
>>> rgb_opencv
array([[[ 91, 235, 39],
[ 91, 235, 39],
[ 91, 235, 39]]], dtype=uint8)
So we can see that there's a small difference in the red channel, which is likely caused by rounding errors. For the OpenCV case we had to round the hue to the nearest even number of degrees (because the integer divide by 2). It should be fairly difficult to see this difference.
If you want to use the more precise conversion, note that colorir.HSV is just calling colorsys.hsv_to_rgb, and doing a whole lot of extra work like building an object. But it doesn't give you anything extra except a normalization. Using colorsys.hsv_to_rgb directly should be faster. Also, colorsys is part of the standard library, using it directly should be preferred.
Use np.apply_along_axis() to iterate over your image. This should be relatively fast, but it will not be as fast as the OpenCV solution.
import colorsys
rgb_colorsys = np.apply_along_axis(
lambda hsv: colorsys.hsv_to_rgb(hsv[0] / 360, hsv[1] / 255, hsv[2] / 255),
2,
hsv,
) * 255
The result:
>>> rgb_colorsys
array([[[ 87.77941176, 235. , 38.70588235],
[ 87.77941176, 235. , 38.70588235],
[ 87.77941176, 235. , 38.70588235]]])
Of course there are other libraries you could try, such as scikit-image:
import skimage.color
rgb_skimage = skimage.color.hsv2rgb(hsv / [360, 255, 255]) * 255
Scikit-image also has a different definition for how it stores the HSV values. It also works in floating-point format, to avoid rounding errors. Result:
>>> rgb_skimage
array([[[ 87.77941176, 235. , 38.70588235],
[ 87.77941176, 235. , 38.70588235],
[ 87.77941176, 235. , 38.70588235]]])
With DIPlib (disclosure: I'm an author):
import diplib as dip
hsv_dip = dip.Convert(dip.Image(hsv, tensor_axis=2), "SFLOAT")
hsv_dip.SetColorSpace("HSV")
rgb_dip = dip.ColorSpaceManager.Convert(hsv_dip / [1, 255, 1], "RGB")
Result:
>>> np.asarray(rgb_dip)
array([[[ 87.77941176, 235. , 38.70588235],
[ 87.77941176, 235. , 38.70588235],
[ 87.77941176, 235. , 38.70588235]]])
Notice how OpenCV is the only implementation that has significant rounding errors, the others produce identical values.
For the floating-point results, np.round(...).astype(np.uint8) will get you an RGB image that you can display normally in pyplot:
>>> np.round(rgb_dip).astype(np.uint8)
array([[[ 88, 235, 39],
[ 88, 235, 39],
[ 88, 235, 39]]], dtype=uint8)

Check if a color is an interpolation of other two colors

Here is my current code:
from PIL import ImageColor
import numpy as np
a=0
colors = [
([17, 15, 100], [50, 56, 200]),
([86, 31, 4], [220, 88, 50]),
([25, 146, 190], [62, 174, 250]),
([103, 86, 65], [145, 133, 128])
]
for (low, high) in colors:
low = np.array(low, dtype="uint8")
high = np.array(high, dtype="uint8")
#if np.array(ImageColor.getcolor('#300103', "RGB"), dtype="uint8") is in between low and high, a=1
and I want to know if a certain color is in between two colors, sort of like cv2.inRange(), but instead of seeing if a picture's pixels are in between two colors and keeping them in the picture if so, I want to see if a single color is inbetween two colors.
A color "in between" two others is a bit problematic, because there is not a single definition for that: if vary colors in the RGB space, as is, in practice, defined in your question, it is a matter of school mathematics: taking the first component (red), check the relative distance between the two colors at the ends (50 - 17 = 33, for your first line), pick the red component of the color being checked, see its distance to the lower of those - for the color #300103, the red component would be "48" in decimal - 48 - 17 = 31. use the factor 31/33, and check if the other 2 components are at the same proportional distance from the respective component in (17, 15, 100).
One might use math.close and allow a fluctuation of 1 or 2 units per component, due to rounding errors.
However, if you might have a set of colors that lie on the gray spectrum attending this criteria, and which have no resemblance to the colors at either edge. (say, if you start with (255, 0, 0) and (0, 0, 255), (128, 0, 128) will match, but it has half the saturation than either color.
Using an HSV color space, rather than RGB would probably get you to colors that perceptually attend better as "in between". But then without knowing your real objective it is hard to say if it makes any sense - for the example above, it would be even worse, as it would match green and other full saturated colors.
I will beg your pardon, but I won't fill in the exact code for the RGB interpolation here, it should not be hard - but it will take sometime to get right - and it might not work for you at all.

Is there any way to manipulate an image using a color curve in python?

I have a series of tif images and was wondering if there is a possibility to write something in python (using maybe scikit-image or OpenCV) to apply a color curve. The data I have looks like the following, where we have a list of x, y, x, y... data like so: (0, 0, 32, 22, 64, 56, 128, 128, 192, 196, 255, 255).
With the new information you provided I think that the following code should do the trick. lut_in is the vector of input gray levels and lut_out the desired output level. Here it's applied for all 3 channels (like in photoshop). The only thing you need is the interpolation to have a LUT (look up table) of 256 elements, that fits the 256 gray levels of the input. You can apply the same method for other color resolutions.
import cv2
import numpy as np
image = cv2.imread('apple.jpg')
lut_in = [0, 127, 255]
lut_out = [0, 80, 255]
lut_8u = np.interp(np.arange(0, 256), lut_in, lut_out).astype(np.uint8)
image_contrasted = cv2.LUT(image, lut_8u)
cv2.imwrite('apple_dark.jpg', image_contrasted)
Input:
Output:
In the comments, some already gave answers on how to apply a color LUT to an image. However when I read your question, I have the impression that you would like to have in fact a 2D colormap, where the color depends on two parameters. If this is the case, I would recommend to visit this post or this one.
Hope this helps!
Here's a way to do this with python PIL instead of OpenCV just in case anyone stumbles upon this answer as I did and doesn't want to use OpenCV.
import numpy as np
from PIL import Image
im = Image.open("apple.png").convert("RGBA")
lut_x = [0, 127, 180, 255]
lut_y = [5, 80, 90, 255]
lut_u8 = np.interp(np.arange(0, 256), lut_x, lut_y).astype(np.uint8)
R, G, B, A = [0, 1, 2, 3]
source = im.split()
out = []
for band in [R, G, B]:
out.append(source[band].point(lut_u8))
out.append(source[A]) # Dont use LUT on the alpha band
merged_img = Image.merge('RGBA', out)
merged_img.show()

'Stretching' histograms (levels) in Numpy, Python

I have grayscale image whose background is, on a 0-255 color scale, a mid-white color with an average pixel color value of 246; the foreground is mid-grey with an average pixel-color value of 186.
I would like to 'shift' every pixel above 246 to 255, every pixel below 186 to zero, and 'stretch' everything between. Is there any ready-made algorithm/process to do this in numpy or python, or must the new levels/histogram be calculated 'manually' (as I have done thus far)?
This is the equivalent of, in Gimp or Photoshop, opening the levels window and selecting, with the white and black eyedropper respectively, a light region we want to make white and a darker region we want to make black: the application modifies the levels/histogram ('stretches' the values between the points selected) accordingly.
Some images of what I'm attempting:
Here's one way -
def stretch(a, lower_thresh, upper_thresh):
r = 255.0/(upper_thresh-lower_thresh+2) # unit of stretching
out = np.round(r*(a-lower_thresh+1)).astype(a.dtype) # stretched values
out[a<lower_thresh] = 0
out[a>upper_thresh] = 255
return out
As per OP, the criteria set was :
'shift' every pixel above 246 to 255, hence 247 and above should become 255.
every pixel below 186 to zero, hence 185 and below should become 0.
Hence, based on above mentioned two requirements, 186 should become something greater than 0 and so on, until 246 which should be lesser than 255.
Alternatively, we can also use np.where to make it a bit more compact -
def stretch(a, lower_thresh, upper_thresh):
r = 255.0/(upper_thresh-lower_thresh+2) # unit of stretching
out = np.round(r*np.where(a>=lower_thresh,a-lower_thresh+1,0)).clip(max=255)
return out.astype(a.dtype)
Sample run -
# check out first row input, output for variations
In [216]: a
Out[216]:
array([[186, 187, 188, 246, 247],
[251, 195, 103, 9, 211],
[ 21, 242, 36, 87, 70]], dtype=uint8)
In [217]: stretch(a, lower_thresh=186, upper_thresh=246)
Out[217]:
array([[ 4, 8, 12, 251, 255],
[255, 41, 0, 0, 107],
[ 0, 234, 0, 0, 0]], dtype=uint8)
If your picture is uint8 and typical picture size, one efficient method is setting up a lookup table:
L, H = 186, 246
lut = np.r_[0:0:(L-1)*1j, 0.5:255.5:(H-L+3)*1j, 255:255:(255-H-1)*1j].astype('u1')
# example
from scipy.misc import face
f = face()
rescaled = lut[f]
For smaller images it is faster (on my setup it crosses over at around 100,000 gray scale pixels) to transform directly:
fsmall = (f[::16, ::16].sum(2)//3).astype('u1')
slope = 255/(H-L+2)
rescaled = ((1-L+0.5/slope+fsmall)*slope).clip(0, 255).astype('u1')

What's the meaning of numbers in numpy arrays?

In numpy, The array method could convert a image to a big array, the question is , whats' the meaning of the numbers in this array?(RGB value? gray-scale value? )
What's more, when I convert a image to mode"1", tried
im = Image.open("test.jpg")
# Some processing ...
im = im.convert("1")
im_arr = array(im,dtype=uint8)
I found that the im_arr array shows not only 0 and 255:
array([[170, 170, 170, ..., 255, 255, 255],
[255, 248, 255, ..., 175, 255, 222],
[255, 255, 247, ..., 175, 170, 171],
...,
[ 32, 105, 110, ..., 32, 124, 32],
[ 32, 32, 32, ..., 101, 115, 39],
[ 41, 10, 32, ..., 109, 111, 115]], dtype=uint8)
Why? (I really want to know the meaning of numbers in array of different modes)
By running im.convert("1"), you are converting your loaded image into a bilevel image which should only have pixel values of 0 or 255. To do this properly,
im_arr = np.array(im.getdata(), dtype = np.uint8).reshape(im.size[0], im.size[1])
With this, you will receive your desired bilevel image, which you could save or view with matplotlib.pyplot.imshow.
The odd number you are receiving are coming from the conversion process to a numpy array. If you choose different data types (np.int, np.int8, np.uint8, ...), you will find that each of these return a different array.
Without knowing much about the object structure of PIL.Image, I couldn't say why this is. But I think it's safe to say that without .getdata() the results are garbage.
The format jpeg does not support mode 1. Save it into a different format, png or bmp, and look at the array for that one instead.
Documentation for PIL Jpeg

Categories