圖片濾波
什麼是濾波
濾波是將訊號中特定波段頻率濾除的操作,是抑制和防止干擾的一項重要措施。是根據觀察某一隨機過程的結果,對另一與之有關的隨機過程進行估計的概率理論與方法。
圖片和波如何結合
在不考慮透明度的情況下,圖片可以由
rgb
構成,如果把rgb單獨抽出來,可以得到3組由(0-255)構成的陣列。再把這3組陣列在座標系中顯示的話,可以得到3條連續的線。通過觀察線的頻率,可以看出圖片顏色的平滑度。如果線段斜率越小說明該線段內的顏色越平滑,反之則反差越大。
如何濾波
方法一:使用AudioContext
濾波這種術語,一般會用在音訊處理上,通過修改音訊的頻率從而達到修改聲音的表現。例如AudioContext就有提供濾波函式createBiquadFilter
,其內部提供了一系列的濾波演算法,例如高通濾波highpass
,低通濾波lowpass
。所以我們可以利用它的api去處理圖片。
上圖出處:影像與濾波
從上圖可以看出曲線波動的地方就是圖片邊緣處,色差越大,波動越大。
方法二:通過卷積演算法
什麼是卷積
這裡對卷積的理解有點抽象,所以直接跳過
卷積應用於圖片
定義
用一個模板和一幅影像進行卷積,對於影像上的一個點,讓模板的原點和該點重合,然後模板上的點和影像上對應的點相乘,然後各點的積相加,就得到了該點的卷積值。對影像上的每個點都這樣處理。由於大多數模板都是對稱的,所以模板不旋轉。卷積是一種積分運算,用來求兩個曲線重疊區域面積。可以看作加權求和,可以用來消除噪聲、特徵增強。
卷積運算圖
此圖出處:onvolutional Neural Networks - Basics
此圖出處:如何理解卷積
通過上面兩張圖中,中間層的是卷積核(模版),可以看出其計算規則。而不同的濾波方式有對應的卷積核。
其實不難看出每個畫素點的卷積計算其實都是和自身周圍的畫素進行計算而得到新的畫素值。
卷積核
卷積核一般為奇數寬高的矩陣,一般為33,55。矩陣越大計算量越大
對應演算法實現
function convolution (pixels:ImageData, weights:number[]) {
const side = Math.round(Math.sqrt(weights.length))
const halfSide = Math.floor(side / 2)
const src = pixels.data
const canvasWidth = pixels.width
const canvasHeight = pixels.height
const temporaryCanvas = document.createElement('canvas')
const temporaryCtx = temporaryCanvas.getContext('2d')!
const outputData = temporaryCtx.createImageData(canvasWidth, canvasHeight)
for (let y = 0; y < canvasHeight; y++) {
for (let x = 0; x < canvasWidth; x++) {
const dstOff = (y * canvasWidth + x) * 4
let sumReds = 0
let sumGreens = 0
let sumBlues = 0
for (let kernelY = 0; kernelY < side; kernelY++) {
for (let kernelX = 0; kernelX < side; kernelX++) {
const currentKernelY = y + kernelY - halfSide
const currentKernelX = x + kernelX - halfSide
if (currentKernelY >= 0 &&
currentKernelY < canvasHeight &&
currentKernelX >= 0 &&
currentKernelX < canvasWidth) {
const offset = (currentKernelY * canvasWidth + currentKernelX) * 4
const weight = weights[kernelY * side + kernelX]
sumReds += src[offset] * weight
sumGreens += src[offset + 1] * weight
sumBlues += src[offset + 2] * weight
}
}
}
outputData.data[dstOff] = sumReds
outputData.data[dstOff + 1] = sumGreens
outputData.data[dstOff + 2] = sumBlues
outputData.data[dstOff + 3] = 255
}
}
return outputData
}
低通濾波
低通濾波去掉了高頻資訊,即細節資訊,留下的低頻資訊代表了概貌。常用的例子,比如美圖秀秀的磨皮,去掉了臉部細節資訊(痘坑,痘印,暗斑等)。
高通濾波
高通濾波會過濾低頻資訊,保留高頻資訊。上面我們說過斜率越高說明顏色差異越大這裡往往是圖片邊緣部分,也表示頻率越高。所以只保留高頻訊號的話,說明只保留了圖片的邊緣部分。所以高通濾波往往用做邊緣處理。
不同的濾波對應卷積核
均值濾波(加權平均模糊)
const weights = [
1/9, 1/9, 1/9,
1/9, 1/9, 1/9,
1/9, 1/9, 1/9,
]
從上面的卷積核可以看出該卷積核的值之和為1,所以的目標是將目標畫素點的值取周圍的平均數,所以用該卷積核處理圖片的結果會是模糊該圖片,不過因為使用的是均值,所以圖片不會很自然。一般使用高斯模糊濾波用作處理圖片模糊。
高斯濾波
高斯模糊就是在均值濾波的基礎上使用正態分佈,通俗講就是9宮格內的數值之和還是1,不過它們不是簡單的均分了而是具有權重。越靠近中心的值會越大,所以高斯模糊相對均值模糊會和原圖更加貼切。
正態分佈的二維函式如下圖:
根據上面的公式可推匯出的高斯矩陣方法:
function createGaussWeights (radius:number, sigma:number) {
const gaussMatrix = []
let gaussSum = 0
radius = Math.floor(radius) || 3
sigma = sigma || radius / 3
const a = 1 / 2 * Math.PI * (sigma ** 2)
const b = -1 / (2 * (sigma ** 2))
let i = 0
// 生成高斯矩陣
for (let y = radius; y >= -radius; y--) {
for (let x = -radius; x <= radius; x++) {
const g = a * Math.exp(b * (x ** 2 + y ** 2))
gaussMatrix[i++] = g
gaussSum += g
}
}
// 歸一化
for (let i = 0, len = gaussMatrix.length; i < len; i++) {
gaussMatrix[i] /= gaussSum
}
return gaussMatrix
}
下圖是3*3,模糊半徑1.5的高斯模糊矩陣
高斯模糊屬於什麼濾波?
這裡高斯模糊和均值濾波都屬於低通濾波,為什麼呢?
因為它們的本質就是將目標畫素取周圍畫素的平均值,所以邊緣的差異性會降低。我們上面說過頻率越高說明顏色差異越大,反過來顏色差異降低的話,頻率也就降低了,相當於過濾了高頻部分了。讓圖片更加平滑
highpass高通濾波
const weights = [
-1, -1, -1,
-1, 8, -1,
-1, -1, -1,
]
這個是高通濾波的卷積核,可以看出是中間為正數,其餘8個為-1,它們之和為0,經過該濾波器,可以看出如果中間的值比四周的大,則會得到放大期和周圍的差值,相反比四周的小的話,則得到的是負數,即黑色。這樣就會得到圖片的邊緣圖。
laplacian拉普拉斯濾波
const weights = [
0, -1, 0,
-1, 4, -1,
0, -1, 0,
]
拉普拉斯濾波也屬於高通濾波,不過它比較的畫素點是上下左右四個,不過最終還是能得到圖片的邊緣效果
應用場景
識別滑塊圖形驗證碼
操作步驟如下
- 使用高斯模糊去除圖片噪點
- 使用高通濾波獲取滑塊位置的邊緣
- 調節圖片亮度,過濾無用邊緣,方便後面計算
- 掃描圖片畫素點,篩選出亮度>10且連續最多的x座標
製作各式各樣濾鏡的圖片
- 高斯模糊
- 圖片銳化(高通濾波)
- 圖片浮雕(浮雕濾波器)
- 運動模糊
// 浮雕濾波器
[
-1, -1, 0,
-1, 0, 1,
0, 1, 1
]
// 運動模糊
[
1/3, 0, 0,
0, 1/3, 0,
0, 0, 1/3,
]