[Prestaties][P0] Gezichtsdetectie blokkeert de UI-thread per foto (standaard aan) en start bij typen opnieuw #2619

Closed
opened 2026-10-28 16:31:30 +00:00 by brenno · 0 comments
Owner

Symptoom

Een presentatie met veel foto's openen of erin typen laat de app haperen of seconden lang "vastlopen". Het is de standaardinstelling, dus iedereen met een fotorijk deck heeft er last van.

Oorzaak

De gezichtsdetectie van de privacycontrole (imagePrivacyRawIssuesProvider) staat standaard aan (privacyChecksEnabled en privacyImageFaceDetection zijn true, lib/models/parts/app_settings.dart:414-415, lib/state/settings_provider.dart:277) en:

  1. draait synchroon op de UI-isolate. ImageFaceScanner.countFaces (lib/services/privacy/image_face_scan_io.dart:184) doet cv.imdecode op volle resolutie (tot 40 MP) en daarna YuNet op drie schalen (kFaceScanWidths = [1920, 1280, 640], regel 40). Dat is één ononderbroken FFI-aanroep: zolang hij loopt, tekent de app geen frame en reageert hij niet op invoer (ook niet op de sluitknop).
  2. start opnieuw bij élke deckwijziging. De provider watcht het hele deck (lib/state/image_privacy_provider.dart:170). De memo (pad+mtime+grootte, #911) vangt herhaling op, maar:
    • een run die eenmaal in de lus zit (regel 184) loopt door tot het einde, ook als zijn uitkomst al achterhaald is — er is geen ref.mounted-toets. Typen tijdens de eerste scan start per toetsaanslag een nieuwe run die dezelfde nog-niet-gecachete foto's opnieuw scant;
    • de memo is een LRU van 256 (regel 100). Bij meer dan 256 verschillende afbeeldingen in de open decks loopt de sequentiële lus de cache telkens leeg (100% misses) en wordt bij elke toetsaanslag álles opnieuw gedecodeerd en gedetecteerd;
    • ook bij een volle cache doet elke toetsaanslag per afbeelding een File.stat() en een gehele herberekening.

Meting

Integratietest op macOS (Apple Silicon, native OpenCV), countFaces per foto, synchroon:

Foto Duur
1920×1080 41 ms
4032×3024 (12 MP) 74 ms
6000×4000 (24 MP) 99 ms

Op een gemiddelde Windows 10-laptop is 2–4× zoveel realistisch. Een deck met 40 telefoonfoto's betekent bij openen dus 40 bevriezingen van 150–400 ms achter elkaar, en bij typen tijdens die scan een veelvoud daarvan.

Voorgestelde oplossing

  1. Uit de UI-isolate: één langlevende achtergrond-isolate (of Isolate.run per foto) die het YuNet-model één keer laadt en een seriële wachtrij afwerkt. dartcv-FFI werkt in een gewone isolate; geef de bytes mee als TransferableTypedData.
  2. Alleen herberekenen als de beeldset verandert: laat de provider niet het deck watchen maar een stabiele sleutel van de afbeeldingen, bv. deckProvider.select((s) => imageSetKey(s.deck)) die een string/record van (pad, dia-index, veld) oplevert. Tekst typen raakt die sleutel niet en start dus geen scan.
  3. Achterhaalde runs stoppen: if (!ref.mounted) return; aan het begin van elke iteratie.
  4. Memo niet als vaste LRU van 256: de waarde is een int + bool; een plafond van bv. 8192 items of een Expando per deck kost bijna niets.
  5. Goedkoper decoderen: IMREAD_REDUCED_COLOR_2/4/8 kiezen op basis van de kopafmetingen (die faceScanDimensionsWithinBudget al leest), zodat een 24 MP-foto nooit op volle grootte in het geheugen komt — de grootste detectieschaal is toch 1920.
  6. Toon in het kwaliteitspaneel dat de beeldcontrole bezig is ("3 van 40 gecontroleerd"), zodat de export-gate niet stil wacht.

Wat overeind moet blijven

Dezelfde uitkomsten (aantal gezichten, unreadable voor HEIC/te groot), dezelfde juridische grens (alleen het aantal rijen uitlezen, zie kop image_face_scan.dart), en de export-gate die op de uitslag wacht.

Toets

  • Unit: typen in een deck met afbeeldingen start géén nieuwe scan (tel countFaces-aanroepen met een nep-scanner).
  • Unit: 300 afbeeldingen, tweede deckwijziging → 0 nieuwe scans.
  • Integratie (integration_test/, macOS/Windows/Linux): tijdens een scan van 20 foto's blijft de langste framepauze < 50 ms.

Onderdeel van de prestatie- en stabiliteitsinspectie van 28-10-2026 (EPIC #2637).

## Symptoom Een presentatie met veel foto's openen of erin typen laat de app haperen of seconden lang "vastlopen". Het is de standaardinstelling, dus iedereen met een fotorijk deck heeft er last van. ## Oorzaak De gezichtsdetectie van de privacycontrole (`imagePrivacyRawIssuesProvider`) staat standaard aan (`privacyChecksEnabled` en `privacyImageFaceDetection` zijn `true`, `lib/models/parts/app_settings.dart:414-415`, `lib/state/settings_provider.dart:277`) en: 1. **draait synchroon op de UI-isolate.** `ImageFaceScanner.countFaces` (`lib/services/privacy/image_face_scan_io.dart:184`) doet `cv.imdecode` op volle resolutie (tot 40 MP) en daarna YuNet op drie schalen (`kFaceScanWidths = [1920, 1280, 640]`, regel 40). Dat is één ononderbroken FFI-aanroep: zolang hij loopt, tekent de app geen frame en reageert hij niet op invoer (ook niet op de sluitknop). 2. **start opnieuw bij élke deckwijziging.** De provider watcht het hele deck (`lib/state/image_privacy_provider.dart:170`). De memo (pad+mtime+grootte, #911) vangt herhaling op, maar: - een run die eenmaal in de lus zit (regel 184) loopt door tot het einde, ook als zijn uitkomst al achterhaald is — er is geen `ref.mounted`-toets. Typen tijdens de eerste scan start per toetsaanslag een nieuwe run die dezelfde nog-niet-gecachete foto's opnieuw scant; - de memo is een LRU van **256** (regel 100). Bij meer dan 256 verschillende afbeeldingen in de open decks loopt de sequentiële lus de cache telkens leeg (100% misses) en wordt bij elke toetsaanslag álles opnieuw gedecodeerd en gedetecteerd; - ook bij een volle cache doet elke toetsaanslag per afbeelding een `File.stat()` en een gehele herberekening. ## Meting Integratietest op macOS (Apple Silicon, native OpenCV), `countFaces` per foto, synchroon: | Foto | Duur | |---|---| | 1920×1080 | 41 ms | | 4032×3024 (12 MP) | 74 ms | | 6000×4000 (24 MP) | 99 ms | Op een gemiddelde Windows 10-laptop is 2–4× zoveel realistisch. Een deck met 40 telefoonfoto's betekent bij openen dus 40 bevriezingen van 150–400 ms achter elkaar, en bij typen tijdens die scan een veelvoud daarvan. ## Voorgestelde oplossing 1. **Uit de UI-isolate**: één langlevende achtergrond-isolate (of `Isolate.run` per foto) die het YuNet-model één keer laadt en een seriële wachtrij afwerkt. dartcv-FFI werkt in een gewone isolate; geef de bytes mee als `TransferableTypedData`. 2. **Alleen herberekenen als de beeldset verandert**: laat de provider niet het deck watchen maar een stabiele sleutel van de afbeeldingen, bv. `deckProvider.select((s) => imageSetKey(s.deck))` die een string/record van (pad, dia-index, veld) oplevert. Tekst typen raakt die sleutel niet en start dus geen scan. 3. **Achterhaalde runs stoppen**: `if (!ref.mounted) return;` aan het begin van elke iteratie. 4. **Memo niet als vaste LRU van 256**: de waarde is een int + bool; een plafond van bv. 8192 items of een Expando per deck kost bijna niets. 5. **Goedkoper decoderen**: `IMREAD_REDUCED_COLOR_2/4/8` kiezen op basis van de kopafmetingen (die `faceScanDimensionsWithinBudget` al leest), zodat een 24 MP-foto nooit op volle grootte in het geheugen komt — de grootste detectieschaal is toch 1920. 6. Toon in het kwaliteitspaneel dat de beeldcontrole bezig is ("3 van 40 gecontroleerd"), zodat de export-gate niet stil wacht. ## Wat overeind moet blijven Dezelfde uitkomsten (aantal gezichten, `unreadable` voor HEIC/te groot), dezelfde juridische grens (alleen het aantal rijen uitlezen, zie kop `image_face_scan.dart`), en de export-gate die op de uitslag wacht. ## Toets - Unit: typen in een deck met afbeeldingen start géén nieuwe scan (tel `countFaces`-aanroepen met een nep-scanner). - Unit: 300 afbeeldingen, tweede deckwijziging → 0 nieuwe scans. - Integratie (`integration_test/`, macOS/Windows/Linux): tijdens een scan van 20 foto's blijft de langste framepauze < 50 ms. Onderdeel van de prestatie- en stabiliteitsinspectie van 28-10-2026 (EPIC #2637).
brenno 2026-10-29 12:19:29 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
LibreKAT/Ocideck#2619
No description provided.