目标能力
- 为用户创建智能云相册
- 支持人脸检测,获取包括人脸位置、表情、年龄等信息
- 支持人脸分组,可以根据人物搜索、展示照片
- 支持照片标签检测,可以根据标签对照片进行分类
- 支持地理位置检测,获取照片拍摄的具体位置、时间,并且可以进行搜索
开始之前
首先需要准备好以下内容:
- 准备阿里云账号,申请好调用 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 时指定了非必选参数
SetId
为user-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
是自动选取出来,用作该组封面图的人脸。您可以通过其ImageUri
和FaceBoundary
指示的人脸框,快速截取出人脸部分的图片作为该分组头像,用于给用户展示。
我们以 GroupId
为 Group-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 可以提供大部分云相册所需要的能力,您可以很方便的在服务中集成该能力,而无需担心数据存储相关问题。
如果您有其他问题,请进入 钉钉用户群 实时交流。