前端圖像處理之濾鏡

前言

濾鏡主要是用來實現圖像的各類特殊效果,好比灰色、顏色反轉、黑白、馬賽克、銳化等,咱們在Photoshop中處理圖片時常常能看到,這些看似很複雜的功能前端同窗經過Canvas也能很容易實現。本文先經過幾個簡單的例子,解釋如何實現簡單的濾鏡效果;以後再介紹卷積的基礎知識,經過卷積運算來實現比較複雜的濾鏡效果。html

1、基礎

一、圖像處理流程中所用到的Canvas API主要有:

  • 清空給定矩形內的指定像素:clearRect(x, y, width, height)
  • 向畫布上面繪製圖片:drawImage(img, x, y, width, height)
  • 返回畫布指定矩形的像素數據:getImageData(x, y, width, height)
  • 將圖像數據放回畫布上:putImageData(imgData, x, y)

二、處理過程

代碼以下:

<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對象,是拷貝了畫布指定矩形的像素數據,以下圖 算法

imageData.data中有四個(寬x高=2x2=4)像素的數據,每一個像素數據,都存在着四方面的信息,即 RGBA 值: R - 紅色 (0-255; 0是黑色,255是純紅色) G - 綠色 (0-255; 0是黑色,255是純綠色) B - 藍色 (0-255; 0是黑色的,255是純藍色) A – 透明度 (0-255; 0是透明的,255是徹底可見不透明的)

2、實現濾鏡

既然咱們知道了像素數據的含義,就能夠在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個數據的值,便可達到簡單濾鏡的效果。可是複雜的濾鏡好比邊緣檢測,就須要用到卷積運算來實現。

3、卷積

卷積是一個經常使用的圖像處理技術。在圖像處理中,卷積操做是使用一個卷積核(kernel)對圖像中的每個像素進行一些列操做,能夠改變像素強度,使用卷積技術,你能夠獲取一些流行的圖像效果,好比邊緣檢測、銳化、模糊、浮雕等。

上圖就是經過卷積運算後,輸出的邊緣檢測圖像效果,若是經過上面簡單濾鏡算法,很難想象咱們能找到物體的邊緣!如今來看一下怎麼實現。

一、卷積運算過程

卷積運算是使用一個卷積覈對輸入圖像中的每一個像素進行一系列四則運算。 卷積核(算子)是用來作圖像處理時的矩陣,一般爲3x3矩陣。 使用卷積進行計算時,須要將卷積核的中心放置在要計算的像素上,一次計算核中每一個元素和其覆蓋的圖像像素值的乘積並求和,獲得的結構就是該位置的新像素值。

計算步驟以下: 一、咱們使用3×3的卷積核,將其覆蓋在輸入圖像,對應的數字相乘,最後所有相加,便可獲得第一個輸出數據; 二、把3×3的卷積核右移一格; 三、重複1的計算過程,獲得第二個數據; 四、重複以上過程。

按照咱們上面講的圖片卷積,若是原始圖片尺寸爲6 x 6,卷積核尺寸爲3 x 3,則卷積後的圖片尺寸爲(6-3+1) x (6-3+1) = 4 x 4,卷積運算後,輸出圖片尺寸縮小了,這顯然不是咱們想要的結果! 爲了解決這個問題,可使用padding方法,即把原始圖片尺寸進行擴展,擴展區域補零,擴展尺寸爲卷積核的半徑(3x3卷積核半徑爲1,5x5卷積核半徑爲2)。

一個尺寸6 x 6的數據矩陣,通過padding後,尺寸變爲8 * 8,卷積運算後輸出尺寸爲6 x 6,保證了圖片尺寸不變化。

二、卷積核特性

  • 大小應該是奇數,這樣它纔有一箇中心,例如3x3,5x5或者7x7。
  • 卷積核上的每一位乘數被稱爲權值,它們決定了這個像素的份量有多重。
  • 它們的總和加起來若是等於1,計算結果不會改變圖像的灰度強度。
  • 若是大於1,會增長灰度強度,計算結果使得圖像變亮。
  • 若是小於1,會減小灰度強度,計算結果使得圖像變暗。
  • 若是和爲0,計算結果圖像不會變黑,但也會很是暗。

三、邊緣檢測

經常使用於檢測物體邊緣的卷積核是一箇中間是8,周圍是-1的3x3數據矩陣。

咱們能感覺到物體的邊緣,是由於邊緣有明顯的色差。假設輸入圖像的部分色值爲10,部分色值爲50,那麼10和50之間就存在色差,邊緣就在這個地方。通過卷積計算以後,咱們能夠看到色值相同的部分都變成了0表現爲黑色,只有邊緣的色值計算結果大於0(色值最小是0,負數色值也是黑色),即色值爲120的邊緣就凸顯出來了! 代碼以下:

// 卷積計算函數
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]; // 銳化卷積核
複製代碼

銳化也是一種針對邊緣處理(加強)的效果,前面有提到「卷積核的總和加起來若是等於1,計算結果不會改變圖像的灰度強度」。因此只要把邊緣檢測卷積核中間的8改成9,就能實現邊緣加強,且圖片亮度不變的銳化效果!

4、總結

圖像處理是一個頗有意思的事情,你們還能夠試試經過navigator.mediaDevices獲取攝像頭video,而後經過requestAnimationFrame實時把當前video的圖片數據經過濾鏡處理後,再畫到canvas中,這樣咱們就獲得了濾鏡處理過的視頻(參考Demo)! 另外若是你看懂了本文的卷積部分,也許你就踏進了【神經網絡與深度學習】的大門,由於卷積運算是神經網絡與深度學習中最基本的組成部分,邊緣檢測只是一個入門樣例,咱們還能夠用來作人臉識別等高級應用,想一想都有一點小激動~

5、Demo

點擊查看


若是你以爲這篇內容對你有價值,請點贊,並關注咱們的官網和咱們的微信公衆號(WecTeam),每週都有優質文章推送:

WecTeam
相關文章
相關標籤/搜索