Developer Week 2024 期间,我们推出了 AI 人脸裁剪的内测版本。这项功能会自动裁剪检测到的人脸周围的 images,这是我们即将推出的 AI 图像处理功能套件中的第一个功能。
AI 人脸裁剪现已在Images for enterprise 中可用。为了将此功能推向普遍,我们将基于 CPU 的原型转移到了 Workers AI 中基于 GPU 的实施,从而能够解决许多技术挑战,包括可能阻碍大规模使用的内存泄漏。
照片由 Suad Kamardeen (@suadkamardeen) 拍摄于 Unsplash
我们在开发人脸裁剪时考虑了两个特定的用例:
社交媒体平台和 AI 聊天机器人。我们观察到很多流量,客户使用 Images 将未经编辑的人物图片转换成整齐、固定形状的较小档案图片。
电子商务平台、同样的产品照片可能会出现在图库页面的缩略图网格中,然后以更大的视图再次出现在单个产品页面上。以下示例说明了裁剪如何将重点从模特的衬衫转移到了他们的眼镜。
照片由 Media Modifier (@mediamodifier) 在 Unsplash 拍摄
在处理大量媒体内容时,准备 images 进行制作可能非常繁琐。有了 Images,您无需手动生成和存储同一张图片的多个版本。取而代之,我们会提供每个图像的副本,每个副本都根据您的规范进行了优化,而您继续仅存储原始图像。
Cloudflare 提供了一个参数库来操纵向最终用户提供图像的方式。例如,您可以通过将图像的宽度
和高度
尺寸设置为 100x100,将图像裁剪为正方形。
默认情况下,images 向原始图像的中心坐标裁剪的。Graduation
参数可以通过改变焦点来影响图像的裁剪方式。您可以指定用作图像焦点的坐标,或让 Cloudflare 自动确定新的焦点。
在裁剪主体偏心的图像时,gravity 参数很有用。照片拍摄:Andrew Small (@andsmall),发表于 Unsplash
Gravity=auto
选项使用一种显着性算法来挑选图像的最佳焦点。显着性检测可以识别图像中在视觉上最重要的部分;然后对该感兴趣区域应用裁剪操作。我们的算法使用颜色、明度和纹理等视觉线索来分析 images,但不考虑图像中的上下文。虽然这种设置对于含有植物和摩天大楼等无生命物体的 images 效果不错,但它不能可靠地考虑人脸等具有上下文意义的主体。
然而,人物图像占据了许多应用带宽使用量的大部分,例如一个 AI 聊天机器人平台,它使用 Images 每月服务超过 4500 万次独特的转换。这让我们有机会进一步改进开发人员优化人物 images 的方式。
可以使用 Gravity=face
选项执行 AI 人脸裁剪,该选项会自动检测哪些像素代表人脸,并使用这些信息来裁剪图像。您还可以影响图像向面部的裁剪程度;Zoom
参数控制图像中包含多少面部周围区域的阈值。
我们精心设计了模型流水线,将隐私和机密性放在首位。此功能不支持面部识别或识别。换而言之,当您使用 Cloudflare 优化时,我们永远不会知道两张不同的 images 描绘了同一个人,也不会识别给定图片中的特定人物。相反,使用 Images 进行 AI 人脸裁剪有意仅限于人脸检测,或识别代表人脸的像素。
我们的第一步是选择一个满足我们要求的开源模型。在幕后,我们的 AI 人脸裁剪使用 RetinaFace —— 一种卷积神经网络模型,用于图像中包含人脸的分类。
一个神经网络是一种机器学习过程,与人脑的工作方式大致相同。基本的神经网络包含三部分:输入层、一个或多个隐藏层,以及输出层。每一层中的节点形成一个互连的网络来传输和处理数据,其中每个输入节点都连接到下一层中的节点。
全连接层将数据从一个层传递到下一层。
数据通过输入层进入,在输入层进行分析,然后传递到第一个隐藏层。所有计算都在隐藏层中完成,其中的结果最终通过输出层交付。
卷积神经网络(CNN)反映了人类看待事物的方式。我们观察他人时,会从抽象的特征开始,如身体的轮廓,然后再处理具体的特征,如眼睛的颜色或唇部的形状。
同样,CNN 逐块处理图像,然后提供最终结果。早期的几层寻找抽象特征,如边缘、颜色和线条;随后的几层寻找随后的层就变得更加复杂,每一层都负责识别构成人脸的各种特征。最后一个全连接层组合所有已分类特征以产生整个图像的最终分类。换句话说,如果图像包含定义人脸的所有个体特征(例如,的东西,然后 CNN 得出结论,该图像包含人脸。
我们需要一个模型能够确定图像是否为人(图像分类),以及人在图像中的确切位置(检测)。在选择模型时,我们考虑的一些因素包括:
在 WIDERFACE 数据集上的性能。这是最先进的人脸检测基准数据集,其中包含 393,703 张加标签人脸的 32,203 张图像,这些人脸在比例、姿态和遮掩方面具有高度可变性。
速度(每秒帧数)。 我们的大多数图像优化请求发生在交付时(而不是图像上传到存储之前),因此我们优先为最终用户的交付性能提供服务。
模型大小。模型越小,运行效率越高。
质量。采用较小模型的性能提升往往会换来质量——关键是在速度和结果之间取得平衡。
我们的初始测试样本包含 500 张 images,这些 images 的因素各不相同,例如 images 中的人脸数量、人脸大小、光线、清晰度和角度。我们测试了各种模型,包括 BlazeFast、R-CNN(及其继任者 Fast R-CNN 和 Faster R-CNN)、RetinaFace 和 YOLO (You Only Look Once)。
BlazeFast 和 R-CNN 等两阶段检测器会建议图像中的潜在对象位置,然后识别这些感兴趣区域中的对象。RetinaFace 和 YOLO 等单阶段检测器可一次性预测对象的位置和类别。在我们的研究中,我们观察到两阶段检测器方法可提供更高的准确性,但执行速度太慢,对于真实流量并不实用。另一方面,单阶段检测器方法高效且性能良好,同时仍然高度准确。
最终,我们选择了 RetinaFace,其显示出 99.4% 的最高精度,并且性能比具有可比值的其他模型更快。我们发现,即使处理包含多张模糊人脸的 images,RetinaFace 也能产生很好的结果:
照片由 Anne Nygärd (@Polarmer Maid) 拍摄于 Unsplash
推理(使用训练模型做出决策的过程)可能对计算资源要求很高,特别是对于非常大的图像。为了保持效率,在向模型发送 images 时,我们将最大大小限制设置为 1024x1024 像素。
我们将这些维度内的 images 直接传递给模型进行分析。但如果宽度或高度尺寸超过 1024 像素,我们就会创建推理图像发送给模型;这是一个较小的副本,与原始图像保持相同的宽高比,并且两个维度上的像素点都不超过 1024。例如,125x2000 的图像将缩小到 64x1024。创建这个调整大小后的临时版本可减少模型需要分析的数据量,从而加快处理速度。
模型会绘制所有的边界框,即图像中定义检测到的人脸的区域。从那里开始,我们构建一个新的外部边界框,其中包含所有单独的方框,根据与图像上、左、下、右边缘最接近的方框计算其左上
和右下
点.
左上
点使用从最左边的盒开始的x
坐标,和从最顶部的盒开始的y
坐标。同样,右下角
使用从最右侧的框开始的x
坐标和从最底部的框开始的y
坐标。这些坐标可从相同的边界盒获取;如果有一个盒子最接近顶部和左侧边缘,我们就会使用它的左上角作为外部边界盒子的左上角
。
AI 人脸裁剪会识别代表人脸的区域,然后根据最顶部、最左侧、最右侧和最底部的边界框确定外部边界框和焦点。
定义外部边界框后,裁剪图像时将使用其中心坐标作为焦点。实验中,我们发现,与其他方法(例如围绕检测到的最大的人脸建立新的焦点)相比,这种方法处理有多张 images 时效果更好、更平衡。
裁剪图像区域是根据外部边界框的尺寸(“d”)和指定的缩放级别(“z”),通过公式(1 收费Zoom
参数接受 0 到 1 之间的浮点数,当 Zoom=1
时,我们将裁剪图像到边界框,而当 Zoom
逐渐接近 0
时,我们将包含更多边界框周围的区域。
考虑一个 2048x2048 的原始图像。首先,我们创建一张 1024x1024 的推理图像,以满足我们对人脸检测的大小限制。其次,我们使用模型的预测来定义外部边界框,本例中我们将使用 100x500。在 Zoom=0.5
时,我们的公式生成一个两倍于边界框的裁剪区域,新的宽度(“w”)和高度(“h”)尺寸为 200x1000:
我们还应用了一个最小
函数,选择输入尺寸和计算尺寸之间的较小数字,确保新的宽度和高度永远不会超过图像本身的尺寸。换而言之,如果您尝试缩小太多,我们将使用图像的完整宽度或高度,而不是定义超出图像边缘的裁剪区域。例如,当 Zoom=0.25
时,我们的公式得出 400x2000 的初始裁剪区域。在这里,由于计算高度(2000)大于输入高度(1024),我们使用输入高度将裁剪区域设置为 400x1024。
最后,我们需要将裁剪区域缩放回原始图像的大小。这仅适用于创建较小的推理图像时。
我们最初将原始 2048x2048 图像按原来的 2 倍缩小,变成了 1024x1024 的推理图像。这意味着我们需要将裁剪区域的尺寸(在我们最新的示例中为 400x1024)乘以 2,才能产生最终结果:800x2048 的裁剪图像。
在 Beta 版中,我们使用 TensorFlow Rust 重写了模型,使其与我们现有的基于 Rust 的堆栈兼容。所有推理计算(模型对人脸进行分类和定位)都是在我们网络的 CPU 上执行。
最初,这种方法效果良好,我们看到了接近实时的结果。
但是,当我们开始不断收到警报,提示我们的底层 Images 服务已接近其内存使用极限时,我们实施的基本限制变得明显了。此时的内存使用增加与最近的任何部署都不符,但一种预感使我们发现,人脸裁剪计算时间图谱也出现了增长,与内存使用量的增长相匹配。进一步追踪证实,AI 人脸裁剪是问题的根源。
当服务耗尽内存时,它会终止进程以释放内存并防止系统崩溃。由于基于 CPU 的实现与其他进程共享 RAM,这可能会导致其他图像优化操作出错。作为应对措施,我们将内存分配器从 glibcmalloc 切换到 jemalloc。这使我们可以在运行时使用更少的内存,因此在全球范围内节省了大约 20 TiB 的 RAM。我们还开始削减人脸裁剪请求的数量,以限制 CPU 使用。
此时,AI 人脸裁剪已经仅限于我们的内部使用和少数测试版客户。这些步骤只是暂时减少了我们的内存消耗。它们不足以处理全球流量,因此我们寻求更可扩展的设计,以便实现长期使用。
鉴于内存使用量警报已迫在眉睫,我们显然需要转用基于 GPU 的方法。
与 CPU 不同,基于 GPU 的实施可以避免与其他进程的争用,因为内存访问通常是专用的,且受到更严格的管理。我们与Workers AI团队合作,他们为内部团队创建了一个框架,旨在将有效负载集成到模型目录中,以实现 GPU 访问。
一些 Workers AI 模型有自己的独立容器;这并非对每个模型都可行,因为将流量路由到多个容器的成本可能很高。通过 Workers AI 使用 GPU 时,数据需要在网络上传输,这可能会带来延迟。这是模型大小特别相关的地方,因为模型较大,网络传输开销变得更加明显。
为解决这个问题,Workers AI 将较小的模型包装在一个容器中,并利用对延迟敏感的路由算法来识别为每个有效负载提供服务的最佳实例。这意味着,可以在没有流量时卸载模型。
调度程序用于优化同一容器中的模型与 GPU 交互的方式和时间。
RetinaFace 运行于 1 GB VRAM 的最小 GPU;它足够小,可以在运行时与类似大小的模型一起热插拔。如果调用 RetinaFace 模型,则 Python 代码将被加载到环境中并执行。
正如预期的那样,在我们将该功能转移到 Workers AI 后,内存使用量显着下降。现在,我们 Images 服务的每个实例消耗大约 150 MiB 内存。
使用这种新方法,内存泄漏对我们服务的整体可用性构成了较小的威胁。Workers AI 在容器中执行模型,因此可以根据需要终止或重新启动模型,而不会影响其他进程。由于人脸裁剪与我们的 Images 服务分开运行,重新启动它不会停止我们的其他图像优化操作。
作为测试版发布的一部分,我们更新了 Cloudflare 博客,对作者图像应用 AI 人脸裁剪。
作者可以提交自己的 images,这些 images 在主博客和个人博客文章中都显示为循环头像。默认情况下,CSS 使 images 在其容器中居中,使偏心的头部位置更加明显。当两张个人资料图片包含不同数量的反向空间时,这也可能导致视觉不平衡,即作者的面部以不同的比例出现:
AI 面部裁剪使由多个作者撰写的帖子看起来更加均衡。
在上面的示例中,Austin 的原始图像在面部周围被紧密裁剪。另一方面,Taylor 的原始图像包括他的身体和较大的背景边缘。因此,Austin 的脸比 Taylor 的更大且更靠近中心。在我们将 AI 面部裁剪用于博客上的个人资料照片后,他们的面部大小显得更加相似,为其共同撰写的帖子创造了更多的平衡和凝聚力。
许多开发人员已经使用 Images 来构建可扩展的媒体管道。我们的目标是通过自动化繁琐的手动任务来加速图像工作流程。
对 Images 团队而言,这仅仅是个开始。我们计划发布新的 AI 能力,包括背景去除和生成式提升等功能。您可以通过在 Images 仪表板中启用转换 来免费试用 AI 面部裁剪。