使用 GenAI 生成自定义 Amazon IVS 直播背景

关于如何使用生成式人工智能 (AI) 背景图像增强 Amazon Interactive Video Service (IVS) 直播效果的分步指南。
发布时间:2024 年 1 月 12 日
生成式人工智能
Amazon IVS
教程
亚马逊云科技
Olawale Olaleye
亚马逊云科技使用经验
中级 - 200
完成所需时间
60 分钟
所需费用

支持亚马逊云科技免费套餐

前提条件

注册 / 登录 亚马逊云科技账户

示例代码
上次更新时间
2024 年 1 月 12 日

为了更快速地展示背景替换效果,上面的动画缩短了图像生成所需的时间。

Amazon Interactive Video Service (Amazon IVS) 是一项托管式视频直播服务,可帮助开发人员创建实时流媒体应用程序。您可以借助 Amazon IVS 为创作者赋能,让他们利用直播活动、直播购物和用户生成内容 (UGC) 等方式吸引观众。

直播内容创作者越来越希望他们所选择的用户生成内容 (UGC) 平台能够提供有趣、动态的背景选项。利用生成式人工智能 (Generative AI) 和 Amazon IVS Broadcast SDK 的强大功能,开发人员可以帮助创作者打造此类体验,使他们的应用程序脱颖而出,进而迎来重大转折。

在本文中,我们将引导您逐步了解如何创建一个 Web 应用程序来替换网络摄像头的画面背景、如何将其与 Amazon Bedrock 中的 Stable Diffusion XL 进行集成以生成含有提示语的背景图像,以及如何通过 Amazon IVS 使用新 GenAI 背景图像提供实时直播体验。Stable Diffusion XL 是一种基于人工智能的模型,可根据文本描述信息生成图像。

您可以在 GitHub 上获取本教程所需的代码。还可以试用此 Web 应用程序的现场演示版

学习内容

  • 如何使用 MediaPipe Image Segmenter 将直播背景替换为自定义图像
  • 如何通过 Amazon IVS 使用自定义背景进行直播
  • 如何通过 Amazon Bedrock 使用生成式人工智能所生成内容替换摄像头画面背景
  • 如何通过 Amazon Bedrock 创建 Amazon Lambda 函数调用 Stable Diffusion 模型

解决方案概述

本教程由下列 7 个部分组成:

  • 第 1 部分 - 纳入依赖项并设置 MediaPipe Image Segmenter
  • 第 2 部分 - 设置用于直播的 Amazon IVS Web Broadcast SDK
  • 第 3 部分 - 设置对 Amazon Bedrock 中 Stable Diffusion 模型的访问权限
  • 第 4 部分 - 创建 Lambda 函数和 API Gateway 以向 Stable Diffusion 模型发送提示语
  • 第 5 部分 - 通过 GenAI 提示您使用新背景图像
  • 第 6 部分 - 使用新图像替换背景
  • 第 7 部分 - 测试直播效果

下图显示了我们将如何使用自定义图像替换摄像头画面背景。从高层次来看,我们会将自定义背景图像和原始摄像头画面写入画布元素,进而从中获取底层像素。同时,我们还会将视频画面传递给 MediaPipe Image Segmenter(Google 提供的一款工具,稍后将详细介绍),此工具将返回一个分割遮罩,用于指示哪些像素更有可能出现在前景或背景中。利用这些数据,我们会将自定义背景图像和摄像头画面中的相应像素复制到画布上。然后,我们从画布中捕获实时视频,并将其传递给 Amazon IVS Web Broadcast SDK 以进行直播。

图中显示了如何使用 Amazon IVS 替换背景的全流程

第 1 部分 - 纳入依赖项并设置 MediaPipe Image Segmenter

若要替换背景,第一步是确定图像中哪些部分是背景,以防止无意中替换掉前景中的内容。这时就需要使用 MediaPipe Image Segmenter。它是 MediaPipe 框架中的一个工具,可帮助开发人员实时执行图像分割。您将使用该工具分割图像的前景和背景。

