web技术分析| 一篇前端图像处理秘籍

在短视频和直播带货霸占了流量铁王座的当下,产品之间的竞争十分激烈,在抖音短视频问世以来,随其后效仿的产品也不计其数。产品要想脱引而出,仅靠画质和流畅度是很难取胜的,不仅要节省流量开支同时还要保证画质的超高清,与其同时还要有丰富的、标新立异的玩法来吸引用户。

随着 AI 技术的成熟,场景也变得丰富、多样了起来,比如:修复老电影换脸特效2K 升 4k体育赛事特效 app

但是无论产品需求怎么变,最终还是得在图像处理上下功夫,那么今天我们就从简单的图片处理入门,由浅入深的去探索视频处理,接触更多的音视频算法,感兴趣的还可以去研究一下这些算法背后的原理,还是很有意思的。

就以体育赛事的足球比赛为例,摄像头在自动跟踪足球(机位快速移动)的过程中保障画质不被撕裂,同时在弱网情绪下保证高清视频整体画质,其中技术难点有很多,除了要使用 AI 计算出关键位置(足球和预判足球飞向的球员)以及弱网情况音视频数据传输(丢包、补发),但是事实上这些都可以被解决(优化)的。例如,弱网情况下,我们只要保证足球及周围几个球员的画质,其他像素点全部虚化处理,很大程度的缓解带宽压力的。

原理分析

图片是由 N 多个有限个像素组成(点阵图非矢量图),每一个像素都有它对应的位置色彩值,像素点的多少决定了图片分辨率的大小,局部的像素点越多,局部的画质越清晰。

图片处理实际就是在不改变图片像素点位置的情况下,对指定像素点的色彩强度以及透明度进行调节

那么图片处理步骤就可以拆解为:

  • 获取图片数据:我们需要指定图片所有像素点的颜色值以及其对应的透明度。
  • 改变图片数据:我们可以改变图片所有(或者指定范围的)像素点的颜色值以及其对应的透明度。

例如,美图秀秀的去痘痘,就是将图片中痘痘的所在像素点位置的颜色强度进行调整,将范围的像素改为贴合肤色的颜色强度即可,如有疑问,请带着疑问继续阅读,疑惑自解。

图片数据与处理

下面我们以 Web 前端为例子,移动端只需要搞懂原理即可,同样适用,只需要注意平台差异即可。

Web 前端通常使用 ImageData 构造函数来创建一个 ImageData 对象。

语法:

// 创建指定数据的 ImageData 对象
const imageData = new ImageData(array, width, height);
// 创建空白数据的 ImageData 对象
const imageData2 = new ImageData(width, height);

ImageData 对象中包含了图片的宽(ImageData.width)、高(ImageData.height)和图片的数据(ImageData.data)等信息:

  • width:

    图片的宽

  • height:

    图片的高

  • data:

    用来存储图片每个像素点的位置以及像素点色彩值的队列,是一个类型为 Uint8ClampedArray 的一维数组。数组元素为 0 ~ 255 区间的整型,包含以 RGBA 顺序的数据的整型数组,数组长度为 4 * ImageData.width * ImageData.height

ImageData 对象中的宽、高可能比较好理解, data 属性我们可以通过下面几个问答来了解一下:

  • 为什么 ImageData.data 的数组元素是 0 ~ 255 区间的整型?

    首先,红、绿、蓝作为三基色,可以合成出各种颜色,根据三基色的色彩值的强度(比例)混合而成,也就意味着三基色的强度等级划分的越详细,混合出来的颜色就更多。
    在为了保证色彩值的丰富的前提下,最终 RGB 被划分为 256 个等级,可以混合出 1600 万种颜色,同时也符合显像芯片以二进制进行存储的要求,刚好是 2 的 8 次方。

    同理,透明度也必须是 0 ~ 255 区间的整型,然而前端我们经常用 0 ~ 1 来表示透明度,也就是说我们要将其转化成 alpha 时,需要除以 255(这个小细节要注意哦)。

  • 为什么 ImageData.data 的数组是一维数组,长度确实分辨率的 4 倍呢?

    因为 ImageData.data 的数组是按照 R,G,B,A 的顺序来记录图片各个像素点的红、绿、蓝三种颜色的色彩强度以及透明度。也就说每 4 个数组元素标识着一个像素点的色彩和透明度。不仅结构层次简单,数据体积和计算速度都得到来很大的提升。

    数组元素下标 4n 的就表示第 n 个像素点红色的色彩强度,4n ~ 4n + 3 这 4 个元素分辨表示第 n 个像素点的红、绿、蓝的色彩强度以及透明度。

    Red = imageData.data[4n]; 
    Green = imageData.data[4n + 1]; 
    Blue = imageData.data[4n + 2];
    Alpha = imageData.data[4n + 3];
    

