Converting an OpenCV BGR 8-bit Image to CIE L*a*b* - c++

I am trying to convert a given Mat representing an RGB image with 8-bit depth to Lab using the function provided in the documentation:
cvtColor(source, destination, <conversion code>);
I have tried the following conversion codes:
CV_RGB2Lab
CV_BGR2Lab
CV_LBGR2Lab
I have received bizarre results each time around, with an "L" value of greater than 100 for some samples, literally <107, 125, 130>.
I am also using Photoshop to check the results - but given that 107 is beyond the accepted range of 0 ≤ L ≤ 100, I can not comprehend what my error is.
Update:
I'll post my overall results here:
Given an image (Mat) represented by 8-bit BGR, the image can be converted by the following:
cvtColor(source, destination, CV_BGR2Lab);
The pixel values can then be accessed in the following manner:
int step = destination.step;
int channels = destination.channels();
for (int i = 0; i < destination.rows(); i++) {
for (int j = 0; j < destination.cols(); j++) {
Point3_<uchar> pixelData;
//L*: 0-255 (elsewhere is represented by 0 to 100)
pixelData.x = destination.data[step*i + channels*j + 0];
//a*: 0-255 (elsewhere is represented by -127 to 127)
pixelData.y = destination.data[step*i + channels*j + 1];
//b*: 0-255 (elsewhere is represented by -127 to 127)
pixelData.z = destination.data[step*i + channels*j + 2];
}
}

If anyone is interested in the range of the other variables a and b I made a small program to test their range.
If you convert all the colors that are represented with RGB to the CieLab used in OpenCV the ranges are:
0 <=L<= 255
42 <=a<= 226
20 <=b<= 223
And if you're using RGB values in the float mode instead of uint8 the ranges will be:
0.0 <=L<= 100.0
-86.1813 <=a<= 98.2352
-107.862 <=b<= 94.4758
P.S. If you want to see how distinguishable (regarding human perception) is a LAB value from another LAB value, you should use the floating point. The scale used to keep the lab values in the uint8 ranges messes up with their euclidean distance.
This is the code I used (python):
L=[0]*256**3
a=[0]*256**3
b=[0]*256**3
i=0
for r in xrange(256):
for g in xrange(256):
for bb in xrange(256):
im = np.array((bb,g,r),np.uint8).reshape(1,1,3)
cv2.cvtColor(im,cv2.COLOR_BGR2LAB,im) #tranform it to LAB
L[i] = im[0,0,0]
a[i] = im[0,0,1]
b[i] = im[0,0,2]
i+=1
print min(L), '<=L<=', max(L)
print min(a), '<=a<=', max(a)
print min(b), '<=b<=', max(b)

That's because L value is in range [0..255] in OpenCV. You can simply scale this value to needed interval ([0..100] in your case).

I am not sure about João Abrantes's range on A and B.
The opencv documentation has clearly mentioned the CIE L*a*b*range.
8 bit images
Thus leading to a range of
0 <= L <= 255
0 <= a <= 255
0 <= b <= 255

In case anyone runs into the same issue:
Please note that in OpenCV (2.4.13), you can not convert CV_32FC3 BGR images into the Lab color space. That is to say:
//this->xImage is CV_8UC3
this->xImage.convertTo(FloatPrecisionImage, CV_32FC3);
Mat result;
cvtColor(FloatPrecisionImage, result, COLOR_BGR2Lab);
this->xImage = result;
will not work
while
Mat result;
cvtColor(this->xImage, result, COLOR_BGR2Lab);
result.convertTo(this->xImage, CV_32FC3);
works like a charm.
I did not track down the reason for said behavior; however it seems off to me, because this in effect puts limits on the image's quality.

Related

How to convert from MATLAB Lab color scale to OpenCV Lab color scale?

