转《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当前时间获取方法在 ...
随机推荐
- Gps定位和wifi定位和基站定位的比较
现在手机定位的方式是:Gps定位,wifi定位,基站定位 Gps定位的前提,手机开启Gps定位模块,在室外,定位的精度一般是几米的范围 wifi定位的前提,手机要开启wifi,连不连上wifi热点都可 ...
- 解决vaio s13笔记本 ubuntu重启卡屏问题
终端 sudo gedit /etc/default/grub 找到GRUB_CMDLINE_LINUX_DEFAULT="quiet splash",添加内核启动参数reboot ...
- 转://Oracle Golden Gate 概念和原理
引言:Oracle Golden Gate是Oracle旗下一款支持异构平台之间高级复制技术,是Oracle力推一种HA高可用产品,简称“OGG”,可以实现Active-Active 双业务中心架构 ...
- P1515 旅行(简单搜索)
非常简单的搜索. 思路:先排序,然后,搜索枚举的时候满足A < 两个旅店 < B,然后,搜索就行了. #include<iostream> #include<algori ...
- 【转】从零开始玩转logback
概述 LogBack是一个日志框架,它与Log4j可以说是同出一源,都出自Ceki Gülcü之手.(log4j的原型是早前由Ceki Gülcü贡献给Apache基金会的)下载地址:http://l ...
- CF980E The Number Games
CF980E The Number Games 给定一棵大小为 \(n\) 的树,第 \(i\) 个点的点权为 \(2^i\) ,删掉 \(k\) 个点及其连边,使得剩下的点组成一个连通块,且权值和最 ...
- Recurrent Neural Network[Content]
下面的RNN,LSTM,GRU模型图来自这里 简单的综述 1. RNN 图1.1 标准RNN模型的结构 2. BiRNN 3. LSTM 图3.1 LSTM模型的结构 4. Clockwork RNN ...
- [CF1137E]Train Car Selection[维护凸壳]
题意 题目链接 分析 首先,如果加到了车头所有之前的车厢都不可能成为答案. 如果加到了车尾,容易发现对于 \(x_2<x_3\) 而言在某个时刻会出现 2 又比 3 优的情况. 具体来讲,如果存 ...
- JVM总括三-字节码、字节码指令、JIT编译执行
JVM总括三-字节码.字节码指令.JIT编译执行 目录:JVM总括:目录 java文件编译后的class文件,java跨平台的中间层,JVM通过对字节码的解释执行(执行模式,还有JIT编译执行,下面讲 ...
- .NET Core Community 第三个千星项目诞生:爬虫 DotnetSpider
本文所有打赏将全数捐赠于 NCC(NCC 的资金目前由 倾竹大人 负责管理),请注明捐赠于 NCC.捐赠情况将由倾竹大人在此处公示. DotnetSpider 至力于打造一个轻量化.高效率.易开发.可 ...