在短视频和直播带货霸占了流量铁王座的当下,产品之间的竞争十分激烈,在抖音短视频问世以来,随其后效仿的产品也不计其数。产品要想脱引而出,仅靠画质和流畅度是很难取胜的,不仅要节省流量开支同时还要保证画质的超高清,与其同时还要有丰富的、标新立异的玩法来吸引用户。
随着 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];
友情提示:本例子不适合去实现,适合去理解理论知识。如果非要去处理,可以使用 chrome 浏览器,并将页面放大至 500%。
假设,我们要绘制一张图片,图片宽为 3 个像素(px)、高为 3 个像素(px),第一个像素是红色,第二个像素的绿色,第三个像素为蓝色,第五个像素是黑色,那么其他像素都为白色。
获取图片的 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 来看看,看看与我们上面猜想的是否一致
通过图片我们可以看出:
不知道小伙伴们看到这里,是否可以获取指定位置的像素点?
我们不仅可以获取图片的数据(ImageData),同时我们还可以通过 ImageData 构造函数,创建图片的数据,最终将图片画出来。我们同样以上面图片为例子,我们如果画出来整张图片呢?
我们已经知道 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
我们可以通过 new 一个 ImageData 来生成一个 ImageData 的实例
const imgData = new ImageData(arr, 3, 3);
友情提示:本例子不适合去实现,适合去理解理论知识。如果非要去处理,可以使用 chrome 浏览器,并将页面放大至 500%。
通过 CanvasRenderingContext2D.putImageData 方法将图片画出来,最终我们可以将图片下载到我们本地。
const canvas = document.getElementById('myCanvas'); const ctx = canvas.getContext('2d'); ctx.putImageData(imgData, 0, 0);
上面我们说过要想对图片进行处理,就必须在不改变像素点位置的情况下,对部分像素点的色彩值进行替换或者灰度调节来实现,在已经搞清楚道图片数据格式的情况下,在对图片处理的就显得轻松许多了。
web 不允许直接修改图片,通常我们使用 canvs 对图片进行处理,最终将处理完的图片保存到本地,因此接下来的 Demo 都是在 canvas 中进行。
Demo 主要由浅入深一步一步的引导大家对图片处理的理解,大家可以按照一下顺序体验 Demo,来消化理解上面的这些知识点:
黑白算法
马赛克算法
今后我会带来更多有趣的 Demo,包括 AI 换脸、高斯模糊、消除阴影、HDR等等。
不知道大家对图像处理技术有没有新的认识呢?下面不妨思考一下下面这些产品运用了什么图像处理技术实现了哪些功能的呢?
下一期web技术分析:我们一起分析主流应用所运用的图像处理技术和原理
|