圖像顏色提取

本文github項目:colorful color
個人codepen連接:圖像顏色提取
the demo
原創文章,轉載請註明javascript

最近想找個小項目練練手,以便熟悉React,因而想到了「圖像顏色提取」這個方向,也有的說法是圖像主題色提取顏色量子化,或者是叫由圖像生成調色板,緣由無他,只是由於漂亮!html

design-seeds.com

「分析」的目的有這麼幾個:java

  • 主要顏色: main color 就是出現頻率最高的顏色,這樣色顏色在設計中經常是用於背景色,提供沉浸式的體驗:

main color

  • 平均顏色: average color 是全部顏色的平均值,和主要顏色同樣能夠用做背景色;
  • 顏色量子化: 顏色量子化在這裏至關因而在提取主題色,結果是圖像中一系列主要顏色的集合,這些顏色能夠經過統計分析獲得,也能夠經過聚類算法生成。同時,主要顏色平均顏色主題色這幾個因子均可以做爲圖像的特徵,特徵能夠用於圖像進一步分析,好比圖像識別與檢索,壓縮等;

color theme

  • 顏色可視化: 圖像自己就是顏色的容器,這個「容器」也是一種可視化的呈現,我想咱們也能夠從另外一個角度觀察顏色——去除圖像內容,僅呈現不一樣顏色的值和他們的權重,好比下面這樣星星點點像星空同樣可視化方案:

color bubble


1、常見顏色量子化算法

1.1 中位切分法

中位切分算法首先把全部像素映射到RGB空間,在這個三維的空間裏反覆切分出子空間,最後將切分空間的像素求均值做爲提取結果。分割區塊時都選擇全部區塊中最大(最長的邊長最大,或體積最大,或像素最多)的區塊,切割點應位於邊方向上,使得分割後兩個區塊的像素各一半的位置,以上是爲中位切分法。流程以下(推薦閱讀:《Color Quantization》):node

1.像素映射到RGB空間:react

median cut 1

2.區塊計算:git

median cut 2

3.中位切分:github

median cut 3

4.反覆切分:算法

median cut 4

5.計算區塊的平均顏色:數據庫

median cut 5

這裏推薦一個採用中位切分法實現(JavaScript)的顏色量子化項目:Color Thiefcanvas

1.2 八叉樹算法

八叉樹算法的核心理念是用八叉樹來劃分顏色空間,而後合併葉節點來逐步聚攏顏色(量子化),八叉樹的解釋可參考《遊戲場景管理的八叉樹算法是怎樣的?》,關鍵就是下面這兩幅圖:

1.建樹過程:

octree

2.合併葉節點:

reduce

具體的解釋可參考文章:《圖片主題色提取算法小結》,做者還寫了一個顏色量子化的node模塊: A theme color extractor module for Node.js

1.3 K-Means聚類法

K均值聚類的思想十分簡單,可分這幾步:

  1. 選取初始的K個質心;
  2. 按照距離質心的遠近對全部樣本進行分類;
  3. 從新計算質心,判斷是否退出條件:

    • 兩次質心的距離足夠小視爲知足退出條件;
    • 不退出則從新回到步驟2;

來看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);
  }

2、簡單實現

2.1 大體流程

  1. canvas讀取本地圖像,作適當縮放;
  2. 統計顏色信息:顏色須要作量子化處理(Color Quantization),RGB空間中一共有255的三次方約1600多萬種顏色,除以8能降採樣到32000多種。RGB值組合爲鍵值,統計每種顏色出現的次數:
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
}
  1. 過濾顏色:過濾孤立的顏色(出現次數太少)和過亮過黑的顏色;
  2. K均值聚類:選取出現頻率最高的K種顏色所謂初始值,由算法聚類出新的穩定的顏色中心;
  3. 計算主要顏色和均值顏色;

2.2 實驗結果

cc example

這張圖的原始分辨率是 1080 x 1800 ,縮放到canvas中分辨率是 216 x 360 (縮放規則是固定最大高度爲360,按原始寬高比例縮放)。選擇顏色降採樣的間隔爲 5,一共是提取了 6251 種顏色,過濾掉出現次數小於 4 和過黑過亮的顏色後剩餘 2555 種顏色。K均值聚類的K設爲 6 ,最終迭代次數是 10 ,耗時 106ms

codepen的原始例子以下:

