文档章节

颜色空间系列2: RGB和CIELAB颜色空间的转换及优化算法

abcijkxyz
 abcijkxyz
发布于 2016/11/22 16:39
字数 2563
阅读 6
收藏 0
点赞 0
评论 0

       颜色空间系列代码下载链接:http://files.cnblogs.com/Imageshop/ImageInfo.rar (同文章同步更新)

      在几个常用的颜色空间中,LAB颜色空间是除了RGB外,最常用的一种之一,不同于RGB色彩空间,Lab 颜色被设计来接近人类视觉。它致力于感知均匀性,它的 L 分量密切匹配人类亮度感知。因此可以被用来通过修改 a 和 b 分量的输色阶来做精确的颜色平衡,或使用 L 分量来调整亮度对比。这些变换在 RGB 或 CMYK 中是困难或不可能的,它们建模物理设备的输出,而不是人类视觉感知。

     关于CIELAB颜色空间的更多原理说明,可见:http://en.wikipedia.org/wiki/Lab_color_space

     本文研究的重点是RGB和LAB之间的快速转换过程。

     首先,RGB和LAB之间没有直接的转换公式,其必须用通道XYZ颜色空间作为中间层,关于RGB和XYZ颜色空间的转换及优化,详见颜色空间系列1

     XYZ------>LAB转换公式如下:一般情况下我们认为Yn,Xn,Zn都为1。

 

     

其中

     

      在上述表达式中,X,Y,Z及t变量的取值范围都是[0,1],对应的L分量的取值范围为[0,100],A和B分量都为[-127,127],因此,如果把L拉升至[0,255],把A,B位移至于[0,255],就可以同RGB颜色空间表达为同一个范围了。即使这样映射后,一般来说,LAB各分量的结果仍为浮点数,这个和RGB不同,但是在很多情况下,为了速度计效率,我们这需结果的取整部分,得到类似于RGB空间的布局。因此,对这类结果的优化更有实际意义。

      关于这样的优化,OpenCv已经做了非常好的工作,各位看客也可以先看看OpenCv的代码,本文未直接沿用其优化,但本文的算法更简单明了,在保证结果无明显变化的同时,速度和效率都有30%以上的提升。

      第一步,我们来看看f(t)这个函数的优化,f(t)是个分段函数,如果直接在函数体中判断,会多一些跳转和比较语句,不利于CPU的流水线工作,因此,我考虑的第一步是是否能用查表法来做。

     在颜色空间系列1文章中,我们知道,转换后的XYZ值得范围是[0,255],而这里的t值范围为[0,1],把if t>(6/29)^3这个算法映射到[0,255],则为 if t>2.26 ,因为XYZ都为整数,即此条件和if t>2等价,可见这里会出现一些漏判点;考虑2.26这个数字的特点,如果我们在把这个结果放大4倍,即XYZ范围为[0,1020],则判断条件随之升级为if t>9.04,取整if t>9,则漏判现象大为减少。这是提的第一点。

     接着上面,这样的话我们就定义一个查找表,查找表大小应该和XYZ的域相同的,即上面的1020(我更喜欢1024),对于表中的元素值,为求速度,当然必须为int 类型,

也就是说,需要把计算出来的小数值放大一定倍数。这里不多说,见下面的代码:

for (I = 0; I < 1024; I++)
    {
        if (I > Threshold)
            LabTab[I] = (int)(Math.Pow((float)I / 1020, 1.0F / 3) * (1 << Shift) + 0.5 );
        else
            LabTab[I] = (int)((29 * 29.0 * I / (6 * 6 * 3 * 1020) + 4.0 / 29) * (1 << Shift) + 0.5 );
    }

     C#语言是强类型语言,一定要注意运算式中各变量的类型,比如上式中的1.0F/3,我常常写成1/3(这个的运算结果为0),结果往往是总觉得程序写得没问题,但运行效果就是不对,找半天BUG也找不到。

     I / 1020的目的还是把值映射到[0,1]范围的。 表达式最后的+0.5是因为(int)强制类型转换时向下取整的,+0.5则为四舍五入的效果。显然,这是我们需要的。

     OK,有了这个查找表,下面的过程就简单了,对于A,B分量,就是进行简单的乘法、移位及加法,而对于L分量,必须有一个放大的过程,而这个过程我们应该直接从其系数入手,如下所示:

const int ScaleLC = (int)(16 * 2.55 * (1 << Shift) + 0.5);
   const int ScaleLT = (int)(116 * 2.55 + 0.5);

     2.55即为放大倍数,注意116这个数字,由于,其后的 f(x)已经进行了放大,该数字就不能再放大了。

     通过以上分析,一个简单的而有高效转换算法就有了:

public static unsafe void ToLAB(byte* From, byte* To, int Length = 1)
    {
        if (Length < 1) return;
        byte* End = From + Length * 3;
        int X, Y, Z, L, A, B;
        byte Red, Green, Blue;
        while (From != End)
        {
            Blue = *From; Green = *(From + 1); Red = *(From + 2);
            X = (Blue * LABXBI + Green * LABXGI + Red * LABXRI + HalfShiftValue) >> (Shift - 2);  //RGB->XYZ放大四倍后的结果
            Y = (Blue * LABYBI + Green * LABYGI + Red * LABYRI + HalfShiftValue) >> (Shift - 2);
            Z = (Blue * LABZBI + Green * LABZGI + Red * LABZRI + HalfShiftValue) >> (Shift - 2);
            X = LabTab[X];          // 进行查表
            Y = LabTab[Y];
            Z = LabTab[Z];
            L = ((ScaleLT * Y - ScaleLC + HalfShiftValue) >>Shift);
            A = ((500 * (X - Y) + HalfShiftValue) >> Shift) + 128;
            B = ((200 * (Y - Z) + HalfShiftValue) >> Shift) + 128;
            *To = (byte)L;          // 不要把直接计算的代码放在这里,会降低速度
            *(To + 1) = (byte)A;    // 无需判断是否存在溢出,因为测试过整个RGB空间的所有颜色值,无颜色存在溢出
            *(To + 2) = (byte)B;
            From += 3;
            To += 3;
        }
    }

    再来看看反转的过程,即LAB-XYZ的算法,理论公式如下:

     

其中:

      

      注意,我这里说的转换有个前期条件,即LAB的数据是用类似于RGB空间的布局表达的,也就是说LAB各元素为byte类型。

      我曾自己的研究过这些算法,如果完全像上面那样靠整数乘法及移位来实现,主要的难度是t^3这个表达式的计算结果会超出int类型的表达范围,而如果用64位的long类型,在目前32位机器依旧占主流配置的情况下,速度会下降很多。因此,我最后的研究还是以空间换时间的方法来实现。具体分析如下:

      观察上式分析,Y的值只于L有关,而L由于我们的限定,只能取[0,255]这256个值,因此建立一个256个元素的查找表即可,而X及Z的值分别于L及A/B有关,需要建立256*256个元素的查找表即可,大约占用0.25MB的内存。查找表的建立如下:

for (I = 0; I < 256; I++)
    {
        T = I * Div116 + Add16;
        if (T > ThresoldF)
            Y = T * T * T;
        else
            Y = MulT * (T - Sub4Div29);
        TabY[I] = (int)(Y * 255 + 0.5);      // 映射到[0,255]
        for (J = 0; J < 256; J++)
        {
            X = T + Div500 * (J - 128);
            if (X > ThresoldF)
                X = X * X * X;
            else
                X = MulT * (X - Sub4Div29);
            TabX[Index] = (int)(X * 255 + 0.5);

            Z = T - Div200 * (J - 128);
            if (Z > ThresoldF)
                Z = Z * Z * Z;
            else
                Z = MulT * (Z - Sub4Div29);
            TabZ[Index] = (int)(Z * 255 + 0.5);
            Index++;
        }
    }

      最终的LAB-RGB转换算法如下:

public static unsafe void ToRGB(byte* From, byte* To, int Length = 1)
    {
        if (Length < 1) return;
        byte* End = From + Length * 3;
        int L, A, B, X, Y, Z;
        int Blue, Green, Red;
        while (From != End)
        {
            L = *(From); A = *(From + 1); B = *(From + 2);
            X = TabX[L * 256 + A];      // *256编译后会自动优化为移位的
            Y = TabY[L];
            Z = TabZ[L * 256 + B];
            Blue = (X * LABBXI + Y * LABBYI + Z * LABBZI + HalfShiftValue) >> Shift;  
            Green = (X * LABGXI + Y * LABGYI + Z * LABGZI + HalfShiftValue) >> Shift;
            Red = (X * LABRXI + Y * LABRYI + Z * LABRZI + HalfShiftValue) >> Shift;
            if (Red > 255) Red = 255; else if (Red < 0) Red = 0;
            if (Green > 255) Green = 255; else if (Green < 0) Green = 0;            // 需要有这个判断
            if (Blue > 255) Blue = 255; else if (Blue < 0) Blue = 0;
            *(To) = (byte)Blue;
            *(To + 1) = (byte)Green;
            *(To + 2) = (byte)Red;
            From += 3;
            To += 3;
        }
    }

      通过以上的分析,可以看出,这个转换的过程代码很简单,清晰,而且效率不菲,对一副4000*3000的数码照片进行RGB->LAB,然后再LAB->RGB算法本体的时间只有250ms。

     还有几个优化的地方就是我的所有的查找表都不是用的C#的数组,而是直接分配内存,这是因为C#的数组在很多情况下会有一个判断是否越界的汇编码,而用非托管内存则不会。

     比如,以下是用非托管内存的数组访问的反汇编:

static int* TabX = (int*)Marshal.AllocHGlobal(256 * 256 * 4);    // 这是原始的定义
X = TabX[L * 256 + A];      // *256编译后会自动优化为移位的
00000037  mov         eax,edi 
00000039  shl         eax,8          // 看到这里的移位没有
0000003c  add         eax,edx 
0000003e  mov         edx,dword ptr ds:[005A1F0Ch] 
00000044  mov         eax,dword ptr [edx+eax*4] 
00000047  mov         dword ptr [ebp-14h],eax

      而用C#的数组方式生产的汇编如下:

static int[] TabX = new int[256 * 256];     // 这是原始的定义
X = TabX[L * 256 + A];      // *256编译后会自动优化为移位的
0000003c  mov         eax,edi 
0000003e  shl         eax,8 
00000041  add         eax,edx 
00000043  mov         edx,dword ptr ds:[02A27C68h]   
00000049  cmp         eax,dword ptr [edx+4]   // 多出这两句代码
0000004c  jae         00000133 
00000052  mov         eax,dword ptr [edx+eax*4+8] 
00000056  mov         dword ptr [ebp-14h],eax

      其实还有很多细节上的优化的东西,比如语句的顺序的讲究,有的时候就是调换下不同行的语句,程序的执行效率就有很多的不同,这主要是编译器的优化不同造成的,比如适当的顺序会让编译器选择某个常用变量为寄存器变量。 还比如有人喜欢用下面的代码

*To++ = (byte)L;
  *To++ = (byte)A;
  *To++ = (byte)B;

     来代替:

*To = (byte)L;          
 *(To + 1) = (byte)A;    
 *(To + 2) = (byte)B;
 To += 3;

     虽然代码看上去简洁了,可你执行后就知道速度反而慢了,为什么,我想我会在适当时候写一些关于C#优化方面的粗浅文章在对此进行解释吧。

     最后附上一些处理的效果,还是拿系列1文章中那些崇洋的新贵门来做实验吧:

     原图:

      

     转换后的综合图像:

     

    L通道:

     

     A通道: 

     

     B通道:

     

     同样的道理,上述快速算法如果进行多次转换,必然也存在精度上的损失。

     LAB空间在以后的肤色检测文章中还会有提到。

 

'*********************************************************************

  转载请保留以下信息:

  作者: laviewpbt

  时间:2013.2.2   11点于家中

  QQ:33184777

  E-Mail : laviewpbt@sina.com

本文转载自:http://www.cnblogs.com/Imageshop/archive/2013/02/02/2889897.html

共有 人打赏支持
abcijkxyz
粉丝 60
博文 6196
码字总数 1876
作品 0
深圳
项目经理
将RGB值转换为灰度值的简单算法

原文地址:点击打开链接 RGB是如何转换为灰度的?这是让人困惑已久的一道难题 1、RGB复合通道转灰度:转换后的色阶值只与RGB空间有关,而与作为目的地的灰度空间无关。也就是说,只要当前的R...

floatdreamed
01/09
0
0
YUV / RGB 格式分析及快速查表算法设计

1 前言 自然界的颜色千变万化,为了给颜色一个量化的衡量标准,就需要建立色彩空间模型来描述各种各样的颜色,由于人对色彩的感知是一个复杂的生理和心理联合作用的过程,所以在不同的应用领...

鉴客
2011/10/01
389
0
​转换色彩空间JavaScript插件--ColorConverter.js

转换色彩空间JavaScript插件 目前支持的色彩空间 RGB RGBA => RGB (单向) CMY CMYK HSV (HSB) HSL XYZ Lab (CIELab) LCH LUV...

Rijn
2015/06/09
445
0
颜色空间:RGB,CMY,HSV,HSL,Lab详解

