1.背景

在发现百日大战时景项目中。有一个创新玩法,就是通过筛选图片主色调来显示如红色系,蓝色系照片。这就涉及到了图片主色调的提取。技术选型为客户端进行图片颜色提取,上传到服务端。但是由于项目时间限制,iOS和Android的图片色调提取算法不一样。Android采用的是Google官方推出的Palette算法,为了统一,在这一期我去研究了一下Palette算法,并将它OC化。最终将作为一个两端统一的技术方案,提供SDK到海纳平台上。

2.Google Palette算法简介

Palette算法是Android Lillipop中新增的特性。它可以从一张图中提取主要颜色,然后把提取的颜色融入的App的UI之中。现在在很多设计出彩的泛前端展示届非常普遍,如知乎等。大致效果如下:

可以看出来Android在Material Design上下了一番功夫。在很多Android官方的demo里,各种炫酷效果层出不穷。那我们就顺势站在巨人的肩膀上,将他人拿手之处,为我所用!

3.Palette算法分析

相比于很多传统的图片提取算法,Palette的特点是不单单是去筛选中出现颜色最多的。而是从使用角度出发,通过六种模式,如活力色彩,柔和色彩等,筛选出更符合人眼筛选视觉焦点的颜色。如夜晚中的霓虹灯,白色背景的产品照。同时,也可以自定义筛选模式,输入自己的筛选规则,得到目标颜色。下面将逐步分析一下每个步骤。

(1)压缩图片,遍历图片像素,引出颜色直方图的概念。并将不同的颜色存入新的颜色数组。

    unsigned int pixelCount;unsigned char *rawData = [self rawPixelDataFromImage:_image pixelCount:&pixelCount];if (!rawData){return;}NSInteger red,green,blue;for (int pixelIndex = 0 ; pixelIndex < pixelCount; pixelIndex++){red   = (NSInteger)rawData[pixelIndex*4+0];green = (NSInteger)rawData[pixelIndex*4+1];blue  = (NSInteger)rawData[pixelIndex*4+2];red = [TRIPPaletteColorUtils modifyWordWidthWithValue:red currentWidth:8 targetWidth:QUANTIZE_WORD_WIDTH];green = [TRIPPaletteColorUtils modifyWordWidthWithValue:green currentWidth:8 targetWidth:QUANTIZE_WORD_WIDTH];blue = [TRIPPaletteColorUtils modifyWordWidthWithValue:blue currentWidth:8 targetWidth:QUANTIZE_WORD_WIDTH];NSInteger quantizedColor = red << 2*QUANTIZE_WORD_WIDTH | green << QUANTIZE_WORD_WIDTH | blue;hist [quantizedColor] ++;}

Palette算法为了减少运算量,加快运算速度。一共做了两个事情,第一是将图片压缩。第二个是将RGB888颜色空间的颜色转变成RGB555颜色空间,这样就会使整个直方图数组以及颜色数组长度大大减小,又不会太影响计算结果。

颜色直方图的概念可以想象成一个颜色柱状分布图,某一柱越高,这柱代表的颜色在图片中也就越多。它本质上是一个int类型的一维数组。

    NSInteger distinctColorCount = 0;NSInteger length = sizeof(hist)/sizeof(hist[0]);for (NSInteger color = 0 ; color < length ;color++){if (hist[color] > 0 && [self shouldIgnoreColor:color]){hist[color] = 0;}if (hist[color] > 0){distinctColorCount ++;}}NSInteger distinctColorIndex = 0;_distinctColors = [[NSMutableArray alloc]init];for (NSInteger color = 0; color < length ;color++){if (hist[color] > 0){[_distinctColors addObject: [NSNumber numberWithInt:color]];distinctColorIndex++;}}

将不同的颜色存进distinctColors,留在后面进行判断。

(2)判断颜色种类是否大于设定的最大颜色数。

