转《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当前时间获取方法在 ...
随机推荐
- 【angularjs】使用angular搭建PC端项目,开关按钮
方法一(使用指令) 1.指令(angular-ui-switch.js) angular.module('uiSwitch', []) app.directive('switch', function ...
- 17 python 初学(迭代器)
生成器都是迭代器,迭代器不一定是生成器 迭代器满足两个条件: 1. 有iter方法 2. 有next方法 # list, tuple, dict, string: iterable(他们都有iter方 ...
- hyperledge工具-configtxlator
参考:http://www.blockchainbrother.com/article/1337 1.作用: 因为无论配置交易文件 .tx和初始区块文件 .block都是二进制格式,用户无法直接编辑. ...
- 无线智联-程序篇:让python与matlab牵手
python和matlab本来是青梅竹马的好基友,奈何相爱相杀,经常放在一起做对比,就好比经常听到的,“你看看隔壁xx家的孩子”,其实每个孩子都有每个孩子的优点,如果能发挥每个孩子的优点,岂不是更好. ...
- python魔法方法、构造函数、序列与映射、迭代器、生成器
在Python中,所有以__双下划线包起来的方法,都统称为"魔术方法".比如我们接触最多的__init__,魔法方法也就是具有特殊功能的方法. 构造函数 构造函数不同于普通方法,将 ...
- git pull 解决 refusing to merge unrelated histories 错误
解决办法: 1.cmd进入项目的根目录. 2.执行下面的命令:git pull origin master --allow-unrelated-histories.可以提交成功. 3.再次push.
- Jsoup+FastJson制作新闻数据接口-Demo
经常用到 编写出来直接拿来用 这个适合在服务端结合servlet来做接口:需要下载jsoup+fastjson两个包 Jsoup使用手册:http://www.open-open.com/jsoup/ ...
- C# 中的相对路径在 Picturebox 中的应用
前言 最近的项目需要将 picturebox 显示网络图片的小功能完成,不想用绝对路径取本地文件里的图片,因为将来要发布给用户的话让用户自己配置会很麻烦的,索性将路径设置成相对路径,将图片放在自己的项 ...
- [WPF]何如在Win7使用Aero2主题
1. 问题 假设我在Windows10的环境新建一个4.6的WPF项目,添加一个ComboBox,并用Blend在这个ComboBox上右键"编辑模板"->"编辑副 ...
- vscode 缩进改为2空格
因为vscode默认启用了"editor.detectIndentation": true”根据文件类型自动设置tabsize的选项. 在配置中加上 "editor.de ...