濾鏡主要是用來實現圖像的各類特殊效果,好比灰色、顏色反轉、黑白、馬賽克、銳化等,咱們在Photoshop中處理圖片時常常能看到,這些看似很複雜的功能前端同窗經過Canvas也能很容易實現。本文先經過幾個簡單的例子,解釋如何實現簡單的濾鏡效果;以後再介紹卷積的基礎知識,經過卷積運算來實現比較複雜的濾鏡效果。html
<canvas id="my_canvas"></canvas> 複製代碼
// 濾鏡函數 function filter (imageData, ctx) { // todo... 處理imageData return imageData; } // 加載圖片 let img = new Image(); img.src = "img.jpg"; img.onload = function () { // canvas let myCanvas = document.querySelector("#my_canvas"); myCanvas.width = 400; myCanvas.height = 300; let myContext = myCanvas.getContext("2d"); // 將圖片繪製到畫布中 myContext.drawImage(img, 0, 0, myCanvas.width, myCanvas.height); // 獲取畫布的像素數據 let imageData = myContext.getImageData(0, 0, myCanvas.width, myCanvas.height); // 處理像素數據 imageData = filter(imageData, myContext); // 將處理過的像素數據放回畫布 myContext.putImageData(imageData, 0, 0); } 複製代碼
處理過程很簡單,但是如何處理像素數據呢?前端
// 從x=0,y=0開始,取寬=2,高=2的像素數據 let imageData = ctx.getImageData(0, 0, 2, 2) console.log(imageData); 複製代碼
getImageData獲取圖片像素數據,方法返回ImageData對象,是拷貝了畫布指定矩形的像素數據,以下圖 算法
既然咱們知道了像素數據的含義,就能夠在filter函數中對像素數據imageData進行相應的數學運算便可,如今咱們對這三隻小狗下手 canvas
顧名思義,就是隻保留紅色值不變,把綠色和藍色去除掉(值設爲0)微信
// 濾鏡函數 - 紅色濾鏡 function filter (imageData, ctx) { let imageData_length = imageData.data.length / 4; // 4個爲一個像素 for (let i = 0; i < imageData_length; i++) { // imageData.data[i * 4 + 0] = 0; // 紅色值不變 imageData.data[i * 4 + 1] = 0; // 綠色值設置爲0 imageData.data[i * 4 + 2] = 0; // 藍色值設置爲0 } return imageData; } 複製代碼
效果以下: markdown
黑白照片效果,將顏色的RGB設置爲相同的值便可使得圖片爲灰色,咱們能夠取三個色值的平均值。網絡
// 濾鏡函數 - 灰色濾鏡 function filter (imageData, ctx) { let imageData_length = imageData.data.length / 4; // 4個爲一個像素 for (let i = 0; i < imageData_length; i++) { let newColor = (imageData.data[i * 4] + imageData.data[i * 4 + 1] + imageData.data[i * 4 + 2]) / 3; imageData.data[i * 4 + 0] = newColor; imageData.data[i * 4 + 1] = newColor; imageData.data[i * 4 + 2] = newColor; } return imageData; } 複製代碼
效果以下: ide
就是RGB三種顏色分別取255的差值函數
// 濾鏡函數 - 反向濾鏡 function filter (imageData, ctx) { let imageData_length = imageData.data.length / 4; // 4個爲一個像素 for (let i = 0; i < imageData_length; i++) { imageData.data[i * 4 + 0] = 255 - imageData.data[i * 4]; imageData.data[i * 4 + 1] = 255 - imageData.data[i * 4 + 1]; imageData.data[i * 4 + 2] = 255 - imageData.data[i * 4 + 2]; } return imageData; } 複製代碼
效果以下: oop
以上,經過控制每一個像素4個數據的值,便可達到簡單濾鏡的效果。可是複雜的濾鏡好比邊緣檢測,就須要用到卷積運算來實現。
卷積是一個經常使用的圖像處理技術。在圖像處理中,卷積操做是使用一個卷積核(kernel)對圖像中的每個像素進行一些列操做,能夠改變像素強度,使用卷積技術,你能夠獲取一些流行的圖像效果,好比邊緣檢測、銳化、模糊、浮雕等。
按照咱們上面講的圖片卷積,若是原始圖片尺寸爲6 x 6,卷積核尺寸爲3 x 3,則卷積後的圖片尺寸爲(6-3+1) x (6-3+1) = 4 x 4,卷積運算後,輸出圖片尺寸縮小了,這顯然不是咱們想要的結果! 爲了解決這個問題,可使用padding方法,即把原始圖片尺寸進行擴展,擴展區域補零,擴展尺寸爲卷積核的半徑(3x3卷積核半徑爲1,5x5卷積核半徑爲2)。
經常使用於檢測物體邊緣的卷積核是一箇中間是8,周圍是-1的3x3數據矩陣。
// 卷積計算函數 function convolutionMatrix(output, input, kernel) { let w = input.width, h = input.height; let iD = input.data, oD = output.data; for (let y = 1; y < h - 1; y += 1) { for (let x = 1; x < w - 1; x += 1) { for (let c = 0; c < 3; c += 1) { let i = (y * w + x) * 4 + c; oD[i] = kernel[0] * iD[i - w * 4 - 4] + kernel[1] * iD[i - w * 4] + kernel[2] * iD[i - w * 4 + 4] + kernel[3] * iD[i - 4] + kernel[4] * iD[i] + kernel[5] * iD[i + 4] + kernel[6] * iD[i + w * 4 - 4] + kernel[7] * iD[i + w * 4] + kernel[8] * iD[i + w * 4 + 4]; } oD[(y * w + x) * 4 + 3] = 255; } } return output; } // 濾鏡函數 function filter (imageData, ctx) { let kernel = [-1, -1, -1, -1, 8, -1, -1, -1, -1]; // 邊緣檢測卷積核 return convolutionMatrix(ctx.createImageData(imageData), imageData, kernel); } 複製代碼
咱們只要使用不一樣的卷積核就能獲得不一樣的圖像處理效果,好比使用下面這個卷積核,就能獲得銳化效果
let kernel = [-1, -1, -1, -1, 9, -1, -1, -1, -1]; // 銳化卷積核 複製代碼
圖像處理是一個頗有意思的事情,你們還能夠試試經過navigator.mediaDevices獲取攝像頭video,而後經過requestAnimationFrame實時把當前video的圖片數據經過濾鏡處理後,再畫到canvas中,這樣咱們就獲得了濾鏡處理過的視頻(參考Demo)! 另外若是你看懂了本文的卷積部分,也許你就踏進了【神經網絡與深度學習】的大門,由於卷積運算是神經網絡與深度學習中最基本的組成部分,邊緣檢測只是一個入門樣例,咱們還能夠用來作人臉識別等高級應用,想一想都有一點小激動~
若是你以爲這篇內容對你有價值,請點贊,並關注咱們的官網和咱們的微信公衆號(WecTeam),每週都有優質文章推送: