利用智能媒体管理(IMM) 搭建云相册指南

目标能力

  • 为用户创建智能云相册
  • 支持人脸检测,获取包括人脸位置、表情、年龄等信息
  • 支持人脸分组,可以根据人物搜索、展示照片
  • 支持照片标签检测,可以根据标签对照片进行分类
  • 支持地理位置检测,获取照片拍摄的具体位置、时间,并且可以进行搜索

开始之前

首先需要准备好以下内容:

  • 准备阿里云账号,申请好调用 API 使用的 AccessKeyId / AccessKeySecret 。
  • 开通 OSS 服务,用于存储用户照片。在 Demo 中,由我们为您准备好照片,您可以在 这里下载压缩包 ,并通过 工具 上传到位于 华东 2(上海) 的 Bucket 中。

在 Demo 中,我们统一上传到 oss://imm-user-wmt-cn-shanghai/cloud-photo-album-demo/ 目录下。

  • 开通 IMM 服务,并创建一个项目。参考 这里 。注意:

    • 项目类型选择 图片标准型
    • QPS 可以选择 1 QPS,目前为 免费 提供。若您需要在线上环境使用,可以根据需要增加 QPS 限额。
    • 项目名称可以自定义,后面的代码以 cloud-photo-album-demo 为例。
    • 地域选择 华东 2(上海)

关于地域选择,请确保 IMM 的项目地域和您照片存储的 OSS 的地域保持一致。在 Demo 中均以 华东 2(上海) 为例,您可以*选择其他已开放 IMM 服务到地域进行测试。

关于计费,请参考 计费说明。后续我们还会提供更多样的计费模型,如按使用量计费等,敬请期待。

关于 Demo 的语言。我们使用 Python 3.7 作为 Demo 的语言。如果您使用其他语言,可以使用 API Explorer 生成您需要的语言的示例代码。参考 Demo 进行简单替换即可。我们支持 Java / Node.js / Go / PHP / Python / .Net / Ruby 。

创建相册

相册是一系列照片的集合,在 IMM 中对应 媒体集Set 这个概念。用户可以在一个相册中进行搜索、人物分组等操作,但不允许跨相册Set进行搜索、人物分组等操作。我们推荐,对每个使用相册对 C 端用户,创建一个相册,即 Set

#!/usr/bin/env python
#coding=utf-8

from aliyunsdkcore.client import AcsClient
from aliyunsdkcore.acs_exception.exceptions import ClientException
from aliyunsdkcore.acs_exception.exceptions import ServerException
from aliyunsdkimm.request.v20170906.CreateSetRequest import CreateSetRequest

# 填入您的 AccessKeyId 和 AccessKeySecret
client = AcsClient('<accessKeyId>', '<accessSecret>', 'cn-shanghai')

# 后续代码 Demo 省略以上内容

request = CreateSetRequest()
request.set_accept_format('json')

request.set_Project("cloud-photo-album-demo")
request.set_SetId("user-uid-0001")

response = client.do_action_with_exception(request)
# python2:  print(response) 
print(str(response, encoding='utf-8'))

运行后,返回结果类似:

{
   "RequestId": "4CF93317-6730-44AE-AEB2-30DF2D12A296",
   "CreateTime": "2020-02-19T09:32:31.836Z",
   "SetName": "",
   "ModifyTime": "2020-02-19T09:32:31.836Z",
   "SetId": "user-uid-0001"
}

至此我们为 0001 号用户创建了相册。

我们这里在调用 API 时指定了非必选参数 SetIduser-uid-0001 ,这是为了能够在实际应用场景时,能够将相册 SetId 和用户 ID 进行名称绑定。方便后续通过诸如 GetSet 接口获取 / 修改这个相册的信息。

Set 的创建数量没有限制,但一个 Set 内的照片、人脸数量是有限制的。请参考 用户限额

添加照片

照片文件实际存储的位置是 OSS 上,我们需要将这些照片索引到 IMM 的 Set 中。在这个过程中,IMM 会对照片的内容进行读取、检测,将其中的元数据(如人脸、标签等)提取出来进行索引。IMM 不会存储、修改 OSS 上的照片文件本身。

每个请求只索引一张照片,因此我们通过一个循环将所有照片添加到相册中。