若要使用 MediaPipe Image Segmenter,我们需要先安装 @mediapipe/tasks-vision。此外还将安装 Webpack,以便稍后捆绑 JavaScript。

npm i @mediapipe/tasks-vision webpack webpack-cli

接下来,在 index.html 文件中创建一些初始 HTML。为了进行直播,我们还会将用于实时传输媒体流的 IVS Web Broadcast SDK 包括进来。将 <SDK Version> 替换为最新版本号

注意:为方便阅读,本文所有 HTML 代码片段中的无关 HTML 属性均已删除。请参见 GitHub 存储库了解完整解决方案。

<!DOCTYPE html>
<html lang="en">
 <head>
 <meta charset="UTF-8" />
 <meta http-equiv="X-UA-Compatible" content="IE=edge" />
 <meta name="viewport" content="width=device-width, initial-scale=1.0" />

 <!-- Import the SDK -->
 <script src="https://web-broadcast.live-video.net/<SDK version>/amazon-ivs-web-broadcast.js"></script>
 </head>

 <body></body>
</html>

现在,将一些重要 HTML 元素添加到我们快速增大的 HTML 文件中。添加 <video> 元素,其中将包含实时摄像头画面,并将用作 MediaPipe Image Segmenter 的输入。同时,创建一个 <canvas> 元素,用于呈现即将播出的画面的预览。您还需要创建第二个 <canvas> 元素,用于呈现 Stable Diffusion 提供的自定义图像,该图像将用作我们的背景。因为含有自定义图像的第二个画布仅用作源,以便通过编程方式将像素从该画布复制到最终画布,所以该画布将被隐藏。

我们还将添加一些按钮来加入和退出 Amazon IVS 中的 Stage。Stage 是一个虚拟空间,参与者可以在其中实时交换视频。若要加入 Stage,您还需要参与者令牌。因此,我们添加了一个输入字段,用于指定该令牌。通常情况下,您的应用程序会通过 Amazon SDK 以编程方式为用户生成此令牌,但在本应用程序中我们进行了简化。参与者令牌既可以用于向 Amazon IVS 标识想要加入的特定 Stage,也可以用作一种身份验证机制。可以使用亚马逊云科技控制台Amazon CLI 或 Amazon SDK 生成该令牌。

最后,我们将添加一个按钮,点击此按钮可向 Stable Diffusion XL 发送提示语以生成图像。点击此按钮会弹出一个模态窗口,可在其中输入提示语,用于描述我们所需的背景图像的外观。

<div>
 <div>
 <label>Participant Token</label>
 <input type="text" />
 </div>
 <div>
 <button>Join Stage</button>
 </div>
 <div>
 <button>Leave Stage</button>
 </div>
</div>

<!-- Local Participant -->
<div>
 <video autoplay></video>
 <div></div>
 <div>
 <button>Mute Mic</button>
 <button>Mute Camera</button>
 </div>
</div>

<div>
 <canvas></canvas>
</div>
<div>
 <div>
 <button>Use GenAI to change my background</button>
 </div>
</div>

<canvas></canvas>

<hr />

<!-- Remote Participants -->
<div>
 <div i></div>
</div>

<!-- Bring up a modal to input a prompt for the background -->
<section>
 <div>
 <h3>Describe your background</h3>
 </div>

 <input />
 <button>Submit</button>
</section>

<div></div>

在结尾的 </body> 标签前添加一个脚本标签,以加载一个已绑定的 JavaScript 文件,该文件将包含用于执行背景替换并将其发布到 Stage 的代码:

<script src="./dist/bundle.js"></script>
 </body>
</html>

然后,创建一个名为 app.js 的 JavaScript 文件,以获取我们在 HTML 页面中创建的画布和视频元素的元素对象。此外,将获取背景更改控件和模型的元素,以备后续使用。若要稍后使用 Amazon IVS 设置直播,请从 IVSBroadcastClient 导入 Stage 和 LocalStageStream。此外,还需导入 ImageSegmenter 和 FilesetResolver 模块。ImageSegmenter 模块将用于执行分割任务。

