跳至主要內容
01 · 專案記錄開發中原型

EchoReel: 裝置端相片回憶系統

在不依賴雲端伺服器的前提下,使用 Apple Vision 與 SwiftUI 製作本機照片回憶影片的 iOS 應用程式。

負責範疇規格定義、架構設計、記憶體控制與測試
運行環境iOS 17+ · SwiftUI
核心框架PhotoKit · Vision · AVFoundation
資料與網路零網路權限 · 本機 SQLite

目前進度與實測現況

專案狀態: 開發中原型 / ONGOING PROTOTYPE
✓ 已完成並正常運作
  • PhotoKit 本機讀取相片,不申請亦無任何網路權限
  • Apple Vision 於裝置端偵測人臉位置與特徵
  • 審查佇列(Review Queue)分群機制,必須手動點擊才確認身分
  • SQLite 本機圖譜儲存已確認的關聯資料
  • AVFoundation 前景運算輸出照片動態回顧影片
⚠ 目前限制與未解決問題
  • 候選人臉特徵向量模型召回率未達門檻(實測 0.585 vs 目標 0.850),已淘汰捨棄
  • 受限於 iOS 記憶體上限,無法於背景執行高負載影片渲染,需維持前景輸出
  • 尚未上架 App Store 亦無外部用戶,僅在個人測試圖庫(1,420 張照片)驗證

01 · 想要解決的問題

主流的雲端相片服務通常會把個人的完整相片庫上傳到遠端伺服器,進行人臉辨識並自動生成回憶合輯。這會衍生隱私上的疑慮:生物特徵資料存放在遠端,且演算法一旦誤判,容易自動把不同人永久合併進同一個相簿紀錄。

EchoReel 是一套從一開始就以「本機優先」為原則打造的 iOS 應用程式。目標是在手機上完成人臉偵測、分群與照片動態回顧輸出,在維持極低記憶體增長(每張照片 ≤0.25 MiB)的同時,確保沒有任何照片或人臉特徵資料離開手機。

02 · 實作前提與限制

零網路權限

App 沙盒完全不宣告網路存取權限。沒有任何遠端呼叫、追蹤碼或第三方分析工具。

記憶體嚴格控管(≤0.25 MiB)

批次處理 4800 萬像素照片時,不能觸發 iOS Jetsam 記憶體強制終止機制。平均每張照片記憶體增長控制在 0.250 MiB 以下。

必須手動確認身分

演算法只能產出候選分群建議,在使用者於介面上親自點擊確認前,絕不自動寫入關聯。

03 · 架構劃分與權限隔離

透過 Swift Actor 將照片讀取、Apple Vision 人臉運算、候選名單與使用者審查介面完全隔離。

資料流程與權限劃分

裝置端相片處理流程

零網路傳輸
01 PhotoKit

01. 本機讀取:透過 PhotoKit 讀取相片,App 本身完全不申請網路權限。

02 Vision

02. 裝置端偵測:由 Apple Vision 找出人臉位置與特徵點,全程在 iPhone 上運算。

03 整理候選

03. 整理候選:把特徵相近的照片挑成候選組,不直接判定屬於同一個人。

04 手動確認人工確認

04. 手動確認:在 SwiftUI 審查介面由使用者確認人物,避免系統誤認。

05 SQLite 儲存

05. 本機儲存:確認後的關聯寫入本機 SQLite,平均每張照片記憶體增加 0.215 MiB。

06 AVFoundation

06. 影片合成:使用 AVFoundation 直接在裝置上輸出回憶影片。

當前步驟:01. 本機讀取:透過 PhotoKit 讀取相片,App 本身完全不申請網路權限。
Actor 權限界線: 負責照片分析的 Background Actor(PhotoAnalysisActor)被禁止直接寫入 SQLite 資料庫。它只能把找出的可能分群以記憶體資料傳給待審清單。只有當使用者在畫面上點擊確認按鈕時,MainActor 才會正式寫入關聯。

04 · 三項主要技術取捨

1. 採用 Apple 原生 Vision,不額外打包自製模型

減少依賴