image_list = [
    # 请注意替换为您自己的 OSS Bucket 和其对应路径。
    # 以下三张为带 EXIF 信息(GPS、拍摄时间等)的照片,在武汉拍摄。
    # 文件名供开发者快速识别,和 AI 对元数据提取能力无关。
    "oss://imm-user-wmt-cn-shanghai/cloud-photo-album-demo/wuhan-flower.jpg",
    "oss://imm-user-wmt-cn-shanghai/cloud-photo-album-demo/wuhan-food.jpg",
    "oss://imm-user-wmt-cn-shanghai/cloud-photo-album-demo/wuhan-river-sunset.jpg",
    # 以下八张为人物照片,两个人物各四张,用于展示人脸分组能力
    "oss://imm-user-wmt-cn-shanghai/cloud-photo-album-demo/mayun-01.jpg",
    "oss://imm-user-wmt-cn-shanghai/cloud-photo-album-demo/mayun-02.jpg",
    "oss://imm-user-wmt-cn-shanghai/cloud-photo-album-demo/mayun-03.jpg",
    "oss://imm-user-wmt-cn-shanghai/cloud-photo-album-demo/mayun-04.jpg",
    "oss://imm-user-wmt-cn-shanghai/cloud-photo-album-demo/zhangyong-01.jpg",
    "oss://imm-user-wmt-cn-shanghai/cloud-photo-album-demo/zhangyong-02.jpg",
    "oss://imm-user-wmt-cn-shanghai/cloud-photo-album-demo/zhangyong-03.jpg",
    "oss://imm-user-wmt-cn-shanghai/cloud-photo-album-demo/zhangyong-04.jpg"
]

for image_uri in image_list:
    request = IndexImageRequest()
    request.set_accept_format('json')

    request.set_SetId("user-uid-0001")
    request.set_Project("cloud-photo-album-demo")
    request.set_ImageUri(image_uri)

    response = client.do_action_with_exception(request)
    # python2:  print(response)
    print(str(response, encoding='utf-8'))

    # 因为我们开通的 QPS 限制为 1,因此这里休眠一段时间来避免触发流量控制
    time.sleep(1.5)

控制台输出的结果应该类似

{"RemarksD":"","RemarksC":"","ExternalId":"","CreateTime":"2020-02-20T07:32:38.918Z","RequestId":"1D4CF498-2851-4490-BA65-456C48B101E6","ModifyTime":"2020-02-20T07:32:38.918Z","RemarksA":"","SetId":"user-uid-0001","RemarksB":"","ImageUri":"oss://imm-user-wmt-cn-shanghai/cloud-photo-album-demo/wuhan-flower.jpg"}
...

此时照片已经被添加至相册中。一般来说,每张照片在添加后 10 秒左右,即可完成其信息提取。在提取完成后,我们即可进行后续分组、搜索等操作。

如果您需要确切的知道一张照片完成索引的时间,可以参考 IndexImage API 文档中的 MNS 通知相关字段,来订阅索引结果。

获取照片信息

下面我们看看 IMM 能检测哪些信息。我们先用一张食物照片举例。使用 GetImage 接口获取其信息。

request = GetImageRequest()
request.set_accept_format('json')

request.set_SetId("user-uid-0001")
request.set_Project("cloud-photo-album-demo")
request.set_ImageUri(
    "oss://imm-user-wmt-cn-shanghai/cloud-photo-album-demo/wuhan-flower.jpg")

response = client.do_action_with_exception(request)
# python2:  print(response)
print(str(response, encoding='utf-8'))

结果如:

{
    "Celebrity": [],
    "FacesModifyTime": "2020-02-20T07:32:45.027Z",
    "OCR": [],
    "CelebrityFailReason": "",
    "Faces": [],
    "OCRStatus": "NotProcessed",
    "Exif": "...此处略...",
    "ImageUri": "oss://imm-user-wmt-cn-shanghai/cloud-photo-album-demo/wuhan-flower.jpg",
    "AddressStatus": "Success",
    "ImageWidth": 3024,
    "RemarksD": "",
    "ImageFormat": "jpg",
    "RemarksC": "",
    "AddressModifyTime": "2020-02-20T07:32:48.078Z",
    "Orientation": "6",
    "CelebrityModifyTime": "",
    "ExternalId": "",
    "SourceType": "image",
    "CelebrityStatus": "NotProcessed",
    "AddressFailReason": "",
    "TagsModifyTime": "2020-02-20T07:32:48.042Z",
    "Location": "30.546285,114.298899",
    "ModifyTime": "2020-02-20T07:32:38.918Z",
    "FileSize": 10940531,
    "Tags": [
        {
            "TagConfidence": 0.9955320954322815,
            "TagLevel": 1,
            "TagName": "植物"
        },
        {
            "TagConfidence": 0.9955320954322815,
            "TagLevel": 2,
            "ParentTagName": "植物",
            "TagName": "花"
        }
    ],
    "ImageTime": "2019-07-13T01:39:32Z",
    "SourceUri": "oss://imm-user-wmt-cn-shanghai/cloud-photo-album-demo/wuhan-flower.jpg",
    "Address": {
        "District": "武昌区",
        "Township": "中华路街街道",
        "AddressLine": "湖北省武汉市武昌区中华路街街道*路92号",
        "Province": "湖北省",
        "Country": "中国",
        "City": "武汉市"
    },
    "CreateTime": "2020-02-20T07:32:38.918Z",
    "RequestId": "33B676B0-5299-4F85-B961-25F2E7DE642D",
    "FacesStatus": "Success",
    "TagsStatus": "Success",
    "RemarksA": "",
    "SetId": "user-uid-0001",
    "ImageHeight": 4032,
    "RemarksB": ""
}

我们能看到一些核心信息:

- 照片信息相关:图片大小、格式、尺寸。照片拍摄时间 `2019-07-13T01:39:32Z` 。
- 标签相关:植物 -> 花。
- 地理位置相关:GPS 经纬度坐标 `30.546285,114.298899` ,位置在 `湖北省武汉市武昌区中华路街街道*路92号` 。

我们再来看一个有人物的照片的检测结果。仅需修改上面代码的 ImageUri 中文件名为 zhangyong-02.jpg 。看一下结果:

{
    "Faces": [
        {
            "FaceQuality": 0.8648459911346436,
            "Age": 38,
            "GenderConfidence": 1,
            "Attractive": 0.82,
            "EmotionDetails": {
                "SAD": 1.4997523622301001E-12,
                "SCARED": 1.3939771646015453E-13,
                "CALM": 3.39213018785145E-10,
                "ANGRY": 3.840853281039322E-14,
                "HAPPY": 1,
                "DISGUSTED": 5.754174670384235E-14,
                "SURPRISED": 9.682720120487986E-13
            },
            "Gender": "MALE",
            "FaceConfidence": 0.9699456095695496,
            "Emotion": "HAPPY",
            "GroupId": "group-not-grouped",
            "FaceId": "2c476fa26dd795a6bbe9f3a781c50a5a8393e7e3e9fec40fcdf0058a6c2cb158",
            "FaceAttributes": {
                "GlassesConfidence": 1,
                "Glasses": "GLASSES",
                "HeadPose": {
                    "Roll": 5.295282363891602,
                    "Yaw": 0.23102417588233948,
                    "Pitch": 10.299762725830078
                },
                "RaceConfidence": 1,
                "Beard": "NONE",
                "MaskConfidence": 1,
                "Race": "YELLOW",
                "BeardConfidence": 1,
                "FaceBoundary": {
                    "Top": 50,
                    "Height": 69,
                    "Width": 67,
                    "Left": 206
                },
                "Mask": "NONE"
            }
        }
    ],
    ...其他略...
}

可以得到一些人脸相关核心信息:

- 基础信息:年龄 38 岁、性别 男、人脸质量 `0.86`
- 心情:开心
- 人脸属性:
  - 戴眼镜
  - 头部朝向:正脸
  - 无胡须
  - 黄种人
  - 无口罩
- 人脸矩形框的位置

人脸分组

接下来我们对相册中的照片按人物进行分组。我们 IndexImage 调用完成后,需要等待至少 15 秒,确保图片的索引、检测均完成。接下来调用人脸聚类 API CreateGroupFacesJob 。这个接口会将 Set 内的照片的人脸按照人物进行分组,并将组的 GroupId 写回到索引信息中。

request = CreateGroupFacesJobRequest()
request.set_accept_format('json')
request.set_SetId("user-uid-0001")
request.set_Project("cloud-photo-album-demo")

response = client.do_action_with_exception(request)
# python2:  print(response)
print(str(response, encoding='utf-8'))

结果类似:

{
    "JobType": "GroupImageFacesJob",
    "RequestId": "075D76F7-4AD1-4129-BD95-DD6D53581D0B",
    "JobId": "GroupImageFacesJob-113a8759-0483-4ad4-9a81-f7bd402c6b40",
    "SetId": "user-uid-0001"
}

此时分组人物还在进行中,我们等待 30 秒左右任务即可完成。

聚类遇到的常见问题,可以参考 [人脸聚类相关 FAQ](
https://help.aliyun.com/knowledge_detail/153359.html)

人脸搜索

首先我们要列出所有的人脸分组,即看看这个 Set 内有几个人物。

request = ListFaceGroupsRequest()
request.set_accept_format('json')
request.set_SetId("user-uid-0001")
request.set_Project("cloud-photo-album-demo")

response = client.do_action_with_exception(request)
# python2:  print(response)
print(str(response, encoding='utf-8'))

返回结果类似:

{
    "FaceGroups": [
        {
            "GroupName": "",
            "CreateTime": "2020-02-20T08:12:29.949Z",
            "ModifyTime": "2020-02-20T08:12:29.949Z",
            "FaceCount": 4,
            "GroupId": "Group-714ca168-5a86-4cc7-b4b1-c7f27ca1eb41",
            "GroupCoverFace": {
                "FaceBoundary": {
                    "Top": 60,
                    "Height": 105,
                    "Width": 127,
                    "Left": 207
                },
                "FaceId": "1d2ee16ee556bbce093be0b3e83c508d5c7da05bea32fa6306670befe85671de",
                "ImageUri": "oss://imm-user-wmt-cn-shanghai/cloud-photo-album-demo/mayun-03.jpg"
            }
        },
        {
            "GroupName": "",
            "CreateTime": "2020-02-20T08:12:29.945Z",
            "ModifyTime": "2020-02-20T08:12:29.945Z",
            "FaceCount": 4,
            "GroupId": "Group-c4474af0-c268-4753-b984-1496cd3bcf7a",
            "GroupCoverFace": {
                "FaceBoundary": {
                    "Top": 50,
                    "Height": 69,
                    "Width": 67,
                    "Left": 206
                },
                "FaceId": "2c476fa26dd795a6bbe9f3a781c50a5a8393e7e3e9fec40fcdf0058a6c2cb158",
                "ImageUri": "oss://imm-user-wmt-cn-shanghai/cloud-photo-album-demo/zhangyong-02.jpg"
            }
        }
    ],
    "RequestId": "D6202A11-20AC-49D8-B0B0-C3743DA252D0",
    "NextMarker":"

从这个 JSON 中我们可以看到,出现了两个人物分组,和我们的预期一致。其中几个关键的字段:

  • FaceCount 指出这个分组内的人脸数量。
  • GroupId 是这个分组的唯一 ID,用于搜索该人物。
  • GroupCoverFace 是自动选取出来,用作该组封面图的人脸。您可以通过其 ImageUriFaceBoundary 指示的人脸框,快速截取出人脸部分的图片作为该分组头像,用于给用户展示。

我们以 GroupIdGroup-714ca168-5a86-4cc7-b4b1-c7f27ca1eb41 的人物为例,搜索出该人物在 Set 内的其他照片。

request = FindImagesRequest()
request.set_accept_format('json')

request.set_SetId("user-uid-0001")
request.set_Project("cloud-photo-album-demo")
request.set_GroupId("Group-714ca168-5a86-4cc7-b4b1-c7f27ca1eb41")

response = client.do_action_with_exception(request)
for image in json.loads(response)["Images"]:
    print(image["ImageUri"])

输出结果类似:

oss://imm-user-wmt-cn-shanghai/cloud-photo-album-demo/mayun-01.jpg
oss://imm-user-wmt-cn-shanghai/cloud-photo-album-demo/mayun-02.jpg
oss://imm-user-wmt-cn-shanghai/cloud-photo-album-demo/mayun-03.jpg
oss://imm-user-wmt-cn-shanghai/cloud-photo-album-demo/mayun-04.jpg

因为我们命名了图片,因此这里可以很容易看到,所搜索到的图片均为同一人物照片。

按内容分组照片

添加到 Set 内的照片,会默认进行 1600 类标签的检测。我们可以通过 ListSetTags 接口看看这个相册内都有哪些内容分组。

request = ListSetTagsRequest()
request.set_accept_format('json')

request.set_SetId("user-uid-0001")
request.set_Project("cloud-photo-album-demo")

response = client.do_action_with_exception(request)
# python2:  print(response)
print(str(response, encoding='utf-8'))

结果如:

{
    "Tags": [
        {
            "TagCount": 7,
            "TagLevel": 2,
            "TagName": "人物特写"
        },
        {
            "TagCount": 7,
            "TagLevel": 1,
            "TagName": "其他场景"
        },
        ...
        {
            "TagCount": 1,
            "TagLevel": 1,
            "TagName": "饮食"
        }
    ],
    "RequestId": "F1E51BFA-34DB-4D64-9F2E-AB5E215C888E",
    "SetId": "user-uid-0001"
}

可以看到这个相册中有若干标签,返回的结果按标签出现的次数降序排列。这可以用于展示用户的相册中有哪些照片类别。并结合后面关于图片搜索能力的介绍,搜索对应标签的照片。

使用各种方式搜索照片

根据地理位置搜索

request = FindImagesRequest()
request.set_accept_format('json')
request.set_SetId("user-uid-0001")
request.set_Project("cloud-photo-album-demo")
request.set_AddressLineContentsMatch("武汉")

response = client.do_action_with_exception(request)
for image in json.loads(response)["Images"]:
    print(image["ImageUri"])

结果如:

oss://imm-user-wmt-cn-shanghai/cloud-photo-album-demo/wuhan-food.jpg
oss://imm-user-wmt-cn-shanghai/cloud-photo-album-demo/wuhan-river-sunset.jpg
oss://imm-user-wmt-cn-shanghai/cloud-photo-album-demo/wuhan-flower.jpg

根据照片内容搜索

request = FindImagesRequest()
request.set_accept_format('json')
request.set_SetId("user-uid-0001")
request.set_Project("cloud-photo-album-demo")
request.set_TagNames('["食物"]')

response = client.do_action_with_exception(request)
for image in json.loads(response)["Images"]:
    print(image["ImageUri"])

结果如:

oss://imm-user-wmt-cn-shanghai/cloud-photo-album-demo/wuhan-food.jpg

根据照片拍摄时间搜索

request = FindImagesRequest()
request.set_accept_format('json')
request.set_SetId("user-uid-0001")
request.set_Project("cloud-photo-album-demo")
request.set_ImageTimeRange(
    '{"Start":"2019-07-13T00:00:00.0Z","End":"2019-07-14T00:00:00.0Z"}')

response = client.do_action_with_exception(request)
for image in json.loads(response)["Images"]:
    print(image["ImageUri"])

结果如:

oss://imm-user-wmt-cn-shanghai/cloud-photo-album-demo/wuhan-food.jpg
oss://imm-user-wmt-cn-shanghai/cloud-photo-album-demo/wuhan-river-sunset.jpg
oss://imm-user-wmt-cn-shanghai/cloud-photo-album-demo/wuhan-flower.jpg

组合搜索

例如我们搜索特定人物开心的照片

request = FindImagesRequest()
request.set_accept_format('json')

request.set_SetId("user-uid-0001")
request.set_Project("cloud-photo-album-demo")
request.set_GroupId("Group-c4474af0-c268-4753-b984-1496cd3bcf7a")
request.set_Emotion("HAPPY")

response = client.do_action_with_exception(request)
for image in json.loads(response)["Images"]:
    print(image["ImageUri"])

可以看到,根据两个条件同时过滤出了对应照片。

oss://imm-user-wmt-cn-shanghai/cloud-photo-album-demo/zhangyong-03.jpg
oss://imm-user-wmt-cn-shanghai/cloud-photo-album-demo/zhangyong-02.jpg

列出所有照片

当然,最常见的情况是按照一定顺序将所有照片展示给用户,我们使用 ListImages 接口即可。这里不做代码示例。

其他功能

增删改查

IMM 对照片、相册 Set 均提供了增删改查对应接口,您可以参考我们的 API 文档 进行使用。

外部 ID 绑定

我们可以帮助您将每一张照片和您自己的系统的 ID 进行绑定。也可以在照片中额外存储一些信息供您搜索。如照片的权限,照片在您系统内的目录结构等等。请参考 IndexImage 的 ExternalId 字段和 Remarks 字段。

视频索引

我们支持将视频索引入相册,并且对视频进行截帧,检测每一帧视频的人脸、标签信息,以便用户进行搜索。请参考 IndexVideo 接口。

结语

以上就是使用智能媒体管理服务 (IMM) 搭建一个智能云相册的方式。可以看出,IMM 可以提供大部分云相册所需要的能力,您可以很方便的在服务中集成该能力,而无需担心数据存储相关问题。

如果您有其他问题,请进入 钉钉用户群 实时交流。

上一篇:jetty 9使用


下一篇:智能媒体管理(IMM) 多媒体文件元数据管理设计