├── README.md ├── note.txt ├── pic ├── boy1.png ├── boy2.png ├── boy3.png ├── boy4.png ├── boy5.png ├── boy6.png ├── boy7.png ├── girl1.png └── girl2.png └── test.py /README.md: -------------------------------------------------------------------------------- 1 | # 《自适应的快速人脸肤色转移》阅读报告 2 | 姓名:吴侃 3 | 学号:14348134 4 | 邮箱:wkcn@live.cn 5 | 6 | ## 前言 7 | 8 | 我阅读的论文的标题为《自适应的快速人脸肤色转移》,这篇论文的作者为魏玮、马军福。这篇论文提出了一种能够自适应、并且快速的人脸肤色转移方法,即将源图像中的人脸肤色转移到目标图像的人脸上,使目标图像的人脸肤色和源图像中的肤色比较相近。我在阅读论文后,对论文提到的算法进行了复现,发现论文中的算法存在瑕疵,没有达到论文中那么理想的效果。为此,我对论文中的算法错误进行更正,提出了改进方法,我也提出了一种论文展望中的“同时处理多幅人脸图像”算法。 9 | 阅读报告分为八个部分,分别是:论文内容、创新点、算法流程、与课本知识的联系、算法本质、算法复现、改进、感受。 10 | 11 | ## 一、论文内容 12 | 13 | 人脸肤色转移技术属于颜色迁移领域中的一个应用。颜色迁移的实质是“在不破坏目标图像纹理的条件下,将源图像的色彩信息传递给目标图像,使变换后的目标图像具有和源图像相似的色彩特性”。 14 | 人脸肤色转移即让目标图像中的人脸肤色尽可能地和源图像中的人脸肤色相近,同时保留人脸的细节。 15 | 16 | ## 二、创新点 17 | 18 | 我把《自适应的快速人脸肤色转移》论文中的方法的创新点归为两点:有针对性、自适应。 19 | 20 | 1. 有针对性 21 | 22 | 论文中提到了Reinhard等人提出的“彩色图像间颜色迁移”算法,这种算法利用颜色全局信息对整体图像进行颜色迁移。而在肤色迁移问题中,像人的头发、眼睛、嘴巴等颜色是不需要进行变换的,变换的部分只是人的皮肤颜色。在一些相片中,除了人之外还存在背景,使得整个图片的色彩信息变得丰富,但背景也是不需要处理的。本篇论文中的肤色迁移方法,首先选出了图片中的属于肤色的像素,再对这些肤色像素进行变换,避免了对非肤色的转换,针对性强。 23 | 24 | 2. 自适应 25 | 26 | 论文中提到的检测肤色像素的方法具有自适应性,即对不同光照、不同肤色的人的相片,都能比较好地检测出属于肤色的像素。主要体现在作者将检测肤色分为了粗提取、细提取两个阶段。粗提取阶段,在YCbCr颜色空间上粗略地提取出人脸肤色像素点,这一个步骤对误识别为肤色有较大的容忍度;细提取阶段,在Lab颜色空间下,对“可能是肤色的像素点”进行a通道、b通道进行颜色频率统计,这里利用了作者发现的一个规律:“根据600份不同纯肤色数据统计得出,在纯肤色区域各分量对应直方图最大值点的下标为中心,左右两边的对应的面积之差不会超过两边界中最大高度的2倍”。作者根据这个规律设计了一个收敛算法,使用这个算法可以进一步检测出纯肤色区域,这也是细提取阶段的关键方法。 27 | 论文中的自适应方法,启发了我思考不同光照环境下,基于颜色进行物体检测的问题,我可以先进行较广颜色范围内的粗提取,再利用颜色分布对粗提取的结果进行细提取。自适应方法和我现在做的空中机器人比赛,基于颜色使用逻辑回归方法检测地面机器人的工作很相关,我可以把自适应方法应用到我的比赛工作中。与此同时,我在比赛中用到的逻辑回归方法,在不同的光照环境下,都能的到较好的效果,我也可以应用逻辑回归方法来改进本篇文章论文的算法。 28 | 29 | ## 三、算法流程 30 | 31 | 论文中的算法分为4个步骤,分别为肤色区间初次聚类(粗提取)、肤色区间的精确聚类(细提取)、肤色转换、颜色矫正。 32 | 33 | ### 1. 肤色区间的初次聚类 34 | 35 | #### 过程: 36 | 37 | 将图像转换到YCbCr空间。 38 | 如果输入像素的颜色落入$$Cr\in[133,173]$$且$$Cb\in[77,127]$$中,就认为该点属于肤色像素。 39 | 用0,1矩阵分别标注出源图像和目标图像的某个像素是否被认为是肤色(注意这里是“被认为是肤色”,即被选出的像素不一定是肤色,而是有很大概率可能是肤色)。 40 | 41 | #### 理论依据: 42 | 作者使用了Chai D和Ngan K N在论文《Locating facial region of a head-and-shoulders colo rimage》提出的方法,这个方法是肤色检测方面著名且比较快速的方法,在我们做“人脸检测”项目中也尝试了这种方法。 43 | 44 | ### 2. 肤色区间的精确聚类 45 | 46 | #### 过程: 47 | 48 | ##### 2.1 将图像转换到Lab颜色空间 49 | 引用:http://blog.csdn.net/carson2005/article/details/7200440 50 | 同RGB颜色空间相比,Lab是一种不常用的色彩空间。它是在1931年国际照明委员会(CIE)制定的颜色度量国际标准的基础上建立起来的。1976年,经修改后被正式命名为CIELab。它是一种设备无关的颜色系统,也是一种基于生理特征的颜色系统。这也就意味着,它是用数字化的方法来描述人的视觉感应。Lab颜色空间中的L分量用于表示像素的亮度,取值范围是[0,100],表示从纯黑到纯白;a表示从红色到绿色的范围,取值范围是[127,-128];b表示从黄色到蓝色的范围,取值范围是[127,-128]。下图所示为Lab颜色空间的图示; 51 | ![](lab.gif) 52 | ##### 2.2 统计a, b通道颜色频率 53 | 54 | 分别对源图像和目标图像中可能是肤色的像素点,统计它们在a, b通道的颜色频率。分别存储为数组Sa[256], Sb[256], Ta[256], Tb[256]. 55 | 56 | 比如: Sa[2]代表源图像被认为是肤色且a通道值为2的像素点个数,Tb[4]代表目标图像被认为是肤色且b通道值为4的像素点个数。 57 | 58 | ##### 2.3 求a, b分量纯肤色收敛区间 59 | 60 | 需要求出源图像a、b分量纯肤色收敛区域$$[SaB_g, SaE_d]$$, $$[SbB_g, SbE_d]$$, 以及目标图像a、b分量纯肤色收敛区域$$[TaB_g, TaE_d]$$, $$[TbB_g, TbE_d]$$. 61 | 62 | 以求$$[SaB_g, SaE_d]$$收敛区域为例: 63 | (注:原文的算法描述有误,这里我重新描述这个算法) 64 | ###### a. 找出使Sa取得最大值的数组下标Si 65 | ###### b. 另$$t_1 = Si - 1, t_2 = Si + 1$$ 66 | ###### c. 求$$t_1$$到Si的总像素个数$$S_1$$, 以及Si到$$t_2$$的总像素个数$$S_2$$ 67 | ###### d. 若$$|S_1 - S_2| > 2 \times max(Sa[t_1], Sa[t_2])或Sa[t_1] = 0或Sa[t_2] = 0$$, 收敛区域确定为$$[t_1, t_2]$$, 否则: 68 | $$t_1 -= 1, t_2 += 1$$, 若$$t_1$$和$$t_2$$都没越界,跳到步骤c. 否则,收敛区域为$$[max(0, t_1), min(255, t_2)]$$ 69 | 70 | 代码实现: 71 | ``` python 72 | def get_border(Sa): 73 | si = np.argmax(Sa) 74 | t1 = si - 1 75 | t2 = si + 1 76 | diff = 0 77 | while t1 >= 0 and t2 <= 255: 78 | diff += (Sa[t1] - Sa[t2]) 79 | if abs(diff) > 2 * max(Sa[t1], Sa[t2]) or Sa[t1] == 0 or Sa[t2] == 0: 80 | return [t1, t2] 81 | t1 -= 1 82 | t2 += 1 83 | t1 = max(0, t1) 84 | t2 = min(255, t2) 85 | return [t1, t2] 86 | 87 | ``` 88 | 其他收敛区域同理可得。 89 | 90 | #### 理论依据: 91 | 92 | 作者发现纯肤色区域(去除人脸杂色,如嘴唇、眼睛、眉毛)后的a, b分量的数据分布呈对称的单峰分布,并且这种单峰走势陡峭。根据600份不同纯肤色数据统计得出,在纯肤色区域各分量对应直方图最大值点的下标为中心,左右两边的对应的面积之差不会超过两边界中最大高度的2倍。 93 | 94 | 求得的收敛区间,也就是对肤色在a或b通道上的分布估计,利用这个分布估计可以对肤色进行更细的筛选,这个收敛算法体现了整个肤色检测算法的自适应能力。 95 | 96 | ### 3. 肤色转换 97 | 98 | #### 过程: 99 | ##### 3.1 求出收敛范围的均值 100 | $$Sa_m = \frac{SaB_g + SaE_d}{2}$$ 101 | $$Sb_m = \frac{SbB_g + SbE_d}{2}$$ 102 | $$Ta_m = \frac{TaB_g + TaE_d}{2}$$ 103 | $$Tb_m = \frac{TbB_g + TbE_d}{2}$$ 104 | ##### 3.2 求出比例系数 105 | $$Rsa_1 = \frac{Sa_m - SaB_g}{Ta_m - TaB_g}$$ 106 | $$Rsa_2 = \frac{Sa_m - SaE_d}{Ta_m - TaE_d}$$ 107 | $$Rsb_1 = \frac{Sb_m - SbB_g}{Tb_m - TbB_g}$$ 108 | $$Rsb_2 = \frac{Sb_m - SbE_d}{Tb_m - TbE_d}$$ 109 | ##### 3.3 颜色转换 110 | 对于Lab空间下的目标图像,选择$$a\in[TaB_g, TaE_d]$$或$$b\in[TbB_g, TbE_d]$$的像素进行颜色转换 111 | 转换公式为:保持L分量不变,$$L' = L$$ 112 | if $$a < Ta_m, a' = Rsa_1 \times (a - Ta_m) + Sa_m$$ 113 | else $$a' = Rsa_2 \times (a - Ta_m) + Sa_m$$ 114 | end 115 | 116 | if $$b < Tb_m, b' = Rsb_1 \times (b - Tb_m) + Sb_m$$ 117 | else $$b' = Rsb_2 \times (b - Tb_m) + Sb_m$$ 118 | end 119 | 120 | #### 理论依据: 121 | 论文中,其说到"求出各收敛范围的均值",但是没有说到求哪一部分的均值,使用肤色像素平均还是区间左右两端的均值。我尝试了多种求平均的方法,最终确定使用区间左右两端的均值,得到的效果比较好。在颜色转换部分,作者使用了按比例变换颜色的方式。在我复现论文算法的过程中,我发现由于L分量保持不变,目标图像的肤色未必能和源图像很接近。 122 | 123 | ### 4. 颜色矫正 124 | 125 | #### 过程: 126 | 若a'在$$[SaB_g, SaE_d]$$外且远离2个单位以上,恢复原值a. 127 | 若a'在$$[SaB_g, SaE_d]$$外且远离2个单位以下,取最近的边界值. 128 | 若a'在$$[SaB_g, SaE_d]$$内,保持为a'. 129 | b'的矫正同理。 130 | #### 理论依据: 131 | 颜色矫正的目的是避免出现干扰的斑点,去除非肤色值的转换,仅留下肤色区域像素值的改变结果。 132 | $$[SaB_g, SaE_d]$$是源图像的肤色a分量收敛区域, 矫正过程使用了这个收敛区域检查变换后的颜色是否在源图像的肤色区域内。但是矫正步骤和肤色转换过程产生了矛盾,因为肤色转换过程的结果必在源图像的收敛区域内。 133 | 134 | ## 四、与课本知识的联系 135 | 这篇论文涉及到了《数字图像处理》课本中提到的颜色空间、直方图和基于阈值的图像分割。 136 | ### 1. 颜色空间 137 | RGB颜色空间和YCbCr颜色空间之间可以线性转换, YCbCr空间的优势是能够简单地限定Cb、Cr的取值范围,就可以粗略的检测出肤色区域; 138 | 而RGB颜色空间和Lab颜色空间之间是非线性转换,需要先通过线性变换从RGB转到XYZ空间,再通过非线性变换从XYZ空间转换到Lab空间。Lab空间的优势在于对于人脸肤色,a分量和b分量是独立的, 画出纯肤色的Lab颜色直方图后,可以发现一个尖峰,并且存在分布规律。 139 | ### 2. 直方图 140 | 直方图可以很好地体现各通道的颜色分布。 141 | 绘制出Lab各分量的颜色直方图后,可以发现:"直方图最大值点的下标为中心,左右两边的对应的面积之差不会超过两边界中最大高度的2倍”的规律。 142 | ### 3. 图像分割 143 | 在YCbCr颜色空间中,用$$Cr\in[133,173]$$且$$Cb\in[77,127]$$粗略检测肤色区域,这是一种基于阈值的图像分割方法,即根据阈值将肤色区域从图像中分割出来。 144 | 145 | ## 五、算法本质 146 | 我觉得本篇论文的本质是通过对颜色分量的线性组合粗略估计肤色区域,再根据非线性组合估计肤色的分布,最终根据比例转换颜色。 147 | 1. 通过对颜色分量的线性组合粗略估计肤色区域 148 | RGB和YCbCr颜色空间可以通过线性变换得到,在YCbCr空间下对分量进行范围限制,实质上是对RGB三个分量进行加权后进行限制。 149 | 2. 根据非线性组合估计肤色的分布 150 | 由于线性组合下估计肤色区域的方法是粗略的,这种方法对于多种肤色、不同环境下的图片,要么估计的范围过大,或者估计的范围过小。因此,使用非线性组合下的分量估计肤色的分布。Lab空间对于人脸肤色,a分量和b分量是独立的,可以分别处理a分量和b分量。当确定a分量出现次数最多的取值时,可认为这个值是当前照片最高频率的肤色值,再从这个值的两边扩展,估计出肤色的分布。最高频率的肤色值是基于统计的,具有很高的适应性;作者提出的区间收敛方法,根据了对大量纯肤色图片的分布规律。估计肤色的精确分布,是该篇论文的点睛之笔。 151 | 152 | ## 六、算法复现 153 | 我对算法进行了复现,发现实验结果和论文结果存在偏差,论文中存在几个不足及错误,我将它们改正后,得到了可以接受的效果。 154 | #### 论文的不足及错误: 155 | 1. 收敛公式错误 156 | 在论文的收敛算法描述中, 157 | 应将$$max(t1,t2)$$更正$$max(Sa(t1), Sa(t2))$$ 158 | 2. 没有描述如何求收敛范围的均值 159 | 我尝试了多种求均值的方法,最终发现取收敛空间两端的均值的效果较好,但这样会导致矫正部分不起任何作用,原因是此时转换后的颜色必在源图像的收敛范围内。 160 | 3. 公式描述不清晰 161 | 公式(12)和(13)的等式和不等式连在一起了。 162 | 163 | 而且我觉得论文应该描述是用哪一种规范进行颜色空间转换,因为不同标准下的颜色转换方法与系数是不一样的。 164 | 在我的测试中,无论是对肤色的粗检测还是细检测,我得到的结果都和论文存在差别,我的算法复现中的处理后的图像,存在的空洞较多。我怀疑论文中的实现使用了开操作和闭操作,但论文没有提及这些操作。我的复现,对于肤色的变换没有论文中的效果明显。使用YCbCr粗略检测肤色也有可能失败。 165 | #### 复现结果: 166 | Lab空间下的a、b分量分布, 其中红色点为a分量,蓝色点为b分量 167 | .和*分别对应源图像和目标图像 168 | ![](distribution.png) 169 | 粗检测(左)和细检测(右) 170 | ![](gg.png) 171 | 源图像和目标图像的细检测肤色区域 172 | ![](twopeople.png) 173 | 肤色迁移结果, 效果比较好: 174 | ![](r1.png) 175 | 肤色检测出错: 176 | ![](boy7.png) 177 | 另一组肤色迁移结果: 178 | ![](r2.png) 179 | 180 | ## 七、改进 181 | 对论文复现后,我对论文有了基本的了解,我提出了几点改进方法。 182 | 1. Lab空间下的高频率肤色 183 | 论文中通过取a、b分量最大值的下标作为肤色分布的中轴,我觉得这种方法会受到噪声的影响。改进方法是使用定长的滑动窗口,找出最大像素和窗口,再取这个窗口的中心作为肤色分布的中轴。滑动窗口的实现的时间复杂度是线性的。 184 | 2. 平均值 185 | 在我的算法复现中,$$Sa_m, Sb_m, Ta_m, Tb_m$$取其对应收敛区间两端点的均值,效果较好。 186 | 3. 多幅人脸图像的肤色迁移 187 | 检测出肤色区域后,找出这些区域的所有连通域。对于每一个连通域,可以假设其对应一个人,单独对这个连通域进行收敛区间的估计和肤色迁移,这样可以处理同一张相片中不同肤色的人的肤色迁移。 188 | 4. 使用逻辑回归粗略检测人脸区域 189 | 由于RGB和YCbCr颜色空间之间可以进行线性转换,为了更好地对肤色区域进行估计,可以将RGB三个分量作为输入特征,训练一个逻辑回归模型。使用逻辑回归模型检测人脸肤色区域时,算法的时间复杂度是线性的。 190 | 191 | ## 八、感受 192 | 在这次阅读论文的过程中,我觉得这篇肤色迁移的文章比较有趣,因此花了比较长的时间对论文进行复现。对于我来说,复现的难点在于颜色空间的转换,以及对算法的调整。我发现通过复现,将复现的结果和论文的结果进行对比,更能提高自己对论文的理解,同时也有资格质疑论文中的结果。在复现的过程中,我发现两个颜色空间的转换,有不同系数的转换方法,比如RGB转YCbCr, 不同的方法得到的是不同的YCbCr,需要注意转换是基于哪一个标准。这篇论文也给了我关于基于颜色的自适应检测方法的启发。 193 | 194 | ## 参考资料 195 | 1. Lab颜色空间 by ChenLee_1 196 | http://blog.csdn.net/carson2005/article/details/7200440 197 | 2. RGB2Lab and Lab2RGB By Mark Ruzon from C code by Yossi Rubner, 23 September 1997. 198 | 199 | ## 附录 200 | 1. 论文 201 | Wei W,Ma J F.Adaptive fast face color transfer[J].Joumal ofIlllage and Graphics,2016,21(2):0129-0134.[魏玮,马军福.自适应的快速人脸肤色转移[J].中国图象图形学报,2016,21(2):0129.0134.]$${}$$[DOI:10.11834/jig.20160201] 202 | 2. 我的算法复现代码地址 203 | https://github.com/wkcn/Adaptive-Fast-Face-Color-Transfer 204 | -------------------------------------------------------------------------------- /note.txt: -------------------------------------------------------------------------------- 1 | Lab 2 | 基于生理特征 3 | http://blog.csdn.net/carson2005/article/details/7200440 4 | 5 | RGB LAB 互换 6 | https://wenku.baidu.com/view/055b8defaeaad1f346933f8f.html 7 | 8 | 对论文的更正和改进: 9 | 10 | 更正: 11 | 收敛公式错误max{t1,t2}应为max{Sa(t1), Sa(t2)} 12 | 平均值(论文用的是纯图像,尝试肤色加权均值,两边均值,两边均值最好) 13 | 肤色选择不一样, 怀疑是颜色空间转换系数的问题 14 | -------------------------------------------------------------------------------- /pic/boy1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkcn/Adaptive-Fast-Face-Color-Transfer/584d6d333ca6e5497deaf4c783b18e46125162a8/pic/boy1.png -------------------------------------------------------------------------------- /pic/boy2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkcn/Adaptive-Fast-Face-Color-Transfer/584d6d333ca6e5497deaf4c783b18e46125162a8/pic/boy2.png -------------------------------------------------------------------------------- /pic/boy3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkcn/Adaptive-Fast-Face-Color-Transfer/584d6d333ca6e5497deaf4c783b18e46125162a8/pic/boy3.png -------------------------------------------------------------------------------- /pic/boy4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkcn/Adaptive-Fast-Face-Color-Transfer/584d6d333ca6e5497deaf4c783b18e46125162a8/pic/boy4.png -------------------------------------------------------------------------------- /pic/boy5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkcn/Adaptive-Fast-Face-Color-Transfer/584d6d333ca6e5497deaf4c783b18e46125162a8/pic/boy5.png -------------------------------------------------------------------------------- /pic/boy6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkcn/Adaptive-Fast-Face-Color-Transfer/584d6d333ca6e5497deaf4c783b18e46125162a8/pic/boy6.png -------------------------------------------------------------------------------- /pic/boy7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkcn/Adaptive-Fast-Face-Color-Transfer/584d6d333ca6e5497deaf4c783b18e46125162a8/pic/boy7.png -------------------------------------------------------------------------------- /pic/girl1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkcn/Adaptive-Fast-Face-Color-Transfer/584d6d333ca6e5497deaf4c783b18e46125162a8/pic/girl1.png -------------------------------------------------------------------------------- /pic/girl2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkcn/Adaptive-Fast-Face-Color-Transfer/584d6d333ca6e5497deaf4c783b18e46125162a8/pic/girl2.png -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | import matplotlib.image as mpimg 5 | import numpy as np 6 | from scipy.misc import imshow, imresize 7 | 8 | def RGB2YCbCr(rgb): 9 | R = rgb[:,:,0] 10 | G = rgb[:,:,1] 11 | B = rgb[:,:,2] 12 | 13 | 14 | Y = 0.257*R+0.504*G+0.098*B+16 15 | Cb = -0.148*R-0.291*G+0.439*B+128 16 | Cr = 0.439*R-0.368*G-0.071*B+128 17 | 18 | return np.dstack([Y, Cb, Cr]) 19 | 20 | def RGB2Lab(rgb): 21 | R = rgb[:,:,0] / 255.0 22 | G = rgb[:,:,1] / 255.0 23 | B = rgb[:,:,2] / 255.0 24 | T = 0.008856 25 | M, N = R.shape 26 | s = M * N 27 | RGB = np.r_[R.reshape((1, s)), G.reshape((1, s)), B.reshape((1, s))] 28 | MAT = np.array([[0.412453,0.357580,0.180423], 29 | [0.212671,0.715160,0.072169], 30 | [0.019334,0.119193,0.950227]]) 31 | XYZ = np.dot(MAT, RGB) 32 | X = XYZ[0,:] / 0.950456 33 | Y = XYZ[1,:] 34 | Z = XYZ[2,:] / 1.088754 35 | 36 | 37 | XT = X > T 38 | YT = Y > T 39 | ZT = Z > T 40 | 41 | Y3 = np.power(Y, 1.0/3) 42 | fX = np.zeros(s) 43 | fY = np.zeros(s) 44 | fZ = np.zeros(s) 45 | 46 | fX[XT] = np.power(X[XT], 1.0 / 3) 47 | fX[~XT] = 7.787 * X[~XT] + 16.0 / 116 48 | 49 | fY[YT] = Y3[YT] 50 | fY[~YT] = 7.787 * Y[~YT] + 16.0 / 116 51 | 52 | fZ[ZT] = np.power(Z[ZT], 1.0 / 3) 53 | fZ[~ZT] = 7.787 * Z[~ZT] + 16.0 / 116 54 | 55 | L = np.zeros(YT.shape) 56 | a = np.zeros(fX.shape) 57 | b = np.zeros(fY.shape) 58 | 59 | L[YT] = Y3[YT] * 116 - 16.0 60 | L[~YT] = 903.3 * Y[~YT] 61 | 62 | a = 500 * (fX - fY) 63 | b = 200 * (fY - fZ) 64 | 65 | return np.dstack([L.reshape(R.shape), a.reshape(R.shape), b.reshape(R.shape)]) 66 | 67 | def Lab2RGB(Lab): 68 | M, N, C = Lab.shape 69 | s = M * N 70 | 71 | L = Lab[:,:,0].reshape((1, s)).astype(np.double) 72 | a = Lab[:,:,1].reshape((1, s)).astype(np.double) 73 | b = Lab[:,:,2].reshape((1, s)).astype(np.double) 74 | 75 | T1 = 0.008856 76 | T2 = 0.206893 77 | 78 | fY = np.power((L + 16.0) / 116, 3.0) 79 | YT = fY > T1 80 | fY[~YT] = L[~YT] / 903.3 81 | Y = fY.copy() 82 | 83 | fY[YT] = np.power(fY[YT], 1.0 / 3) 84 | fY[~YT] = 7.787 * fY[~YT] + 16.0 / 116 85 | 86 | fX = a / 500.0 + fY 87 | XT = fX > T2 88 | X = np.zeros((1, s)) 89 | X[XT] = np.power(fX[XT], 3) 90 | X[~XT] = (fX[~XT] - 16.0 / 116) / 7.787 91 | 92 | fZ = fY - b / 200.0 93 | ZT = fZ > T2 94 | Z = np.zeros((1, s)) 95 | Z[ZT] = np.power(fZ[ZT], 3) 96 | Z[~ZT] = (fZ[~ZT] - 16.0 / 116) / 7.787 97 | 98 | X = X * 0.950456 99 | Z = Z * 1.088754 100 | MAT = np.array([[ 3.240479,-1.537150,-0.498535], 101 | [-0.969256, 1.875992, 0.041556], 102 | [0.055648,-0.204043, 1.057311]]) 103 | RGB = np.dot(MAT, np.r_[X,Y,Z]) 104 | R = RGB[0, :].reshape((M,N)) 105 | G = RGB[1, :].reshape((M,N)) 106 | B = RGB[2, :].reshape((M,N)) 107 | return np.clip(np.round(np.dstack([R,G,B]) * 255), 0, 255).astype(np.uint8) 108 | 109 | 110 | 111 | def count(w): 112 | return dict(zip(*np.unique(w, return_counts = True))) 113 | def count_array(w, size): 114 | d = count(w) 115 | return np.array([d.get(i, 0) for i in range(size)]) 116 | 117 | def get_border(Sa): 118 | si = np.argmax(Sa) 119 | t1 = si - 1 120 | t2 = si + 1 121 | diff = 0 122 | while t1 >= 0 and t2 <= 255: 123 | diff += (Sa[t1] - Sa[t2]) 124 | if abs(diff) > 2 * max(Sa[t1], Sa[t2]) or Sa[t1] == 0 or Sa[t2] == 0: 125 | print "Sa", Sa[t1], Sa[t2] 126 | return [t1, t2] 127 | t1 -= 1 128 | t2 += 1 129 | t1 = max(0, t1) 130 | t2 = min(255, t2) 131 | return [t1, t2] 132 | 133 | 134 | def deal(rgb): 135 | y = RGB2YCbCr(rgb) 136 | b = (y[:,:,1] >= 77) & (y[:,:,1] <= 127) & (y[:,:,2] >= 133) & (y[:,:,2] <= 173) 137 | lab = np.round(RGB2Lab(rgb)).astype(np.int) 138 | # a, b += 128 139 | lab[:,:,1:3] += 128 140 | # 0 ~ 255 141 | Sa = count_array(lab[:,:,1][b], 256) 142 | Sb = count_array(lab[:,:,2][b], 256) 143 | SaBorder = get_border(Sa) 144 | SbBorder = get_border(Sb) 145 | b2 = (((lab[:,:,1] >= SaBorder[0]) & (lab[:,:,1] <= SaBorder[1])) | ((lab[:,:,2] >= SbBorder[0]) & (lab[:,:,2] <= SbBorder[1]))) 146 | plt.subplot(121) 147 | plt.imshow(b, "gray") 148 | plt.subplot(122) 149 | plt.imshow(b2, "gray") 150 | plt.show() 151 | return lab, b2, Sa, Sb, SaBorder, SbBorder, np.mean(lab[:,:,1][b2]), np.mean(lab[:,:,2][b2]) 152 | 153 | def face_color_transfer(source, target): 154 | slab, sb, Sa, Sb, [sab, sae],[sbb, sbe], sam, sbm = deal(source) 155 | tlab, tb, Ta, Tb, [tab, tae],[tbb, tbe], tam, tbm = deal(target) 156 | print "[sab, sae] = [%d, %d], [sbb, sbe] = [%d, %d]" % (sab,sae,sbb,sbe) 157 | print "[tab, tae] = [%d, %d], [tbb, tbe] = [%d, %d]" % (tab,tae,tbb,tbe) 158 | 159 | print sam, sbm, tam, tbm 160 | sam = (sab + sae) / 2.0 161 | sbm = (sbb + sbe) / 2.0 162 | tam = (tab + tae) / 2.0 163 | tbm = (tbb + tbe) / 2.0 164 | print sam, sbm, tam, tbm 165 | 166 | plt.plot(Sa, 'r.') 167 | plt.plot(Ta, 'r*') 168 | plt.plot(Sb, 'b.') 169 | plt.plot(Tb, 'b*') 170 | plt.show() 171 | 172 | rsa1 = (sam - sab) * 1.0 / (tam - tab) 173 | rsa2 = (sae - sam) * 1.0 / (tae - tam) 174 | rsb1 = (sbm - sbb) * 1.0 / (tbm - tbb) 175 | rsb2 = (sbe - sbm) * 1.0 / (tbe - tbm) 176 | print rsa1, rsa2, rsb1, rsb2 177 | 178 | def transfer(a, sam, tam, rsa1, rsa2, sab, sae): 179 | aold = a.copy() 180 | b = a < tam 181 | a[b] = rsa1 * (a[b] - tam) + sam 182 | a[~b] = rsa2 * (a[~b] - tam) + sam 183 | # Correction 184 | b1 = (a < sab) & (a > sab - 2) 185 | b2 = (a > sae) & (a < 2 + sae) 186 | b3 = (a > sab) & (a < sae) 187 | b4 = ~(b1 | b2 | b3) 188 | a[b1] = sab 189 | a[b2] = sae 190 | print np.sum(b1), np.sum(b2), np.sum(b3), np.sum(b4) 191 | #a[b4] = aold[b4] 192 | return a 193 | 194 | plt.subplot(121) 195 | plt.imshow(sb, "gray") 196 | plt.subplot(122) 197 | plt.imshow(tb, "gray") 198 | plt.show() 199 | 200 | tlab[:,:,1][tb] = transfer(tlab[:,:,1][tb], sam, tam, rsa1, rsa2, sab, sae) 201 | tlab[:,:,2][tb] = transfer(tlab[:,:,2][tb], sbm, tbm, rsb1, rsb2, sbb, sbe) 202 | tlab[:,:,1:3] -= 128 203 | tlab[:,:,1:3] = np.clip(tlab[:,:,1:3], -128, 128) 204 | return Lab2RGB(tlab) 205 | 206 | def imread(filename): 207 | im = mpimg.imread(filename) 208 | if im.dtype != np.uint8: 209 | im = np.clip(np.round(im * 255), 0, 255).astype(np.uint8) 210 | return im 211 | 212 | # RGB 213 | source = imread("pic/boy6.png") 214 | target = imread("pic/girl2.png") 215 | res = face_color_transfer(source, target) 216 | 217 | plt.subplot(131) 218 | plt.title("source") 219 | plt.imshow(source) 220 | 221 | plt.subplot(132) 222 | plt.title("target") 223 | plt.imshow(target) 224 | 225 | plt.subplot(133) 226 | plt.title("Transfered") 227 | plt.imshow(res) 228 | 229 | plt.show() 230 | --------------------------------------------------------------------------------