C语言RGB转Lab色域

RGB颜色空间

RGB颜色是一种便于计算机识别的色彩模式,采用加色法,R为红色,G为绿色,B为蓝色,三种颜色从0(关闭)到255(亮度最大).

一般有24位的RGB888,三个分量都为8位,和16位的RGB565,三个分量分别为5位6位5位.

设备相关

因为颜色的视觉效果,是基于设备和材料的。当设备和材料改变时,对应的颜色效果就会发生变化,我们称这样的颜色空间为设备相关颜色空间,如RGB和CMYK颜色空间。当不同呈色模式的设备来表现同一颜色时,对应的颜色数值是不同的。

而如在HSB的基础上建立起来的CIE Lab*颜色空间,直接用一组数值来模拟人类的颜色视觉,而不是使用一组所需要的数值去驱动一个特定设备来生成颜色,其色彩数值与设备无关,称为设备无关颜色空间。

XYZ颜色空间

RGB是一种设备相关的颜色空间,每一种设备使用RGB模型时,都有不太相同的定义,如在显示器和打印机上就不太一样,因此在使用RGB模型时,这些设备之间关于RGB的定义,并不能通用。我们在比较两种颜色时,经常会忽略光照的因素,如果比较两种颜色所处的光照环境不一致,那将毫无意义。

RGB匹配光谱时可能会出现负值,并且没有考虑亮度,而设备无关的颜色空间需要考虑亮度,对此CIE制定了两条标准

    定义了XYZ颜色空间,一种与RGB相关的想象基色系统,更适于颜色计算 定义了标准光源

XYZ是对RGB的一种线性变换,变换后颜色空间使用XYZ为三基色,三个值都用正数匹配所有颜色。

为什么变换一下就能设备无关呢,因为绿波是中波,横跨了光谱坐标,蓝色是短波,红色是长波,而比短波短或比长波长的电磁波我们就看不见了,人眼对绿色最为敏感,故Y定义为为亮度,又包含了绿色信息。

XYZ不是RGB三者的组合,X值近一半由R组成,Y值80%由G组成,Z值99%都是B。

Lab颜色空间

Lab色域是CIE(国际照明委员会)制定的一种色彩模式,由一个亮度通道和两个颜色通道组成

    L: 亮度 a: -128绿色 -> +127红色分量, b: -128蓝色 -> +127黄色分量

Lab色域是基于人对颜色的感觉设计的,与人视觉工作方式接近,概括人眼能看到的所有颜色,如果Lab三通道变化幅度一样,其给人在视觉上变化幅度也相近,要比RGB,CMYK等颜色空间有更广的色域等

隐藏a通道 隐藏b通道

RGB565转Lab

gamma校正

上面的色彩空间都是线性增长的,但人眼对亮度感知能力却不是线性关系的,相对纯白(亮度100%)和纯黑(亮度0%),人眼对中等亮度的感知并非中灰,当人眼接受20%左右亮度光源,才会觉得这是中等亮度.

RGB转XYZ

需要归一化后进行gamma校正

$ left{egin{matrix} R= m gamma (frac{r}{255} ) G= m gamma (frac{g}{255} ) B= m gamma (frac{b}{255} ) end{matrix} ight. $

$ m gamma(x)=left{egin{matrix} left ( frac{x+0.055}{1.055} ight ) ^{2.4}& x>0.04045 frac{x}{12.92} end{matrix} ight. $

$ egin{bmatrix}X Y  end{bmatrix}= M*egin{bmatrix}R G B end{bmatrix} $

$ M=egin{bmatrix} 0.4124 & 0.3576 & 0.1805 0.2126 & 0.7152 & 0.0722 0.0193 & 0.1192 & 0.9505 end{bmatrix} $

XYZ色域转Lab色域

$ f(t)=left {egin{matrix} t^{1/3} & t>(frac{6}{29} )^3 frac{1}{3}(frac{29}{6} )^2t+frac{4}{29} end{matrix} ight . $

$ left{egin{matrix} L=116f(Y_1)-16 a=500(f(X/X_n)-f(Y/Y_n)) b=200*(f(Y_1)-f(Z_1)) end{matrix} ight. $

C代码实现

inline float gamma(float x)
{return x>0.04045?pow((x+0.055f)/1.055f,2.4f):x/12.92;};

void RGBToLab(unsigned char*rgbImg,float*labImg)
{
    float B=gamma(rgbImg[0]/255.0f);
    float G=gamma(rgbImg[1]/255.0f);
    float R=gamma(rgbImg[2]/255.0f);
    float X=0.412453*R+0.357580*G+0.180423*B;
    float Y=0.212671*R+0.715160*G+0.072169*B;
    float Z=0.019334*R+0.119193*G+0.950227*B;

  float X/=0.95047;
  float Y/=1.0;
  float Z/=1.08883;

    float FX = X > 0.008856f ? pow(X,1.0f/3.0f) : (7.787f * X +0.137931f);
    float FY = Y > 0.008856f ? pow(Y,1.0f/3.0f) : (7.787f * Y +0.137931f);
    float FZ = Z > 0.008856f ? pow(Z,1.0f/3.0f) : (7.787f * Z +0.137931f);
    labImg[0] = Y > 0.008856f ? (116.0f * FY - 16.0f) : (903.3f * Y);
    labImg[1] = 500.f * (FX - FY);
    labImg[2] = 200.f * (FY - FZ);
}

这是未经过优化的代码,对每个像素三通道都进行原公式运算,运算量非常大,不适于在性能有限的单片机上跑.

而gamma和f(t)都是比较固定的运算,可以转换为查表运算,将浮点转换为整型,将乘除转换为移位.

static int LabTable[1024];
static int GamaTable1[32];
static int GamaTable2[64];//B有6位,特殊对待
void CreateTable() // 初始化建表
{
  for (int I = 0; I < 1024; I++)
  {
    if (I > 9)
      LabTable[I] = (int)(pow((float)I / 1024, 1.0F / 3.0F) * 1024 );
    else
      LabTable[I] = (int)(7.787F * I + 141.2 );
  }
  for (int J = 0; J < 32; J++)
  {
    float x = J/32.0F;
    x = x>0.04045?pow((x+0.055f)/1.055f,2.4f):x/12.92;
    GamaTable1[J] = (int)(x*1024);
  }
  for (int K = 0; K < 64; K++)
  {
    float y = K/64.0F;
    y = y>0.04045?pow((y+0.055f)/1.055f,2.4f):y/12.92;
    GamaTable2[K] = (int)(y*1024);
  }
}

在2021年电赛题中的应用,图中屏幕上的白色区域就是农夫山泉包装,摄像头为凌瞳彩色摄像头.

这样对每一帧图像,遍历其像素,调用以下代码即可得到Lab三通道值.

x=(455026*GamaTable1[r]+394489*GamaTable2[g]+199046*GamaTable1[b])>>20;
y=(223002*GamaTable1[r]+749900*GamaTable2[g]+75675*GamaTable1[b])>>20;
z=(18619*GamaTable1[r]+114786*GamaTable2[g]+915097*GamaTable1[b])>>20;
L = y > 9 ? (116 * LabTable[y] - 16384)>> 10: (903 * LabTable[y])>> 10;
A = (500 * (LabTable[x] - LabTable[y]))>> 10;
B = (200 * (LabTable[y] - LabTable[z]))>> 10;
参考
经验分享 程序员 微信小程序 职场和发展