const {
 Stage,
 LocalStageStream,
 SubscribeType,
 StageEvents,
 ConnectionState,
 StreamType,
} = IVSBroadcastClient;

const canvasElement = document.getElementById("canvas");
const background = document.getElementById("background");
const backgroundPromptInput = document.getElementById(
 "genai-background-prompt"
);
const backgroundChangeBtn = document.getElementById(
 "background-prompt-submit-btn"
);

const canvasCtx = canvasElement.getContext("2d");
const backgroundCtx = background.getContext("2d");
const video = document.getElementById("webcam");

// For GenAI prompt to change background
const modal = document.getElementById("modal");
const overlay = document.getElementById("overlay");
const openModalBtn = document.getElementById("button-open");

import { ImageSegmenter, FilesetResolver } from "@mediapipe/tasks-vision";

接下来,使用 async 函数创建一个 ImageSegmenter 实例。ImageSegmenter 将分割图像并以遮罩形式返回结果。创建 ImageSegmenter 实例时,我们将使用自拍分割模型。该模型便于确定图像中的哪些像素位于前景中,哪些像素位于背景中。

现在我们逐行分析 createImageSegmenter() 函数。该函数使用 MediaPipe 创建图像分割模型。首先,它使用 FilesetResolver 从 MediaPipe NPM 软件包中加载用于执行视觉任务的 WebAssembly (WASM) 模块。借助 WASM 模块,我们可以直接在浏览器中运行诸如图像分割推理等计算密集型任务,而无需安装其他软件包。它将调用 ImageSegmenter.createFromOptions 来初始化新的分割器模型。

将传递给 ImageSegmenter.createFromOptions 的选项包括:

  • baseOptions:指定模型资产路径(托管在 Google Storage 中的 TensorFlow Lite 模型),并设置代理以使用 GPU。
  • runningMode:设置将在视频帧上运行的模型。
  • outputCategoryMask:指示模型输出一种类别遮罩,而不仅仅是边界框。类别遮罩表示图像中某个给定像素更有可能位于前景中还是背景中。

加载完成后,该函数将返回一个通过初始化的 ImageSegmenter 对象解析的 Promise。这样,我们就可以异步初始化模型,而不会阻碍其余 JavaScript 代码的执行。

const createImageSegmenter = async () => {
 const audio = await FilesetResolver.forVisionTasks(
 "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.2/wasm"
 );

 imageSegmenter = await ImageSegmenter.createFromOptions(audio, {
 baseOptions: {
 modelAssetPath:
 "https://storage.googleapis.com/mediapipe-models/image_segmenter/selfie_segmenter/float16/latest/selfie_segmenter.tflite",
 delegate: "GPU",
 },
 runningMode: "VIDEO",
 outputCategoryMask: true,
 });
};

第 2 部分 - 设置用于直播的 Amazon IVS Web Broadcast SDK

在本部分中,我们将介绍如何使用 Amazon IVS Web Broadcast SDK 通过画布实时直播摄像头画面。在开始直播之前,我们需要理解三个核心概念,这些概念是实现实时直播的关键。

  • Stage:供参与者在其中交换音频或视频的虚拟空间。Stage 类是主机应用程序与 SDK 之间的主要交互点。
  • StageStrategy:供主机应用程序向 SDK 传递所需 Stage 状态的接口。
  • 事件:您可以使用 Stage 实例来传递状态变化,例如有人退出或加入 Stage 等事件。

我们首先要获取用户的实时摄像头画面。稍后,我们将利用此视频流中的像素数据,具体是将其写入画布元素,并结合自定义图像来创建虚拟背景。创建一个名为 init() 的函数,以从用户摄像头获取 MediaStream。我们稍后将调用该函数,并从 Stable Diffusion XL 传入图像 URL。默认情况下,我们使用一张海滩图像。