一个理解 ImageData 的例子

友情提示:本例子不适合去实现,适合去理解理论知识。如果非要去处理,可以使用 chrome 浏览器,并将页面放大至 500%。

假设,我们要绘制一张图片,图片宽为 3 个像素(px)、高为 3 个像素(px),第一个像素是红色,第二个像素的绿色,第三个像素为蓝色,第五个像素是黑色,那么其他像素都为白色。

web技术分析| 一篇前端图像处理秘籍

理解 ImageData.data 的长度

获取图片的 ImageData:

// canvas画布的高斯模糊效果
const canvas = document.getElementById(‘myCanvas‘);
const ctx = canvas.getContext(‘2d‘);
const img = new Image();

// 这里直接修改图片的路径
img.src = "./test2.png";
img.onload = function () {
	// 设置canvas的宽高
	canvas.width = img.width;
	canvas.height = img.height;
	// 将图像绘制到canvas上面
	ctx.drawImage(img, 0, 0, img.width, img.height);
	// 从画布获取整个图片第数据
	const originImageData = ctx.getImageData(0, 0, img.width, img.height);
	consolt.log(‘originImageData‘, originImageData);
};

那么这张图片的 ImageData 肯定是这样的:

// originImageData
ImageData: {
	width: 3,
	height: 3,
	data: Uint8ClampedArray(36),
	...
}

分辨率 = 图片的宽 * 图片的高 = 9
ImageData.data 的长度 = 分辨率 * 4 = 36

理解 ImageData.data 的数据

web技术分析| 一篇前端图像处理秘籍

就从图片上来看,我们可以得知这些分辨率的色彩值为:

  • 第一个像素点为红色
    • RGBA 值分别为:Red = 255,Green = 0, Blue = 0,Alpha = 255
  • 第二个像素点为绿色
    • RGBA 值分别为:Red = 0,Green = 255,Blue = 0,Alpha = 255
  • 第三个像素点为蓝色
    • RGBA 值分别为:Red = 0,Green = 0,Blue = 255, Alpha = 255
  • 第五个像素点为黑色
    • RGBA 值分别为:Red = 0,Green = 0,Blue = 0,Alpha = 255
  • 其他均为白色
    • RGBA 值分别为:Red = 255,Green = 255,Blue = 255,Alpha = 255

那么我们将获取到的 ImageData.data 来看看,看看与我们上面猜想的是否一致

web技术分析| 一篇前端图像处理秘籍

通过图片我们可以看出:

  • 每 4 个元素描述一个像素点的色彩值,分别为 红色饱和度,绿色饱和度,蓝色饱和度以及透明饱和度,区间范围在 0 ~ 255 之间的整型。
  • 数组下标为 4 * n 的数值,代表第 n 个像素的起始位置
    • 4 * n 为红色饱和度
    • 4 * n + 1 代表绿色饱和度
    • 4 * n + 2 代表蓝色饱和度
    • 4 * n + 3 代表透明饱和度

不知道小伙伴们看到这里,是否可以获取指定位置的像素点?

用 ImageData 把图片画出来

我们不仅可以获取图片的数据(ImageData),同时我们还可以通过 ImageData 构造函数,创建图片的数据,最终将图片画出来。我们同样以上面图片为例子,我们如果画出来整张图片呢?

构造 ImageData.data

我们已经知道 ImageData.data 是一个类型为 Uint8ClampedArray 的一维数组,而且我们也知道 Image.data 的色彩饱和度,那么第一步我们就是要生成 ImageData.data

