I need to convert 32F images to L*ab color space, apply another algorithm (denoising), and then convert them back to BGR.
I have been having some problems that I think may be due to the accuracy of the BGR2LAB conversion. I did some investigating, and I may be right…
-
I tried loading an RGB image (a Lena), converting to L*ab and then back again. The same image is not exactly recovered. The difference between the original and the final image is about 0.001 (0=black, 1=white), varying pixel to pixel. Code to demonstrate this is at the end. This error is extremely significant for me. It basically destroys my data.
-
I compared the result of the L*ab conversion to various calculators. Again, they are close, but not quite the same. For example, using the code at the end, for (R,G,B)=(0.3,0.5,0.6), OpenCV gives L*ab of (50.866699, -9.875, -18.9375), compared to (50.58 -12.24 -19.41), or (50.88, -9.78, -18.99), and others. I’m not sure why there is the variation.
-
I also tried writing my own BGR2L*ab conversion functions (at the end). For some reason, I haven’t been able to get that to work either, so this has turned out to be a dead end.
Is this the expected level of accuracy for the CvtColor function? Am I just expecting too much of it? Is there anything I can do to enhance its accuracy? If not, I will need to think of an alternative way to denoise luminance and color separately.
I also tried taking my data non-linear (applying a stretch), so that the error level would be less significant compared to the signal. This works, kind of, but the error is still there, and introduces strange artefacts, and limits the effectiveness of subsequent operations.
Comparing an image to itself after conversion to L*ab and back again:
int main(int argc,char** argv)
{
cv::Mat image=imread(argv[1],cv::IMREAD_UNCHANGED);
if(!image.empty()) // check for invalid input
{
if(image.channels()==4) cv::cvtColor(image,image,cv::COLOR_BGRA2BGR);
if(image.depth()==CV_8U) image.convertTo(image,CV_32F,1.0/255.0); // convert 8 bit integer to 32 bit float scaling to range 0 to 1
else if(image.depth()==CV_16U) image.convertTo(image,CV_32F,1.0/65535.0); // convert 16 bit integer to 32 bit float scaling to range 0 to 1
else if(image.depth()==CV_16F) image.convertTo(image,CV_32F); // convert 16 bit integer to 32 bit float without rescaling
}
cv::Mat cvLab;
cv::cvtColor(image,cvLab,cv::COLOR_BGR2Lab);
cv::Mat backAgain;
cv::cvtColor(cvLab,backAgain,cv::COLOR_Lab2BGR);
std::cout<<image-backAgain<<std::endl;
return 0;
}
Converting BGR pixel to L*ab and outputting the result:
float r[1] = { 0.3 };
float g[1] = { 0.5 };
float b[1] = { 0.6 };
cv::Mat R = cv::Mat(1, 1, CV_32F, r);
cv::Mat G = cv::Mat(1, 1, CV_32F, g);
cv::Mat B = cv::Mat(1, 1, CV_32F, b);
std::vector<cv::Mat> channels;
channels.push_back(B);
channels.push_back(G);
channels.push_back(R);
cv::Mat image;
cv::merge(channels,image);
cv::Mat myLab;
cv::cvtColor(image,myLab,cv::COLOR_BGR2Lab);
std::cout<<myLab<<std::endl;
My own BGR2L*ab conversion function:
void BGR2Lab(const cv::Mat src,cv::Mat& dst)
{
float c[9]={0.412453,0.357580,0.180423,
0.212671,0.715160,0.072169,
0.019334,0.119193,0.950227};
std::vector<cv::Mat> planes;
cv::split(src,planes);
cv::Mat X=c[0]*planes[2]+c[1]*planes[1]+c[2]*planes[0];
cv::Mat Y=c[3]*planes[2]+c[4]*planes[1]+c[5]*planes[0];
cv::Mat Z=c[6]*planes[2]+c[7]*planes[1]+c[8]*planes[0];
X/=0.950456;
Z/=1.088754;
cv::Mat mask=cv::Mat(Y.size(),CV_8UC1);
cv::inRange(Y,0,0.008856,mask); // mask=white for pixels less than or equal to 0.008856
cv::Mat L=cv::Mat::zeros(Y.size(),CV_32FC1);
cv::pow(Y,1./3,L);
L=116.*L-16.;
L.setTo(0.,mask);
cv::Mat L2=903.3*Y;
cv::bitwise_not(mask,mask);
L2.setTo(0.,mask);
L+=L2;
auto f=[](cv::Mat &t)
{
cv::Mat mask=cv::Mat(t.size(),CV_8UC1);
cv::inRange(t,0,0.008856,mask); // mask=white for pixels less than or equal to 0.008856
cv::Mat f=cv::Mat::zeros(t.size(),CV_32FC1);
cv::pow(t,1./3,f);
f.setTo(0,mask);
cv::Mat f2=7.787*t+16./116;
cv::bitwise_not(mask,mask);
f2.setTo(0,mask);
f+=f2;
return f.clone();
};
cv::Mat fX=f(X);
cv::Mat fY=f(Y);
cv::Mat fZ=f(Z);
cv::Mat a=500.*(fX-fY);
cv::Mat b=200.*(fY-fZ);
std::vector<cv::Mat> LabPlanes={ L,a,b };
cv::merge(LabPlanes,dst);
}