本文github项目:colorful color
个人codepen连接:图像颜色提取
the demo
原创文章,转载请注明javascript
最近想找个小项目练练手,以便熟悉React,因而想到了“图像颜色提取”这个方向,也有的说法是图像主题色提取,颜色量子化,或者是叫由图像生成调色板,缘由无他,只是由于漂亮!html
“分析”的目的有这么几个:java
中位切分算法首先把全部像素映射到RGB空间,在这个三维的空间里反复切分出子空间,最后将切分空间的像素求均值做为提取结果。分割区块时都选择全部区块中最大(最长的边长最大,或体积最大,或像素最多)的区块,切割点应位于边方向上,使得分割后两个区块的像素各一半的位置,以上是为中位切分法。流程以下(推荐阅读:《Color Quantization》):node
1.像素映射到RGB空间:react
2.区块计算:git
3.中位切分:github
4.反复切分:算法
5.计算区块的平均颜色:数据库
这里推荐一个采用中位切分法实现(JavaScript)的颜色量子化项目:Color Thief。canvas
八叉树算法的核心理念是用八叉树来划分颜色空间,而后合并叶节点来逐步聚拢颜色(量子化),八叉树的解释可参考《游戏场景管理的八叉树算法是怎样的?》,关键就是下面这两幅图:
1.建树过程:
2.合并叶节点:
具体的解释可参考文章:《图片主题色提取算法小结》,做者还写了一个颜色量子化的node模块: A theme color extractor module for Node.js
K均值聚类的思想十分简单,可分这几步:
从新计算质心,判断是否退出条件:
来看js的实现:
/* colors: 全部样本 seeds: 初始质心 max_step: 最大迭代次数 */ kMC(colors, seeds, max_step) { let iteration_count = 0; while (iteration_count++ < max_step) { // divide colors into different categories with duff's device classifyColor(colors, seeds); // compute center of category let len = colors.length; let hsl_count = []; let category; while (len--) { category = colors[len].category; // ...... } // quit or not let flag = hsl_count.every((ele, index) => { // ...... }); if (flag) { break; } } console.log("KMC iteration " + iteration_count); }
let r_key = Math.floor(r / 8) * 1000000; let g_key = Math.floor(g / 8) * 1000; let b_key = Math.floor(b / 8); let key = r_one + g_one + b_one; if(keys.indexOf(key)<0){ // 未找到key,则新加入key }else{ // 找到则出现次数加1 }
这张图的原始分辨率是 1080 x 1800 ,缩放到canvas中分辨率是 216 x 360 (缩放规则是固定最大高度为360,按原始宽高比例缩放)。选择颜色降采样的间隔为 5,一共是提取了 6251 种颜色,过滤掉出现次数小于 4 和过黑过亮的颜色后剩余 2555 种颜色。K均值聚类的K设为 6 ,最终迭代次数是 10 ,耗时 106ms。
codepen的原始例子以下:
这方案执行下来会有一些问题:
这部分采用了brain,它应该是简单的BP神经网络。训练数据采用的是图虫网的热门图片。目前带评分的图像数据库比较少,并且评分每每是综合的,掺杂了其它(构图,主题,光影,人物等)因素,难以分离出只与色彩相关的评分,因此我是按照本身的喜爱对训练数据进行了评分,因此结果会很是强烈的接近我我的的喜爱。
另外神经网络的输入项也是比较关键的,由于它必需要正确反映颜色相关的图像信息,我提取的是:
let info = { colorCount: (Math.log10(colorInfo.length)), average:0, variance: 0, top50Count: 0, top50Average: 0, top50Variance: 0, top20Count: 0, top20Average: 0, top20Variance: 0, top10Count: 0, top10Average: 0, top10Variance: 0, top5Count: 0, top5Average: 0, top5Variance: 0 };
数据分为四类,评分从高到低分别是:100,85,75,65。
以前是采用的RGB空间,三个冷冰冰的数字并不能让咱们很好的分辨不一样色彩,因而这里我试着转换到HSL空间:色相(H)、饱和度(S)、明度(L),这三个颜色通道相互之间的叠加能获得各式各样的颜色,这个颜色空间几乎包括了人类视力所能感知的全部颜色,是目前运用最广的颜色系统之一。
RGB和HSL的转换可参考《javascript HEX十六进制与RGB, HSL颜色的相互转换》。
转换到HSL空间对于咱们提取颜色的目标有如下好处:
影响整个算法运行时间的关键步骤是颜色信息的统计,而统计环节中最耗时的是key的检测,存储key的容器长度会愈来愈长,采用indexOf
的方式会愈来愈耗时,实验证实绝大部分的时间都是耗费在这一步上。因此不妨试试查找二叉树这样的数据结构,二叉树的优点在于每次查找的时间会指数级降低,以此加快程序运行。
可是,我用js实现这种数据结构的结果并不理想,运行时间基本与indexOf
一致,甚至大部分时候还会略微多一点。我以为缘由在于:虽然每次查找重复key的时间减小了,可是每次新加入key的步骤变得复杂了,并且indexOf()
是 native code ,运行效率应该比咱们本身实现的js代码高。综合起来看,在必定的样本量区间,仍是使用原生的indexOf
效率更高,这个区间在本文指的是 1000~3000 种颜色,固然我仍是相信当颜色更多的时候,二叉树仍是有它的优点的。我实现的代码以下:
这是个很是实用的技巧(通过我屡次验证),感受已经离不开它了!
let len = colors.length; let count = (len / 8) ^ 0; let start = len % 8; while (start--) { // do something } while (count--) { // do something }
测试结果:jsprof。
对图像进行模糊能够减小色彩的种类,从而加速提取算法,这应该是可行的,可是我尚未加入到项目中,我探索的比较快,效果比较好的模糊算法的实现以下:
最开始只是想熟悉react,结果到后面,项目的重心就彻底偏向于算法和动画了。我以为React对SVG仍是比较友好的,各类动画属性均可以放到state中。我的感觉SVG动画相对于CSS的优点在于:更加灵活,更加容易完成复杂动画效果,兼容性更好,底层优化更流畅。
canvas动画的优点是比较流畅,SVG动画在移动端仍是有不少肉眼可见的掉帧卡顿的,并且SVG会让HTML变得很大很乱,可能让有洁癖的你不舒服。
无论什么动画最终都仍是归结于:数学,好比: