亚马逊AWS官方博客

在 Amazon SageMaker 上托管 Libcom 图片融合服务

图像融合技术广泛应用于多个行业,如电商营销素材和广告创意等领域。其目标是将前景插入到背景图像中,通过解决外观、几何和语义上的不一致性,实现逼真和谐的合成图像。生成式人工智能技术在为商品生成背景时,常常会对商品整体进行修改,导致出现“货不对版”的问题。为控制这一问题,我们推荐一款卓越的图片融合工具库 Libcom(the library of image composition)。该工具库提供了各种图片融合场景所需的模型,这些预训练模型开箱即用,用户还可以利用自有风格的数据进行进一步训练,以满足特定效果。Amazon SageMaker 是一个云机器学习平台,可用于为几乎任何使用场景构建、训练和部署模型。Notebook 实例为构建机器学习模型提供了灵活的环境,省去了用户在管理底层计算基础设施上花费的时间和精力。本文介绍了如何在 Amazon SageMaker 上部署 Libcom 图片融合模型。

方案总览

上图是在 Amazon SageMaker 上搭建和使用 Libcom 模型的整体架构。本文采用 BYOC(Bring Your Own Container)的方式,也就是将现有代码构建成自定义 Docker 镜像的方式迁移到 Amazon SageMaker,适合于现有代码与依赖库的关系比较复杂的情况。

首先我们使用一个亚马逊云科技海外账号,具有访问 Amazon CloudFormation 和 Amazon SageMaker 的权限。登陆亚马逊云科技 Console 后,以 us-east-1 区为例,选择打开 CloudFormation 服务,点击 Create Stack(with new resource),上传模版文件,通过自动化的方式部署本次实验所需要的环境和代码。

点击下一步,输入Stack Name

确认创建 IAM Resource 后提交

等待几分钟 CloudFormation Stack 创建完成后,会提供一个 Notebook 实例,实例上已包含克隆好的初始化代码

在亚马逊云科技的 Console 里选择 SageMaker 服务,在左边导航栏打开 Notebook Instance,找到创建好的 Notebook Instance: libcom-sm-notebook,点击 Open JupyterLab 打开 Notebook 实例,进到 host-libcom-on-sagemaker-endpoint 目录后,看到两个 Notebook:

  1. deploy.ipynb:负责下载模型、打包推理代码、构建镜像、创建在线推理端点和实验结束后的资源清理
  2. predict.ipynb:提供了样图的测试步骤和结果展示

创建 SageMaker Libcom Endpoint

按照 deploy.ipynb 中的步骤顺序执行即可。本文使用 Libcom 提供的两种模型:

  • FOPAHeatMapModel 基于一对前景-背景图,预测所有位置的合理性分数,输出最佳位置的合成图像
  • ImageHarmonizationModel 调整前景照明,使其与背景相匹配

为节约推理时间,我们提前将模型下载到本地,和推理代码一起打包到镜像中

# download models
!chmod +x download_pretrained_model.sh && bash download_pretrained_model.sh

下载完成后,把模型和推理代码打包成镜像,推送到 Amazon ECR

# build containers used by sageMaker
!chmod +x build_and_push.sh && bash build_and_push.sh $region_name

因为是 BYOC 模式,需要遵循 SageMaker Hosting 定义的一套镜像构建范式,重点说明如下,细节请参考代码文件。

  • 容器启动命令,参考代码中的 serve 文件
    docker run image serve
  • 容器应用的心跳检测服务
    @app.route('/ping', methods=['GET'])
    def ping():
        #Determine if the container is working and healthy. In this sample container
        # Declare it healthy if we can load the model successfully."""
        # health = ScoringService.get_model() is not None 
        status = 200 #if health else 404
        return flask.Response(response='\n', status=status, mimetype='application/json')
    
  • 容器应用的推理服务
    import flask
    . . .
    app = flask.Flask(__name__)
    . . .
    @app.route('/invocations', methods=["POST"])
    def invoke(request):
        # model() is a hypothetical function that gets the inference output:
        resp_body = model(request)
        return flask.Response(resp_body, mimetype='text/plain')
    

