Javascript图像处理—平滑处理实现原理_javascript技巧

前言

上一篇文章,我们讲解了图像的虚拟边缘,这篇文章开始进行平滑(也就是模糊)处理。

基本原理

这里直接引用OpenCV 2.4+ C++ 平滑处理和OpenCV 2.4+ C++ 边缘梯度计算的相关内容:

平滑也称模糊, 是一项简单且使用频率很高的图像处理方法。

平滑处理时需要用到一个滤波器

。 最常用的滤波器是线性

滤波器,线性滤波处理的输出像素值(例如:)是输入像素值(例如:)的加权平均:

    

称为核

, 它仅仅是一个加权系数。

这里涉及一种叫做“卷积”的运算,那么卷积是什么呢?

卷积是在每一个图像块与某个算子(核)之间进行的运算。

核?!

nbsp;
dsds

核就是一个固定大小的数值数组。该数组带有一个锚点

 ,一般位于数组中央。

 可是这怎么运算啊?

假如你想得到图像的某个特定位置的卷积值,可用下列方法计算:

  1. 将核的锚点放在该特定位置的像素上,同时,核内的其他值与该像素邻域的各像素重合;将核内各值与相应像素值相乘,并将乘积相加;将所得结果放到与锚点对应的像素上;对图像所有像素重复上述过程。

用公式表示上述过程如下:

    

在图像边缘的卷积怎么办呢?

计算卷积前,需要通过复制源图像的边界创建虚拟像素,这样边缘的地方也有足够像素计算卷积了。这就是为什么上一篇文章需要做虚拟边缘函数。

 

均值平滑

均值平滑实际上就是内核元素全是1的卷积运算,然后再除以内核的大小,用数学表达式来表示就是:

  

下面我们来实现均值平滑函数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大小必须是奇数");
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分别是核的横向和纵向大小,并且必须是正奇数。

高斯平滑

最有用的滤波器 (尽管不是最快的)。 高斯滤波是将输入数组的每一个像素点与高斯内核

卷积将卷积和当作输出像素值。

参考一维高斯函数,我们可以看见,他是个中间大两边小的函数。

所以高斯滤波器其加权数是中间大,四周小的。

其二维高斯函数为:

     

其中 

 为均值 (峰值对应位置),

 代表标准差 (变量

 和 变量

 各有一个均值,也各有一个标准差)。

这里参考OpenCV的实现,不过应该还有优化空间,因为还没用到分离滤波器。

首先我们做一个getGaussianKernel来返回高斯滤波器的一维数组。

复制代码 代码如下:

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;
};

然后通过两个这个一维数组,便可以计算出一个完整的高斯内核,再利用blur里面用到的循环方法,就可以算出高斯平滑后的矩阵了。

复制代码 代码如下:

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必须是奇数。");
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;
}

中值平滑

中值滤波将图像的每个像素用邻域 (以当前像素为中心的正方形区域)像素的

中值代替 。

依然使用blur里面用到的循环,只要得到核中的所有值,再通过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必须是奇数");
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;
};

时间: 2025-01-01 17:49:13

Javascript图像处理—平滑处理实现原理_javascript技巧的相关文章

浅析JavaScript动画模拟拖拽原理_javascript技巧

模拟拖拽的原理: x1等于div.offsetLeft y1等于div.offsetTop x2等于ev.clientX(ev表示event事件) y2等于ev.clientY 当我们在方块上按下鼠标的时候,x2-x1即可确定.移动鼠标之后,我们用鼠标当前的位置即x4.y4减去x2-x1.y2-y1就可以得到方块现在的位置. 代码: <!DOCTYPE html> <html lang="en"> <head> <meta charset=&q

Javascript图像处理—阈值函数实例应用_javascript技巧

