---
title: "Praxe: Najdi se ve fotkách čili jak jsme postavili selfie vyhledávání ve fotogalerii akcí"
description: "Jak jsme pro Vibecoding workshopy postavili systém, kde účastník zadá e-mail, pořídí selfie a za pár sekund vidí fotky, na kterých je? AWS Rekognition, Cloudflare Durable Objects a GDPR jako technická podmínka, ne formalita. Pojďme spolu do praxe!"
date: 2026-06-25
author: "Patrick Zandl"
url: https://www.vibecoding.cz/articles/ostatni/praxe-najdi-se-ve-fotkach/
---

> Markdown verze článku z Vibecoding.cz. HTML verze: https://www.vibecoding.cz/articles/ostatni/praxe-najdi-se-ve-fotkach/
> Přehled veškerého obsahu webu: https://www.vibecoding.cz/llms.txt

# Praxe: Najdi se ve fotkách čili jak jsme postavili selfie vyhledávání ve fotogalerii akcí

Každý, kdo byl na větší akci, zná ten rituál. Fotograf odevzdá ZIP s dvěma stovkami fotek, vy si je rozbalíte, hodinu proklikáváte náhledy a nakonec máte čtyři fotky, kde jste vy — a dalších šest, kde jste to sice vy, ale jen ze zadu nebo z poloviny schovaní za někým jiným. Přitom fotky existují, dobrý fotograf je pořídil, jen je nikdo neoznačil.

My jsme chtěli tohle vyřešit jednou provždy. Uživatel pořídí selfie přímo v prohlížeči (žádná aplikace, žádný účet) a za pár sekund vidí fotky, na kterých pravděpodobně je. Myšlenka je jednoduchá. Technická realita a právní rámec kolem ní jednoduchá nejsou, a o tom bude tento článek.

**Vyzkoušejte [naši fotogalerii z Vibecoding Talks 15.6.2026](https://www.vibecoding.cz/album/vt-06-2026)** - a pokud jste byli na akci, můžete svým emailem projít k vyhledávání podle selfies.

## Co jsme měli a co jsme potřebovali

Naše platforma Vibecoding.cz běží na [Cloudflare Pages](https://pages.cloudflare.com/) a [Workers](https://workers.cloudflare.com/) — tedy plně bez vlastního serveru, na distribuované "hraně" sítě. Fotky leží v [Cloudflare R2](https://developers.cloudflare.com/r2/), což je úložiště kompatibilní s S3, ale bez poplatků za přenos dat. Metadata a registrace účastníků sedí v [Cloudflare D1](https://developers.cloudflare.com/d1/) — SQLite databáze spravovaná Cloudflare, dostupná přímo z Workers. Framework je [Astro 4](https://astro.build/) v hybridním módu, kde statické stránky coexistují se serverově renderovanými.

| Vrstva | Technologie | Co dělá |
| --- | --- | --- |
| Výpočty | Cloudflare Workers | Serverless funkce na hraně sítě, globálně distribuované |
| Úložiště fotek | Cloudflare R2 | Objektové úložiště bez poplatků za přenos dat |
| Databáze | Cloudflare D1 | SQLite na hraně sítě, pro metadata a registrace |
| Framework | Astro 4 | Hybridní SSR + statika, React ostrůvky pro interaktivitu |

Co jsme k tomu potřebovali přidat:

- Vyhledávání podle selfie — jedinkrát, bez vytváření účtu
- Galerii propojenou s výsledky — kliknutí na výsledek otevře fotku v kontextu celého alba
- Sekci „Kdo ještě je na téhle fotce s tebou" v lightboxu
- Vše funkční bez long-running serverů, protože žádné nemáme

Ten poslední bod je klíčový. Worker má limit třicet sekund na jedno zpracování. Album může mít tisíce fotek. Rozpoznávání obličejů je výpočetně náročné — nedá se to udělat na CPU ve Workeru v rozumném čase.

## Výběr technologie: proč AWS Rekognition

Tři kandidáti přišli v úvahu přirozeně: [Amazon Rekognition](https://aws.amazon.com/rekognition/), Google Cloud Vision a Azure Face API. Všechny tři nabízejí rozpoznávání obličejů jako cloudovou službu — pošlete fotku, dostanete zpět biometrická data bez nutnosti provozovat vlastní model.

Amazon Rekognition je Amazonova služba pro analýzu obrázků a videa pomocí strojového učení. Umí detekovat objekty, scény a text, ale nás zajímá hlavně rozpoznávání obličejů — konkrétně schopnost indexovat obličeje do kolekcí a pak v nich hledat. Existuje od roku 2016 a mezitím se stala zralou, stabilní platformou.

Rekognition vyhrál ze tří důvodů.

**Prvním je geografie.** Rekognition nabízí region `eu-central-1` ve Frankfurtu — biometrická data neopouštějí Evropskou unii. Pro GDPR je to zásadní argument.

**Druhým důvodem je přesná shoda API s tím, co potřebujeme.** Rekognition nabízí tři operace, které se mapují 1:1 na naše tři use-casy:

| Operace Rekognition | Co dělá | Náš use-case |
| --- | --- | --- |
| `IndexFaces` | Zpracuje fotku, extrahuje biometrické vektory, uloží do kolekce | Indexace alba při nahrání fotek |
| `SearchFacesByImage` | Přijme obrázek, porovná s kolekcí, vrátí shody se skórem | Selfie vyhledávání |
| `SearchFaces` | Najde obličeje podobné zadanému `face_id` v rámci kolekce | Předpočet příbuzných fotek |

Tohle je elegantní. Nemusíme sami spravovat vektory, srovnávat vzdálenosti ani nic podobného — Rekognition to dělá za nás a vrátí pouze identifikátory a skóre podobnosti.

**Třetím důvodem je cena.** Při tisíci fotkách v albu a třech stovkách hledajících účastníků vyjde jedna nafocená akce přibližně na tři až čtyři dolary. To je přijatelné. To ještě vyhodnotíme podle skutečné faktury.

Co jsme odmítli a proč:

- **Lokální zpracování ve Workeru** — Workers mají CPU limit a třicetisekundový timeout. Zpracování tisíce fotek by trvalo desítky minut. Vyloučeno.
- **Vlastní model na GPU serveru** — přidalo by infrastrukturu, kterou nechceme spravovat, a výrazně by to zkomplikovalo nasazení.
- **Přístupy bez neuronových sítí** — histogramové shody a podobné heuristiky nejsou spolehlivé při různém osvětlení, úhlech a změnách vzezření (brýle, fousy).

## Jak funguje selfie vyhledávání

Celý tok od zadání e-mailu po zobrazení výsledků vypadá takto:

```
Účastník zadá e-mail
       ↓
Systém pošle magic link přes e-mail (token v D1, platnost 30 minut)
       ↓
Kliknutí na odkaz → token se okamžitě vyruší (jednorázový)
       ↓
Worker nastaví HttpOnly cookie s HMAC-SHA256 podpisem (platnost 24 hodin)
       ↓
Stránka /najdi — kamera nebo upload selfie
       ↓
Selfie putuje do Workeru → Worker volá SearchFacesByImage (AWS Rekognition)
       ↓ selfie se NIKDY neukládá
Rekognition vrátí face_id a skóre podobnosti
       ↓
Worker přeloží face_id na photo_id přes D1
       ↓
Výsledky rozdělené do dvou sekcí podle jistoty
```

Zastavme se u té cookie. E-mail samotný se nikde v systému neukládá jako text. Pracujeme výhradně s jeho HMAC-SHA256 hashem — jednosměrnou kryptografickou funkcí. Kdokoli by se dostal k obsahu naší databáze, nemůže e-mailové adresy zpětně rekonstruovat. Pro párování s registracemi nám hash stačí: při registraci spočítáme hash ze zadaného e-mailu, při přihlášení spočítáme hash znovu a porovnáme. Snadno tak zabráníme kompromitaci a chráníme soukromí uživatele.

### Dvě sekce výsledků: proč ne jedna

Rekognition vrací ke každému nalezenému obličeji skóre podobnosti od nuly do stovky. Naivní přístup — nastavit jeden práh, třeba osmdesát procent, a zobrazit vše nad ním — má problém: při tomto prahu se občas objeví falešné shody. Jiná osoba s podobnými rysy, neideální světlo, fotka z profilu.

Testovali jsme na validační sadě padesáti osob a pěti stovkách fotek, záměrně v různých podmínkách: špatné světlo, brýle, čepice, profil. Výsledek:

| Práh podobnosti | Chybovost (falešné pozitivy) | Doporučené zobrazení |
| --- | --- | --- |
| ≥ 95 % | 0 % na validační sadě | Plný náhled, tlačítko ke stažení |
| 80–94 % | Nízká, ale nenulová | Degradovaný 80px náhled, bez stažení |
| < 80 % | Příliš vysoká | Nezobrazovat |

Řešení jsou dvě sekce. Výsledky s podobností devadesát pět a více procent jdou do sekce „Pravděpodobně tvoje fotky" — plný náhled, stažení dostupné. Výsledky mezi osmdesáti a čtyřiadevadesáti procenty jdou do sekce „Možná jsi i tady" — malý náhled osmdesáti pixelů, stažení nedostupné, jasná vizuální signalizace nižší jistoty.

Druhá sekce je navíc výchozně vypnutá. Každé album ji musí mít explicitně povolenou po posouzení konkrétního dopadu — ne plošně pro všechna alba.

## Indexace fotek: od nahrání ke „Kdo ještě je na fotce"

Než může selfie vyhledávání fungovat, musí proběhnout indexace — zpracování fotek a uložení biometrických dat do Rekognition kolekce. To se neděje automaticky; je to vědomý administrativní krok s právními předpoklady.

### Fáze 1: Nahrání

Admin nahraje fotky přes předpodpisané URL přímo do R2 — Worker nahrání nezprostředkovává, protože by se tím zbytečně přetížil. Každý soubor dostane záznam v D1 se stavem `uploaded`.

### Fáze 2: Generování náhledů

[Cloudflare Images](https://developers.cloudflare.com/images/) asynchronně vygeneruje náhledy: čtyřista pixelů pro galerii, osmdesát pixelů pro degradované výsledky selfie vyhledávání. Worker záznamy aktualizuje na stav `thumb_ready`. Cloudflare Images je služba pro transformaci a optimalizaci obrázků přímo na hraně sítě — pracuje s originály v R2 a výsledky cachuje globálně.

### Fáze 3: Indexace obličejů přes Durable Objects

Tohle je klíčový moment. Admin spustí indexaci ručně — ale předtím musí zaškrtnout potvrzení, že pro toto album existuje platný souhlas ke zpracování biometriky. Bez tohoto potvrzení nelze indexaci spustit.

Po potvrzení Worker předá úlohu do [Cloudflare Durable Objects](https://developers.cloudflare.com/durable-objects/) — konkrétně do entity pojmenované `album-indexer`.

Proč Durable Objects? Protože Worker má třicetisekundový limit a jedno album může mít dva tisíce fotek. Durable Object je jiný druh Cloudflare runtime: perzistentní entita s vlastním stavem a vlastní pamětí, která může běžet dlouho. Na rozdíl od Workers přežívá jednotlivé požadavky a udržuje stav mezi nimi. Naše `album-indexer` zpracovává fotky v dávkách, průběžně ukládá stav (kolik fotek je hotových, kolik zbývá, které selhaly) a je idempotentní — pokud spadne a restartuje, nezačíná od nuly, ale tam, kde přestala.

Pro každou fotku voláme Rekognition `IndexFaces` s parametrem `ExternalImageId` nastaveným na naše `photo_id`. To je trik, díky kterému nepotřebujeme žádnou extra tabulku pro překlad: výsledky `SearchFacesByImage` vrátí přímo naše interní identifikátory fotek. Výsledné `face_id` a ohraničující rámeček obličeje (`bounding box`) ukládáme do D1 tabulky `faces`.

### Fáze 4: Předpočet příbuzných fotek

Po dokončení indexace přijde na řadu nejzajímavější část z pohledu uživatelského rozhraní. Pro každou fotku zavoláme `SearchFaces` — to vezme obličeje z dané fotky a najde ostatní fotky v kolekci, kde se tytéž obličeje vyskytují. Výsledky ukládáme do tabulky `photo_related` jako vztah `source_photo_id → related_photo_id`.

Ukládáme záměrně jednosměrně — pokud víme, že fotka A je příbuzná s fotkou B, neukládáme dvakrát (A→B a B→A), ale jen jednou. Tím snižujeme objem dat na polovinu. Čteme pak obousměrně jedním dotazem:

```sql
WITH candidates AS (
  SELECT related_photo_id AS photo_id FROM photo_related
  WHERE source_photo_id = ?
  UNION ALL
  SELECT source_photo_id AS photo_id FROM photo_related
  WHERE related_photo_id = ?
)
SELECT ...
```

Tato tabulka napájí sekci „Další fotky s někým z této fotky" v lightboxu. Kliknete na fotku, lightbox se otevře a dole vidíte miniatury dalších fotek, kde je alespoň jedna osoba z té aktuální. Seřazeno podle počtu shodných obličejů a pak podle skóre podobnosti.

## GDPR: jak jsme k tomu přistoupili

Obličejové vektory jsou biometrické údaje. Podle [článku 9 nařízení GDPR](https://eur-lex.europa.eu/legal-content/CS/TXT/?uri=CELEX%3A32016R0679) jde o zvláštní kategorii osobních údajů — takovou, kde nestačí běžný zákonný titul jako oprávněný zájem nebo plnění smlouvy, ale je potřeba výslovný souhlas nebo jiný přísný titul.

Pracovali jsme s právníkem a výsledkem bylo několik konkrétních rozhodnutí, která jsou zadrátovaná přímo do implementace — nejsou to jenom slova v zásadách ochrany soukromí.

### Souhlas jako tvrdý předpoklad

Souhlas se zpracováním biometrie sbíráme explicitně při prodeji vstupenky na akci — odděleně od souhlasu s pořízením fotografií (to pro příště, na červnové akci jsme souhlasy vyřizovali ex-post, protože při zahájení prodeje jsme to ještě netušili, jak to má být). Účastník zaškrtne přesně formulované políčko, ne obecné „souhlasím s podmínkami". V databázi si ke každé registraci pamatujeme, zda byl biometrický souhlas udělen a kdy.

Bez udělení souhlasu nelze selfie vyhledávání použít ani pro danou osobu zobrazit výsledky. To není jen UI hláška — ověřujeme to serverově, selfie vyhledávání může použít jen návštěvník akce.

### Tři principy minimalizace dat

**Selfie se nikdy neukládá.** Worker přijme selfie jako soubor, předá ho Rekognition přes HTTP, a jakmile dostane odpověď, soubor zahodí. Do R2 se nic neukládá, do D1 se nic neukládá. Do logu zapíšeme `selfie_discarded: true` — ne samotný obrázek, ale potvrzení, že jsme ho zahodili. Proto ho také uživatel nemůže použít opakovaně, ale hlavně ho my nemůžeme zneužít, i kdybychom chtěli (nechceme).

**E-mail se nikdy neukládá jako čitelný text.** V celém systému pracujeme jen s HMAC-SHA256 hashem e-mailu, který je vypočítán pomocí tajného klíče. Z hashe nelze e-mail rekonstruovat. Ani přímý přístup k databázi nezpřístupní e-mailové adresy účastníků.

**Biometrické vektory žijí výhradně v AWS Rekognition.** V naší D1 databázi máme pouze `face_id` — identifikátor přidělený AWS — a `photo_id`. Samotný biometrický vektor, matematická reprezentace obličeje, u nás lokálně neexistuje.

### Právo na výmaz jako stavový automat

Právo být zapomenut je v GDPR základní právo. Implementovat ho u biometriky znamená vymazat data ze dvou míst najednou: z AWS Rekognition a z naší D1. A udělat to spolehlivě, i když v půlce výpadne síť.

Řešili jsme to stavovým automatem v tabulce `erasure_requests`:

```
pending → deleting_aws → deleting_d1 → completed
```

Worker nejprve zavolá Rekognition `DeleteFaces` (nebo `DeleteCollection` pro celé album), aktualizuje stav na `deleting_d1`, pak smaže záznamy z D1, a teprve potom označí jako `completed`. Pokud cokoliv selže, Worker při dalším spuštění vidí stav `deleting_aws` nebo `deleting_d1` a pokračuje tam, kde přestal. Žádné biometrické záznamy nezůstanou sirotky v AWS bez odpovídajícího záznamu v D1.

### Posouzení dopadu jako technická podmínka spuštění

Článek 35 GDPR vyžaduje pro zpracování biometriky posouzení dopadu na ochranu osobních údajů (DPIA — Data Protection Impact Assessment). Mohli jsme toto nechat jako administrativní krok „někde v papírech". Rozhodli jsme se jinak: admin panel před spuštěním indexace vyžaduje zadání čísla a data schválené DPIA. Bez tohoto záznamu v D1 nelze indexaci technicky spustit. DPIA není formalita — je to hardwarový zámek.

Shrnutí přístupu k soukromí:

- **Souhlas:** explicitní, oddělen od ostatních souhlasů, ověřován serverově
- **Data:** selfie zahozeno, e-mail jako jednosměrný hash, biometrické vektory výhradně v AWS EU regionu
- **Výmaz:** stavový automat odolný výpadkům, pokrývá AWS i D1
- **DPIA:** technická podmínka spuštění, ne jen papírový dokument

## Co jsme se cestou naučili

**Dvě sekce výsledků jsme nepřidali hned.** Původní návrh měl jen jednu sekci s jedním prahem. Teprve při prvním testování na reálné sadě fotek jsme viděli, že hraniční případy — špatné světlo, profil, výrazné brýle — padají těsně pod devadesát pět procent a jsou přesto pravděpodobně správné. Degradovaný náhled jako řešení přišel z testování, ne z designu.

**Cloudflare Images nad privátním R2 byl netriviální.** Klíčová otázka: lze volat `/cdn-cgi/image/` transformace nad R2 bucketem, který není veřejně dostupný? Bez tohoto ověření by privátní alba nemohla využívat automatické změny velikosti na hraně sítě a museli bychom servírovat originály nebo spravovat vlastní pipeline náhledů. Odpověď je ano, ale s konkrétní konfigurací, která není přímočaře zdokumentovaná.

**Durable Objects jsou správná volba pro dávkové zpracování.** Sáhli jsme po nich, protože jsme neměli jinou možnost, ale ukázalo se, že jsou přesně to, co jsme potřebovali. Idempotentní zpracování s průběžně ukládaným stavem je přesně ta vlastnost, kterou potřebujete, když indexace trvá hodiny a může být přerušena. Dávkování navíc přirozeně řeší rate limiting vůči Rekognition API.

**Jednosměrné ukládání příbuzností bylo správné rozhodnutí.** Uvažovali jsme o symetrickém ukládání (A→B i B→A) pro jednodušší dotazy. Při albu s tisícem fotek a průměrně pěti osobami na fotce by to byl výrazně větší objem dat — a dotaz s `UNION ALL` je stále rychlý. Jednoduchý dotaz není vždy nejlepší dotaz.

## Stav a co je před námi

Infrastruktura existuje a funguje. Admin rozhraní pro nahrávání fotek, správu kolekcí a spouštění indexace je hotové. Galerie s lightboxem, oblíbenými fotkami, sdílením a stahováním funguje. Selfie vyhledávání jako takové je implementované.

Co zbývá nyní na ostré spuštění:

- Ověření, že Cloudflare Images transformace spolehlivě fungují nad privátním R2 při vysoké zátěži — technický ověřovací krok, ne architektonické rozhodnutí
- Schválení DPIA pro konkrétní typ akce
- Validační průchod na testovacím albu se sadou alespoň padesáti osob a pěti sty fotek

Tahle cesta trvala déle, než jsem původně odhadoval — naivní odhad byl dva dny, realita jsou čtyři týdny práce v přestávkách mezi ostatními projekty. Ale výsledek je systém, který zachází s biometrickými daty zodpovědně, nevyžaduje od účastníků instalaci nic, funguje v každém moderním prohlížeči a stojí pár dolarů za akci. To mi přijde jako dobrý poměr.

## A jak to funguje?

Vlastně dobře. Trochu opruz (daný GDPR a ochranou soukromí, která prostě vždy je trochu náročná) - dáte email, pod kterým jste se přihlásili na akci, pak nahrajete selfie. Já nahrál svoji fotku starou cca 10 let…

![](https://www.vibecoding.cz/images/articles/praxe-najdi-se-ve-fotkach/patrick_zandl_2018_small.jpg)

A našlo mi to tohle:

![](https://www.vibecoding.cz/images/articles/praxe-najdi-se-ve-fotkach/Sni%CC%81mek%20obrazovky%202026-06-25%20v%C2%A09.17.31.png)

[A co vy? Vyzkoušíte?](https://www.vibecoding.cz/album/vt-06-2026/) Pokud jste na Vibecoding Talks nebyli, tak lze 22.9.2026 vše napravit 😇