I am trying to sample colors in MATLAB using the Color Thresholder App and then use the L * a * b output in OpenCV. But there seems to be a scale mismatch. The following are the L * a * b scales in MATLAB and OpenCV:
MATLAB: 0 <= L <= 100; -100 <= a <= 100; and -100 <= b <= 100
OpenCV: 0 <= L <= 100; -127 <= a <= 127; and -127 <= b <= 127
according to these two sources : Source 1; and Source 2
It seems like we need the following L * a * b ranges for 8 bit images in OpenCV :
0 <= L <= 255; 0 <= a <= 255; and 0 <= b <= 255
How do we convert from MATLAB to OpenCV L * a * b color scale for 8 - bit images ?
Matlab uses the International Color Consortium's specifications for color representation in their image processing toolbox. The specification for ICC profiles are nearly universally used for color specification and conversions.
ICC Lab specified that LAB is used for the profile conversion space in various resolutions. For 8 bits, unsigned 8 bit values are used. L* is mapped 0->0 and 100->255. For a* and b* the values are limited to between -128 and +127. Thus the actual encoding adds 128 to a* and b* to produce an unsigned values between 0 and 255.
Representation for these and other, larger bit sizes can be found on tables 12 and 13 in section 6.3.4.2 in the specification here:
http://color.org/specification/ICC1v43_2010-12.pdf
Most programs such as Photoshop, Tiff files, and such use the ICC formats. Additionally, Matlab's functions include various conversion functions such as lab2uint8(lab) which can be used to convert flating point Lab values to their proper representation in fixed sizes such as unsigned 8 bit ints.

How i can take the average of 100 image using opencv?

i have 100 image, each one is 598 * 598 pixels, and i want to remove the pictorial and noise by taking the average of pixels, but if i want to use Adding for "pixel by pixel"then dividing i will write a loop until 596*598 repetitions for one image, and 598*598*100 for hundred of image.
is there a method to help me in this operation?
You need to loop over each image, and accumulate the results. Since this is likely to cause overflow, you can convert each image to a CV_64FC3 image, and accumualate on a CV_64FC3 image. You can use also CV_32FC3 or CV_32SC3 for this, i.e. using float or integer instead of double.
Once you have accumulated all values, you can use convertTo to both:
make the image a CV_8UC3
divide each value by the number of image, to get the actual mean.
This is a sample code that creates 100 random images, and computes and shows the
mean:
#include <opencv2\opencv.hpp>
using namespace cv;
Mat3b getMean(const vector<Mat3b>& images)
{
if (images.empty()) return Mat3b();
// Create a 0 initialized image to use as accumulator
Mat m(images[0].rows, images[0].cols, CV_64FC3);
m.setTo(Scalar(0,0,0,0));
// Use a temp image to hold the conversion of each input image to CV_64FC3
// This will be allocated just the first time, since all your images have
// the same size.
Mat temp;
for (int i = 0; i < images.size(); ++i)
{
// Convert the input images to CV_64FC3 ...
images[i].convertTo(temp, CV_64FC3);
// ... so you can accumulate
m += temp;
}
// Convert back to CV_8UC3 type, applying the division to get the actual mean
m.convertTo(m, CV_8U, 1. / images.size());
return m;
}
int main()
{
// Create a vector of 100 random images
vector<Mat3b> images;
for (int i = 0; i < 100; ++i)
{
Mat3b img(598, 598);
randu(img, Scalar(0), Scalar(256));
images.push_back(img);
}
// Compute the mean
Mat3b meanImage = getMean(images);
// Show result
imshow("Mean image", meanImage);
waitKey();
return 0;
}
Suppose that the images will not need to undergo transformations (gamma, color space, or alignment). The numpy package lets you do this quickly and succinctly.
# List of images, all must be the same size and data type.
images=[img0, img1, ...]
avg_img = np.mean(images, axis=0)
This will auto-promote the elements to float. If you want the as BGR888, then:
avg_img = avg_img.astype(np.uint8)
Could also do uint16 for 16 bits per channel. If you are dealing with 8 bits per channel, you almost certainly won't need 100 images.
Firstly- convert images to floats. You have N=100 images. Imagine that a single image is an array of average pixel values of 1 image. You need to calculate an array of average pixel values of N images.
Let A- array of average pixel values of X images, B - array of average pixel values of Y images. Then C = (A * X + B * Y) / (X + Y) - array of average pixel values of X + Y images. To get better accuracy in floating point operations X and Y should be approximately equal
You may merge all you images like subarrays in merge sort. In you case merge operation is C = (A * X + B * Y) / (X + Y) where A and B are arrays of average pixel values of X and Y images

