קיבוץ פנים מבוסס בינה מלאכותית לגלריות תמונות במובייל
תארו לעצמכם: למשתמש יש 50,000 תמונות בגלריה. מציאת כל הצילומים עם אדם מסוים ידנית לוקחת שעות. גלריה טיפוסית מכילה עשרות אלפי תמונות, וכל צילום עשוי לכלול מספר פנים — בינה מלאכותית היא הכרחית. קיבוץ אוטומטי לפי פנים פותר זאת, אך דורש יישום זהיר: דיוק זיהוי, מהירות חילוץ הטמעות, וקיבוץ נכון. אנו מתמחים בצינורות כאלה: 8+ שנים בפיתוח מובייל, 20+ פרויקטים עם ראייה ממוחשבת. הפתרון שלנו פועל כולו על המכשיר: זיהוי פנים (Vision/ML Kit) → חילוץ הטמעות עם MobileFaceNet (וקטור 128-ממדי) → קיבוץ DBSCAN. גישה זו מבטיחה פרטיות, מהירות, ופעולה לא מקוונת. עיבוד על המכשיר חוסך עד 70% בעלויות תשתית ענן בהשוואה לפתרונות שרת — ומפחית את עלות הבעלות הכוללת (TCO) של האפליקציה.
כיצד קיבוץ בינה מלאכותית פועל על המכשיר
הצינור מורכב משלושה שלבים, כל אחד מותאם למעבדים ניידים.
זיהוי פנים
ב-iOS, אנו משתמשים ב-VNDetectFaceRectanglesRequest ממסגרת Vision:
let request = VNDetectFaceRectanglesRequest { req, _ in
guard let faces = req.results as? [VNFaceObservation], !faces.isEmpty else { return }
for face in faces {
let faceRect = VNImageRectForNormalizedRect(face.boundingBox, width, height)
self.extractEmbedding(from: originalImage.cropping(to: faceRect)!)
}
}ב-Android — ML Kit let request = VNDetectFaceRectanglesRequest { req, _ in guard let faces = req.results as? [VNFaceObservation], !faces.isEmpty else { return } for face in faces { let faceRect = VNImageRectForNormalizedRect(face.boundingBox, width, height) self.extractEmbedding(from: originalImage.cropping(to: faceRect)!) } } (אינטגרציה פשוטה) או MediaPipe FaceDetector (שליטה רבה יותר). הבחירה תלויה בדרישות הדיוק וגודל המודל.
חילוץ הטמעות
Apple אינה מספקת API מובנה לזיהוי פנים (רק זיהוי). אנו משתמשים ב-MobileFaceNet — מודל קומפקטי (1–3 MB) הפועל דרך Core ML. לאחר נורמליזציה L2, המרחק הקוסינוס בין הטמעות של אותו אדם הוא <0.3, בין אנשים שונים >0.6. סף של 0.4–0.5 עובד היטב בפועל.
func extractEmbedding(from faceImage: CGImage) -> [Float]? {
guard let input = try? MobileFaceNetInput(face_image: MLMultiArray(from: resize(faceImage, to: CGSize(width: 112, height: 112)))) else { return nil }
guard let output = try? facenetModel.prediction(input: input) else { return nil }
let embedding = (0..<128).map { output.embedding[$0].floatValue }
return l2Normalize(embedding)
}
func l2Normalize(_ v: [Float]) -> [Float] {
let norm = sqrt(v.reduce(0) { $0 + $1 * $1 })
return norm > 0 ? v.map { $0 / norm } : v
} קיבוץ
לקיבוץ ללא מספר קבוע מראש של קבוצות, אנו משתמשים ב-DBSCAN (קיבוץ מרחבי מבוסס צפיפות). ב-Swift, אנו מיישמים זאת עם Accelerate/BLAS לחישוב מרחק קוסינוס:
func dbscan(embeddings: [[Float]], eps: Float = 0.45, minPoints: Int = 2) -> [Int] {
var labels = Array(repeating: -1, count: embeddings.count)
var clusterId = 0
for i in 0..<embeddings.count {
guard labels[i] == -1 else { continue }
let neighbours = rangeQuery(embeddings: embeddings, idx: i, eps: eps)
if neighbours.count < minPoints { continue }
labels[i] = clusterId
var seeds = neighbours
while !seeds.isEmpty {
let q = seeds.removeFirst()
if labels[q] == -1 {
labels[q] = clusterId
}
if labels[q] != clusterId { continue }
labels[q] = clusterId
let qNeighbours = rangeQuery(embeddings: embeddings, idx: q, eps: eps)
if qNeighbours.count >= minPoints {
seeds.append(contentsOf: qNeighbours)
}
}
clusterId += 1
}
return labels
}לגלריות עם 20,000+ הטמעות, DBSCAN עם O(n²) יכול לרוץ 10–30 שניות. האצה מושגת דרך חיפוש שכן קרוב מקורב (ANN) באמצעות FAISS (עם קישורי Swift), ומפחיתה את המורכבות ל-O(n log n).
מידע נוסף על מודל MobileFaceNet
MobileFaceNet היא רשת קונבולוציונית בת 4 שכבות המחלצת הטמעות 128-ממדיות. גודל המודל הוא רק 1–3 MB, אידיאלי למכשירים ניידים. הוא אומן על מערך הנתונים MS-Celeb-1M.למה על המכשיר עדיף על פני שרת
| קריטריון | על המכשיר | בצד השרת |
|---|---|---|
| פרטיות | ההטמעות לעולם לא עוזבות את המכשיר | דורש הסכמה להעברת נתונים ביומטריים |
| מהירות | מיידי, ללא השהיית רשת | תלוי בחיבור ובעומס |
| מצב לא מקוון | פונקציונליות מלאה ללא אינטרנט | דורש חיבור קבוע |
| עלות תשתית | ללא עלויות שרת | הוצאות GPU ואחסון גבוהות |
עיבוד על המכשיר מהיר פי 3–5 לגלריות טיפוסיות (עד 10,000 תמונות) ועומד במלואו ב-GDPR, CCPA ותקנות אחרות. עלויות מחשוב ענן יורדות לאפס, מה שחשוב במיוחד לסטארטאפים עם תקציבים מוגבלים.
השוואת מודלי הטמעות
| מודל | גודל | ממדיות | דיוק (LFW) |
|---|---|---|---|
| MobileFaceNet | 1-3 MB | 128 | 99.2% |
| FaceNet | 10-30 MB | 512 | 99.6% |
| ArcFace | 20-50 MB | 512 | 99.8% |
ליישומים ניידים, MobileFaceNet הוא אופטימלי — איזון בין דיוק וביצועים.
ביצועים בגלריות גדולות
גלריה אמיתית מכילה 5,000–50,000 תמונות. פנים מופיעות בכ-30–40% מהן. לדוגמה, 10,000 תמונות עם פנים, 2 פנים בממוצע = 20,000 הטמעות. DBSCAN מעבד אותן ב-10–30 שניות באייפון 14. עם FAISS — 2–3 שניות. אנו רושמים את משימת הרקע דרך FaceDetector (iOS 13+):
BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.app.faceGrouping") { task in
let bgTask = task as! BGProcessingTask
self.runFaceGrouping(completion: {
bgTask.setTaskCompleted(success: true)
})
bgTask.expirationHandler = {
/* save progress */
}
} אחסון תוצאות
ההטמעות הן נתונים ביומטריים, ולכן אנו מאחסנים אותן מקומית ב-Core Data עם הצפנת Data Protection (func extractEmbedding(from faceImage: CGImage) -> [Float]? { guard let input = try? MobileFaceNetInput(face_image: MLMultiArray(from: resize(faceImage, to: CGSize(width: 112, height: 112)))) else { return nil } guard let output = try? facenetModel.prediction(input: input) else { return nil } let embedding = (0..<128).map { output.embedding[$0].floatValue } return l2Normalize(embedding) } func l2Normalize(_ v: [Float]) -> [Float] { let norm = sqrt(v.reduce(0) { $0 + $1 * $1 }) return norm > 0 ? v.map { $0 / norm } : v } ). מזהה המיפוי הוא func dbscan(embeddings: [[Float]], eps: Float = 0.45, minPoints: Int = 2) -> [Int] { var labels = Array(repeating: -1, count: embeddings.count) var clusterId = 0 for i in 0..<embeddings.count { guard labels[i] == -1 else { continue } let neighbours = rangeQuery(embeddings: embeddings, idx: i, eps: eps) if neighbours.count < minPoints { continue } labels[i] = clusterId var seeds = neighbours while !seeds.isEmpty { let q = seeds.removeFirst() if labels[q] == -1 { labels[q] = clusterId } if labels[q] != clusterId { continue } labels[q] = clusterId let qNeighbours = rangeQuery(embeddings: embeddings, idx: q, eps: eps) if qNeighbours.count >= minPoints { seeds.append(contentsOf: qNeighbours) } } clusterId += 1 } return labels } , לא התמונה עצמה. ההטמעות אינן מסונכרנות ל-iCloud ללא הסכמה מפורשת של המשתמש.
מה כלול בעבודה
- ניתוח — הערכת גודל גלריה, בחירת מודלי זיהוי וקיבוץ.
- עיצוב — ארכיטקטורת צינור, אינטגרציה עם Core Data / Room.
- יישום — קידוד ב-Swift/Kotlin, אימון/כיול ספים.
- בדיקות — בדיקות A/B על גלריות אמיתיות, אימות דיוק קיבוץ.
- תיעוד — תיאור API, מדריך אינטגרציה, מדריך תצורה.
- תמיכה — אחריות ל-3 חודשים, תחזוקה במהלך עדכוני מערכת הפעלה.
הצוות שלנו עם 8 שנות ניסיון והסמכות Apple/Google מבטיח מסירה בזמן. הזמינו פיתוח קיבוץ בינה מלאכותית על המכשיר לאפליקציה שלכם. קבלו ייעוץ — נמצא את הפתרון האופטימלי לגלריה שלכם.
לוחות זמנים
צינור בסיסי על המכשיר (זיהוי, הטמעות, קיבוץ) לגלריות בינוניות לוקח 2–3 שבועות. גרסה ניתנת להרחבה עם FAISS, עיבוד רקע, עדכונים מצטברים וממשק משתמש לוקחת 4–5 שבועות. צרו קשר — נחשב עלות ולוחות זמנים באופן אישי.
FaceNet: A Unified Embedding for Face Recognition and Clustering, Schroff et al.