const arr = new Uint8ClampedArray(36);
// 第1个像素点,红色,rgba(255, 0, 0, 1)
arr[0] = 255
arr[1] = 0
arr[2] = 0
arr[3] = 255

// 第2个像素点,绿色,rgba(0, 255, 0, 1)
arr[4] = 0
arr[5] = 255
arr[6] = 0
arr[7] = 255

// 第3个像素点,蓝色,rgba(0, 0, 255, 1)
arr[8] = 0
arr[9] = 0
arr[10] = 255
arr[11] = 255

// 第4个像素点,白色,rgba(255, 255, 255, 1)
arr[12] = 255
arr[13] = 255
arr[14] = 255
arr[15] = 255

// 第5个像素点,黑色,rgba(0, 0, 0, 1)
arr[16] = 0
arr[17] = 0
arr[18] = 0
arr[19] = 255

// 第6个像素点,白色,(255, 255, 255, 1)
arr[20] = 255
arr[21] = 255
arr[22] = 255
arr[23] = 255

// 第7个像素点,白色,(255, 255, 255, 1)
arr[24] = 255
arr[25] = 255
arr[26] = 255
arr[27] = 255

// 第8个像素点,白色,(255, 255, 255, 1)
arr[28] = 255
arr[29] = 255
arr[30] = 255
arr[31] = 255

// 第9个像素点,白色,(255, 255, 255, 1)
arr[32] = 255
arr[33] = 255
arr[34] = 255
arr[35] = 255

构造 ImageData

我们可以通过 new 一个 ImageData 来生成一个 ImageData 的实例

const imgData = new ImageData(arr, 3, 3);
canvas 画出来

友情提示:本例子不适合去实现,适合去理解理论知识。如果非要去处理,可以使用 chrome 浏览器,并将页面放大至 500%。

通过 CanvasRenderingContext2D.putImageData 方法将图片画出来,最终我们可以将图片下载到我们本地。

const canvas = document.getElementById(‘myCanvas‘);
const ctx = canvas.getContext(‘2d‘);
ctx.putImageData(imgData, 0, 0);

Demo 体验

上面我们说过要想对图片进行处理,就必须在不改变像素点位置的情况下,对部分像素点的色彩值进行替换或者灰度调节来实现,在已经搞清楚道图片数据格式的情况下,在对图片处理的就显得轻松许多了。

web 不允许直接修改图片,通常我们使用 canvs 对图片进行处理,最终将处理完的图片保存到本地,因此接下来的 Demo 都是在 canvas 中进行。

Demo 主要由浅入深一步一步的引导大家对图片处理的理解,大家可以按照一下顺序体验 Demo,来消化理解上面的这些知识点:

  • 获取图片任意像素点的颜色值
  • 将图片(黑白)致灰
  • 给图片添加马赛克

获取图片指定像素点的 色彩值

代码地址

web技术分析| 一篇前端图像处理秘籍

黑白照片

代码地址

黑白算法

web技术分析| 一篇前端图像处理秘籍

马赛克

代码地址

马赛克算法

web技术分析| 一篇前端图像处理秘籍

更多(敬请期待)

今后我会带来更多有趣的 Demo,包括 AI 换脸、高斯模糊、消除阴影、HDR等等。

写在最后

不知道大家对图像处理技术有没有新的认识呢?下面不妨思考一下下面这些产品运用了什么图像处理技术实现了哪些功能的呢?

  • 美图秀秀等修图软件是如何工作的?
  • 剪映等视频处理软件是如何实现视频剪辑、添加特效
  • 对音视频行业更深一步的认识

下一期web技术分析:我们一起分析主流应用所运用的图像处理技术和原理

  • 视频处理
  • Demo 体验
  • bilibili 视频超分算法
  • tiktok、映客、花椒特效处理
  • avatar “蚂蚁牙黑” 实现原理

web技术分析| 一篇前端图像处理秘籍

上一篇:android 微信听筒无声


下一篇:transfer.sh -- 使用 curl 从命令行上传文件并返回下载地址的文件分享服务(可自架服务端)