評估考量:評估過打包自製的 CoreML 人臉特徵模型,與直接調用 iOS 內建的 Apple Vision 框架。
採用的做法:決定使用 iOS 內建的 Vision 框架。系統會跨應用共用 Neural Engine 權重,讓 App 安裝檔大小從 85MB 縮減至 14MB,同時確保硬體加速。
結果與取捨:不同 iOS 小版本間的 API 行為需個別處理相容性,已透過單元測試與適配層封裝解決。

2. 降採樣縮圖管線與記憶體主動釋放

記憶體控制

評估考量:批次讀取 4800 萬像素的 ProRAW 原始檔時,讀到第 30 張就容易吃光手機記憶體導致閃退。
採用的做法:在解碼前使用 CGImageSourceCreateThumbnailAtIndex 先將尺寸限制在 512px,並以 autoreleasepool 明確釋放暫存物件。
結果與取捨:在超過 1,000 張照片的批次測試中,平均每張照片記憶體增長維持在 0.215 MiB,安全低於 0.250 MiB 的上限。

3. 拒絕模糊門檻的自動合併

資料準確性

評估考量:市面上許多相簿軟體會自行設定相似度門檻(如餘弦距離 < 0.6)直接將照片歸為同一人。
採用的做法:對於相似度落在中間模糊區間的照片,一律歸入審查佇列(Review Queue),絕不自動綁定身分。
結果與取捨:在測試圖庫中維持零錯誤合併;使用者對自己的人臉相簿擁有 100% 的決定權。

05 · 遇過最棘手的問題

AVFoundation 影片輸出時的記憶體累積

問題原因:製作動態平移縮放(Ken Burns)效果時,使用 AVVideoCompositionCoreAnimationTool 會快速累積 CALayer 與 CVPixelBuffer 暫存,釋放速度趕不上產生速度。

解決方式:重寫輸出流程,透過自訂的 VideoExportSessionCoordinator 控制預先讀取幀數並重複利用緩衝區;改用 AVVideoCompositing 搭配 Metal 著色器直接處理畫面變換。

測試結果:透過 Xcode Instruments 檢查,連續跑 20 次 60 秒 1080p 影片輸出均無記憶體洩漏,常駐記憶體維持在 68 MB 以下。

06 · 失敗的實驗:自製人臉特徵模型

把未達標準的實驗老實記錄下來,是工程開發重要的一環。當候選方案未達測試門檻時,及早淘汰比勉強上線更重要。

門檻紀錄 · ECHOREEL-021
未達標 · 已捨棄
測試對象
候選人臉特徵模型
預期目標
Recall@50 ≥ 0.850000
實測結果
0.585117 (-0.264883)
淘汰原因: 測試的候選模型召回率只有 0.585,未達 0.85 的標準,容易把不同親友誤判在一起。為避免搞砸使用者的相片分類,我直接捨棄這款模型,改用 Apple 原生 Vision API 搭配手動確認。

07 · 測試覆蓋與實機驗證

62/62 個 SWIFT 測試通過

包含相片庫掃描、人臉邊界正規化與 SQLite 關聯查詢的單元與整合測試。

3/3 模擬器自動化流程

以 XCUITest 跑完首次引導、相簿掃描分類與影片匯出流程。

317 個 SWIFT 原始碼檔案零警告

符合嚴格的 SwiftLint 規則與 Swift 5.9/6.0 並行安全性檢查。

312/312 檔案版本吻合

目前版本與本機封存代碼的一致性比對完全相符。

08 · 專案回顧

專案心得:在手機上做這類應用,本質上是系統資源與記憶體管理的功課,而不只是挑選模型。如果一跑背景掃描就讓手機發燙閃退,或是匯出影片時畫面卡死,模型再先進也沒有意義。明確的並行權限隔離與記憶體控制,遠比追求複雜的模型結構更關鍵。

如果重做會做的調整:如果重來一次,我會在前期先準備好更多元角度與光線的人臉測試資料集,提早在電腦上把 Apple Vision 的極限測清楚,而不是等到裝上手機才逐一驗證邊界情況。