转《JavaScript中的图片处理与合成》
引言:
本系列现在构思成以下4个部分:
通过这些积累,我封装了几个项目中常用的功能:
图片合成 图片裁剪 人像抠除
之前文章主要介绍了裁剪/旋转/合成等基础类型的图片处理(文字的合成编写中...???),我们开始来介绍算法类型的图片处理技术!~~✈️✈️✈️
这类型的重点主要在于 算法 和 性能 层面,在前端由于js
及设备性能的限制,通常表现并不理想。在真正的线上业务中,为了追求更好的用户体验,只能运行一些相对比较轻量级的,性能好的算法。由服务端来进行进行,会是更好的选择。
Tips: 由于我对算法方面并没有很深的理解,因此本文主要是一些算法外层及基础原理的讲解,不涉及算法本身。希望大家谅解哈~?
我们以下面两个?来做初步的了解:
(一) 万圣节小应用
效果图如下:
这个小应用是一个万圣节活动。人物脸部的木偶妆容确实很炫酷,但是这里需要复杂的人脸识别,模型比对以及妆容算法,放在前端性能堪忧,因此让服务端来处理,显然是更好的选择。而边框和背景图的模糊处理,这类型的处理就比较适合放在前端了,首先性能能接受,而且更具灵活性,能在不同入口随时替换不同的边框素材。
对于服务端的妆容算法,由于我对算法并没有深入研究,在这里就不班门弄斧了,我们就直接来梳理下前端的部分:
- 发送原图给服务端,接受 妆容处理 后的效果图;
- 下载效果图后,缩放成合适大小后进行 模糊化处理 ,得到模糊后的结果图;
- 将结果图 / 模糊图 / 边框进行 像素级的融合 ;
Tips: 这里使用的全是像素级别的算法融合,通过基础类型的合成,同样可以实现。
算法性能提升
图片算法处理实质原理其实是 遍历像素点,对像素点的RGBA值进行改造。对于改造算法本身,本文就不深入了,不过可以与大家分享下相关的经验。
众所周知,一个好的算法,一个最重要的指标便是性能,而如何提升性能呢?一种是 算法优化 ,提高循环内部的性能或者优化遍历算法,算法中的性能会由于遍历的存在被放大无数倍。另一种则是 减少像素点。
像素点的遍历是一个算法的重要性能消耗点,循环次数直接决定着算法的性能。而像素点的数量与图片的大小尺寸成正向指数级增长,因此 适当的缩放图片源后再去处理,对性能的提升十分巨大。例如一张2000*2000
的图片,像素点足足有400万个,意味着需要遍历400万次,而把图片缩小成 800*800
时,循环次数为64万,这里我做过一个测试:
let st = new Date().getTime();
let imgData = [];
for (let i = 0; i < n * 10000; i += 4) {
let r = getRandom(0,255),
g = getRandom(0,255),
b = getRandom(0,255),
a = 1;
if (r <= 30 && g <= 30 && b<= 30) a = 0;
imgData[i] = r;
imgData[i + 1] = g;
imgData[i + 2] = b;
imgData[i + 3] = a;
}
let et = new Date().getTime();
let t = et - st;
console.log(`${n}万次耗时:${et - st}ms`, imgData);
测试结果为(mac-chrome-20次取平均):
图片尺寸 | 像素数量 | 耗时(ms) | 缩放倍数 | 提升 |
---|---|---|---|---|
2000*2000 |
400万 | 168 | 1 | 0% |
1600*1600 |
256万 | 98 | 0.8 | 42% |
1200*1200 |
144万 | 64 | 0.6 | 62% |
800*800 |
64万 | 32 | 0.4 | 81% |
400*400 |
16万 | 10 | 0.2 | 94% |
可以看出图片的缩小,对性能有非常显著的提升。这里有个特点,性能收益会随着缩放系数的变大而越来越低,当缩放系数为0.8时,性能已经大大提升了42%,而继续缩放为0.6时,收益便开始大幅降低,只提升了20%。同时缩放图片意味着质量的下降,所以这里需要寻找一个 平衡点 ,在不影响结果图效果的前提下,尽可能地提升性能,这需要根据算法对图片质量的要求来定。
另外,对 原图的裁剪也是个很好的办法,裁剪掉多余的背景部分,也能达到减少遍历次数,提升性能的效果。
模糊算法
小应用中模糊部分使用的是 StackBlur.js
的模糊算法,应用代码如下:
// 缩放妆容图;
let srcImg = scaleMid(imgData);
// 创建模糊结果图的容器;
let blurCvs = document.createElement('canvas'),
blurCtx = blurCvs.getContext('2d');
// 先复制一份原图数据,;
let blurImg = blurCtx.createImageData(srcImg.width, srcImg.height);
let size = srcImg.width * srcImg.height * 4;
for (let i = 0; i < size; i++) {
blurImg.data[i] = srcImg.data[i];
}
// 缩放成400*400的大小;
blurImg = scale(blurImg, 400);
// 进行模糊处理;
StackBlur.imageDataRGBA(blurImg, 0, 0, blurImg.width, blurImg.height, 1);
// 处理完后再放大为800*800;
blurImg = scale(blurImg, 800);
图像融合
我们已经准备好合成最终效果图的所有素材了,模糊背景 / 妆容图 / 边框素材,最后一步便是将三者进行融合,融合的原理是 根据最终效果图分区域,在不同区域分别填入对应的素材数据:
// 图片融合
function mix(src, blur, mtl) {
// 最终结果图为固定800*800;纵向800的数据;
for (let i = 0; i < 800; i++) {
let offset1 = 800 * i;
// 横向800的数据;
for (let j = 0; j < 800; j++) {
let offset = (offset1 + j) * 4;
// 在特定的位置填入素材;
if (i <= 75 || i >= 609 || j <= 126 || j >= 676) {
let alpha = mtl.data[offset + 3] / 255.0;
mtl.data[offset] = alpha * mtl.data[offset] + (1 - alpha) * blur.data[offset];
mtl.data[offset + 1] = alpha * mtl.data[offset + 1] + (1 - alpha) * blur.data[offset + 1];
mtl.data[offset + 2] = alpha * mtl.data[offset + 2] + (1 - alpha) * blur.data[offset + 2];
mtl.data[offset + 3] = 255;
} else {
let alpha = mtl.data[offset + 3] / 255.0;
let x = i - 75;
let y = j - 126;
let newOffset = (x * 550 + y) * 4;
mtl.data[offset] = alpha * mtl.data[offset] + (1 - alpha) * src.data[newOffset];
mtl.data[offset + 1] = alpha * mtl.data[offset + 1] + (1 - alpha) * src.data[newOffset + 1];
mtl.data[offset + 2] = alpha * mtl.data[offset + 2] + (1 - alpha) * src.data[newOffset + 2];
mtl.data[offset + 3] = 255;
}
}
}
return mtl;
}
(二) 抠除人像
这是一个基于服务端的人像mask
层,在前端把人像抠出的服务,这样便可以进一步做背景的融合和切换,现在已经用在多个线上项目中了。
人像抠除)
这里需要基于由服务端处理后的两张效果图:
带背景的结果图和mask
图:
1、我们需要先将mask
图进行处理:
// 绘制mask;
// mask_zoom: 既为了优化性能所做的缩放系数;
mask = document.createElement('canvas');
maskCtx = mask.getContext('2d');
mask.width = imgEl.naturalWidth * ops.mask_zoom;
mask.height = imgEl.naturalHeight * ops.mask_zoom / 2;
maskCtx.drawImage(imgEl, 0, - imgEl.naturalHeight * ops.mask_zoom / 2, imgEl.naturalWidth * ops.mask_zoom , imgEl.naturalHeight * ops.mask_zoom);
2、去除mask
的黑色背景,变成透明色,这里需要用到像素操作:
// 获取图片数据;
let maskData = maskCtx.getImageData(0, 0, mask.width, mask.height);
// 遍历改造像素点,将接近黑色的点的透明度改成0;
for (let i = 0; i < data.length; i += 4) {
let r = data[i],
g = data[i + 1],
b = data[i + 2];
if (r <= 30 && g <= 30 && b<= 30)data[i + 3] = 0;
}
// 将改造后的数据重新填回mask层中;
maskCtx.putImageData(maskData, 0, 0);
3、图像融合,这里用到了一个神奇的canvas
方法,相信大家听过,但并不熟悉 --- globalCompositeOperation
,该值可以修改canvas
的融合模式,有多种融合模式大家可以自行研究,这里使用的是source-in
;
// 创建最终效果图容器;
result = document.createElement('canvas');
resultCtx = result.getContext('2d');
result.width = imgEl.naturalWidth;
result.height = imgEl.naturalHeight;
// 先绘制mask图层做为背景;
resultCtx.drawImage(mask, 0, 0, imgEl.naturalWidth, imgEl.naturalHeight);
// 修改融合模式
resultCtx.globalCompositeOperation = 'source-in';
// 绘制带背景的结果图
resultCtx.drawImage(origin, 0, 0);
最终得到的效果图:
最后就可以使用这种人像图与任何背景或者素材根据业务需求再做融合了。
结语
转《JavaScript中的图片处理与合成》的更多相关文章
- 转《在浏览器中使用tensorflow.js进行人脸识别的JavaScript API》
作者 | Vincent Mühle 编译 | 姗姗 出品 | 人工智能头条(公众号ID:AI_Thinker) [导读]随着深度学习方法的应用,浏览器调用人脸识别技术已经得到了更广泛的应用与提升.在 ...
- face-api.js:一个在浏览器中进行人脸识别的 JavaScript 接口
Mark! 本文将为大家介绍一个建立在「tensorflow.js」内核上的 javascript API——「face-api.js」,它实现了三种卷积神经网络架构,用于完成人脸检测.识别和特征点检 ...
- TensorFlow.js之安装与核心概念
TensorFlow.js是通过WebGL加速.基于浏览器的机器学习js框架.通过tensorflow.js,我们可以在浏览器中开发机器学习.运行现有的模型或者重新训练现有的模型. 一.安装 ...
- 在Java中直接调用js代码(转载)
http://blog.csdn.net/xzyxuanyuan/article/details/8062887 JDK1.6版添加了新的ScriptEngine类,允许用户直接执行js代码. 在Ja ...
- 第十一章:WEB浏览器中的javascript
客户端javascript涵盖在本系列的第二部分第10章,主要讲解javascript是如何在web浏览器中实现的,这些章节介绍了大量的脚本宿主对象,这些对象可以表示浏览器窗口.文档树的内容.这些章节 ...
- 在Java中直接调用js代码
JDK1.6版添加了新的ScriptEngine类,允许用户直接执行js代码. 在Java中直接调用js代码 不能调用浏览器中定义的js函数,会抛出异常提示ReferenceError: “alert ...
- TensorFlow.js入门(一)一维向量的学习
TensorFlow的介绍 TensorFlow是谷歌基于DistBelief进行研发的第二代人工智能学习系统,其命名来源于本身的运行原理.Tensor(张量)意味着N维数组,Flow(流)意味着 ...
- JavaScript权威指南--WEB浏览器中的javascript
知识要点 1.客户端javascript window对象是所有客户端javascript特性和API的主要接入点.它表示web浏览器的一个窗口或窗体,并且可以用window表示来引用它.window ...
- 解决webkit浏览器中js方法中使用window.event提示未定义的问题
这实际上是一个浏览器兼容性问题,根源百度中一大堆,简要说就是ie中event对象是全局变量,所以哪里都能使用到,但是webkit内核的浏览器中却不存在这个全局变量event,而是以一个隐式的局部变量的 ...
- JS Date当前时间:获取日期时间方法在各浏览器中的差异
转自:http://www.feiesoft.com/00047/<script type="text/javascript"> // JS Date当前时间获取方法在 ...
随机推荐
- idea在maven打包时运行Test测试, 导致打包失败, 乱七八糟的错误
在maven打包时运行Test测试, 导致打包失败, 乱七八糟的错误 在maven projects中图标toggle'skip Tests' Mode //宏杰帮助 网上案例:https://blo ...
- ubuntu18.04 pip换源 永久修改
1. 创建pip.conf文件 cd ~/.pip 如果提示目录不存在的话,我们要自行创建一个,再进入目录 mkdir ~/.pip cd ~/.pip 在.pip目录下创建一个pip.conf文件 ...
- 自然语言处理之LDA主题模型
1.LDA概述 在机器学习领域,LDA是两个常用模型的简称:线性判别分析(Linear Discriminant Analysis)和 隐含狄利克雷分布(Latent Dirichlet Alloca ...
- 【vue】vue +element 搭建项目,vuex中的store使用
概述: 每一个 Vuex 应用的核心就是 store(仓库).“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state).Vuex 和单纯的全局对象有以下两点不同: Vuex 的 ...
- JAVA Exception
博客背景音乐设置 晴天博客(酷)
- iis 网页HTTP 错误 404.3 - Not Found解决方案
一. 1.依次打开控制面板→程序和功能→打开或关闭Windwos功能 2.在打开的Windows功能窗口中依次展开Internet信息服务→万维网服务→应用程序开发功能,将需要的功能选项前面的勾上,确 ...
- abp 基于api接口的页面内容提交
项目中,后端api接口需要接收来自页面提交的数据.注意下拉控件对应值,应该按以下方式赋值 @Html.DropDownListFor(m => m.IsFolder, new List<S ...
- mybatis百科-列映射类ResultMapping
目录 1 成员变量 2 构造函数 3 其他函数 3.1 setter 和 getter 函数 3.2 equals 和 hashCode 函数 3.3 toString 函数 4 内部类 Builde ...
- 2018年Java生态行业报告
0 Java版本使用占比 毫无疑问,Java8被广泛用于生产环境! Java 9和Java 10的使用率非常低,不到5%. 2017年,Java 7和更早的版本的数量大约是24.4%,现在是10. ...
- 微信小程序支付异常:requestPayment:fail no permission
今天在调试微信小程序支付时碰到了这个问题,支付参数都正常生成了,在调用 wx.requestPayment 进行支付时遇到了这个报错,查了一下发现是开发者工具中 AppID 写错了,用的 AppID ...