前言 上一篇文章,我们讲解了图像处理中的亮度和对比度的变化,这篇文章我们来做一个阈值函数. 最简单的图像分割方法 阈值是最简单的图像分割方法. 比如为了从下图中分割出苹果,我们利用前景与背景的灰度差值,通过设定一个阈值,对于该像素大于这个阈值时就以黑色表示,小于便以灰色表示. 五种阈值类型 和OpenCV一样,我们将提供五种阈值类型,方便使用. 下面是原图像的波形表示,纵坐标表示像素点的灰度值大小,蓝线是阈值大小. 二进制阈值化 公式表示是: 图像表示是: 可见超过该阈值的就变成最大值(即255

javascript图像处理—边缘梯度计算函数_javascript技巧

前言 上一篇文章,我们讲解了图像处理中的膨胀和腐蚀函数,这篇文章将做边缘梯度计算函数. 图像的边缘 图像的边缘从数学上是如何表示的呢? 图像的边缘上,邻近的像素值应当显著地改变了.而在数学上,导数是表示改变快慢的一种方法.梯度值的大变预示着图像中内容的显著变化了. 用更加形象的图像来解释,假设我们有一张一维图形.下图中灰度值的"跃升"表示边缘的存在: 使用一阶微分求导我们可以更加清晰的看到边缘"跃升"的存在(这里显示为高峰值): 由此我们可以得出:边缘可以通过定位梯

实例分析浏览器中“JavaScript解析器”的工作原理_javascript技巧

浏览器在读取HTML文件的时候,只有当遇到<script>标签的时候,才会唤醒所谓的"JavaScript解析器"开始工作. JavaScript解析器工作步骤: 1."找一些东西": var. function. 参数:(也被称之为预解析) 备注:如果遇到重名分为以下两种情况: 遇到变量和函数重名了,只留下函数 遇到函数重名了,根据代码的上下文顺序,留下最后一个 2.逐行解读代码. 备注:表达式可以修改预解析的值 JS解析器在执行第一步预解析的时候,会

JavaScript代码里的判断小结_javascript技巧

比较判断 比较判断是比较两个值,返回一个布尔值,表示是否满足比较条件.JavaScript一共提供了8个比较运算符,这里主要说一下严格相等运算符和相等运算符的区别 严格相等运算符=== 判断 返回 两个值类型不同 false 两个值都是null/undefined/true/false true 两个值其中之一为NaN false 两个值都为数值且值相等 true 两个值都为字符串,且值相等 true 两个值都指向同一个引用类型 true 1 === "1" // false true

浅谈JavaScript 覆盖原型以及更改原型_javascript技巧

覆盖原型 //囚犯示例 //1.定义原型对象 var proto = { sentence : 4, //监禁年限 probation: 2 //缓刑年限 }; //2.定义原型对象的构造函数 var Prisoner = function(name, id) { this.name = name; this.id = id; }; //3.将构造函数关联到原型 Prisoner.prototype = proto; //4.实例化对象--采用工厂函数实例化对象 var makePrisoner

JavaScript中的时间处理小结_javascript技巧

废话不多说了,主要通过以下七个方面给大家总结了时间处理相关知识. 1.获取当前时间 function getNowTime() { return new Date(); } 2.时间与天数相加 function getTimeAddDays(time, days) { return new Date(time.getTime() + days * 24 * 60 * 60 * 1000); } 3.获取并格式化日期:年-月-日 function getFormatDate(time) { ret

浅析在javascript中创建对象的各种模式_javascript技巧

最近在看<javascript高级程序设计>(第二版) javascript中对象的创建 •工厂模式 •构造函数模式 •原型模式 •结合构造函数和原型模式 •原型动态模式 面向对象的语言大都有一个类的概念,通过类可以创建多个具有相同方法和属性的对象.虽然从技术上讲,javascript是一门面向对象的语言,但是javascript没有类的概念,一切都是对象.任意一个对象都是某种引用类型的实例,都是通过已有的引用类型创建:引用类型可以是原生的,也可以是自定义的.原生的引用类型有:Object.A

javascript排序函数实现数字排序_javascript技巧

javascript排序函数实现数字排序 <script> function SortNumber(obj,func) //定义通用排序函数 { //参数验证,如果第一个参数不是数组或第二个参数不是函数则抛出异常 if(!(obj instanceof Array) || !(func instanceof Function)) { var e = new Error(); //生成错误信息 e.number = 100000; //定义错误号 e.message = "参数无效&q