javascript
canvas 实现图片局部模糊_JavaScript中的图片处理与合成(四)
引言:
本系列現在構思成以下4個部分:
- 基礎類型圖片處理技術之縮放、裁剪與旋轉(傳送門);
- 基礎類型圖片處理技術之圖片合成(傳送門);
- 基礎類型圖片處理技術之文字合成(傳送門);
- 算法類型圖片處理技術(傳送門);
通過這些積累,我封裝了幾個項目中常用的功能:
圖片合成 圖片裁剪 人像摳除
之前文章主要介紹了裁剪/旋轉/合成等基礎類型的圖片處理(文字的合成編寫中... ),我們開始來介紹算法類型的圖片處理技術!~~??????
這類型的重點主要在于 算法 和 性能 層面,在前端由于js及設備性能的限制,通常表現并不理想。在真正的線上業務中,為了追求更好的用戶體驗,只能運行一些相對比較輕量級的,性能好的算法。由服務端來進行進行,會是更好的選擇。
Tips: 由于我對算法方面并沒有很深的理解,因此本文主要是一些算法外層及基礎原理的講解,不涉及算法本身。希望大家諒解哈~我們以下面兩個 來做初步的了解:
(一) 萬圣節小應用
萬圣節效果
效果圖如下:
這個小應用是一個萬圣節活動。人物臉部的木偶妝容確實很炫酷,但是這里需要復雜的人臉識別,模型比對以及妝容算法,放在前端性能堪憂,因此讓服務端來處理,顯然是更好的選擇。而邊框和背景圖的模糊處理,這類型的處理就比較適合放在前端了,首先性能能接受,而且更具靈活性,能在不同入口隨時替換不同的邊框素材。
對于服務端的妝容算法,由于我對算法并沒有深入研究,在這里就不班門弄斧了,我們就直接來梳理下前端的部分:
- 發送原圖給服務端,接受 妝容處理 后的效果圖;
- 下載效果圖后,縮放成合適大小后進行 模糊化處理 ,得到模糊后的結果圖;
- 將結果圖 / 模糊圖 / 邊框進行 像素級的融合 ;
算法性能提升
圖片算法處理實質原理其實是 遍歷像素點,對像素點的RGBA值進行改造。對于改造算法本身,本文就不深入了,不過可以與大家分享下相關的經驗。
眾所周知,一個好的算法,一個最重要的指標便是性能,而如何提升性能呢?一種是 算法優化 ,提高循環內部的性能或者優化遍歷算法,算法中的性能會由于遍歷的存在被放大無數倍。另一種則是 減少像素點。
像素點的遍歷是一個算法的重要性能消耗點,循環次數直接決定著算法的性能。而像素點的數量與圖片的大小尺寸成正向指數級增長,因此 適當的縮放圖片源后再去處理,對性能的提升十分巨大。例如一張2000*2000的圖片,像素點足足有400萬個,意味著需要遍歷400萬次,而把圖片縮小成 800*800 時,循環次數為64萬,這里我做過一個測試:
let st = new Date().getTime(); let imgData = []; for (let i = 0; i < n * 10000; i += 4) {let r = getRandom(0,255),g = getRandom(0,255),b = getRandom(0,255),a = 1;if (r <= 30 && g <= 30 && b<= 30) a = 0;imgData[i] = r;imgData[i + 1] = g;imgData[i + 2] = b;imgData[i + 3] = a; } let et = new Date().getTime(); let t = et - st; console.log(`${n}萬次耗時:${et - st}ms`, imgData);測試結果為(mac-chrome-20次取平均):
| 圖片尺寸 | 像素數量 | 耗時(ms) | 縮放倍數 | 提升 | |:-------:|:--------:|:--------:|:------:|:-----:| |2000*2000| 400萬 | 168 | 1 | 0% | |1600*1600| 256萬 | 98 | 0.8 | 42% | |1200*1200| 144萬 | 64 |0.6 | 62% | |800*800| 64萬 | 32 |0.4 | 81% | |400*400| 16萬 | 10 |0.2| 94% |
可以看出圖片的縮小,對性能有非常顯著的提升。這里有個特點,性能收益會隨著縮放系數的變大而越來越低,當縮放系數為0.8時,性能已經大大提升了42%,而繼續縮放為0.6時,收益便開始大幅降低,只提升了20%。同時縮放圖片意味著質量的下降,所以這里需要尋找一個 平衡點 ,在不影響結果圖效果的前提下,盡可能地提升性能,這需要根據算法對圖片質量的要求來定。
另外,對 原圖的裁剪也是個很好的辦法,裁剪掉多余的背景部分,也能達到減少遍歷次數,提升性能的效果。
模糊算法
小應用中模糊部分使用的是 StackBlur.js 的模糊算法,應用代碼如下:
// 縮放妝容圖; let srcImg = scaleMid(imgData);// 創建模糊結果圖的容器; let blurCvs = document.createElement('canvas'),blurCtx = blurCvs.getContext('2d');// 先復制一份原圖數據,; let blurImg = blurCtx.createImageData(srcImg.width, srcImg.height); let size = srcImg.width * srcImg.height * 4; for (let i = 0; i < size; i++) {blurImg.data[i] = srcImg.data[i]; }// 縮放成400*400的大小; blurImg = scale(blurImg, 400);// 進行模糊處理; StackBlur.imageDataRGBA(blurImg, 0, 0, blurImg.width, blurImg.height, 1);// 處理完后再放大為800*800; blurImg = scale(blurImg, 800);圖像融合
我們已經準備好合成最終效果圖的所有素材了,模糊背景 / 妝容圖 / 邊框素材,最后一步便是將三者進行融合,融合的原理是 根據最終效果圖分區域,在不同區域分別填入對應的素材數據:
// 圖片融合 function mix(src, blur, mtl) {// 最終結果圖為固定800*800;縱向800的數據;for (let i = 0; i < 800; i++) {let offset1 = 800 * i;// 橫向800的數據;for (let j = 0; j < 800; j++) {let offset = (offset1 + j) * 4;// 在特定的位置填入素材;if (i <= 75 || i >= 609 || j <= 126 || j >= 676) {let alpha = mtl.data[offset + 3] / 255.0;mtl.data[offset] = alpha * mtl.data[offset] + (1 - alpha) * blur.data[offset];mtl.data[offset + 1] = alpha * mtl.data[offset + 1] + (1 - alpha) * blur.data[offset + 1];mtl.data[offset + 2] = alpha * mtl.data[offset + 2] + (1 - alpha) * blur.data[offset + 2];mtl.data[offset + 3] = 255;} else {let alpha = mtl.data[offset + 3] / 255.0;let x = i - 75;let y = j - 126;let newOffset = (x * 550 + y) * 4;mtl.data[offset] = alpha * mtl.data[offset] + (1 - alpha) * src.data[newOffset];mtl.data[offset + 1] = alpha * mtl.data[offset + 1] + (1 - alpha) * src.data[newOffset + 1];mtl.data[offset + 2] = alpha * mtl.data[offset + 2] + (1 - alpha) * src.data[newOffset + 2];mtl.data[offset + 3] = 255;}}}return mtl; }(二) 摳除人像
這是一個基于服務端的人像mask層,在前端把人像摳出的服務,這樣便可以進一步做背景的融合和切換,現在已經用在多個線上項目中了。
人像摳除
這里需要基于由服務端處理后的兩張效果圖:
帶背景的結果圖和mask圖:
1、我們需要先將mask圖進行處理:
// 繪制mask; // mask_zoom: 既為了優化性能所做的縮放系數; mask = document.createElement('canvas'); maskCtx = mask.getContext('2d'); mask.width = imgEl.naturalWidth * ops.mask_zoom; mask.height = imgEl.naturalHeight * ops.mask_zoom / 2; maskCtx.drawImage(imgEl, 0, - imgEl.naturalHeight * ops.mask_zoom / 2, imgEl.naturalWidth * ops.mask_zoom , imgEl.naturalHeight * ops.mask_zoom);2、去除mask的黑色背景,變成透明色,這里需要用到像素操作:
// 獲取圖片數據; let maskData = maskCtx.getImageData(0, 0, mask.width, mask.height);// 遍歷改造像素點,將接近黑色的點的透明度改成0; for (let i = 0; i < data.length; i += 4) {let r = data[i],g = data[i + 1],b = data[i + 2];if (r <= 30 && g <= 30 && b<= 30)data[i + 3] = 0; }// 將改造后的數據重新填回mask層中; maskCtx.putImageData(maskData, 0, 0);3、圖像融合,這里用到了一個神奇的canvas方法,相信大家聽過,但并不熟悉 --- globalCompositeOperation,該值可以修改canvas的融合模式,有多種融合模式大家可以自行研究,這里使用的是source-in;
// 創建最終效果圖容器; result = document.createElement('canvas'); resultCtx = result.getContext('2d'); result.width = imgEl.naturalWidth; result.height = imgEl.naturalHeight;// 先繪制mask圖層做為背景; resultCtx.drawImage(mask, 0, 0, imgEl.naturalWidth, imgEl.naturalHeight);// 修改融合模式 resultCtx.globalCompositeOperation = 'source-in';// 繪制帶背景的結果圖 resultCtx.drawImage(origin, 0, 0);最終得到的效果圖:
最后就可以使用這種人像圖與任何背景或者素材根據業務需求再做融合了。
結語
算法部分由于本人才疏學淺,沒辦法深入闡述,只能分享一些比較粗淺的經驗,期望能在未來往更底層的領域發展。
不過還是期望能對大家有所啟發。~~有圖片處理興趣的童鞋,可以隨時與我探討哈,期望得到大神的指導,也希望js能在圖片處理領域有更快的發展。
感謝大家的閱讀。~~~ 。
總結
以上是生活随笔為你收集整理的canvas 实现图片局部模糊_JavaScript中的图片处理与合成(四)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 将null转换成数组_Javscript
- 下一篇: modelandview使用过程_面试问