Files
fotkyBuzalkovi/NAVRH.md
T
administrator 7e05384c1f notebookVb
2026-05-24 07:59:25 +02:00

10 KiB
Raw Blame History

FotkyBuzalkovi — návrh systému a poznámky z analýzy

Pracovní dokument shrnující rozhodnutí a poznatky z prvotní analýzy projektu. Datum diskuze: 2026-05-21


1. Cíl projektu

  • Organizovat a tagovat cca 200 000 rodinných fotografií
  • Lokální nasazení (žádný cloud, žádní externí uživatelé)
  • Současný stav: prázdný projekt s ukázkovými fotkami v demo_fotky/

2. Architektonické rozhodnutí

Finální stack: jen PostgreSQL + filesystem

┌─────────────────────┐
│  Python aplikace    │
└──────────┬──────────┘
           │
    ┌──────┴──────┐
    ▼             ▼
┌─────────┐  ┌──────────┐
│PostgreSQL│  │Filesystem│
│ metadata │  │ .jpg/.png│
└─────────┘  └──────────┘

Co bylo zvažováno a zavrženo

Technologie Verdikt Důvod
MongoDB vynecháno JSONB v PostgreSQL nahradí veškerou potřebnou funkcionalitu
Redis vynecháno (zatím) Pro lokální 200k fotek bez webových uživatelů zbytečný
MySQL vynecháno Projekt používá PostgreSQL (jiná databáze byla v původním plánu)

Kdy přidat Redis (pozdější fáze)

  • Web UI s rychlým vyhledáváním (cache výsledků)
  • Paralelní workery pro import / generování thumbnailů
  • Background fronty (zpracování AI tagů)

Kdy přidat něco dalšího

  • pgvector extension pro PostgreSQL — až budeme chtít sémantické hledání (CLIP embeddings)
  • Elasticsearch — kdyby PostgreSQL fulltext nestačil (u 200k řádků nestane)

3. JSONB v PostgreSQL vs MongoDB BSON

Terminologie

  • BSON = binární JSON od MongoDB (s typy jako ObjectId, Date, Decimal128)
  • JSONB = binární JSON v PostgreSQL (vlastní formát, ne BSON, ale funkčně podobný)

Srovnání vyhledávání

MongoDB:

db.photos.find({ "exif.camera": "Canon EOS 5D" })
db.photos.find({ "exif.iso": { $gte: 800 } })
db.photos.find({ "tags": { $in: ["dovolená", "moře"] } })

PostgreSQL JSONB:

SELECT * FROM photos WHERE exif_data->>'camera' = 'Canon EOS 5D';
SELECT * FROM photos WHERE (exif_data->>'iso')::int >= 800;
SELECT * FROM photos WHERE exif_data @> '{"camera": "Canon EOS 5D"}';

Co umí stejně

Funkce MongoDB PostgreSQL JSONB
Filtr podle pole v JSON ano ano (->>, @>)
Vnořené cesty "a.b.c" data#>>'{a,b,c}'
Existence klíče $exists ? operátor
Index na JSON cestu ano ano (GIN)
Fulltext ano ano

Výhody PostgreSQL JSONB pro náš případ

  1. JOINy s tabulkami tagů a kamer — v Mongo by to vyžadovalo $lookup (pomalé)
  2. Transakce napříč JSONB a relačními tabulkami
  3. Jeden backup nástroj (pg_dump)

4. Identita fotky — 4 úrovně hashů

Klíčový problém: Když změníte byť jediný keyword v IPTC, SHA256 souboru se kompletně změní. Jak detekovat, že je to stále ta samá fotka?

┌─────────────────────────────────────────────────────────────┐
│  Stejná identita                          Stejný obsah      │
│  ←─────────────────────────────────────────────────────→    │
│                                                              │
│  SHA256        Pixel hash      Perceptual      Embedding    │
│  souboru       (jen pixely)    hash (pHash)    (CLIP/DINO)  │
│                                                              │
│  byte-by-byte  metadata        +recomprese     +sémantika   │
│  identické     ignorováno      +resize         podobnost    │
│                                +crop                         │
└─────────────────────────────────────────────────────────────┘

1. SHA256 souboru (sha256_file)

  • Detekuje přesnou kopii souboru
  • Změna IPTC/EXIF, otočení, znovuuložení → jiný hash

2. Pixel hash (sha256_pixels)

  • SHA256 dekódovaných pixelů (po aplikaci EXIF orientation)
  • Stejná fotka po změně metadat → identický hash
  • Toto je odpověď na otázku "jak detekovat, že je to ta samá fotka po změně keywords"
with Image.open(path) as img:
    img = ImageOps.exif_transpose(img)
    if img.mode != "RGB":
        img = img.convert("RGB")
    pixel_hash = hashlib.sha256(img.tobytes()).hexdigest()

3. Perceptual hash (pHash, dHash, wHash)

  • 64-bit "otisk obsahu" — vizuálně podobné fotky → blízké hashe
  • Porovnává se přes Hamming distance (bin(h1 ^ h2).count("1"))
  • < 10 = velmi podobné, > 20 = odlišné
  • Detekuje: recompresi, resize, drobné úpravy, watermark, crop
  • Knihovna: imagehash

4. Deep learning embedding (budoucnost)

  • Vektor 5121024 dimenzí z CLIP / DINOv2
  • Sémantická podobnost ("dvě fotky stejné scény z různých úhlů")
  • Uložení v PostgreSQL přes pgvector extension

Workflow při importu nové fotky

1. Spočítej sha256_file
   ↓