const init = async (
 srcImageUrl = "https://d1l5n2avb89axj.cloudfront.net/beach.jpg"
) => {
 localCamera = await navigator.mediaDevices.getUserMedia({
 video: true,
 audio: false,
 });
};

接下来,为了让观众看到我们发布的视频,需要从第一个画布元素中捕获 MediaStream,并将其分配给 segmentationStream。现在,MediaStream 将只包含我们的摄像头画面。稍后,我们将添加逻辑以便用自定义图像替换背景。

准备好想要发布给观众的 MediaStream 后,我们就需要加入一个 Stage。加入 Stage 后,我们就可以向 Stage 中的观众或其他参与者直播视频。如果我们不想再进行直播,可以退出 Stage。现在,我们添加事件侦听器,用于在最终用户点击加入或退出 Stage 按钮时侦听点击事件,并实现相应的逻辑。

const segmentationStream = canvasElement.captureStream();

joinButton.addEventListener("click", () => {
 joinStage(segmentationStream);
});

leaveButton.addEventListener("click", () => {
 leaveStage();
});

接下来,将来自本地摄像头的 MediaStream 分配给我们的视频元素。此外,我们还将在每次加载摄像头帧时调用一个自定义回调函数(将其命名为 renderVideoToCanvas)。在下文中,我们将实现此函数并进行详细说明。

video.srcObject = localCamera;
video.addEventListener("loadeddata", renderVideoToCanvas);

现在,完整的 init 函数如下所示。

const init = async () => {
 localCamera = await navigator.mediaDevices.getUserMedia({
 video: true,
 audio: false,
 });
 const segmentationStream = canvasElement.captureStream();

 joinButton.addEventListener("click", () => {
 joinStage(segmentationStream);
 });

 leaveButton.addEventListener("click", () => {
 leaveStage();
 });

 video.srcObject = localCamera;
 video.addEventListener("loadeddata", renderVideoToCanvas);
};

接下来,添加 joinStage 函数的逻辑。在此函数中,我们将从用户的麦克风中获取 MediaStream,以便将其发布到 Stage。发布是指将音频和/或视频发送到 Stage,以便其他参与者可以看到已加入的参与者或听到其声音。我们还需要通过定义 shouldSubscribeToParticipant、shouldPublishParticipant 和 stageStreamsToPublish 函数来实现 StageStrategy 接口。

首先,我们需要实现 stageStreamsToPublish 函数。此函数用于确定哪些音频和视频流要发布。为此,它会返回一个 LocalStageStream 实例数组。使用来自麦克风和画布且分配给 segmentationStream 的 MediaStream 实例,我们可以创建 LocalStageStream 实例。然后,我们需要在 stageStreamsToPublish 函数中以数组形式返回刚刚创建的 LocalStageStream 实例。这样,观众就能听到我们的音频,看到我们的视频。

接下来,只需返回 true,即可实现 shouldPublishParticipant 函数。此函数指示您作为本地参与者是否应该发布媒体流。

接下来,将实现 shouldSubscribeToParticipant 函数。此函数指示当远程参与者加入 Stage 时我们的应用程序是仅订阅其音频、同时订阅音频和视频,还是不订阅任何内容。我们需要音频和视频,因此返回 SubscribeType.AUDIO_VIDEO。

若要完成 joinStage 函数,需要新建一个 Stage 对象,以便将刚刚设置的参与者令牌和策略对象作为参数传入其中。参与者令牌用于向 Stage 进行身份验证,以及识别我们要加入的 Stage。通过在控制台上创建一个 Stage,然后使用控制台或 Amazon SDK 在该 Stage 中创建一个参与者令牌,您即可获得参与者令牌。稍后,我们将对 Stage 对象调用 join 方法来加入 Stage。

