File size: 2,417 Bytes
b510add
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3020394
 
 
 
 
 
 
 
 
 
b510add
 
 
3020394
 
b510add
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
"""Détection de la qualité de l'image (floue / sombre / surexposée / valide).

Fonction indépendante du moteur de reconnaissance : elle s'applique à
l'image (ou au crop d'écran) avant toute tentative de lecture des chiffres,
conformément au §6.2 du cahier des charges.
"""
import cv2
import numpy as np
from typing import Dict, Any, Optional

from .config import RuleConfig, load_config


def estimate_image_quality(img: np.ndarray,
                           ocr_results: Optional[list] = None,
                           cfg: Optional[RuleConfig] = None) -> Dict[str, Any]:
    """Estime la qualité de l'image et retourne un label + un score.

    Heuristiques : variance du Laplacien (flou) et luminosité moyenne.
    """
    if cfg is None:
        cfg = load_config()

    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) if len(img.shape) == 3 else img.copy()
    blur = cv2.Laplacian(gray, cv2.CV_64F).var()
    mean_brightness = float(np.mean(gray))

    # La luminosité est vérifiée AVANT le flou. Raison (constatée sur des
    # photos réelles sombres mais nettes) : la variance du Laplacien dépend
    # elle-même du contraste, qui s'effondre dans une image sombre même
    # quand les contours sont parfaitement nets. Tester le flou en premier
    # classait donc à tort des photos sombres comme "blurry" au lieu de
    # "dark" — deux causes qui appellent des messages/corrections différents
    # côté utilisateur (reprendre la photo avec plus de lumière n'est pas le
    # même geste que la stabiliser). Le flou n'est un diagnostic fiable que
    # sur une image dont la luminosité est déjà normale.
    if mean_brightness < cfg.dark_threshold:
        label = "dark"
    elif mean_brightness > cfg.bright_threshold:
        label = "bright"
    elif blur < cfg.blur_threshold:
        label = "blurry"
    elif ocr_results is not None and len(ocr_results) < 2:
        label = "unclear"
    else:
        label = "valid"

    quality_score = 0.0
    if label == "valid":
        quality_score = 0.95
    elif label == "unclear":
        quality_score = 0.20
    elif label in ("blurry", "dark", "bright"):
        quality_score = 0.35
    else:
        quality_score = 0.50

    return {
        "image_quality": label,
        "quality_score": round(min(1.0, quality_score), 2),
        "blur_variance": round(blur, 2),
        "brightness": round(mean_brightness, 2),
    }