颜色空间(彩色模型、色彩空间、 彩色系统etc)是对色彩的一种描述方式,定义有很多种,区别在于面向不同的应用背景。 例如显示器中采用的RGB颜色空间是基于物体发光定义的(RGB正好对应光的...

li_wen01
2017/06/05
0
0
【Python+OpenCV】实现RGB转HSI

cv2.cvtColor函数封装了各种颜色空间之间的转换,唯独没有RGB与HSI之间的转换,网上查来查去也只有C++或MATLAB版本的,自己要用到python里,所以就写写python版本的。 HSI颜色模型是一个满足...

lwplwf
2017/08/23
0
0
老旧黑白片修复机——使用卷积神经网络图像自动着色实战(附PyTorch代码)

摘要: 照片承载了很多人在某个时刻的记忆,尤其是一些老旧的黑白照片,尘封于脑海之中,随着时间的流逝,记忆中对当时颜色的印象也会慢慢消散,这确实有些可惜。技术的发展会解决一些现有的...

阿里云云栖社区
06/04
0
0
视频监控之颜色模型——RGB、HSV模型

目前常用的颜色模型可分为两类,一类面向诸如彩色显示器或打印机之类的硬件设备,另一类面向以彩色处理为目的的应用,如动画中的彩色图形。面向硬件设备的最常用彩色模型是 RGB 模型,而面向...

小薇
2013/03/01
0
0
RGB和YUV的转换与区别

YUV与RGB互转各种公式- http://www.cnblogs.com/luoyinjie/p/7219319.html > YUV YUV是被欧洲电视系统所采用的一种颜色编码方法(属于PAL),是PAL和SECAM模拟彩色电视制式采用的颜色空间。在...

shareus
2017/11/12
0
0
解决问题的艺术:半小时编程实现照片的反转负冲特效

如何直接有效的解决问题是一门艺术。我们是做产品、做系统、做服务的,不是玩技术的,需要做的是在最短的时间内以最有效的方式来解决工作中面对的难题。就在刚才,用了半小时不到时间,俺用一...

最美的回忆
2017/03/06
0
0
Camera 图像处理原理分析

色彩篇(一) 1 前言 做为拍照手机的核心模块之一,camera sensor效果的调整,涉及到众多的参数,如果对基本的光学原理及sensor软/硬件对图像处理的原理能有深入的理解和把握的话,对我们的工...

Jerikc
2013/09/25
0
1

没有更多内容

加载失败,请刷新页面

加载更多

下一页

轻松搭建svn版本管理工具+svnmanager管理客户端

前面的文章有写过svn版本管理工具的安装是基于svn的安装包进行安装,对于svn与apache的结合还得下svn和apache的模块进行结合过程比较繁琐,今天来介绍下通过centos的yum来安装svn能够快速安装...

javazyw
16分钟前
0
0
keepalived配置高可用集群

Linux集群概述 根据功能划分为两大类:高可用和负载均衡 高可用集群通常为两台服务器,一台工作,另外一台作为冗余,当提供服务的机器宕机,冗余将接替继续提供服务 实现高可用的开源软件有:...

TaoXu
21分钟前
0
0
mysql联表批处理操作

1 概述 mysql中的单表增删改查操作,可以说是基本中的基本. 实际工作中,常常会遇到一些基本用法难以处理的数据操作,譬如遇到主从表甚至多级关联表的情况(如一些历史问题数据的批量处理),考虑到...

社哥
24分钟前
0
0
IntelliJ IDEA 详细图解最常用的配置,适合刚刚用的新人。

刚刚使用IntelliJ IDEA 编辑器的时候,会有很多设置,会方便以后的开发,磨刀不误砍柴工。 比如:设置文件字体大小,代码自动完成提示,版本管理,本地代码历史,自动导入包,修改注释,修改...

kim_o
39分钟前
0
0
Google Java编程风格指南

目录 前言 源文件基础 源文件结构 格式 命名约定 编程实践 Javadoc 后记 前言 这份文档是Google Java编程风格规范的完整定义。当且仅当一个Java源文件符合此文档中的规则, 我们才认为它符合...

niithub
41分钟前
0
0
java.net.MalformedURLException异常说明

1.异常片段 Java代码中,在进行URL url = new URL(urllink)操作时,提示以下异常信息,该类异常主要问题出在参数urllink上面。 异常片段1 java.net.MalformedURLException at java.ne...

lqlm
41分钟前
1
0
CentOS7修改mysql5.6字符集

解决办法:CentOS7下修改MySQL数据库字符编码为UTF-8,UTF-8包含全世界所有国家所需要的字符集,是国际编码。 具体操作如下: 1.进入MySQL [root@tianqi-01 ~]# mysql -uroot -p Enter passw...

河图再现
43分钟前
0
0
DevExpress v18.1新版亮点——WPF篇(一)

用户界面套包DevExpress v18.1日前终于正式发布,本站将以连载的形式为大家介绍各版本新增内容。本文将介绍了DevExpress WPF v18.1 的新功能,快来下载试用新版本!点击下载>> Accordion Co...

Miss_Hello_World
45分钟前
0
0
Rancher 2.0集群与工作负载告警

Rancher 2.0操作指南。本文将step by step演示如何使用Rancher 2.0中集成的告警功能,包括设置通知程序、设置集群级别以及工作负载级别的告警。 在Rancher 1.x时期,告警功能是很多Rancher用...

RancherLabs
50分钟前
1
0
Python中字符串拼接的N中方法

python拼接字符串一般有以下几种方法: ①直接通过(+)操作符拼接 s = 'Hello'+' '+'World'+'!'print(s) 输出结果:Hello World! 使用这种方式进行字符串连接的操作效率低下,因为python中...

木头释然
52分钟前
9
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部