census color

這方案執行下來會有一些問題:

  • K均值種子點的選取對結果的影響較大;
  • 計算聚類中心的時候不光是RGB三個值,還加入了顏色出現次數這個值,因此K比較小時,新的聚類中心可能不會收斂到醒目的點綴顏色上,這和咱們的視覺感覺是不一致的,可是若是選擇K爲10,對於上面的圖像是可以收斂到紅色的。

3、神經網絡評分

這部分採用了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。


4、改進

4.1 顏色空間的選擇

以前是採用的RGB空間,三個冷冰冰的數字並不能讓咱們很好的分辨不一樣色彩,因而這裏我試着轉換到HSL空間:色相(H)、飽和度(S)、明度(L),這三個顏色通道相互之間的疊加能獲得各式各樣的顏色,這個顏色空間幾乎包括了人類視力所能感知的全部顏色,是目前運用最廣的顏色系統之一。
RGB和HSL的轉換可參考《javascript HEX十六進制與RGB, HSL顏色的相互轉換》

轉換到HSL空間對於咱們提取顏色的目標有如下好處:

  • 原來的RGB中三個值同樣重要,對於HSL咱們可使用不一樣的參數分別去處理三個通道,好比對於色相能夠稠密採樣,對於明度和飽和度能夠適當稀疏採樣;
  • 對於不一樣顏色的控制更加精細準確,原始的RGB空間中咱們很難判斷兩個不一樣顏色之間他們的RGB值關係,可是對於HSL咱們只要關注色相就能夠了(其它兩個通道也頗有用,只是這裏選擇忽略它們);

4.2 二叉樹與indexOf

影響整個算法運行時間的關鍵步驟是顏色信息的統計,而統計環節中最耗時的是key的檢測,存儲key的容器長度會愈來愈長,採用indexOf的方式會愈來愈耗時,實驗證實絕大部分的時間都是耗費在這一步上。因此不妨試試查找二叉樹這樣的數據結構,二叉樹的優點在於每次查找的時間會指數級降低,以此加快程序運行。
可是,我用js實現這種數據結構的結果並不理想,運行時間基本與indexOf一致,甚至大部分時候還會略微多一點。我以爲緣由在於:雖然每次查找重複key的時間減小了,可是每次新加入key的步驟變得複雜了,並且indexOf()native code ,運行效率應該比咱們本身實現的js代碼高。綜合起來看,在必定的樣本量區間,仍是使用原生的indexOf效率更高,這個區間在本文指的是 1000~3000 種顏色,固然我仍是相信當顏色更多的時候,二叉樹仍是有它的優點的。我實現的代碼以下:

二叉樹前序/中序/後序遍歷

4.3 duff's device

這是個很是實用的技巧(通過我屢次驗證),感受已經離不開它了!

let len = colors.length;
let count = (len / 8) ^ 0;
let start = len % 8;
while (start--) {
  // do something
}
while (count--) {
  // do something  
}

測試結果:jsprof

4.4 模糊加速

對圖像進行模糊能夠減小色彩的種類,從而加速提取算法,這應該是可行的,可是我尚未加入到項目中,我探索的比較快,效果比較好的模糊算法的實現以下:

canvas blur


5、SVG與canvas動畫

最開始只是想熟悉react,結果到後面,項目的重心就徹底偏向於算法和動畫了。我以爲React對SVG仍是比較友好的,各類動畫屬性均可以放到state中。我的感覺SVG動畫相對於CSS的優點在於:更加靈活,更加容易完成複雜動畫效果,兼容性更好,底層優化更流暢。
canvas動畫的優點是比較流暢,SVG動畫在移動端仍是有不少肉眼可見的掉幀卡頓的,並且SVG會讓HTML變得很大很亂,可能讓有潔癖的你不舒服。

SVG halo animation

無論什麼動畫最終都仍是歸結於:數學,好比:

這個動畫的難點在於隨機的佈局算法,關鍵在於碰撞的檢測與碰撞後的移動,本質依賴於幾何和物理中的運動定律:

bubble chart

這個動畫的難點在於找到一個神奇的數學公式,雖然我本身也不知道怎麼回事,可是變換數學公式,有時候就能實現有規律的動畫,而有規律再加上色彩,很大機率就是好的動畫:

canvas wave

相關文章
相關標籤/搜索