File size: 2,697 Bytes
1ea7ba6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
65
66
67
68
69
70
71
72
73
74
75
"""

Hand-crafted feature extraction: LBP (texture) + GLCM (texture) + HSV histogram (color).

Used by the texture branch and color branch of SpiceFusionNet.

"""
import numpy as np
import cv2

try:
    from skimage.feature import local_binary_pattern, graycomatrix, graycoprops
    _SKIMAGE = True
except ImportError:
    _SKIMAGE = False

import config


def _require_skimage():
    if not _SKIMAGE:
        raise ImportError(
            "scikit-image is required for texture features.\n"
            "Install: pip install scikit-image"
        )


def extract_lbp(img_rgb: np.ndarray) -> np.ndarray:
    """Local Binary Pattern histogram — 10-d vector."""
    _require_skimage()
    gray = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2GRAY)
    lbp = local_binary_pattern(gray, config.LBP_P, config.LBP_R, method="uniform")
    n_bins = config.LBP_P + 2
    hist, _ = np.histogram(lbp.ravel(), bins=n_bins, range=(0, n_bins), density=True)
    return hist.astype(np.float32)


def extract_glcm(img_rgb: np.ndarray) -> np.ndarray:
    """GLCM texture features — 48-d vector (6 props × 2 dist × 4 angles)."""
    _require_skimage()
    gray = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2GRAY)
    gray = (gray // (256 // config.GLCM_LEVELS)).astype(np.uint8)
    gray = np.clip(gray, 0, config.GLCM_LEVELS - 1)

    angles_rad = [np.deg2rad(a) for a in config.GLCM_ANGLES_DEG]
    glcm = graycomatrix(
        gray,
        distances=config.GLCM_DISTANCES,
        angles=angles_rad,
        levels=config.GLCM_LEVELS,
        symmetric=True,
        normed=True,
    )
    props = ["contrast", "dissimilarity", "homogeneity", "energy", "correlation", "ASM"]
    feats = []
    for p in props:
        feats.extend(graycoprops(glcm, p).ravel())
    return np.array(feats, dtype=np.float32)


def extract_texture(img_rgb: np.ndarray) -> np.ndarray:
    """Concatenate LBP + GLCM → 58-d texture vector."""
    return np.concatenate([extract_lbp(img_rgb), extract_glcm(img_rgb)])


def extract_hsv(img_rgb: np.ndarray) -> np.ndarray:
    """HSV histogram — 100-d color vector (H=36 + S=32 + V=32)."""
    hsv = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2HSV)
    h = np.histogram(hsv[:, :, 0], bins=config.HSV_H_BINS, range=(0, 180),  density=True)[0]
    s = np.histogram(hsv[:, :, 1], bins=config.HSV_S_BINS, range=(0, 256),  density=True)[0]
    v = np.histogram(hsv[:, :, 2], bins=config.HSV_V_BINS, range=(0, 256),  density=True)[0]
    return np.concatenate([h, s, v]).astype(np.float32)


def extract_all(img_rgb: np.ndarray):
    """Returns (texture: 58-d, color: 100-d)."""
    return extract_texture(img_rgb), extract_hsv(img_rgb)