const joinStage = async (segmentationStream) => {
 if (connected || joining) {
 return;
 }
 joining = true;

 const token = document.getElementById("token").value;

 if (!token) {
 window.alert("Please enter a participant token");
 joining = false;
 return;
 }

 // Retrieve the User Media currently set on the page
 localMic = await navigator.mediaDevices.getUserMedia({
 video: false,
 audio: true,
 });
 cameraStageStream = new LocalStageStream(
 segmentationStream.getVideoTracks()[0]
 );
 micStageStream = new LocalStageStream(localMic.getAudioTracks()[0]);

 const strategy = {
 stageStreamsToPublish() {
 return [cameraStageStream, micStageStream];
 },
 shouldPublishParticipant() {
 return true;
 },
 shouldSubscribeToParticipant() {
 return SubscribeType.AUDIO_VIDEO;
 },
 };

 stage = new Stage(token, strategy);
};

最后,我们添加一些逻辑来侦听 Stage 事件。当您加入的 Stage 状态发生变化,例如有人加入或退出 Stage 时,就会发生这些事件。利用这些事件,您可以动态地更新 HTML 代码,以便在新参与者加入时显示其视频画面,并在他们退出时将其从显示区域中移除。setupParticipant 和 teardownParticipant 函数可分别执行这些操作。最后,我们对 Stage 对象调用 join 方法来加入 Stage。

// Other available events:
// https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-guides/stages#events
stage.on(StageEvents.STAGE_CONNECTION_STATE_CHANGED, (state) => {
 connected = state === ConnectionState.CONNECTED;

 if (connected) {
 joining = false;
 controls.classList.remove("hidden");
 } else {
 controls.classList.add("hidden");
 }
});

stage.on(StageEvents.STAGE_PARTICIPANT_JOINED, (participant) => {
 console.log("Participant Joined:", participant);
});

stage.on(
 StageEvents.STAGE_PARTICIPANT_STREAMS_ADDED,
 (participant, streams) => {
 console.log("Participant Media Added: ", participant, streams);

 let streamsToDisplay = streams;

 if (participant.isLocal) {
 // Ensure to exclude local audio streams, otherwise echo will occur
 streamsToDisplay = streams.filter(
 (stream) => stream.streamType === StreamType.VIDEO
 );
 }

 const videoEl = setupParticipant(participant);
 streamsToDisplay.forEach((stream) =>
 videoEl.srcObject.addTrack(stream.mediaStreamTrack)
 );
 }
);

stage.on(StageEvents.STAGE_PARTICIPANT_LEFT, (participant) => {
 console.log("Participant Left: ", participant);
 teardownParticipant(participant);
});

try {
 await stage.join();
} catch (err) {
 joining = false;
 connected = false;
 console.error(err.message);
}

第 3 部分 - 设置对 Amazon Bedrock 中 Stable Diffusion 模型的访问权限

现在,我们已经设置好应用程序,可以流式传输本地摄像头的视频,但除了硬编码的默认背景图像之外,我们还没有自定义背景图像。此时就需要使用 Amazon Bedrock 和 Stable Diffusion XL 图像生成模型。若要设置 Amazon Bedrock,请遵循入门指南执行操作。设置好 Amazon Bedrock 之后,前往控制台的 Model access(模型访问)页面,然后进入 Manage model access(管理模型访问)页面。选中 Stability AI 下 SDXL 0.8(Stable Diffusion XL Beta 0.8 模型的简称)旁边的复选框,然后点击 Request model access(请求模型访问)。这是用于生成图像的 Stability AI 基础模型的早期测试版。

现在,我们已具有访问权限,可使用 Amazon CLI 通过简单的提示语对其进行快速测试。若要运行以下注释,需要使用 Amazon CLI V2 或后续版本。如果您使用的是某个早期版本,请按照链接中的说明更新 Amazon CLI

aws bedrock-runtime invoke-model \
--model-id stability.stable-diffusion-xl \
--body "{\"text_prompts\":[{\"text\":\"beach with palm trees\"}],\"cfg_scale\":10,\"seed\":0,\"steps\":50}" \
--cli-binary-format raw-in-base64-out \
--region us-east-1

