File size: 7,457 Bytes
2ae889c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
import os
import cv2
import numpy as np
from skimage.feature import local_binary_pattern, graycomatrix, graycoprops

# ---------------------------------------------------------------------
# 1. Color Features
# ---------------------------------------------------------------------
def get_average_color(image):
    """
    Compute the average color in BGR format (3 values).
    """
    return np.mean(image, axis=(0, 1))  # shape: (3,)

def get_small_color_hist(image, h_bins=8, s_bins=2, v_bins=2):
    """
    Compute a *reduced* color histogram in HSV space:
      - h_bins: number of bins for Hue
      - s_bins: number of bins for Saturation
      - v_bins: number of bins for Value
    
    Total bins = h_bins * s_bins * v_bins.
    """
    hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
    hist = cv2.calcHist(
        [hsv], 
        [0, 1, 2], 
        None, 
        [h_bins, s_bins, v_bins], 
        [0, 180, 0, 256, 0, 256]
    )
    cv2.normalize(hist, hist)
    return hist.flatten()  # shape: (h_bins*s_bins*v_bins,)

# ---------------------------------------------------------------------
# 2. LBP (Local Binary Patterns)
# ---------------------------------------------------------------------
def get_lbp_histogram(image, num_points, radius):
    # Ensure the image is grayscale: only convert if it has more than one channel.
    if len(image.shape) > 2 and image.shape[2] != 1:
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    else:
        gray = image

    # Compute the LBP representation of the image.
    # (Assuming you're using skimage's local_binary_pattern)
    from skimage.feature import local_binary_pattern
    lbp = local_binary_pattern(gray, num_points, radius, method="uniform")

    # Build the histogram of the LBP.
    n_bins = int(num_points + 2)
    hist, _ = np.histogram(lbp.ravel(), bins=n_bins, range=(0, n_bins))

    # Normalize the histogram.
    hist = hist.astype("float")
    hist /= (hist.sum() + 1e-6)
    
    return hist

# ---------------------------------------------------------------------
# 3. GLCM (Gray Level Co-occurrence Matrix)
# ---------------------------------------------------------------------
def get_glcm_features(image,
                      distance=1,
                      angles=[0],
                      properties=('contrast', 'homogeneity', 'energy', 'correlation')):
    """
    Compute a small set of GLCM features:
      - distance=1, angles=[0]  (or [0, np.pi/2] if you want more orientations)
      - properties = a reduced subset for simpler texture capture
    
    Returns a flattened array of property values across all angles.
    """
    glcm = graycomatrix(
        image,
        distances=[distance],
        angles=angles,
        levels=256,
        symmetric=True,
        normed=True
    )
    feats = []
    for prop in properties:
        vals = graycoprops(glcm, prop)
        feats.append(vals.ravel())  # Flatten N-dim array
    glcm_features = np.concatenate(feats)
    return glcm_features  # shape: (len(properties)*len(angles),)

# ---------------------------------------------------------------------
# 4. Combined Feature Extraction
# ---------------------------------------------------------------------
def extract_features_from_image(image):
    """
    Returns a DICTIONARY of feature sets:
      - 'average_color': 3 values (B, G, R) from the original image.
      - 'lbp_hist': Histogram from Local Binary Patterns (texture).
      - 'glcm_features': GLCM features (contrast, homogeneity, energy, correlation).
      - 'edge_density': Scalar representing the fraction of edge pixels.
      - 'edge_orient_hist': Normalized histogram (8 bins) of edge orientations.
      - 'combined_features': Concatenation of all the above features.
      
    This function supports both color and grayscale images.
    """
    import cv2
    import numpy as np

    # --- (A) Color Features ---
    # Ensure a 3-channel image for color feature extraction.
    if len(image.shape) == 2 or image.shape[2] == 1:
        image_color = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
    else:
        image_color = image
    avg_color = get_average_color(image_color)  # Expected shape: (3,)

    # --- (B) Texture Features: LBP and GLCM ---
    # Use grayscale image for texture features.
    if len(image.shape) == 2:
        gray_image = image
    else:
        gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    lbp_hist = get_lbp_histogram(gray_image, num_points=8, radius=1)  # e.g., shape = (10,)
    glcm_feats = get_glcm_features(
        gray_image,
        distance=1,
        angles=[0],  # Single orientation
        properties=('contrast', 'homogeneity', 'energy', 'correlation')
    )  # e.g., shape = (4,)

    # --- (C) Edge-Related Features ---
    # Preprocessing: Blur to reduce noise before edge detection.
    blurred = cv2.GaussianBlur(gray_image, (9, 9), 0)
    
    # Compute Canny edges using fixed thresholds (these might be adapted based on context).
    threshold1, threshold2 = 0, 100
    edges = cv2.Canny(blurred, threshold1=threshold1, threshold2=threshold2)
    
    # Edge Density: Ratio of edge pixels to total pixels.
    edge_density = np.sum(edges > 0) / float(edges.size)
    
    # Compute gradient orientations using Sobel operators.
    grad_x = cv2.Sobel(blurred, cv2.CV_64F, 1, 0, ksize=3)
    grad_y = cv2.Sobel(blurred, cv2.CV_64F, 0, 1, ksize=3)
    magnitude, angle = cv2.cartToPolar(grad_x, grad_y, angleInDegrees=True)
    
    # Use only edge pixels for orientation histogram.
    angles = angle[edges > 0]
    hist_bins = 8
    if angles.size > 0:
        edge_orient_hist, _ = np.histogram(angles, bins=hist_bins, range=(0, 360))
        edge_orient_hist = edge_orient_hist.astype("float")
        edge_orient_hist /= (edge_orient_hist.sum() + 1e-6)  # Normalize histogram.
    else:
        edge_orient_hist = np.zeros(hist_bins, dtype="float")

    # --- (D) Combine All Features ---
    combined_features = np.concatenate([
        avg_color,                       # 3 values
        lbp_hist,                        # e.g., 10 values
        glcm_feats,                      # e.g., 4 values
        np.array([edge_density]),        # 1 value
        edge_orient_hist                 # 8 values
    ])

    return {
        'average_color': avg_color,
        'lbp_hist': lbp_hist,
        'glcm_features': glcm_feats,
        'edge_density': edge_density,
        'edge_orient_hist': edge_orient_hist,
        'combined_features': combined_features
    }


# ---------------------------------------------------------------------
# 5. Example Usage
# ---------------------------------------------------------------------
if __name__ == "__main__":
    import sys

    # Provide the path to an image file
    # e.g., python feature_extractor_min.py images/wood_example.jpg

    
    image_path = './wood_patches/patch_012.png'
    image = cv2.imread(image_path, cv2.IMREAD_COLOR)

    if image is None:
        print(f"Error: Unable to read image at {image_path}")
    else:
        feats = extract_features_from_image(image)
        print("Feature Shapes:")
        for k, v in feats.items():
            if isinstance(v, np.ndarray):
                print(f"  {k}: shape={v.shape}")
            else:
                print(f"  {k}: {v}")
        
        print("\nCombined Feature Vector:")
        print(feats['combined'])
        print("Combined Feature Length:", len(feats['combined']))