cvtColor( InputArray src, OutputArray dst, int code, int dstCn=0 );
这是opencv中色彩转换函数。当由Lab转RGB时,code = COLOR_Lab2RGB,输出的RGB值域[0,1],再乘255,输出像素值即可归至[0,255]。
色彩空间转换过程中,接口函数做了截断处理,即R,G,B的值,若<0,修改为0;若>1,修改为1。opencv2.4.10源码截断处理如下所示:
#define clip(value) value < 0.0f ? 0.0f : value > 1.0f ? 1.0f : value;
float ro = clip(C0 * x + C1 * y + C2 * z);
float go = clip(C3 * x + C4 * y + C5 * z);
float bo = clip(C6 * x + C7 * y + C8 * z);
如果转换的图像没有alpha通道,最后还要做gamma处理增强对比度:
if (gammaTab)
{
ro = splineInterpolate(ro * gscale, gammaTab, GAMMA_TAB_SIZE);
go = splineInterpolate(go * gscale, gammaTab, GAMMA_TAB_SIZE);
bo = splineInterpolate(bo * gscale, gammaTab, GAMMA_TAB_SIZE);
}
常见的转换方法,是没有这个步骤处理的。
所以,如果不做gamma处理,用博主https://blog.csdn.net/lz0499/article/details/77345166中介绍的方法即可,思路非常清晰,代码也很容易理解。其结果与opencv不做gamma处理的结果是完全一样的。但如果要做gamma处理,同时不做截断处理,由于本人对cvtColor函数没有彻底看懂,就粗暴的把转换部分代码摘取出来,并去掉了clip处理。代码如下:
enum { LAB_CBRT_TAB_SIZE = 1024, GAMMA_TAB_SIZE = 1024 };
static ushort sRGBGammaTab_b[256], linearGammaTab_b[256];
static float sRGBGammaTab[GAMMA_TAB_SIZE*4], sRGBInvGammaTab[GAMMA_TAB_SIZE*4];
static const float GammaTabScale = (float)GAMMA_TAB_SIZE;
static float LabCbrtTab[LAB_CBRT_TAB_SIZE*4];
static const float LabCbrtTabScale = LAB_CBRT_TAB_SIZE/1.5f;
enum
{
yuv_shift = 14,
xyz_shift = 12,
R2Y = 4899,
G2Y = 9617,
B2Y = 1868,
BLOCK_SIZE = 256
};
#define lab_shift xyz_shift
#define gamma_shift 3
#define lab_shift2 (lab_shift + gamma_shift)
#define LAB_CBRT_TAB_SIZE_B (256*3/2*(1<<gamma_shift))
static ushort LabCbrtTab_b[LAB_CBRT_TAB_SIZE_B];
static const float XYZ2sRGB_D65[] =
{
3.240479f, -1.53715f, -0.498535f,
-0.969256f, 1.875991f, 0.041556f,
0.055648f, -0.204043f, 1.057311f
};
static const float D65[] = { 0.950456f, 1.f, 1.088754f };
class CLab2RGBImpl
{
public:
void init(int blueIdx,bool _srgb)
{
srgb = _srgb;//当没有alpha通道时,srgb = true
blueInd = blueIdx;//蓝通道索引:转rgb,blueIdx = 2;转bgr,blueIdx = 0;
initLabTabs();
const float* _coeffs = XYZ2sRGB_D65;
const float* _whitept = D65;
for( int i = 0; i < 3; i++ )
{
coeffs[i+(blueIdx^2)*3] = _coeffs[i]*_whitept[i];
coeffs[i+3] = _coeffs[i+3]*_whitept[i];
coeffs[i+blueIdx*3] = _coeffs[i+6]*_whitept[i];
}
}
void convert(const cv::Mat& matSrc,cv::Mat& matDst)
{
int i, dcn = 3;
const float* gammaTab = srgb ? sRGBInvGammaTab : 0;
float gscale = GammaTabScale;
float C0 = coeffs[0], C1 = coeffs[1], C2 = coeffs[2],
C3 = coeffs[3], C4 = coeffs[4], C5 = coeffs[5],
C6 = coeffs[6], C7 = coeffs[7], C8 = coeffs[8];
static const float lThresh = 0.008856f * 903.3f;
static const float fThresh = 7.787f * 0.008856f + 16.0f / 116.0f;
if (matSrc.ptr(0) == NULL)
{
return;
}
int nCn = matSrc.channels();
int nWidth = matSrc.cols;
int nHeigh = matSrc.rows;
if (nCn != 3)
{
return;
}
if (matDst.ptr(0) == NULL)
{
matDst = cv::Mat(matSrc.size(),matSrc.type());
}
float* pSrcData = (float*)(matSrc.ptr(0));
float* pDstData = (float*)(matDst.ptr(0));
for (int y = 0; y < nHeigh; y++)
{
for (int x = 0; x < nWidth; x++)
{
float li = pSrcData[0];
float ai = pSrcData[1];
float bi = pSrcData[2];
float y, fy;
if (li <= lThresh)
{
y = li / 903.3f;
fy = 7.787f * y + 16.0f / 116.0f;
}
else
{
fy = (li + 16.0f) / 116.0f;
y = fy * fy * fy;
}
float fxz[] = { ai / 500.0f + fy, fy - bi / 200.0f };
for (int j = 0; j < 2; j++)
if (fxz[j] <= fThresh)
fxz[j] = (fxz[j] - 16.0f / 116.0f) / 7.787f;
else
fxz[j] = fxz[j] * fxz[j] * fxz[j];
float x = fxz[0], z = fxz[1];
//cv源码在此做截断,即ro,go,bo:小于0赋值0,大于1赋值1
float ro = C0 * x + C1 * y + C2 * z;
float go = C3 * x + C4 * y + C5 * z;
float bo = C6 * x + C7 * y + C8 * z;
if (gammaTab) //没有alpha通道,做下面处理。相比常见的转换方法,如果没有此步骤,结果一样
{
ro = splineInterpolate(ro * gscale, gammaTab, GAMMA_TAB_SIZE);
go = splineInterpolate(go * gscale, gammaTab, GAMMA_TAB_SIZE);
bo = splineInterpolate(bo * gscale, gammaTab, GAMMA_TAB_SIZE);
}
pDstData[0] = ro;
pDstData[1] = go;
pDstData[2] = bo;
pSrcData += 3;
pDstData += 3;
}
}
}
protected:
static void initLabTabs()
{
static bool initialized = false;
if(!initialized)
{
float f[LAB_CBRT_TAB_SIZE+1], g[GAMMA_TAB_SIZE+1], ig[GAMMA_TAB_SIZE+1], scale = 1.f/LabCbrtTabScale;
int i;
for(i = 0; i <= LAB_CBRT_TAB_SIZE; i++)
{
float x = i*scale;
f[i] = x < 0.008856f ? x*7.787f + 0.13793103448275862f : cvCbrt(x);
}
splineBuild(f, LAB_CBRT_TAB_SIZE, LabCbrtTab);
scale = 1.f/GammaTabScale;
for(i = 0; i <= GAMMA_TAB_SIZE; i++)
{
float x = i*scale;
g[i] = x <= 0.04045f ? x*(1.f/12.92f) : (float)pow((double)(x + 0.055)*(1./1.055), 2.4);
ig[i] = x <= 0.0031308 ? x*12.92f : (float)(1.055*pow((double)x, 1./2.4) - 0.055);
}
splineBuild(g, GAMMA_TAB_SIZE, sRGBGammaTab);
splineBuild(ig, GAMMA_TAB_SIZE, sRGBInvGammaTab);
for(i = 0; i < 256; i++)
{
float x = i*(1.f/255.f);
sRGBGammaTab_b[i] = (ushort)max((int)(255.f*(1 << gamma_shift)*(x <= 0.04045f ? x*(1.f/12.92f) : (float)pow((double)(x + 0.055)*(1./1.055), 2.4))), 0);
linearGammaTab_b[i] = (ushort)(i*(1 << gamma_shift));
}
for(i = 0; i < LAB_CBRT_TAB_SIZE_B; i++)
{
float x = i*(1.f/(255.f*(1 << gamma_shift)));
LabCbrtTab_b[i] = (ushort)max((int)((1 << lab_shift2)*(x < 0.008856f ? x*7.787f + 0.13793103448275862f : cvCbrt(x))), 0);
}
initialized = true;
}
}
// computes cubic spline coefficients for a function: (xi=i, yi=f[i]), i=0..n
template<typename _Tp> static void splineBuild(const _Tp* f, int n, _Tp* tab)
{
_Tp cn = 0;
int i;
tab[0] = tab[1] = (_Tp)0;
for(i = 1; i < n-1; i++)
{
_Tp t = 3*(f[i+1] - 2*f[i] + f[i-1]);
_Tp l = 1/(4 - tab[(i-1)*4]);
tab[i*4] = l; tab[i*4+1] = (t - tab[(i-1)*4+1])*l;
}
for(i = n-1; i >= 0; i--)
{
_Tp c = tab[i*4+1] - tab[i*4]*cn;
_Tp b = f[i+1] - f[i] - (cn + c*2)*(_Tp)0.3333333333333333;
_Tp d = (cn - c)*(_Tp)0.3333333333333333;
tab[i*4] = f[i]; tab[i*4+1] = b;
tab[i*4+2] = c; tab[i*4+3] = d;
cn = c;
}
}
// interpolates value of a function at x, 0 <= x <= n using a cubic spline.
template<typename _Tp> static inline _Tp splineInterpolate(_Tp x, const _Tp* tab, int n)
{
// don't touch this function without urgent need - some versions of gcc fail to inline it correctly
int ix = min(max(int(x), 0), n-1);
x -= ix;
tab += ix*4;
return ((tab[3]*x + tab[2])*x + tab[1])*x + tab[0];
}
private:
int dstcn;
float coeffs[9];
bool srgb;
int blueInd;
};
使用时,首先定义类CLab2RGBImpl,然后init初始化,最后convert转换。当图像比较大,需要分块转换时,只用初始化一次。