2. Najdi v DB stejný sha256_file?
   ANO → identická kopie, přeskočit
   NE  → ↓
3. Spočítej sha256_pixels
   ↓
4. Najdi v DB stejný sha256_pixels?
   ANO → stejná fotka, jen jiná metadata → updatuj, nepřidávej duplikát
   NE  → ↓
5. Spočítej phash, ulož do DB
   ↓
6. (Volitelně) najdi podobné: WHERE hamming(phash, ?) < 10

Poznámka k JPEG

  • Před hashingem vždy aplikovat EXIF orientation (ImageOps.exif_transpose)
  • Konvertovat do RGB pro konzistenci
  • Jinak by se rotovaná fotka vs nerotovaná lišila

5. Tři vrstvy metadat ve fotce

Standard Co tam je Kdo to vyplňuje
EXIF Technická data (clona, ISO, čas, GPS, model kamery) Kamera automaticky
IPTC Popisná data (titulek, popis, klíčová slova, autor, copyright) Člověk / software
XMP Modernější nástupce IPTC od Adobe, často duplikuje + edits, ratings, regions Software (Lightroom, Apple Photos)

Pro náš případ

  • iPhone vyplňuje hlavně EXIF a XMP (ne IPTC)
  • Apple Photos do XMP ukládá rozpoznané obličeje (mwg-rs:Regions) — i se jmény, pokud je pojmenuješ!
  • iOS screenshoty mají v XMP description: "Screenshot" → automatická detekce
  • Pokud budeme tagovat, je dobré tagy ukládat i do IPTC Keywords — přežijí export do jiné aplikace

6. Výsledky exploration na 7 demo fotkách

Souhrn dat z explore_photos.py

# Soubor Mpx EXIF tagů IPTC XMP GPS Kamera
1 2026-04-22 09.05.08.jpg 24.47 121 0 4 ano iPhone 16 Pro Max
2 2026-04-24 15.15.47.jpg 12.19 107 0 0 ne iPhone 13 Pro Max
3 2026-05-02 10.04.44.png 3.57 12 0 1 ne Screenshot
4 2026-05-18 06.22.37.jpg 12.19 105 0 4 ne iPhone 13 Pro Max
5 2026-05-18 06.22.41.jpg 12.19 105 0 4 ne iPhone 13 Pro Max
6 2026-05-18 13.54.47.jpg 12.19 98 6 0 ne iPhone 13 Pro Max
7 2026-05-18 14.10.59.jpg 2.36 0 0 0 ne (sirotek)

Klíčové objevy

  1. Perceptual hash funguje — fotky [4] a [5] (pořízené 4 sekundy po sobě) mají Hamming distance pouze 4 = klasický burst snapshot.

  2. Apple face detection v XMP — fotky 1, 4, 5 mají face_regions_count: 1. iPhone už detekoval obličej a uložil to do XMP. Můžeme číst přímo, bez vlastní AI.

  3. Screenshot rozpoznán — foto [3] má v XMP description: "Screenshot" → automatická kategorizace.

  4. Foto [7] = sirotek — žádné metadata. Pravděpodobně přeposlané přes WhatsApp/Messenger, kde se EXIF maže. Pro 200k fotek bude tato kategorie významná.

  5. GPS jen u 1/7 fotek — u většiny iPhone fotek máte vypnuté lokační služby.

  6. Time zone — fotky mají OffsetTime: +02:00. Datum pořízení ukládat jako TIMESTAMPTZ (s timezone).

  7. ExifRead > Pillow — Pillow má GPS bug ('int' object has no attribute 'items'). Primární parser bude ExifRead.

  8. MakerNote (Apple binary blob) — obsahuje burst ID, HDR příznak, focus distance. Pro rozluštění potřeba exiftool (volat přes pyexiftool).


7. Databázové schéma

Kompletní aktuální schéma (tabulky, sloupce, indexy) viz SCHEMA.md.


8. Otevřené otázky

Foto bez EXIF (sirotek typu [7])

  • a) Importovat (s taken_at z mtime)
  • b) Odmítnout jako "nezpracovatelnou"
  • c) Importovat, ale označit processing_status = 'no_metadata'

Detekce duplikátů (shoda sha256_pixels)

  • a) Přeskočit (nepřidávat duplicitu)
  • b) Sloučit (aktualizovat metadata u existujícího záznamu)
  • c) Uložit oba, jen označit jako související

Storage layout fotek

  • a) Necháme v aktuálním umístění, do DB jen cestu
  • b) Kopie do archiv/YYYY/MM/původní_název.jpg
  • c) Kopie do archiv/{sha[:2]}/{sha}.jpg (content-addressable = auto-dedup na FS)

9. Co dál — nápady k prozkoumání

  • exiftool — rozluští Apple MakerNote (burst ID by potvrdilo, že [4] a [5] patří k sobě)
  • Embedded thumbnail z EXIF — telefon ukládá malou náhledovku přímo v souboru → rychlejší galerie bez generování
  • CLIP embeddings + pgvector — sémantické vyhledávání ("ukaž fotky pejsků na pláži")
  • Reverse geocoding — z GPS souřadnic na čitelné místo (Nominatim / Photon, lokálně)
  • Apple Photos jména osob — z XMP mwg-rs:Regions jdou číst i jména, pokud jsou v Photos pojmenovaná
  • Datum z názvu souboru — fallback když chybí EXIF i sensible mtime (regex z "2026-05-18 13.54.47.jpg")

10. Aktuální stav a struktura projektu

Viz README.md — hlavní rozcestník projektu.