最大颜色数在设计上可以设计为接收入参,满足不同使用者的需要,默认值为16。这个值不宜过大,因为如果过大的话,图片颜色会分的很散,图片颜色比较分散的时候,得出来的颜色可能会偏向某一小部分颜色,而不是从整体上来综合判断。而当图片筛选出来的颜色种类小于MaxColorNum的时候,整个流程会简单很多。

        for (NSInteger i = 0;i < distinctColorCount ; i++){NSInteger color = [_distinctColors[i] integerValue];NSInteger population = hist[color];NSInteger red = [TRIPPaletteColorUtils quantizedRed:color];NSInteger green = [TRIPPaletteColorUtils quantizedGreen:color];NSInteger blue = [TRIPPaletteColorUtils quantizedBlue:color];red = [TRIPPaletteColorUtils modifyWordWidthWithValue:red currentWidth:QUANTIZE_WORD_WIDTH targetWidth:8];green = [TRIPPaletteColorUtils modifyWordWidthWithValue:green currentWidth:QUANTIZE_WORD_WIDTH targetWidth:8];blue = [TRIPPaletteColorUtils modifyWordWidthWithValue:blue currentWidth:QUANTIZE_WORD_WIDTH targetWidth:8];color = red << 2 * 8 | green << 8 | blue;TRIPPaletteSwatch *swatch = [[TRIPPaletteSwatch alloc]initWithColorInt:color population:population];[swatchs addObject:swatch];}

这里引出了一个新的概念,叫Swatch(样本)。Swatch是最终被作为参考进行模式筛选的数据结构,它有两个最主要的属性,一个是Color,这个Color是最终要被展示出来的Color,所以需要的是RGB888空间的颜色。另外一个是Population,它来自于hist直方图。是作为之后进行模式筛选的时候一个重要的权重因素。但是如果颜色个数超出了最大颜色数,则需要进行第3步。

(3)通过VBox分裂的方式,找到代表平均颜色的Swatch。

        _priorityArray = [[TRIPPaletteVBoxArray alloc]init];_colorVBox = [[VBox alloc]initWithLowerIndex:0 upperIndex:distinctColorIndex colorArray:_distinctColors];[_priorityArray addVBox:_colorVBox];// split the VBox[self splitBoxes:_priorityArray];//Switch VBox to Swatchself.swatchArray = [self generateAverageColors:_priorityArray];

VBox是一个新的概念,它理解起来稍微抽象一点。可以这样来理解,我们拥有的颜色过多,但是我们只需要提取出例如16种颜色,需要需要用16个“筐”把颜色相近的颜色筐在一起,最终用每个筐的平均颜色来代表提取出来的16种主色调。它的属性如下:


@interface VBox :NSObject@property (nonatomic,assign) NSInteger lowerIndex;@property (nonatomic,assign) NSInteger upperIndex;@property (nonatomic,strong) NSMutableArray *distinctColors;@property (nonatomic,assign) NSInteger population;@property (nonatomic,assign) NSInteger minRed;@property (nonatomic,assign) NSInteger maxRed;@property (nonatomic,assign) NSInteger minGreen;@property (nonatomic,assign) NSInteger maxGreen;@property (nonatomic,assign) NSInteger minBlue;@property (nonatomic,assign) NSInteger maxBlue;@end

其中lowerIndex和upperIndex指的是在所有的颜色数组distinctColors中,VBox所持有的颜色范围。Population代表的是这个颜色范围中,一共有多少个像素点。其它的则代表R,G,B值各自的最大最小值。
它决定了该VBox的Volume。范围越大,Volume越大,当分裂VBox的时候,总是分裂当前队列中VBox里Volume最大的那个。

- (void)splitBoxes:(TRIPPaletteVBoxArray*)queue{//queue is a priority queue.while (queue.count < maxColorNum) {VBox *vbox = [queue objectAtIndex:0];if (vbox != nil && [vbox canSplit]) {// First split the box, and offer the result[queue addVBox:[vbox splitBox]];// Then offer the box back[queue addVBox:vbox];}else{NSLog(@"All boxes split");return;}}
}