运行此注释将返回一个 JSON 响应(如下面截取的示例),说明这次调用是否成功,并以 Base64 编码字符串的形式返回图像。

{
 "result": "success",
 "artifacts": [
 { "seed": 0, "base64": "iVBORw0KGg…", "finishReason": "SUCCESS" }
 ]
}

第 4 部分 - 创建 Lambda 函数和 API Gateway 以向 Stable Diffusion 模型发送提示语

我们将创建一个 Lambda 函数,以编程方式从我们的 Web 应用程序访问模型。为此,前往 Lambda 控制台,以 Python 3.9 作为运行时环境创建一个函数。在 Lambda 函数中添加以下代码

此代码通过适用于 Python 的 Amazon SDK 调用我们刚刚通过 Bedrock 控制台添加的 Stable Diffusion XL 模型。在 Lambda 函数中,我们希望所发送的 JSON 请求体中包含用于描述生成图像的提示语,以及所需宽度和高度(以像素为单位)。然后,将这些参数传递给适用于 Python 的 Amazon SDK,以调用 Stable Diffusion 模型,并以 Base64 编码字符串的形式返回生成图像。然后,我们会将从 Lambda 函数返回的 Base64 编码字符串用作 JSON 对象,供 Web 应用程序使用。

import io
import boto3
from botocore.exceptions import ClientError
import json
import csv
import base64

brt = boto3.client(service_name='bedrock-runtime')
model_id = 'stability.stable-diffusion-xl'

def lambda_handler(event, context):
 body = json.loads(event['body'])

 request_body = json.dumps({
 "text_prompts": [{"text": body['prompt']}],
 "cfg_scale": 1,
 "seed": 0,
 "steps": 50,
 "width": body['width'],
 "height": body['height']
 })

 try:
 bedrock_response = brt.invoke_model(
 body=request_body,
 modelId=model_id,
 contentType="application/json",
 accept="application/json"
 )

 response_body = json.loads(bedrock_response['body'].read())
 base64_image = response_body["artifacts"][0]["base64"]

 return {
 "statusCode": 200,
 "body": json.dumps({"base64_image": base64_image})
 }
 except ClientError as e:
 return {
 "statusCode": e.response['ResponseMetadata']['HTTPStatusCode'],
 "body": json.dumps({"message": f"Error invoking model: {e.response['Error']['Message']}"})
 }

请注意,截至本文撰写时,Lambda 函数随附的适用于 Python 的 Amazon SDK (Boto3) 并不包含 Bedrock 运行时。如果在调用此 Lambda 函数时出现 "Unknown service: 'bedrock-runtime'" 错误,请按照链接说明创建一个将使用最新版本 Boto3 的层。然后,您需要为新层添加 Lambda 函数配置。完成添加后,此 Lambda 函数将返回一个类似于以下内容的 JSON 响应。

请注意,Base64 编码字符串很长,为方便说明,以下示例仅截取其中一段。

{
 "base64_image": "iVBORw0KGgoAAAANSUhEUgAAAoAAAAIACAIAAACdH0qsAAABZ..."
}

接下来,创建一个 API Gateway,用于触发我们刚刚创建的 Lambda 函数。打开 API Gateway 控制台,点击 Create API(创建 API),然后构建一个 HTTP API。添加一个 Lambda 集成,然后从下拉菜单中选择刚刚创建的 Lambda 函数。为 API 指定名称,然后点击 Next(下一步)。

在下一个页面中,配置 POST 路由,并将资源路径和集成目标保持不变。点击 Next(下一步)以配置 Stage。移除默认 Stage,并添加 Stage 名称“image”。启用自动部署程序,然后点击 Next(下一步)。然后,会显示一个类似于下面所示的页面,可在此页面中查看您指定的配置。点击 Create(创建)完成操作。