详细的推理服务参考 libcom/libcom/predictor.py 中的定义,本次推理代码允许用户输入 4 种方法,将在下一章测试环节详细展开说明。

Libcom/libcom/predictor.py 部分内容:

@app.route('/invocations', methods=['POST'])
def transformation():
    data = json.loads(flask.request.data)
    function_name = data.get("function",None)
    if function_name == "heatmap_compose":
        result = heatmap_compose(data)
    elif function_name == "simple_compose":
        result = simple_compose(data)
    elif function_name == "pct_mode":
        result = pctnet(data)
    elif function_name == "cdt_mode":
        result = cdtnet(data)

部署成功后,在 SageMaker→Endpoints 观测到 Endpoint 处于 InService 状态

测试 SageMaker Libcom Endpoint

打开 predict.ipynb 根据提供的步骤顺序执行即可。

在图片融合场景中,我们需要向模型提供前景物体的信息,以便进行图片的合成和和谐化处理。为了实现这一目标,我们使用蒙版来标记前景物体。蒙版的获取方法有很多种,其中最常见的是使用 SAM(Segment Anything Model),本文不再详述。

1. 热力图与最佳放置位置

将准备的前景图模特,模特蒙版和背景图卧室组成请求体发送给推理端点。推理服务会根据前景图和背景图包含的信息,生成一张热力图,热力图中高亮的位置表示背景图的最佳放置位置。基于热力图的生成结果,定制推理代码取了热力图最亮的点,作为背景图中最适合放置前景的位置,执行图片合成。

## Generate FOPA image and placement suggestion
background_path = "uploads/bg.png"
foreground_path = "uploads/fg.jpg"
foreground_mask_path = "uploads/fg_mask.png"
bg=encode_image(background_path)
fg=encode_image(foreground_path)
fg_mask=encode_image(foreground_mask_path)
function = "heatmap_compose"
payload = {
    "function":function,
    "background":bg,
    "foreground":fg,
    "foreground_mask":fg_mask,
    "scale_x":0.8
}
# Prepare the request body as a JSON object
request_body = json.dumps(payload)
# Send the inference request to the endpoint
response = client.invoke_endpoint(
    EndpointName=endpoint_name,
    Body=request_body,
    ContentType="application/json"
)
# Parse the response
response_body = response["Body"].read().decode("utf-8")
fopa_result = json.loads(response_body)
bbox=fopa_result['bboxes']
print(bbox)
heatmap=save_image_from_base64(fopa_result['heatmap_image'],'results','heatmap')
comp_img=save_image_from_base64(fopa_result['comp_image'],'results','comp_img')
comp_mask=save_image_from_base64(fopa_result['comp_mask'],'results','comp_mask')
grid_img  = make_image_grid([heatmap, comp_img, comp_mask])
from IPython.display import display,Image
cv2.imwrite(heatmap, grid_img)
display(Image(filename=heatmap))

根据热力图放置效果:

测试阶段用户可能会调整合并的位置,推理代码中提供用户自定义位置 bbox 放置前景图片。

# prepare images
background_path = "uploads/bg.png"
foreground_path = "uploads/fg.jpg"
foreground_mask_path = "uploads/fg_mask.png"
bg=encode_image(background_path)
fg=encode_image(foreground_path)
fg_mask=encode_image(foreground_mask_path)
function = "simple_compose"
bbox = [376, 187, 550, 791]
print(bbox)
payload = {
    "function":function,
    "background":bg,
    "foreground":fg,
    "foreground_mask":fg_mask,
    "bbox":bbox
}
# Prepare the request body as a JSON object
request_body = json.dumps(payload)
# Send Image Compose inference request to the endpoint
response = client.invoke_endpoint(
    EndpointName=endpoint_name,
    Body=request_body,
    ContentType="application/json"
)
# Parse the response
response_body = response["Body"].read().decode("utf-8")
compose_result = json.loads(response_body)
# Process the result
#decode_image(compose_result['comp_image'])
#decode_image(compose_result['comp_mask'])
comp_image=save_image_from_base64(compose_result['comp_image'],'results','comp_image')
comp_mask=save_image_from_base64(compose_result['comp_mask'],'results','comp_mask')
grid_img  = make_image_grid([comp_image, comp_mask])
from IPython.display import display,Image
cv2.imwrite(comp_image, grid_img)
display(Image(filename=comp_image))

根据指定坐标放置效果:

大家会注意到,每次合成图片后,服务端还会返回一张合成图的前景位置蒙版,这是用于给合成图片做后续光影和谐处理的输入使用。

2. 图片合成后的和谐化

Libcom 提供了两种和谐化算法,PCTNet(像素预测变换)和 CDTNet(像素和谐变换),供不同的场景和实际效果选用。

使用 CDTNet:

# choose cdt mode
comp_image_path = "uploads/comp_image.png"
comp_mask_path = "uploads/comp_mask.png"
comp_image=encode_image(comp_image_path)
comp_mask=encode_image(comp_mask_path)
function = "cdt_mode"
payload = {
    "function":function,
    "comp_image":comp_image,
    "comp_mask":comp_mask
}
#payload
# Prepare the request body as a JSON object
request_body = json.dumps(payload)
# Send the inference request to the endpoint
response = client.invoke_endpoint(
    EndpointName=endpoint_name,
    Body=request_body,
    ContentType="application/json"
)
# Parse the response
response_body = response["Body"].read().decode("utf-8")
harmony_result = json.loads(response_body)
harmony_image=save_image_from_base64(harmony_result['cdt_result'],'results','harmony')
grid_img  = make_image_grid([comp_image_path, harmony_image])
from IPython.display import display,Image
cv2.imwrite(harmony_image, grid_img)
display(Image(filename=harmony_image))

使用 CDTNet 的效果(左侧模特原图,右侧 CDTNet 效果图):

使用 PCTNet 的效果(左侧模特原图,右侧 PCTNet 效果图):

在 PCTNet 预训练模型中,我们注意到在预测室内光源后,人物的暗色调被过度强调,不太符合人物和谐化的场景。然而,在下面的商品处理示例中表现的就更自然。

(左侧香水合成到背景的原图,右侧对香水瓶做 PCTNet 和谐化效果图)

资源清理

实验结束后,执行 deploy.ipynb 中的代码,清理资源,避免浪费

# delete endpoint for cost saving
endpoint_name = 'libcom'
model_name='libcom'
import boto3
sagemaker_client = boto3.client("sagemaker")
# Delete the endpoint
sagemaker_client.delete_endpoint(EndpointName=endpoint_name)
# Delete the endpoint configuration
sagemaker_client.delete_endpoint_config(EndpointConfigName=endpoint_name)
# Delete the model
sagemaker_client.delete_model(ModelName=model_name)

总结

生成式 AI 的出现极大地提高了我们创造创意、尝试新风格的效率。然而,在商品展示等场景中,真实性仍然是一个不可或缺且至关重要的需求。本文演示了如何在 Amazon SageMaker 上部署 libcom 图片融合服务,借助 Amazon SageMaker 提供的完全托管、可扩展的基础设施,轻松满足各种规模的生产负载需求。

参考链接

本篇作者

林益龙

亚马逊云科技解决方案架构师,专注于在企业中推广云计算与人工智能的最佳实践。曾担任运维经理、解决方案架构师等岗位,拥有多年的企业 IT 运维和架构设计经验。

罗新宇

亚马逊云科技解决方案架构师,在架构与开发领域有非常丰富的实践经验,目前致力于 Serverless 在云原生架构中的应用。