VBox的分裂规则是像素中点分裂,从lowerIndex递增到upperIndex,如果某一个点让整个像素个数累加起来大于了VBox像素个数的一半,则这个点就是splitPoint。而优先队列的排序规则是,队首永远是Volume最大的VBox,从大概率上来讲,这总是代表像素个数最多的VBox。当VBox个数大于最大颜色个数的时候,则return,获得优先队列中每个VBox的平均颜色。并生成平均颜色,之后将每个VBox转换成了一个一个的Swatch。

(4)找到某一种模式下得分最高的Swatch,也就是获得了最终的色调提取值。

在Palette算法里,“模式”对应的数据结构是target。它对颜色的识别和筛选不是使用的RGB色彩空间,而采用的是HSL颜色模型。它的主要属性如下:

@interface TRIPPaletteTarget()@property (nonatomic,strong) NSMutableArray *saturationTargets;@property (nonatomic,strong) NSMutableArray *lightnessTargets;@property (nonatomic,strong) NSMutableArray* weights;@property (nonatomic,assign) BOOL isExclusive; // default to true@property (nonatomic,assign) PaletteTargetMode mode;@end

Target主要保存了饱和度和明度以及权重的数组。数组里保存了最小值,最大值,和目标值。这些参数都是后面用来给HSL颜色值评分用的。这些值是经过Google的团队进行调优之后,筛选出来的值。可以说是整套算法中最有价值的参数。

- (TRIPPaletteSwatch*)getMaxScoredSwatchForTarget:(TRIPPaletteTarget*)target{CGFloat maxScore = 0;TRIPPaletteSwatch *maxScoreSwatch = nil;for (NSInteger i = 0 ; i<_swatchArray.count; i++){TRIPPaletteSwatch *swatch = [_swatchArray objectAtIndex:i];if ([self shouldBeScoredForTarget:swatch target:target]){CGFloat score = [self generateScoreForTarget:target swatch:swatch];if (maxScore == 0 || score > maxScore){maxScoreSwatch = swatch;maxScore = score;}}}return maxScoreSwatch;
}

通过这些已经经过调优的参数,可以得出每一项的得分:饱和度得分,明度得分,像素Population得分,将三项得分加起来,可以得到该Target评估得分最高的Swatch,也就是我们最终要提取的对应颜色值。分值具体的具体方法如下:

- (CGFloat)generateScoreForTarget:(TRIPPaletteTarget*)target swatch:(TRIPPaletteSwatch*)swatch{NSArray *hsl = [swatch getHsl];float saturationScore = 0;float luminanceScore = 0;float populationScore = 0;if ([target getSaturationWeight] > 0) {saturationScore = [target getSaturationWeight]* (1.0f - fabsf([hsl[1] floatValue] - [target getTargetSaturation]));}if ([target getLumaWeight] > 0) {luminanceScore = [target getLumaWeight]* (1.0f - fabsf([hsl[2] floatValue] - [target getTargetLuma]));}if ([target getPopulationWeight] > 0) {populationScore = [target getPopulationWeight]* ([swatch getPopulation] / (float) _maxPopulation);}return saturationScore + luminanceScore + populationScore;
}

(5)Palette算法OC化效果展示。

图上红框部分即是筛选出来的主题色。

4.最后

该算法已经运用在了飞猪发现广场的时景项目中(Android版本)。下一期,iOS端也会切换成这种提取算法。并且将这套算法沉淀在基础线,只需要使用UIImage+Palette的接口即可调用。考虑到它的使用场景,会尽快沉淀为SDK,供集团内其它App使用。