Map integer to color in rainbow spectrum

I want to color k clusters of points in a 2D grid. Right now I using a naive approach.
I'm using RGB to set a color, the G component is fix, R is counted down gradually, B is counted up gradually. So the first cluster has R set to 255 and the last to 0, vice versa for B.
int r = 255, g = 80, b = 0;
// do stuff
int step = 255 / k;
// loop over data
int cluster = getCurrentCluster();
int currentR = r - (cluster * step);
int currentG = g;
int currentB = b + (cluster * step);
The current solution is working and effektive. It's possible to differentiate the clusters by colors
But I don't like it, and would prefer rainbow colors or at least a richer spectrum.
How can I achieve that? How can I map an integer in interval [0, k) to a color that meets my requirements?
Another approach that came to my mind was to map the integer to a wave length in a given interval, e.g. 400 nm to 800 nm (should roughly be the rainbow spectrum, if I recall correctly) and convert the wavelength to RGB.
If you want to map a linear range to a rainbow like spectrum then you are better off starting with a color space like HSV and then convert to RGB.
Here you find the details of the conversion
HSV will give the nicest results, but needs trigonometry.
Instead, consider three functions:
R: r = x < 256 ? 255 - x : x < 512 ? 0 : x - 512
G: g = x < 256 ? x : x < 512 ? 512 - x : 0
B: b = x < 256 ? 0 : x < 512 ? x - 256 : 768 - x
These may be easier and faster, although less aestethically pleasing (not so a nice yellow, orange, etc.)

OpenCV double mat shows up as all white

I have a type 6 (double-valued, single channel) mat with data ranging from 0 to 255. I can print out the data using the following code:
double* data = result.ptr<double>();
for(int i = 0; i < rows; i++)
for(int j = 0; j < cols; j++)
std::cout<<data[i*step+j]<<"\t";
And this appears perfectly normal--in the range from 0 to 255 and the size that I'd expect. However, when I try to show the image:
imshow(window_name, result);
waitKey();
I just get a white image. Just white pixels. Nothing else.
Loading other images from files and displaying in the window works fine.
Using Windows 7, OpenCV 233
cv::imshow works in following ways -
If the image is 8-bit unsigned, it is displayed as is.
If the image is 16-bit unsigned or 32-bit integer, the pixels are divided by 256. That is, the value range [0,255*256] is mapped to [0,255].
If the image is 32-bit floating-point, the pixel values are multiplied by 255. That is, the value range [0,1] is mapped to [0,255].
Your matrix lies in the 3rd category where imshow is expecting the values to be between 0 and 1 and so it multiplies it by 255. Since your values are already between 0 and 255, you are getting unwanted result. So normalizing the pixels between 0 and 1 will work.
You need to normalize your floating point image so that the values are between 0.0 - 1.0 if you're using imshow. I bet your values are over 1.0 and thus those pixels are all set to 255, giving you the white image.

Converting byte number to grayscale and vica versa

I got a number between 0-255 and need to convert it to a RGB grayscale color. And how do I convert a RGB-color to a grayscale value between 0-255?
The common formula is luminosity = 0.30 * red + 0.59 * green + 0.11 * blue. Matches the human eye's color perception, doesn't otherwise correct for display device gamma.
If you have a number 0 <= x <= 255 representing a grayscale value, the corresponding RGB tuple is simply (x,x,x).