Yüz tanıma ve KVKK
Selfie ile etkinlik fotoğrafı bulma nasıl çalışır? Yüz tanıma ve KVKK
Photonza’nın “Beni Bul” özelliği: tarayıcıda selfie imzası, sunucuda fotoğraf taraması, gerçek bir düğünle yeniden ayarlanan eşikler ve rızaya bağlı yüz verisi saklama.


KISA CEVAP
Photonza’da misafirin selfie’si tarayıcıda YuNet ile bulunur ve SFace ile 128 boyutlu bir imzaya çevrilir; görüntü cihazdan çıkmaz. Etkinlik fotoğrafları sunucuda aynı modellerle taranır, imzalar kosinüs benzerliğiyle karşılaştırılır: 0,48 üstü (net yüzde) kesin, 0,42 üstü olası. Yalnızca açık rıza vermiş bir misafire benzeyen yüzlerin imzası saklanır ve tüm yüz verisi etkinlik bitişinden en geç 30 gün sonra silinir.
Selfie ile fotoğraf bulma hangi adımlardan oluşuyor?
Photonza’da bu özelliğin adı “Beni Bul”. Misafir etkinliğe QR ile girer, selfie çeker ve etkinliğe yüklenen fotoğraflardan kendisinin göründüklerini görür. Arkada dört adım var:
- Yüz bulma: YuNet dedektörü her yüz için bir kutu, bir güven skoru ve 5 nirengi noktası (iki göz, burun, iki ağız köşesi) verir.
- Hizalama: bu 5 nokta, en küçük kareler benzerlik dönüşümüyle 112×112 piksellik standart bir yüz kesitine oturtulur.
- İmza: SFace modeli kesitten 128 boyutlu bir vektör üretir, vektör L2 ile normalize edilir.
- Karşılaştırma: selfie imzası ile fotoğraflardaki yüz imzaları arasında kosinüs benzerliği hesaplanır, sonuç eşiklere göre “kesin” ya da “olası” diye etiketlenir.
İki model de OpenCV Zoo’dan geliyor: YuNet MIT, SFace Apache-2.0 lisanslı. Kayıtlı model kimliği yunet-2023mar+sface-2021dec-int8; tarayıcıya inen dosyalar yaklaşık 230 KB (YuNet) ve 9,9 MB (int8 SFace). Dış bir yüz tanıma servisi kullanılmıyor, yani yüz verisi üçüncü bir API’ye gitmiyor.
Selfie tarafında iki küçük ayrıntı var. İmza tek bir pozdan değil, selfie ile onun yatay ayna görüntüsünün imzalarının ortalamasından oluşur; aynada göz ve ağız köşesi noktalarının sırası da yer değiştirilir. Karede görüntünün %6’sından büyük birden fazla yüz varsa misafirden yalnızca kendi yüzüyle yeniden çekmesi istenir. Misafir farklı açılardan 5 selfie’ye kadar ekleyebilir; aramada en iyi eşleşen profil kazanır.
Neden ilk modeli bırakıp YuNet + SFace’e geçtik?
İlk sürüm BlazeFace ile yüz bulup Human kütüphanesinin FaceRes modeliyle 1024 boyutlu imza üretiyordu. 23 Eylül 2026’da 7 gerçek grup fotoğrafı ve aynı kişinin 4 fotoğrafta göründüğü bir selfie ile iki modeli yan yana ölçtük:
- Eski model: aynı kişinin en iyi benzerlikleri 0,50–0,65, yabancıların en yükseği 0,53. Dört fotoğraftan yalnızca biri, o da “olası” olarak bulundu.
- YuNet + SFace (int8, 128 boyut): aynı kişi 0,37–0,55, yabancıların en yükseği 0,255 (o da görüntü genişliğinin %0,9’u kadar küçük bir yüz). Dört fotoğrafın dördü bulundu, yanlış eşleşme yok.
Eski modelde sorun eşiğin yeri değildi: aynı kişi ile yabancıların benzerlik aralıkları iç içe geçiyordu, hangi eşiği seçsek ya kaçırıyor ya da başkasını gösteriyorduk. Yeni modelde mutlak sayılar daha düşük ama iki dağılım arasında belirgin bir boşluk var; eşik ayarı ancak böyle bir boşluk varsa işe yarıyor.
Geçişin bir maliyeti oldu. İmza boyutu 1024’ten 128’e indiği için 20260924000000_face_v2.sql göçü vektör sütunlarını VECTOR(128) yaptı, HNSW dizinlerini yeniden kurdu ve eski bütün yüz verisini sildi. Misafirlerin selfie profilleri de silindiğinden “Beni Bul” yeni bir selfie istedi; saklama süresi içindeki etkinlikler yeniden tarama kuyruğuna girdi. Belgede canlıya alma sırası da yazılı: önce web, sonra veritabanı göçü, en son tarama sunucusu. Sunucu göçten önce güncellenirse 128 boyutlu imza 1024’lük sütuna yazılamıyor.
Hesaplama nerede çalışıyor: tarayıcı mı, sunucu mu?
İkisi de. Selfie tarayıcıda işleniyor: onnxruntime-web ile WASM üzerinde, modeller sitenin kendi /models/face-v2/ yolundan yükleniyor. Sunucuya giden istek yalnızca 128 sayıdan oluşan imzayı ve tespit skorunu taşıyor; selfie görüntüsü cihazdan çıkmıyor. Ölçülen süreler model yükleme için 0,3 sn, selfie işleme için 0,3 sn.
Etkinlik fotoğrafları ise yüzlerce ya da binlerce olabildiği için sunucuda taranıyor. Tarama sunucusu bir Cloudflare Container: onnxruntime-node ve görüntü çözmek için sharp. sharp EXIF yönünü düzeltiyor; eski tfjs tabanlı çözücü bunu yapmadığı için yan dönmüş fotoğraflarda yüz kaçırılıyordu. Fotoğraf yüklenince bir veritabanı tetikleyicisi iş kuyruğuna satır ekliyor, dakikada bir çalışan cron kapsayıcıyı uyandırıyor, kapsayıcı işleri FOR UPDATE SKIP LOCKED ile alıyor. En fazla 5 kapsayıcı açılabiliyor; kuyruk boşalınca boşta çalışan kapsayıcılar kapatılıyor.
İki tarafın aynı sayıyı üretmesi şart, yoksa tarayıcıdaki selfie ile sunucudaki fotoğraf aynı uzaya düşmez. Bunun için çekirdek kod (src/lib/face-v2/) DOM’a bağımlı olmayan saf TypeScript; sunucuya derleme adımıyla lib/face-v2.mjs olarak gidiyor. Bir oracle testi çıktıları OpenCV’nin kendi FaceDetectorYN/FaceRecognizerSF sınıflarıyla karşılaştırıyor: kutu ve nirengi farkı 1,5 pikselin altında, imza kosinüsü en az 0,995 olmalı. Aynı selfie’nin tarayıcı (WASM) ve sunucu (Node) imzaları arasında ölçülen kosinüs 0,9987.
Büyük karelerde küçük yüzler kaçmasın diye dedektör 1920 piksel uzun kenarla çalışıyor; 2000 pikseli aşan fotoğraflar ayrıca %15 bindirmeli 2×2 parçaya bölünüp taranıyor, kareyi dolduran yakın yüzler için 640 piksellik kaba bir geçiş daha yapılıyor. Fotoğraf başına en fazla 50 yüz alınıyor. Yerel makinede ölçülen hız fotoğraf başına 0,71 sn (eski hatta 1,82 sn).
Eşleşme eşiği nasıl belirlendi ve gerçek düğünde neden değişti?
İlk eşikler OpenCV’nin SFace için önerdiği 0,363 kosinüs değeri ve 7 fotoğraflık test setiyle kondu. 28 Eylül 2026’da ilk gerçek düğünün sonuçları (220 fotoğraf, 1.603 yüz, selfie çeken 37 misafir) tek tek gözle kontrol edildi ve test setinin eşiklerinin gevşek kaldığı görüldü: 0,33–0,40 bandı çoğunlukla başka birine aitti, aynı kişinin doğru eşleşmeleri 0,44–0,77 arasındaydı. Bugünkü kurallar src/lib/face-utils.ts içinde:
- 0,55 ve üstü: yüzün boyutuna bakılmadan “kesin”.
- 0,48 ve üstü: yüz genişliği görüntünün en az %2’si ve dedektör skoru en az 0,5 ise “kesin”.
- 0,42 ve üstü: “olası”. Altı hiç gösterilmez.
Küçük ya da bulanık bir yüz yüksek benzerlik verse bile ancak “olası” olabiliyor; amaç kapalı bir etkinlikte misafire başkasının fotoğrafını göstermemek.
Aynı gün iki kural daha değişti. Fotoğraftan fotoğrafa benzerlikle sonuçları genişleten “kişi kümeleme” adımı kaldırıldı: gerçek düğünde tek bir yanlış tohum, 150 fotoğrafta görünen gelinin karelerini kadın misafirlerin sonuçlarına taşıyordu. Yerine “bu yüz kime ait?” kuralı geldi: bir yüz, etkinlikte ona en çok benzeyen misafire veriliyor.
Başka bir misafirin selfie’si en az 0,03 daha yakınsa yüz bu misafirin sonucundan düşüyor. Aynı kişinin farklı tarayıcılardan açtığı hesaplar (selfie’leri birbirine 0,55 ve üstü benzeyenler) rakip sayılmıyor. Belgede açıkça yazdığı gibi, ölçüm seti hâlâ küçük ve eşikler yeni etkinliklerle yeniden kalibre edilecek.
Yüz verisi nerede ve hangi koşulla saklanıyor?
Fotoğraflarda “Beni Bul”u hiç kullanmayan insanlar da var: diğer misafirler, garsonlar, çocuklar. Bu yüzden 25 Eylül 2026’dan beri yeni etkinliklerin varsayılanı face_store_policy = 'consented'. Tarama sunucusu her yüzün imzasını hesaplıyor, bellekte etkinliğin rızalı selfie profilleriyle karşılaştırıyor ve yalnızca bir rızalı profile 0,33 ve üstü benzeyen yüzün imzasını face_embeddings tablosuna yazıyor. Diğer imzalar hiçbir yere yazılmadan atılıyor. Saklanan her imza, hangi profillere benzediğini matched_profile_ids dizisinde tutuyor.
Saklama eşiği (0,33) gösterim eşiğinden (0,42) bilerek düşük: gösterim eşiği gerçek veriyle yeniden ayarlanabilsin diye biraz pay bırakılıyor, ama misafire yalnızca gösterim eşiğini geçenler gösteriliyor.
Selfie’sini sonradan çeken misafir için ne yapılıyor? Her yüz için imza içermeyen bir tespit kaydı (face_detections) tutuluyor: kutu, 5 nirengi noktası, skor ve hizalı 112×112 kesit. Yeni bir selfie profili gelince tetikleyici bir yeniden tarama isteği açıyor; sunucu kesitleri yeniden imzalayıp yalnızca o profille karşılaştırıyor, tam fotoğrafı yeniden çözmüyor. Kesitler veritabanında bytea olarak, kayıpsız WebP biçiminde (yaklaşık 12 KB) duruyor. JPEG denendi: q92 kalitede 242 yüzlük ölçümde imza 0,08’e kadar kaydı, en düşük gidiş-dönüş kosinüsü 0,88 çıktı; kayıpsız WebP birebir aynı imzayı veriyor.
1.000 fotoğraflık yük testinde 34.577 tespit ve yaklaşık 414 MB kesit oluştu. Tek çağrıda yapılan yeniden tarama 15 dakikayı aşıp 900 saniyelik kiralamayı doldurunca baştan başlıyordu; şimdi 45 saniyelik dilimlerle çalışıyor ve bir imleçle kaldığı yerden devam ediyor. Bu tablolara yalnızca sunucu tarafındaki servis rolü erişebiliyor; organizatör de dâhil hiçbir kullanıcı rolüne izin verilmiyor.
KVKK açık rıza akışı ekranda ve sunucuda nasıl uygulanıyor?
Bu bölüm hukuki görüş değil; ürünün ne yaptığını anlatıyor. Yüz imzası KVKK’da özel nitelikli kişisel veri sayılıyor; Photonza bunu misafirin açık rızasına dayandırıyor ve kurguyu buna göre kurdu.
- Aydınlatma ile rıza ayrı. Selfie öncesindeki ekranda üç maddelik bir özet ve Biyometrik Veri Aydınlatma Metni’nin tamamına bağlantı var; aydınlatma onaya bağlanmıyor. Rıza kutusunun metni, Açık Rıza Metni’nin kendisi. Ayrım, belgelerde 2026/347 sayılı ilke kararına dayandırılıyor.
- İki ayrı kutu, ikisi de varsayılan boş: biyometrik rıza ve 18 yaş beyanı. “Beni Bul” 18 yaş altına kapalı.
- Reddetmek hizmeti kısıtlamıyor. “Vazgeç” diyen misafir galeriyi kullanmaya devam ediyor. Gereklilik ve ölçülülük değerlendirmesi de bunu temel alıyor: biyometrik olmayan yol (galeride gezinme) herkese ek maliyetsiz açık, “Beni Bul” onun yerine değil yanında sunuluyor.
- Sunucu rızasız imza yazmıyor.
/api/register-face, kullanıcının o etkinlik için güncel metin sürümüne ait, geri alınmamış birkind='biometric'kaydı arıyor; yoksa ve istek uygun bir rıza bilgisi taşımıyorsaconsent_requireddönüyor ve hiçbir şey yazılmıyor. Rıza kaydı, kutu işaretlendiği istekte imzadan önce oluşturuluyor. - Rıza sürümlü. Metin anlam değiştirecek şekilde güncellenince
LEGAL_VERSIONS.biometricdeğişiyor (bugünkü değer2026-09-25e) ve yeni selfie kaydında rıza ekranı yeniden açılıyor.
Rıza kayıtları user_consents tablosunda kullanıcı, etkinlik, tür, sürüm, dil, kabul ve geri alma zamanıyla tutuluyor. Endpoint’ler ayrıca misafirin etkin katılımcı olmasını, etkinliğin açık olmasını, etkinlikte yüz tanımanın açık olmasını ve fotoğrafçının “kendini bulabilsin” iznini kontrol ediyor. Rıza metni amacı sınırlıyor: kimlik tespiti, etkinlikler arası eşleştirme, model eğitimi ve reklam yok.
Yüz verisi ne zaman ve nasıl siliniyor?
Saklama süresi tek bir kurala bağlı: yüz verisi en geç etkinlik bitişinden 30 gün sonra etkinlikle birlikte siliniyor. Saklama ve İmha Politikası bu süreyi, misafirin indirme döneminde de fotoğraflarını bulabilmesiyle gerekçelendiriyor. Süre dolan etkinlik silinmek üzere işaretleniyor, saatlik çalışan bir temizlik işi dosyaları depolamadan, kayıtları veritabanından siliyor. Endpoint’ler de aynı sınırı kendileri uyguluyor: bitişten 30 gün sonra selfie kaydı ve arama kapanıyor.
Daha erken silinme yolları:
- “Yüz verimi sil” düğmesi
delete_my_face_dataRPC’sini çağırıyor: selfie profilleri ve eşleşmeler siliniyor, rıza kaydınawithdrawn_atyazılıyor. Etkinlikten ayrılmak (leave_event) aynı fonksiyonu çağırıyor. - Profil silinince bir tetikleyici (
trg_release_profile_face_embeddings) o profili saklanan fotoğraf imzalarından düşüyor; başka bir rızalı profile bağlı kalmayan imza siliniyor. Bekleyen yeniden tarama istekleri de CASCADE ile gidiyor. - Fotoğraf silinirse ona ait imzalar ve tespit kayıtları (kesitler dâhil) yabancı anahtar CASCADE’i ile aynı anda siliniyor.
Silme de ölçekte ayrı bir iş çıktı. Yük testindeki büyük etkinliği tek ifadeyle silmek (35 bin satır, yaklaşık 420 MB kesit) veritabanındaki 8 saniyelik statement_timeout sınırını aştı ve HTTP 500 ile etkinlik silinmeden kaldı. Şimdi purge_event_face_data ve purge_user_face_data yüz verisini sınırlı partilerle, 0 dönene kadar siliyor; etkinlik ya da hesap silme ondan sonra çalışıyor.
Rıza kayıtları ise ispat için ilişki sürdükçe tutuluyor; geri alınan, yenisiyle değiştirilen ya da kullanıcısı silinen kayıtlar üç yıl sonra temizleniyor. İlk sürüm üç yılı dolan aktif rızaları da siliyordu; KVKK denetiminde fark edilip ayrı bir göçle düzeltildi.
Benzer bir özellik kuracaksanız neye dikkat etmeli?
Photonza’da bu özelliği kurarken ve bir gerçek düğünde düzeltirken kendimize çıkardığımız liste:
- Modeli eşikten önce ölçün. Aynı kişi ile yabancıların benzerlik aralıkları örtüşüyorsa hiçbir eşik sorunu çözmez; YuNet + SFace geçişi bizde 1/4’ten 4/4’e bu yüzden çıktı.
- Tarayıcı ve sunucu aynı imzayı üretmeli. Ortak saf bir çekirdek ve referans uygulamaya karşı bir oracle testi (bizde kosinüs ≥ 0,995) olmadan iki taraf sessizce ayrışabilir.
- Eşikleri test setiyle değil gerçek etkinlikle kalibre edin ve sonuçları gözle kontrol edin. Gelin gibi yüzlerce karede görünen biri, zincirleme genişleyen her algoritmayı bozabiliyor.
- Küçük ve düşük skorlu yüzleri “kesin” saymayın. Yanlış pozitif, kapalı bir etkinlikte başkasının fotoğrafını göstermek demek.
- Rıza vermemiş kişilerin imzasını saklamayın. Yeniden tarama gerekiyorsa şablon yerine kesit tutun ve kayıpsız biçim kullanın.
- Rızayı ekranda değil sunucuda zorlayın: sürümlü rıza kaydı yoksa yazma yok.
- Silmeyi tetikleyici ve CASCADE ile veri modeline gömün, büyük silmeleri partilere bölün.
- Biyometrik olmayan bir alternatif (galeri) her zaman ücretsiz ve eşit erişilebilir olsun.
AMG Yazılım olarak bu tür yüz eşleştirme, rıza ve veri saklama kurgusu içeren web ürünleri geliştiriyoruz.
PROJENİZE UYARLAYALIM
Web Geliştirme
Next.js ile kurumsal site, ürün sitesi ve SaaS paneli. Hız ve arama görünürlüğü baştan planlanır.
