文档章节

Google Palette算法详解以及OC化

AllenOR灵感
 AllenOR灵感
发布于 2017/09/10 01:08
字数 2110
阅读 3
收藏 0

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)压缩图片,遍历图片像素,引出颜色直方图的概念。并将不同的颜色存入新的颜色数组。

unsignedintpixelCount;    

unsignedchar*rawData = [self rawPixelDataFromImage:_image pixelCount:&pixelCount];

if(!rawData){

   return;    

}    

NSIntegerred,green,blue;

for (intpixelIndex =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:redcurrentWidth:8targetWidth:QUANTIZE_WORD_WIDTH];

green= [TRIPPaletteColorUtils modifyWordWidthWithValue:greencurrentWidth:8targetWidth:QUANTIZE_WORD_WIDTH];

blue= [TRIPPaletteColorUtils modifyWordWidthWithValue:bluecurrentWidth:8targetWidth:QUANTIZE_WORD_WIDTH];

NSInteger quantizedColor =red<<2*QUANTIZE_WORD_WIDTH |green<< QUANTIZE_WORD_WIDTH |blue;        hist [quantizedColor] ++;    

}

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

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

NSInteger distinctColorCount =0;

NSIntegerlength= sizeof(hist)/sizeof(hist[0]);

for (NSIntegercolor=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(NSIntegercolor=0;color0;color < length;color++){

      if (hist[color] > 0){

          [_distinctColors addObject: [NSNumber numberWithInt:color]];

          distinctColorIndex++;

      }

}

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

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

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

for(NSInteger i =0;i < distinctColorCount ; i++){            

     NSIntegercolor= [_distinctColors[i] integerValue];            

     NSInteger population = hist[color];            

     NSIntegerred= [TRIPPaletteColorUtils quantizedRed:color];            

     NSIntegergreen= [TRIPPaletteColorUtils quantizedGreen:color];            

     NSIntegerblue= [TRIPPaletteColorUtils quantizedBlue:color];

     red= [TRIPPaletteColorUtils modifyWordWidthWithValue:redcurrentWidth:QUANTIZE_WORD_WIDTH targetWidth:8];

     green= [TRIPPaletteColorUtils modifyWordWidthWithValue:greencurrentWidth:QUANTIZE_WORD_WIDTH targetWidth:8];

     blue= [TRIPPaletteColorUtils modifyWordWidthWithValue:bluecurrentWidth:QUANTIZE_WORD_WIDTH targetWidth:8];color=red<<2*8|green<<8|blue;            

     TRIPPaletteSwatch *swatch = [[TRIPPaletteSwatch alloc]initWithColorInt:colorpopulation:population];            

     [swatchs addObject:swatch];        }

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

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

_priorityArray = [[TRIPPaletteVBoxArray alloc]init];        

_colorVBox = [[VBox alloc]initWithLowerIndex:@upperIndex:distinctColorIndexcolorArray:_distinctColors];        [_priorityArrayaddVBox:_colorVBox];

// split the VBox

[selfsplitBoxes:_priorityArray];

//Switch VBox to Swatch

self.swatchArray = [selfgenerateAverageColors:_priorityArray];

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

@interfaceVBox:NSObject

@property(nonatomic,assign)NSIntegerlowerIndex;

@property(nonatomic,assign)NSIntegerupperIndex;

@property(nonatomic,strong)NSMutableArray*distinctColors;

@property(nonatomic,assign)NSIntegerpopulation;

@property(nonatomic,assign)NSIntegerminRed;

@property(nonatomic,assign)NSIntegermaxRed;

@property(nonatomic,assign)NSIntegerminGreen;

@property(nonatomic,assign)NSIntegermaxGreen;

@property(nonatomic,assign)NSIntegerminBlue;

@property(nonatomic,assign)NSIntegermaxBlue;

@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 = [queueobjectAtIndex:0];

          if (vbox != nil && [vbox canSplit]) {

              // First split the box, and offer the result

             [queueaddVBox:[vbox splitBox]];

              // Then offer the box back

              [queueaddVBox: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颜色模型。它的主要属性如下:

@interfaceTRIPPaletteTarget()

@property(nonatomic,strong)NSMutableArray*saturationTargets;

@property(nonatomic,strong)NSMutableArray*lightnessTargets;

@property(nonatomic,strong)NSMutableArray* weights;

@property(nonatomic,assign)BOOLisExclusive;// 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 ([selfshouldBeScoredForTarget:swatchtarget:target]){            

                 CGFloatscore= [self generateScoreForTarget:targetswatch:swatch];

                 if (maxScore ==0||score> maxScore){                

                     maxScoreSwatch =swatch;

                     maxScore =score;

                  }        

              }    

           }    

           return maxScoreSwatch;

}

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

