本文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變得很大很亂,可能讓有潔癖的你不舒服。
無論什麼動畫最終都仍是歸結於:數學,好比: