Javascript圖像處理—平滑處理實現(xiàn)原理
前言
上一篇文章,我們講解了圖像的虛擬邊緣,這篇文章開始進(jìn)行平滑(也就是模糊)處理。
基本原理
這里直接引用OpenCV 2.4+ C++ 平滑處理和OpenCV 2.4+ C++ 邊緣梯度計算的相關(guān)內(nèi)容:
平滑也稱模糊, 是一項簡單且使用頻率很高的圖像處理方法。
平滑處理時需要用到一個濾波器
。 最常用的濾波器是線性 濾波器,線性濾波處理的輸出像素值(例如:)是輸入像素值(例如:
)的加權(quán)平均:
, 它僅僅是一個加權(quán)系數(shù)。
稱為核
這里涉及一種叫做“卷積”的運算,那么卷積是什么呢?
卷積是在每一個圖像塊與某個算子(核)之間進(jìn)行的運算。
核?!
nbsp;dsds
核就是一個固定大小的數(shù)值數(shù)組。該數(shù)組帶有一個錨點
,一般位于數(shù)組中央。
可是這怎么運算???
假如你想得到圖像的某個特定位置的卷積值,可用下列方法計算:
將核的錨點放在該特定位置的像素上,同時,核內(nèi)的其他值與該像素鄰域的各像素重合;將核內(nèi)各值與相應(yīng)像素值相乘,并將乘積相加;將所得結(jié)果放到與錨點對應(yīng)的像素上;對圖像所有像素重復(fù)上述過程。
用公式表示上述過程如下:
在圖像邊緣的卷積怎么辦呢?
計算卷積前,需要通過復(fù)制源圖像的邊界創(chuàng)建虛擬像素,這樣邊緣的地方也有足夠像素計算卷積了。這就是為什么上一篇文章需要做虛擬邊緣函數(shù)。
均值平滑
均值平滑實際上就是內(nèi)核元素全是1的卷積運算,然后再除以內(nèi)核的大小,用數(shù)學(xué)表達(dá)式來表示就是:
下面我們來實現(xiàn)均值平滑函數(shù)blur:
function blur(__src, __size1, __size2, __borderType, __dst){
if(__src.type && __src.type == "CV_RGBA"){
var height = __src.row,
width = __src.col,
dst = __dst || new Mat(height, width, CV_RGBA),
dstData = dst.data;
var size1 = __size1 || 3,
size2 = __size2 || size1,
size = size1 * size2;
if(size1 % 2 !== 1 || size2 % 2 !== 1){
console.error("size大小必須是奇數(shù)");
return __src;
}
var startX = Math.floor(size1 / 2),
startY = Math.floor(size2 / 2);
var withBorderMat = copyMakeBorder(__src, startY, startX, 0, 0, __borderType),
mData = withBorderMat.data,
mWidth = withBorderMat.col;
var newValue, nowX, offsetY, offsetI;
var i, j, c, y, x;
for(i = height; i--;){
offsetI = i * width;
for(j = width; j--;){
for(c = 3; c--;){
newValue = 0;
for(y = size2; y--;){
offsetY = (y + i) * mWidth * 4;
for(x = size1; x--;){
nowX = (x + j) * 4 + c;
newValue += mData[offsetY + nowX];
}
}
dstData[(j + offsetI) * 4 + c] = newValue / size;
}
dstData[(j + offsetI) * 4 + 3] = mData[offsetY + startY * mWidth * 4 + (j + startX) * 4 + 3];
}
}
}else{
console.error("不支持類型。");
}
return dst;
}
其中size1和size2分別是核的橫向和縱向大小,并且必須是正奇數(shù)。
高斯平滑
最有用的濾波器 (盡管不是最快的)。 高斯濾波是將輸入數(shù)組的每一個像素點與高斯內(nèi)核
卷積將卷積和當(dāng)作輸出像素值。參考一維高斯函數(shù),我們可以看見,他是個中間大兩邊小的函數(shù)。
所以高斯濾波器其加權(quán)數(shù)是中間大,四周小的。
其二維高斯函數(shù)為:
其中




這里參考OpenCV的實現(xiàn),不過應(yīng)該還有優(yōu)化空間,因為還沒用到分離濾波器。
首先我們做一個getGaussianKernel來返回高斯濾波器的一維數(shù)組。
function getGaussianKernel(__n, __sigma){
var SMALL_GAUSSIAN_SIZE = 7,
smallGaussianTab = [[1],
[0.25, 0.5, 0.25],
[0.0625, 0.25, 0.375, 0.25, 0.0625],
[0.03125, 0.109375, 0.21875, 0.28125, 0.21875, 0.109375, 0.03125]
];
var fixedKernel = __n & 2 == 1 && __n <= SMALL_GAUSSIAN_SIZE && __sigma <= 0 ? smallGaussianTab[__n >> 1] : 0;
var sigmaX = __sigma > 0 ? __sigma : ((__n - 1) * 0.5 - 1) * 0.3 + 0.8,
scale2X = -0.5 / (sigmaX * sigmaX),
sum = 0;
var i, x, t, kernel = [];
for(i = 0; i < __n; i++){
x = i - (__n - 1) * 0.5;
t = fixedKernel ? fixedKernel[i] : Math.exp(scale2X * x * x);
kernel[i] = t;
sum += t;
}
sum = 1 / sum;
for(i = __n; i--;){
kernel[i] *= sum;
}
return kernel;
};
然后通過兩個這個一維數(shù)組,便可以計算出一個完整的高斯內(nèi)核,再利用blur里面用到的循環(huán)方法,就可以算出高斯平滑后的矩陣了。
function GaussianBlur(__src, __size1, __size2, __sigma1, __sigma2, __borderType, __dst){
if(__src.type && __src.type == "CV_RGBA"){
var height = __src.row,
width = __src.col,
dst = __dst || new Mat(height, width, CV_RGBA),
dstData = dst.data;
var sigma1 = __sigma1 || 0,
sigma2 = __sigma2 || __sigma1;
var size1 = __size1 || Math.round(sigma1 * 6 + 1) | 1,
size2 = __size2 || Math.round(sigma2 * 6 + 1) | 1,
size = size1 * size2;
if(size1 % 2 !== 1 || size2 % 2 !== 1){
console.error("size必須是奇數(shù)。");
return __src;
}
var startX = Math.floor(size1 / 2),
startY = Math.floor(size2 / 2);
var withBorderMat = copyMakeBorder(__src, startY, startX, 0, 0, __borderType),
mData = withBorderMat.data,
mWidth = withBorderMat.col;
var kernel1 = getGaussianKernel(size1, sigma1),
kernel2,
kernel = new Array(size1 * size2);
if(size1 === size2 && sigma1 === sigma2)
kernel2 = kernel1;
else
kernel2 = getGaussianKernel(size2, sigma2);
var i, j, c, y, x;
for(i = kernel2.length; i--;){
for(j = kernel1.length; j--;){
kernel[i * size1 + j] = kernel2[i] * kernel1[j];
}
}
var newValue, nowX, offsetY, offsetI;
for(i = height; i--;){
offsetI = i * width;
for(j = width; j--;){
for(c = 3; c--;){
newValue = 0;
for(y = size2; y--;){
offsetY = (y + i) * mWidth * 4;
for(x = size1; x--;){
nowX = (x + j) * 4 + c;
newValue += (mData[offsetY + nowX] * kernel[y * size1 + x]);
}
}
dstData[(j + offsetI) * 4 + c] = newValue;
}
dstData[(j + offsetI) * 4 + 3] = mData[offsetY + startY * mWidth * 4 + (j + startX) * 4 + 3];
}
}
}else{
console.error("不支持的類型");
}
return dst;
}
中值平滑
中值濾波將圖像的每個像素用鄰域 (以當(dāng)前像素為中心的正方形區(qū)域)像素的
中值代替 。依然使用blur里面用到的循環(huán),只要得到核中的所有值,再通過sort排序便可以得到中值,然后錨點由該值替代。
function medianBlur(__src, __size1, __size2, __borderType, __dst){
if(__src.type && __src.type == "CV_RGBA"){
var height = __src.row,
width = __src.col,
dst = __dst || new Mat(height, width, CV_RGBA),
dstData = dst.data;
var size1 = __size1 || 3,
size2 = __size2 || size1,
size = size1 * size2;
if(size1 % 2 !== 1 || size2 % 2 !== 1){
console.error("size必須是奇數(shù)");
return __src;
}
var startX = Math.floor(size1 / 2),
startY = Math.floor(size2 / 2);
var withBorderMat = copyMakeBorder(__src, startY, startX, 0, 0, __borderType),
mData = withBorderMat.data,
mWidth = withBorderMat.col;
var newValue = [], nowX, offsetY, offsetI;
var i, j, c, y, x;
for(i = height; i--;){
offsetI = i * width;
for(j = width; j--;){
for(c = 3; c--;){
for(y = size2; y--;){
offsetY = (y + i) * mWidth * 4;
for(x = size1; x--;){
nowX = (x + j) * 4 + c;
newValue[y * size1 + x] = mData[offsetY + nowX];
}
}
newValue.sort();
dstData[(j + offsetI) * 4 + c] = newValue[Math.round(size / 2)];
}
dstData[(j + offsetI) * 4 + 3] = mData[offsetY + startY * mWidth * 4 + (j + startX) * 4 + 3];
}
}
}else{
console.error("類型不支持");
}
return dst;
};
相關(guān)文章
uni-app網(wǎng)絡(luò)請求、數(shù)據(jù)緩存實例詳解
這篇文章主要介紹了uni-app網(wǎng)絡(luò)請求、數(shù)據(jù)緩存的相關(guān)知識,本文通過實例代碼給大家講解的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-11-11Lerna入門之管理TypeScript monorepo教程
這篇文章主要為大家介紹了Lerna入門之管理TypeScript monorepo教程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11JavaScript詞法作用域與調(diào)用對象深入理解
關(guān)于 Javascript 的函數(shù)作用域、調(diào)用對象和閉包之間的關(guān)系很微妙,關(guān)于它們的文章已經(jīng)有很多,本文做了一些總結(jié),需要的朋友可以參考下2012-11-11javascript圖片切換綜合實例(循環(huán)切換、順序切換)
這篇文章主要介紹了javascript圖片切換綜合實例,包括javascript圖片循環(huán)切換、javascript圖片順序切換,兩張圖片的切換,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-01-01JavaScript實現(xiàn)數(shù)據(jù)類型的相互轉(zhuǎn)換
這篇文章主要為大家詳細(xì)介紹了JavaScript實現(xiàn)數(shù)據(jù)類型的相互轉(zhuǎn)換,感興趣的朋友可以參考一下2016-03-03微信小程序scroll-view實現(xiàn)滾動穿透和阻止?jié)L動的方法
這篇文章主要介紹了微信小程序scroll-view實現(xiàn)滾動穿透和阻止?jié)L動的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-08-08