- (CGFloat)generateScoreForTarget:(TRIPPaletteTarget*)targetswatch:(TRIPPaletteSwatch*)swatch{    

    NSArray *hsl = [swatch getHsl];

    floatsaturationScore =0;

    floatluminanceScore =0;

    floatpopulationScore =0;

    if([targetgetSaturationWeight] >0) {        

        saturationScore = [targetgetSaturationWeight]        

        * (1.0f - fabsf([hsl[1] floatValue] - [targetgetTargetSaturation]));    

}

if([targetgetLumaWeight] >0) {        

   luminanceScore = [targetgetLumaWeight]        

   * (1.0f - fabsf([hsl[2] floatValue] - [targetgetTargetLuma]));    

   }

   if([targetgetPopulationWeight] >0) {        

       populationScore = [targetgetPopulationWeight]        

       * ([swatch getPopulation] / (float) _maxPopulation);    

   }

   returnsaturationScore + luminanceScore + populationScore;

}

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


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

4.最后

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

本文转载自:http://www.jianshu.com/p/b7f79c55b066

AllenOR灵感
粉丝 11
博文 2635
码字总数 83001
作品 0
程序员
私信 提问
fir.im Weekly - 热门 iOS 第三方库大盘点

本期 fir.im Weekly 收集的热度资源,大部分关于Android、iOS 开发工具、源码和脑洞大开的 UI 动画,希望给你带来更多的工作创意与灵感。 盘点国内程序员不常用的热门iOS第三方库 @ios122 的...

风起云飞fir_im
2015/10/21
82
0
CTNetworking源码拾遗

一句话简介:CTNetworking为casa大神针对iOS网络层方案的一个架构实例。 架构详解: 传送门 Github: 传送门 PS: 本拾遗系列文章只专注于代码以及工程层面知识点拾遗,架构层面作者文章已经进行...

AliThink
2016/09/05
0
0
iphone开发技术要学习的内容

一.iOS基础   1 开发环境搭建以及IOS组件、框架的概要介绍。   2 mac操作系统与iOS操作系统   3 xcode IDE开发环境的初始   二.C语言基础   1数据类型、表达式与控制流程语句   ...

和谐中原植物酒
2015/08/24
153
2
Android Palette颜色提取

Palette介绍 Palette是Google在5.0中引入的来获取bitmap颜色值的一个工具类,为了兼容以前的版本,所以放在Support v7 Library中,在使用该类之前,需要在gradle中添加引用。Palette顾名思义...

IamOkay
2018/04/03
241
0
传统汽车巨头丰田抛出最大胆竞争对手名单:谷歌、苹果、Facebook

  美国当地时间1月8日,英伟达CEO黄仁勋在拉斯维加斯CES现场的“I Am AI”主题演讲引爆全场,但在这之后,还有一场热度几乎不逊于此的发布会,那就是丰田汽车(Toyota)的CES专场发布。  ...

DeepTech深科技
2018/01/10
0
0

没有更多内容

加载失败,请刷新页面

加载更多

OSChina 周六乱弹 —— 早上儿子问我他是怎么来的

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @凉小生 :#今日歌曲推荐# 少点戾气,愿你和这个世界温柔以待。中岛美嘉的单曲《僕が死のうと思ったのは (曾经我也想过一了百了)》 《僕が死の...

小小编辑
今天
1K
12
Excption与Error包结构,OOM 你遇到过哪些情况,SOF 你遇到过哪些情况

Throwable 是 Java 中所有错误与异常的超类,Throwable 包含两个子类,Error 与 Exception 。用于指示发生了异常情况。 Java 抛出的 Throwable 可以分成三种类型。 被检查异常(checked Exc...

Garphy
今天
22
0
计算机实现原理专题--二进制减法器(二)

在计算机实现原理专题--二进制减法器(一)中说明了基本原理,现准备说明如何来实现。 首先第一步255-b运算相当于对b进行按位取反,因此可将8个非门组成如下图的形式: 由于每次做减法时,我...

FAT_mt
昨天
16
0
好程序员大数据学习路线分享函数+map映射+元祖

好程序员大数据学习路线分享函数+map映射+元祖,大数据各个平台上的语言实现 hadoop 由java实现,2003年至今,三大块:数据处理,数据存储,数据计算 存储: hbase --> 数据成表 处理: hive --> 数...

好程序员官方
昨天
25
0
tabel 中含有复选框的列 数据理解

1、el-ui中实现某一列为复选框 实现多选非常简单: 手动添加一个el-table-column,设type属性为selction即可; 2、@selection-change事件:选项发生勾选状态变化时触发该事件 <el-table @sel...

everthing
昨天
11
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部