Google Palette算法详解以及OC化相关推荐

  1. 【强化学习】Policy Gradient算法详解

    DeepMind公开课https://sites.google.com/view/deep-rl-bootcamp/lectures David Silver教程 http://www0.cs.ucl ...

  2. PnP算法详解(超详细公式推导)

    PnP算法详解 PnP概述 PnP数学模型 PnP求解方法 DLT直接线性变换法 EPnP EPnP的特点 步骤 理论推倒 1.控制点及齐次重心坐标系 2.控制点的选择 3.计算控制点在相机坐标系下的 ...

  3. Matlab人脸检测算法详解

    这是一个Matlab人脸检测算法详解 前言 人脸检测结果 算法详解 源代码解析 所调用函数解析 bwlabel(BW,n) regionprops rectangle 总结 前言 目前主流的人脸检测与 ...

  4. 基础排序算法详解与优化

    文章图片存储在GitHub,网速不佳的朋友,请看<基础排序算法详解与优化> 或者 来我的技术小站 godbmw.com 1. 谈谈基础排序 常见的基础排序有选择排序.冒泡排序和插入排序.众 ...

  5. 【目标检测】Faster RCNN算法详解

    转载自:http://blog.csdn.net/shenxiaolu1984/article/details/51152614 Ren, Shaoqing, et al. "Faster ...

  6. Faster RCNN算法详解

    Ren, Shaoqing, et al. "Faster R-CNN: Towards real-time object detection with region proposal ne ...

  7. YOLOv5算法详解

    目录 1.需求解读 2.YOLOv5算法简介 3.YOLOv5算法详解 3.1 YOLOv5网络架构 3.2 YOLOv5实现细节详解 3.2.1 YOLOv5基础组件 3.2.2 输入端细节详解 3 ...

  8. CenterNet算法详解

    Objects as Points-论文链接-代码链接 目录 1.需求解读 2.CenterNet算法简介 3.CenterNet算法详解 3.1 CenterNet网络结构 3.2 CenterNe ...

  9. SoftPool算法详解

    Refining activation downsampling with SoftPool-论文链接-代码链接 目录 1.需求解读 2.SoftPool算法简介 3.SoftPool算法详解 3.1 ...

最新文章

  1. Android系统自带样式(android:theme)(转)
  2. Fragment系列总结(一)Fragment概念与生命周期
  3. 获取远程文章内容时,显示图片的两种方式
  4. intellij idea 2018 license 可用无废话
  5. 前端 input怎么显示null_小猿圈WEB前端之HTML5+CSS3面试题(一)
  6. 2019年7月前CSDN最新排名
  7. testlink自带java api_java如何连接testlink
  8. css中调整高度充满_6个很棒的PostCSS插件,让您成为一个CSS向导
  9. java读取文件中的数组中_使用java中的数组从文本文件中读取关键字
  10. Android 显式意图(Intent) 与 隐式意图
  11. 在O(1)的时间内删除链表节点
  12. iPhone Web App及优缺点【书摘】
  13. Springboot——quartz简单配置和使用
  14. Android Excel 解析 xls 和 xlsx,方法也可以很简单
  15. oracle建表插数据
  16. C++ 验证DH算法
  17. c语言表达ch是大写英文字母,如何用C语言输出26个英文字母和其ascii码的对照表...
  18. Python数据可视化:5种绘制柱状图表的方法(附源码)
  19. imshow显示图像为纯白或者纯黑或者为杂点(noise)原因分析(下篇)
  20. android pdf阅读器开发_PDF to EPUB Converter Mac(PDF转EPUB转换器)

热门文章

  1. Spring Boot Maven插件
  2. Sqlserver中一直在用又经常被忽略的知识点一
  3. 10个操作数的随机四则运算(二)
  4. Struts2+Spring3.1+Hibernate3.3的整个项目
  5. Python学习笔记之五:类定义
  6. VS2010没有Intellisense(智能感知)的解决办法
  7. php代码审计工具_【学习笔记】PHP代码审计入门:代码审计实例2
  8. python绘制动点_Python asyncore / asynchat 基本传输实验 - Jacky Liu's Blog
  9. 5G 信令流程 — UE 寻呼(Paging)流程
  10. Kong APIGW — Plugins — Traffic Control