I'm currently trying to start with an original RGB image, convert it to LUV, perform some operations (namely, rotate the hues), then rotate it back to RGB for display purposes. However, I'm encountering a vexing issue where the RGB-to-LUV conversion (and vice versa) seems to be changing the image. Specifically, if I begin with an LUV image, convert it to RGB, and then change it back to LUV, without changing anything else, the original image is different. This has happened for both the Python (cv2) and Matlab (open source) implementations of the color conversion algorithms, as well as my own hand-coded ones based on. Here is an example:
luv1 = np.array([[[100,6.12,0]]]).astype('float32')
rgb1 = cv2.cvtColor(luv1,cv2.COLOR_Luv2RGB)
luv2 = cv2.cvtColor(rgb1,cv2.COLOR_RGB2Luv)
print(luv2)
[[[99.36293 1.3064307 -1.0494182]]]
As you can see, the LUV coordinates have changed from the input. Is this because certain LUV coordinates have no direct match in RGB space?
Yes, remove the astype('uint8') bit in your code, and the difference should disappear if the conversion is implemented correctly.
You can see the equations for the conversion in Wikipedia. There is nothing there that is irreversible, the conversions are perfect inverses of each other.
However, this conversion contains a 3rd power, which does stretch some values significantly. The rounding of the conversion to an integer can introduce a significant shift of color.
Also, the Luv domain is highly irregular and it might not be easy to verify that Luv values will lead to a valued RGB value. Your statement "I've verified that luv1 has entries that all fall in the allowable input ranges" makes me believe that you think the Luv domain is a box. It is not. The ranges for u and v change with L. One good exercise is to start with a sampling of the RGB cube, and map those to Luv, then plot those points to see the shape of the Luv domain. Wikipedia has an example of what this could look like for the sRGB gamut.
The OpenCV cvtColor function will clamp RGB values to the [0,1] range (if of type float32), leading to irreversible changes of color if the input is out of gamut.
Here is an example that shows that the conversion is reversible. I start with RGB values because these are easy to verify as valid:
import numpy as np
import cv2
rgb1 = np.array([[[1.0,1.0,1.0],[0.5,1.0,0.5],[0.0,0.5,0.5],[0.0,0.0,0.0]]], 'float32')
luv1 = cv2.cvtColor(rgb1, cv2.COLOR_RGB2Luv)
rgb2 = cv2.cvtColor(luv1, cv2.COLOR_Luv2RGB)
np.max(np.abs(rgb2-rgb1))
This returns 2.8897537e-06, which is numerical precision for 32-bit floats.
Related
Given the RGB percentages of a color, e.g. "wild watermelon", (99%, 42%, 52%), the right RGB values are (252, 108, 133).
But when I tried
rgb_percent = ('99%', '42%','52%')
rgb_values = tuple(int(int(s.strip('%')) * 2.55) for s in rgb_percent)
print(rgb_values)
[out]:
(252, 107, 132)
The color looks similar but they are not the right values. How to convert RGB percentage to RGB values correctly?
Should I be rounding up/down them instead or casting the values to integer? This seems closer but not the expected values still:
tuple(int(round(int(s.strip('%')) * 2.55, 0)) for s in rgb_percent)
[out]:
(252, 107, 133)
You cannot get exactly the same values, because you round numbers. 100 and 255 are not multiples. Your percentage RGB seems to be all integer, so you lost at minimum 1 bit of information (and possibly also 2 bits). So you cannot expect exact values on all cases. (You cannot map 100 numbers into 256 values, and get all 256 values).
But the good: you do not need it (usually). Your eyes are not so sensible (and we cannot see all combination of RGB with 256x256x256 values: our eyes can distinguish a lot less colours, so on some case you will see no differences, and on some, it is just barely noticeable). In addition some monitor panel trick displaying only 64 different shades per channel (and few people notice it). Consider that if you took data from a JPEG image, you get an additional conversion (data are not stored as RGB data, but as YCC).
To get the best choice, you need to know the original algorithm (do they round or trunc? or you should have a larger example test, and check the differences between different algorithms (e.g. how many cases are precise, how many are just 1 off, 2 off, 3 off).
Note: you have 3 possibilities: round(), math.floor(), and math.ceil(). I would avoid using int: according Python documentation, it seems the result depend on the system standard library (it could be truncated, or rounded). Note: math.trunc() and math.floor() gives the same results for positive (or zero) numbers.
In general, I would prefer round: it gives expected results for 0% and 100% (ceil is similar for these extreme values).
PS: there is not "correct RGB values". Historically RGB were given as numbers from 0 to 100 (and possibly as floating point number), or from 0.0 to 1.0. Just HTML pushed for 0 to 255, but it causes problems if you have a screens which can display more colours. And probably for RGB you mean R*G*B*, so not a linear RGB, but a OETF corrected RGB).
And no: there is not reputable sources. Nobody follows standards, so do not expect people which converted the colour channel (e.g. to 99%) are using exact the same method (we programmers are lazy). And standards tend to uses floating points. Note: internally a program may use floating point numbers, so in a colour picker you may get different colours with same displayed %-values, depending on how you input the colour. Or do you refer to a specific standard which specifies %-values? We cannot agree if black is 0,0,0 or 16,16,16 (e.g. MPEG-2, or TVs, and other limited range RGB devices), or how do do or interpret the gamma correction (ok: recent and less-recent standards are more precise, but most programs didn't get it). Also when reading your "RGB" values, in past I would assume it would be sRGB (so colour space of web and Microsoft), but since few years Apple use a different colour space (Apple P3), and other are following extending sRGB. Chaos. So just test different methods (and use your eyes to check quality).
Instead of rediscovering the nature of different color codes I researched the packages and find one usable and detailed package for this. When we look at the code of this package similar problems have been mentioned and solved. With this package with the help of bit reverse engineering I found this way:
#pip install webcolors #to install
import webcolors as wco
hex = wco.rgb_percent_to_hex(('99%','42%','52%'))
wco.hex_to_rgb(hex)
Resut is IntegerRGB(red=252, green=107, blue=133).
Although there is no rgb_percent_to_rgb function instead of this I converted percentage values to HEX and then converted them to values.
Yes, round up before casting them to int values but remember float values aren't precise so you won't always get the expected output
I have recently started studying steganography and I've come across a problem that I just don't seem to understand. Basically, the image is a png which contains a hidden flag in it.
When you extract the bit planes from the image, you can see that there's an image in the blue and green planes that you can see in the red one. To reveal the flag in clear text, you have to remove those images from the red one by XORing the LSB or something. I am not totally sure.
This is what the image in the red plane looks like if you don't remove the others.
My question is how do I go about doing this kind of thing? This is the image in question.
Actually the hidden image is in the lowest 3 bit planes. Doing a full bit decomposition makes that clear.
Start by loading the image to a numpy array, which will have dimensions MxNx3.
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image
img = Image.open('stego.png')
data = np.array(img)
All you have to do now is XOR each colour plane with another and then keep the 3 least significant bits (lsb).
extracted = (data[...,0] ^ data[...,1] ^ data[...,2]) & 0x07
plt.imshow(extracted)
plt.show()
In case it wasn't obvious, the & 0x07 part is an AND operation with the binary number 00000111, just written in hexadecimal for conciseness.
If you don't keep all 3 lsb, then you'll either be missing some letters in the solution, or everything will be there but some edges won't be as smooth. The first of these is critically important.
In OpenCV (Python), to convert RGB to YCbCr we use:
imgYCC = cv2.cvtColor(img, cv2.COLOR_BGR2YCR_CB)
What if i want to come back to RGB?
Check the docs for color conversions. You can see all of the available color conversion codes here: Conversion Color Codes.
For the colorspaces available, you can generally transform both ways---COLOR_BGR2YCrCb (i.e. BGR-to-YCrCb) and COLOR_YCrCb2BGR (i.e. YCrCb-to-BGR). Also, OpenCV uses BGR ordering, not RGB ordering. Regardless, to answer the specific question at hand, simply convert back using the opposite order of the colorspaces:
img_bgr = cv2.cvtColor(imgYCC, cv2.COLOR_YCrCb2BGR)
Note: cv2.COLOR_YCrCb2BGR is equivalent to cv2.COLOR_YCR_CB2BGR, I just find the first variant easier to read. Since these transformations (on uint8 images especially), there's some rounding going on so you won't necessarily get the exact same image going back and fourth. But you shouldn't be more than like 1 off at a few of the locations.
I have a .png image that contains three grayscale values. It contains black (0), white (255) and gray (128) blobs. I want to resize this image to a smaller size while preserving only these three grayscale values.
Currently, I am using scipy.misc.imresize to do it but I noticed that when I reduce the size, the edges get blurred and now contains more than 3 grayscale values.
Does anyone know how to do this in python?
From the docs for imresize, note the interp keyword argument:
interp : str, optional
Interpolation to use for re-sizing
(‘nearest’, ‘lanczos’, ‘bilinear’, ‘bicubic’ or ‘cubic’).
The default is bilinear filtering; switch to nearest and it will instead use the exact color of the nearest existing pixel, which will preserve your precise grayscale values rather than trying to linearly interpolate between them.
I believe that PIL.Image.resize does exactly what you want. Take a look at the docs.
Basically what you need is:
from PIL import Image
im = Image.open('old.png')
# The Image.NEAREST is the default, I'm just being explicit
im = im.resize((im.size[0]/2, im.size[1]/2), Image.NEAREST)
im.save('new.png')
Actually you can pretty much do that with the scipy.misc.imresize
Take a look at its docs.
The interp parameter is what you need. If you set it to nearest the image colors won't be affected.
I am using OpenCV to read and display an image. I am trying to do a scalar multiplication but it is being displayed very differently for two similar approaches:
img = cv2.imread('C:/Python27/user_scripts/images/g1.jpg', -1)
cv2.imshow('img_scaled1', 0.5*img)
cv2.waitKey(0)
cv2.imshow('img_scaled2', img/2)
cv2.waitKey(0)
In the 1st case, hardly anything is displayed. 2nd case works fine.
It seems to me that imshow() does not support numpy array of floats.
I want to use the first method. Can somebody help?
There are lot of pitfall when using images. This one seems like a type issue.
imshowaccept uint8 arrays in range(0,256) (256 excluded), and float arrays in range(0.0,1.0). When doing a=a*.5, you have a float array out of range, so no warranty on the result.
A solution is to cast the array in the uint8 type by:
imshow((a*.5).astype(np.uint8))
or
imshow((a*.5).astype('uint8'))