作者部落格地址: 讓你的文字自動適配背景顏色
網傳,產品經理要求App開發人員,讓使用者App的主題顏色能根據手機殼自動調整。
剛好筆者要做一個類似的事情,根據背景顏色自動改變文字的顏色,以便於使用者識別。
正文
產品設計了一個人機校驗元件,大致長這個樣子。背景會每次隨機取不同圖片,開始的時候,箭頭設定為藍色。在背景為藍色的時候,使用者就分辨箭頭就有些困難了。怎麼解決這個問題呢?
思路與實現
第一步
取到箭頭底部背景的範圍座標。這個比較簡單,基本運算就搞定,done
第二步
要識別圖片,我們需要藉助 Canvas,將圖片繪製到 Canvas 上,來操作影像資料。
建立 Canvas 容器
const c4 = document.createElement('canvas')
c4.width = 190
c4.height = 190
const ctx4 = c4.getContext('2d')
放入圖片
// 識別圖片
const image = new Image()
image.onload = () => {
ctx4?.drawImage(image, 0, 0, 191, 190) // 繪製圖片到 Canvas
const color = analysisColor(ctx4?.getImageData(0, 0, 191, 190)) // 分析顏色分佈
setFontColor(color) // 設定字型顏色
resolve(true) // 完成Promise
}
image.src = images[`code-${index}`] // 取本次隨機圖片的地址設定到 image
跨域問題
可是進展並沒有那麼順利,背景圖片不在同域下面,Canvas 不允許跨域的圖片,怎麼辦呢?
第三步
既然 Canvas 不允許跨域的圖片,在無後端代理支援的情況下,怎麼高效的解決這個問題呢?(本地是跨域,線上同域)
把圖片下載的本地!藉助 XMLHttpRequest 將圖片先快取到本地轉成 Blob 物件,Canvas 是可以訪問本地 Blob 的資料。
下載圖片,解決圖片跨域問題
// 下載圖片,解決圖片跨域問題
const xhr = new XMLHttpRequest()
xhr.open('get', images[`code-${index}`], true)
xhr.responseType = 'blob'
xhr.onload = function loaded() {
if (this.status === 200) {
const blob = this.response
image.src = window.URL.createObjectURL(blob)
}
}
xhr.send()
第四步
解決了跨域問題,接下來就是分析顏色了,getImageData 取到的就是圖片 rgba 的陣列。
這個時候就可以將計算好的座標,代入到圖片 rgba 裡面計算其分佈。
說到這裡就要補一下影像演算法的知識了。
許多從自然場景中拍攝的影像,其色彩分佈上會給人一種和諧、一致的感覺;反過來,在許多介面設計應用中,我們也希望選擇的顏色可以達到這樣的效果,但對一般人來說卻並不那麼容易,這屬於色彩心理學的範疇。從彩色影像中提取其中的主題顏色,不僅可以用於色彩設計,也可用於影像分類、搜尋、識別等,本文分別總結並實現影像主題顏色提取的幾種演算法,包括顏色量化法(ColorQuantization)、聚類(Clustering)和顏色建模的方法
顏色量化演算法
彩色影像一般採用RGB色彩模式,每個畫素由RGB三個顏色分量組成。隨著硬體的不斷升級,彩色影像的儲存由最初的8位、16位變成現在的24位、32真彩色。所謂全綵是指每個畫素由8位($2^8$=0~255)表示,紅綠藍三原色組合共有1677萬(256 x 256 x 256 )萬種顏色,如果將RGB看作是三維空間中的三個座標,可以得到下面這樣一張色彩空間圖:
當然,一張影像不可能包含所有顏色,我們將一張彩色影像所包含的畫素投射到色彩空間中,可以更直觀地感受影像中顏色的分佈:
因此顏色量化問題可以用所有向量量化(vector quantization, VQ)演算法解決。這裡採用開源影像處理庫 Leptonica 中用到的兩種演算法:中位切分法、八叉樹演算法。
這裡核心使用中位切分法(Median cut) 參考專案 Github: color-thief
// 計算圖片中間值
function analysisColor(rgbaArray: any) {
// Todo something,返回該區域顏色的主色
}
第五步
到這裡,這個需求就算實現了基本核心的部分了,但是在執行過程中,發現效能消耗極大。大部分花在了 Canvas 繪製和影像遍歷上
怎麼來優化這個過程呢?能不能只提取影像的特徵資訊進行分析呢?
帶著這兩個問題,查閱了影像特徵演算法相關的文獻後,找到了 方向梯度直方圖(Histogram of Oriented Gradient, HOG) 這個演算法。
基HOG特徵
方向梯度直方圖(Histogram of Oriented Gradient, HOG)特徵是一種在計算機視覺和影像處理中用來進行物體檢測的特徵描述子。它通過計算和統計影像區域性區域的梯度方向直方圖來構成特徵。Hog特徵結合 SVM分類器已經被廣泛應用於影像識別中,尤其在行人檢測中獲得了極大的成功。需要提醒的是,HOG+SVM進行行人檢測的方法是法國研究人員Dalal 在2005的CVPR上提出的,而如今雖然有很多行人檢測演算法不斷提出,但基本都是以HOG+SVM的思路為主。
主要思想
在一副影像中,區域性目標的表象和形狀(appearance and shape)能夠被梯度或邊緣的方向密度分佈很好地描述。(本質:梯度的統計資訊,而梯度主要存在於邊緣的地方)。
具體的實現方法是
首先將影像分成小的連通區域,我們把它叫細胞單元。然後採集細胞單元中各畫素點的梯度的或邊緣的方向直方圖。最後把這些直方圖組合起來就可以構成特徵描述器。
提高效能
把這些區域性直方圖在影像的更大的範圍內(我們把它叫區間或block)進行對比度歸一化(contrast-normalized),所採用的方 法是:先計算各直方圖在這個區間(block)中的密度,然後根據這個密度對區間中的各個細胞單元做歸一化。通過這個歸一化後,能對光照變化和陰影獲得更 好的效果。
優點
與其他的特徵描述方法相比,HOG有很多優點。首先,由於HOG是在影像的區域性方格單元上操作,所以它對影像幾何的和光學的形變都能保持很好的不 變性,這兩種形變只會出現在更大的空間領域上。其次,在粗的空域抽樣、精細的方向抽樣以及較強的區域性光學歸一化等條件下,只要行人大體上能夠保持直立的姿 勢,可以容許行人有一些細微的肢體動作,這些細微的動作可以被忽略而不影響檢測效果。因此HOG特徵是特別適合於做影像中的人體檢測的。
HOG特徵提取演算法的實現過程
第六步
基於此,來做我們自己的演算法實現。將原圖在繪製時,按照等比平鋪,一步步的繪製到 Canvas 格子上去。隨著尺寸的縮小,影像的特徵依然得以保留,大致效果如下。
在實驗多個不同的壓縮尺寸後,發現 16x16 這個尺寸能兼顧特徵與識別效能,再小一些的格子比如 8x8 就會丟失特徵值。
貼一下大致的實現過程
const checkBack = async (index: number) => {
return new Promise((resolve) => {
// 計算圖片中間值
function analysisColor(rgbaArray: any) {
// Todo something,返回該區域顏色的主色
}
const c4 = document.createElement('canvas') // 壓縮尺寸計算用
c4.width = 16
c4.height = 16
const ctx4 = c4.getContext('2d')
// 識別圖片
const image = new Image()
image.onload = () => {
ctx4?.drawImage(image, 0, 0, 17, 16) // 繪製圖片到 Canvas
const color = analysisColor(ctx4?.getImageData(0, 0, 17, 16)) // 分析顏色分佈
setFontColor(color) // 設定字型顏色
resolve(true) // 完成Promise
}
// 下載圖片,解決圖片跨域問題
const xhr = new XMLHttpRequest()
xhr.open('get', images[`code-${index}`], true)
xhr.responseType = 'blob'
xhr.onload = function loaded() {
if (this.status === 200) {
const blob = this.response
image.src = window.URL.createObjectURL(blob)
console.log(image.src)
}
}
xhr.send()
})
}
最後
我們再來看看優化後,分析過程的耗時,差不多提升了 100 倍的速度!!!
最終的效果圖:
本作品採用《CC 協議》,轉載必須註明作者和本文連結