创建 API Gateway 后,可以按照链接说明获取调用 URL(请按照 New REST API(新建 REST API)控制台选项卡下的说明进行操作)。复制该调用 URL,我们将在下一步中使用它。

第 5 部分 - 通过 GenAI 提示您使用新背景图像

现在添加逻辑,以便在点击更改背景按钮时侦听该点击事件。点击该按钮将弹出一个模态窗口,其中包含一个输入框,可在其中提供我们所需的背景描述。在提交表单后,该表单将调用我们的 API Gateway 端点,并通过 Amazon Bedrock 对 Stable Diffusion 模型运行推理以返回图像。返回的图像采用 Base64 编码字符串的形式。随后我们直接将其绘制到之前创建的第二个 <canvas> 元素中。然后,我们将此 Base64 编码字符串传递给 initBackgroundCanvas 函数以实现其功能(将在下文对此进行说明)。

backgroundChangeBtn.addEventListener("click", async function () {
 backgroundChangeBtn.disabled = true;
 overlay.removeEventListener("click", closeModal);
 backgroundChangeBtn.innerText = "Please wait. This can take up to 10 sec.";

 // Get the input field's text value
 const prompt = backgroundPromptInput.value;
 // Create a JSON object with the text value
 const requestBody = {
 prompt,
 width: 640,
 height: 512,
 };

 // Convert the JSON object to a JSON string
 var requestBodyJSON = JSON.stringify(requestBody);

 // Make a POST request using the Fetch API
 const response = await fetch("INSERT YOUR API GATEWAY INVOKE URL HERE", {
 method: "POST",
 headers: {
 "Content-Type": "application/json",
 },
 body: requestBodyJSON,
 });

 const json = await response.json();
 closeModal();
 initBackgroundCanvas(json.base64_image);
 backgroundChangeBtn.disabled = false;
 openModalBtn.addEventListener("click", openModal);
 backgroundChangeBtn.innerText = "Submit";
});

initBackgroundCanvas 函数定义如下。此外,它已添加到 app.js 文件中。此函数只会简单地将我们生成的图像直接呈现在画布上。这样,我们就可以使用 Canvas API 从 <canvas> 元素中复制单个像素来替换背景。

const initBackgroundCanvas = (base64Image) => {
 let img = new Image();
 img.onload = () => {
 backgroundCtx.clearRect(0, 0, canvas.width, canvas.height);
 backgroundCtx.drawImage(img, 0, 0);
 };
 img.src = "data:image/png;base64," + base64Image;
};

第 6 部分 - 使用新图像替换背景

为了使用新图像替换摄像头画面中的背景,我们现在将实现前面提到的 renderVideoToCanvas 函数。此函数将视频画面呈现到 HTML 中的第二个画布元素上。我们需要将视频画面呈现到画布上,以便使用 Canvas 2D API 从中提取前景像素。在此过程中,我们还将向 ImageSegmenter 实例传递一个视频帧,并使用 segmentforVideo 方法分割此视频帧中的前景和背景。当 segmentforVideo 方法返回时,它会调用我们的自定义回调函数 replaceBackground 来替换背景。

创建 replaceBackground 函数,此函数将自定义背景图像与摄像头画面中的前景图像合并,以替换背景。首先,此函数会从之前创建的两个画布元素中获取自定义背景图像和视频画面的底层像素数据。然后,它将通过 ImageSegmenter 所提供的用于指示前景中像素的遮罩进行迭代。在迭代期间,此函数会选择性地将包含用户摄像头画面的像素复制到相应的背景像素数据中。完成复制后,它会将前景像素数据最终转换为背景像素数据,并将其绘制到画布上。

