iOS照片应用面部识别技术深度解析

本文深入分析iOS照片应用的面部识别技术架构,涵盖mediaanalysis.db、Photos.sqlite等数据库解析,详细介绍人脸检测、特征提取和联系人匹配的技术实现细节。

DFIR峰会演讲后续:Lucky (iOS) 13 - 下注时刻(通过 @bizzybarney)

照片中的人脸识别

我想扩展我在DFIR峰会演讲中的一个方面,即深入研究照片应用程序,以及该研究的一些衍生内容。在关注人脸识别主题时,似乎有必要简要介绍从拍摄照片到在照片应用程序中将人名放在人脸旁边的整个过程。

当您使用原生相机拍摄照片时,根据用户选项,至少会发生一些标准操作。它最终将新拍摄的照片写入 /private/var/mobile/Media/DCIM/1**APPLE/IMG_0001.HEIC / .JPG。拍摄照片时,Photos.sqlite 数据库会更新以反映有关照片的大量元数据,稍后将对此进行介绍。此外,还会创建 “PreviewWellImage.tiff”。“PreviewWellImage.tiff” 代表您在打开照片应用程序并看到最近图像的预览时看到的照片,在这种情况下就是刚刚用相机拍摄的照片。

用户的照片起始于 ../100APPLE/ 目录,但随着保存的照片和视频越来越多,此目录会向上迭代(101APPLE、102APPLE 等)。如果用户开启了 iCloud 同步,则会发生其他几种行为 - 但这留待以后讨论。

让我们关注照片应用程序中内置的分析和智能功能。我能够输入文本字符串,并且我的照片会立即被搜索其中匹配的对象。我的照片中有一个专门用于"人物"的部分,其中姓名已与 Apple 分析过的人脸相关联。

媒体分析数据库

照片的某些分析部分发生在 mediaanalysis.db 文件中。此文件正在分析媒体文件并进行评分,产生的结果似乎会反馈到分析的其他部分。一些需要重点关注的评分结果集中在人类、人脸和宠物上。

路径: /private/var/mobile/Media/MediaAnalysis/mediaanalysis.db

mediaanalysis.db 文件的 ‘Results’ 表包含 ‘assetId’(代表媒体文件)、‘resultsType’(特定的分析类型和在该媒体文件中找到的评分值)以及一个 BLOB(二进制大对象),该 BLOB 是一个二进制的 .plist (bplist)。您可以在下图中看到,‘assetId’ 3 有许多与之关联的 ‘resultsType’,并且选择了 ‘resultsType’ 为 1 的 BLOB,在右侧您可以看到 bplist。

该 bplist 可以通过将 bplist 保存为文件,然后使用 ‘plutil’ 将其打印到文本文件来输出为文本文件。如下红色星号旁边所示,打印的文本是该 .bplist 的清晰呈现,它告诉我们 ‘resultsType’ 为 1 与基于评分的 Faces 相关联。

我对剩余的数据重复了这个过程,并为每个结果类型写了简要描述,尽管我的设备上仍然有一些类型没有任何结果。

对此数据库运行 SQL 查询后,您可以对结果进行排序,以可能仅查看具有 ‘humanBounds’ 和 ‘humanConfidence’ 结果类型的文件。“assets” 表中的 ’localIdentifier’ 列是一个 UUID,与 Photos.sqlite 的 ZGENERICASSETS 表匹配。

以下是针对 mediaanalysis.db 文件的查询。它又大又丑,但如果您感兴趣,请测试一下,不过这部分似乎只是构建到我们稍后在 Photos.sqlite 中看到的内容中,所有内容在那里汇集在一起。

1
select	a.id,	a.localIdentifier as "Local Identifier",	a.analysisTypes as "Analysis Types",	datetime(a.dateModified+978307200, 'unixepoch') as "Date Modified (UTC)",	datetime(a.dateAnalyzed+978307200, 'unixepoch') as "Date Analyzed (UTC)",CASE	when results.resultsType = 1 then "Face Bounds / Position / Quality"	when results.resultsType = 2 then "Shot Type"	when results.resultsType = 3 then "Duration / Quality / Start"	when results.resultsType = 4 then "Duration / Quality / Start"	when results.resultsType = 5 then "Duration / Quality / Start"	when results.resultsType = 6 then "Duration / Flags / Start"	when results.resultsType = 7 then "Duration / Flags / Start"	when results.resultsType = 15 then "Duration / Quality / Start"	when results.resultsType = 19 then "Duration / Quality / Start"	when results.resultsType = 22 then "Duration / Quality / Start"	when results.resultsType = 23 then "Duration / Quality / Start"	when results.resultsType = 24 then "Duration / Quality / Start"	when results.resultsType = 25 then "Duration / Quality / Start"	when results.resultsType = 27 then "Duration / Quality / Start"	when results.resultsType = 36 then "Duration / Quality / Start"	when results.resultsType = 37 then "Duration / Quality / Start"	when results.resultsType = 38 then "Duration / Quality / Start"	when results.resultsType = 39 then "Duration / Quality / Start"	when results.resultsType = 48 then "Duration / Quality / Start"	when results.resultsType = 8 then "UNK"	when results.resultsType = 11 then "UNK"	when results.resultsType = 13 then "UNK" 	when results.resultsType = 21 then "UNK” 	when results.resultsType = 26 then "UNK"	when results.resultsType = 31 then "UNK"	when results.resultsType = 42 then "UNK"	when results.resultsType = 45 then "UNK"	when results.resultsType = 49 then "UNK"	when results.resultsType = 9 then "Attributes - junk"	when results.resultsType = 10 then 'Attributes - sharpness'	when results.resultsType = 12 then "Attributes - featureVector"	when results.resultsType = 14 then "Attributes - Data"	when results.resultsType = 16 then "Attributes - orientation"	when results.resultsType = 17 then 'Quality'	when results.resultsType = 18 then "Attributes - objectBounds"	when results.resultsType = 20 then "Saliency Bounds and Confidence"	when results.resultsType = 28 then "Attributes - faceId / facePrint"	when results.resultsType = 29 then "Attributes - petsBounds and Confidence"	when results.resultsType = 30 then "Various Scoring Values"	when results.resultsType = 32 then "Attributes - bestPlaybackCrop"	when results.resultsType = 33 then "Attributes - keyFrameScore / keyFrameTime"	when results.resultsType = 34 then "Attributes - underExpose"	when results.resultsType = 35 then "Attributes - longExposureSuggestionState / loopSuggestionState"	when results.resultsType = 40 then "Attributes - petBounds and Confidence"	when results.resultsType = 41 then "Attributes - humanBounds and Confidence"	when results.resultsType = 43 then "Attributes - absoluteScore/ humanScore/ relativeScore"	when results.resultsType = 44 then "Attributes - energyValues/ peakValues"	when results.resultsType = 46 then "Attributes - sceneprint/ EspressoModelImagePrint"	when results.resultsType = 47 then "Attributes - flashFired, sharpness, stillTime, texture"end as "Results Type",	hex(results.results) as "Results BLOB"from assets aleft join results on results.assetId=a.id

文本分析数据库

在深入研究 Photos.sqlite 文件之前,我想首先指出记录文本字符串的文件,这显然是 Apple 对照片进行对象分析的结果。此文件存储文本结果,使我们能够在照片应用程序中搜索文本字符串并返回结果。这些文本字符串不一定是任何特定用户活动的结果,而是 Apple 自动对用户媒体文件部署分析的输出。

路径: /private/var/mobile/Media/PhotoData/Caches/search/psi.sqlite

psi.sqlite 中的 ‘word_embedding’ 表包含 ‘word’ 和 ’extended_word’ 列,它们只是存储在 BLOB 中的字符串。使用 DB Browser for SQLite,您可以将表导出为 CSV,它会很清晰地打印出 BLOB 中的字符串。另外还有一个名为 ‘collections’ 的表,其中有 ’title’ 和 ‘subtitle’ 列,似乎是已使用或将要使用的"回忆"和"类别"的历史记录。

psi.sqlite 中本文要提到的最后一个表是 ‘groups’ 表。在 groups 表中,‘content_string’ 包含一些非常有趣的数据。我最初只是想找到"Green Bay"这个词,因为它出现在我搜索字母"g"的文本搜索结果中。但我发现的远比这有趣。我确实找到了"Green Bay",但还在另一个 BLOB 中找到了"Green Bay Packers vs. Miami Dolphins"。那个 BLOB 是 Apple 为我添加的一点额外风味。我不确定他们是简单地使用了我照片中嵌入的地理坐标(因为我在兰博球场),还是分析了照片内容并找到了各种迈阿密海豚队的球衣。但这是一个非常有趣的、绝对准确的工件,为我放在那里。谢谢 Apple!

照片数据库分析

现在让我们来处理 Photos.sqlite,但仅限于与人脸识别以及将人物照片与实际姓名相关联相关的部分。因为老实说,如果有人想解析它的每一寸并维护该支持,这个单一文件几乎是一项全职工作。

路径: /private/var/mobile/Media/PhotoData/Photos.sqlite

我的 Photos.sqlite 实例是个庞然大物,大小超过 300MB,包含 67 个充满了我照片数据的表。我们将重点关注两个表 - ZDETECTEDFACEZPERSON

ZDETECTEDFACE

此表包含指示人脸特征的值,包括年龄估计、头发颜色、秃顶、性别、眼镜和面部毛发。此外,还有左右眼是否闭上的指示器,以及左眼、右眼、嘴巴和中心的 X 和 Y 轴测量值。因此,此表中的数据非常精细,并且研究起来非常有趣。谁不喜欢看老照片呢?

ZPERSON

此表包含 Apple 能够从媒体文件中识别出某张脸的次数计数。因此,在我的设备上,我在数百张照片中被识别出姓名,但也有一些我的照片没有将我的姓名与我的脸关联起来。

对于每个识别出的人脸,都会分配一个 UUID(唯一标识符)。因此,尽管分析部分可能无法将人脸与姓名联系起来,但它可以将所有识别出的未知人脸实例分组为同一个人。

如果人脸与保存的联系人之间有关联,则 ZCONTACTMATCHINGDICTIONARY 列的 BLOB 数据可能显示全名和电话号码。这同样可以通过将 bplist 打印到 .txt 文件来实现。

1
select	zga.z_pk,	zga.ZDIRECTORY as "Directory",	zga.ZFILENAME as "File Name",CASE	when zga.ZFACEAREAPOINTS > 0 then "Yes"	else "N/A"	end as "Face Detected in Photo",CASE 	when zdf.ZAGETYPE = 1 then "Baby / Toddler"	when zdf.ZAGETYPE = 2 then "Baby / Toddler"	when zdf.ZAGETYPE = 3 then "Child / Young Adult"	when zdf.ZAGETYPE = 4 then "Young Adult / Adult"	when zdf.ZAGETYPE = 5 then "Adult"end as "Age Type Estimate",case	when zdf.ZGENDERTYPE = 1 then "Male"	when zdf.ZGENDERTYPE = 2 then "Female"	else "UNK"end as "Gender",	zp.ZDISPLAYNAME as "Display Name", 	zp.ZFULLNAME as "Full Name",	zp.ZFACECOUNT as "Face Count",CASE		when zdf.ZGLASSESTYPE = 3 then "None"	when zdf.ZGLASSESTYPE = 2 then "Sun"	when zdf.ZGLASSESTYPE = 1 then "Eye"	else "UNK"end as "Glasses Type",CASE	when zdf.ZFACIALHAIRTYPE = 1 then "None"	when zdf.ZFACIALHAIRTYPE = 2 then "Beard / Mustache"	when zdf.ZFACIALHAIRTYPE = 3 then "Goatee"	when zdf.ZFACIALHAIRTYPE = 5 then "Stubble"	else "UNK"end as "Facial Hair Type",CASE		when zdf.ZBALDTYPE = 2 then "Bald"	when zdf.ZBALDTYPE = 3 then "Not Bald"end as "Baldness",CASE	when zga.zlatitude = -180	then 'N/A'	else zga.ZLATITUDEend as "Latitude",CASE	when zga.ZLONGITUDE = -180 	then 'N/A' 	else zga.ZLONGITUDEend as "Longitude",	datetime(zga.zaddeddate+978307200, 'unixepoch') as "Date Added (UTC)",	ZMOMENT.ztitle as "Location Title"from zgenericasset zgaleft join zmoment on zmoment.Z_PK=zga.ZMOMENTleft join ZDETECTEDFACE zdf on zdf.ZASSET=zga.Z_PKleft join ZPERSON zp on zp.Z_PK=zdf.ZPERSONwhere zga.ZFACEAREAPOINTS > 0

以下是此分析的输出示例,与元数据来源的照片配对。您可以看到它能够通过姓名识别我和我的两个女儿,并准确评估我们的性别、我的太阳镜和面部毛发。

请测试、验证,并通过 Twitter @bizzybarney 向我提出任何问题或疑虑。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计