function replaceBackground(result) {
 let imageData = canvasCtx.getImageData(
 0,
 0,
 video.videoWidth,
 video.videoHeight
 ).data;
 let backgroundData = backgroundCtx.getImageData(
 0,
 0,
 video.videoWidth,
 video.videoHeight
 ).data;
 const mask = result.categoryMask.getAsFloat32Array();
 let j = 0;

 for (let i = 0; i < mask.length; ++i) {
 const maskVal = Math.round(mask[i] * 255.0);

 j += 4;
 // Only copy pixels on to the background image if the mask indicates they are in the foreground
 if (maskVal <255) {
 backgroundData[j] = imageData[j];
 backgroundData[j + 1] = imageData[j + 1];
 backgroundData[j + 2] = imageData[j + 2];
 backgroundData[j + 3] = imageData[j + 3];
 }
 }

 // Convert the pixel data to a format suitable to be drawn to a canvas
 const uint8Array = new Uint8ClampedArray(backgroundData.buffer);
 const dataNew = new ImageData(
 uint8Array,
 video.videoWidth,
 video.videoHeight
 );
 canvasCtx.putImageData(dataNew, 0, 0);
 window.requestAnimationFrame(renderVideoToCanvas);
}

第 7 部分 - 测试直播效果

若要在本地运行此应用程序,需要创建一个名为 webpack.config.js 的 Webpack 配置文件来捆绑 JavaScript,如下所示:

const path = require("path");
module.exports = {
 entry: ["./app.js"],
 output: {
 filename: "bundle.js",
 path: path.resolve(__dirname, "dist"),
 },
};

更新 package.json 文件,以便在运行构建脚本时将 Webpack 作为 JavaScript 捆绑程序运行。指定 webpack.config.js 作为您的 Webpack 配置文件。

{
 "dependencies": {
 "@mediapipe/tasks-vision": "^0.10.6",
 "copy-webpack-plugin": "^11.0.0",
 "html-webpack-plugin": "^5.5.3",
 "path": "^0.12.7"
 },
 "name": "ivs-stages-simple-background-replacement",
 "version": "1.0.0",
 "scripts": {
 "test": "echo \"Error: no test specified\" && exit 1",
 "build": "webpack --config webpack.config.js"
 },
 "keywords": [],
 "author": "",
 "license": "ISC",
 "description": "",
 "devDependencies": {
 "webpack": "^5.88.2",
 "webpack-cli": "^5.1.4"
 }
}

现在,通过命令行运行构建脚本以捆绑 JavaScript。这样,我们就可以顺利地在 app.js 文件中使用 import 语句。

npm run build

最后,从包含 index.html 的目录中启动一台简单 HTTP 服务器,并打开 localhost:8000 以查看结果。您应该会看到一个包含新背景图像的本地摄像头画面。或者,我们也可以考虑将 http-server 用作 HTTP 服务器。

python3 -m http.server -d ./

使用浏览器窗口,在亚马逊云科技控制台上创建一个参与者令牌,将其输入 Web 应用程序中的输入框,然后点击 Join stage(加入 Stage)。现在,您将包含自定义背景图像的视频画面发布到 Stage。

若要测试加入 Stage 的其他人是否能看到您的头像,请在另一个浏览器窗口中打开 Amazon IVS 实时流 Web 示例,创建另一个参与者令牌,并将其输入该浏览器窗口,然后点击 Join stage(加入 Stage)。现在,您应该能看到自己的视频画面,该画面已应用自定义背景。加入 Stage 的其他观众也会看到您的视频画面。您可以点击更改背景按钮,以提示您更改新的背景图像,几秒钟后,您会看到自己的视频画面已更新为新背景图像!

总结

您已学完本教程。现在应该能够使用人工智能生成式背景调整直播体验,从而为观众营造出动感而精致的视觉效果。人工智能生成式背景将为您和您的直播观众打开一扇通往全新精彩世界的大门。祝您直播愉快,在充满无限可能的全新直播环境中尽情发挥创意!

关于作者

Tony Vu 是 Twitch 的高级合作伙伴工程师。他专门负责评估与 Amazon Interactive Video Service (IVS) 集成的合作伙伴技术,致力于为我们的 IVS 客户开发和提供全面的联合解决方案。