File size: 8,452 Bytes
738077c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
import numpy as np
import cv2
import os
import gradio as gr
from skimage.feature import graycomatrix, graycoprops, local_binary_pattern
from skimage.io import imread
from skimage.color import rgb2gray
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import classification_report, confusion_matrix
from sklearn.preprocessing import StandardScaler

# Paths to dataset
DATASET_PATH = ""
CATEGORIES = ["stone", "brick", "wood"]

def preprocess_image(image):
    """Ensure the image is 2D grayscale and integer type."""
    if image is None:
        print("❌ Warning: Received an empty image.")
        return None

    # Remove extra batch dimensions if they exist
    if len(image.shape) == 4:
        print(f"⚠️ Removing extra dimension: {image.shape}")
        image = np.squeeze(image, axis=0)

    # Convert RGBA to RGB if necessary
    if len(image.shape) == 3 and image.shape[2] == 4:
        print(f"⚠️ Converting RGBA to RGB: {image.shape}")
        image = cv2.cvtColor(image, cv2.COLOR_BGRA2BGR)

    # Convert to grayscale if needed
    if len(image.shape) == 3 and image.shape[2] == 3:
        image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    # Convert to uint8
    return image.astype(np.uint8)

def extract_glcm_features(image):
    """Extract GLCM texture features from a grayscale image."""
    gray = preprocess_image(image)
    if gray is None:
        return None

    glcm = graycomatrix(gray, distances=[1, 3, 5], angles=[0, np.pi/4, np.pi/2, 3*np.pi/4],
                        levels=256, symmetric=True, normed=True)
    contrast = graycoprops(glcm, 'contrast').flatten()
    correlation = graycoprops(glcm, 'correlation').flatten()
    energy = graycoprops(glcm, 'energy').flatten()
    homogeneity = graycoprops(glcm, 'homogeneity').flatten()

    return np.hstack([contrast, correlation, energy, homogeneity])

def extract_lbp_features(image):
    """Extract LBP texture features from a grayscale image."""
    gray = preprocess_image(image)
    if gray is None:
        return None

    radius = 1
    points = 8 * radius
    lbp = local_binary_pattern(gray, P=points, R=radius, method="uniform")
    
    # Compute histogram
    n_bins = int(lbp.max() + 1)
    hist, _ = np.histogram(lbp.ravel(), bins=n_bins, range=(0, n_bins), density=True)

    return hist

def augment_image(image):
    """Perform multiple augmentation techniques on an image."""
    augmented = []

    # Flip horizontally
    augmented.append(cv2.flip(image, 1))

    # Flip vertically
    augmented.append(cv2.flip(image, 0))

    # Rotate 90 degrees clockwise
    augmented.append(cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE))

    # Rotate 90 degrees counterclockwise
    augmented.append(cv2.rotate(image, cv2.ROTATE_90_COUNTERCLOCKWISE))

    # Add Gaussian noise
    noise = np.random.normal(0, 10, image.shape).astype(np.uint8)
    noisy_image = cv2.add(image, noise)
    augmented.append(noisy_image)

    return augmented

def load_dataset():
    """Load images and extract features while handling different image formats."""
    features_glcm, labels_glcm = [], []
    features_lbp, labels_lbp = [], []
    original_images = []  # Store original images for augmentation

    for label, category in enumerate(CATEGORIES):
        category_path = os.path.join(DATASET_PATH, category)

        def is_image_file(file):
            return file.lower().endswith((".png", ".jpg", ".jpeg", ".bmp", ".tiff"))

        image_files = [f for f in os.listdir(category_path) if is_image_file(f)]

        for img_file in image_files:
            img_path = os.path.join(category_path, img_file)
            image = imread(img_path)

            if image is None:
                print(f"❌ Skipping: {img_path} (Failed to load)")
                continue

            image = preprocess_image(image)
            original_images.append(image)  # Store for augmentation

            glcm_features = extract_glcm_features(image)
            lbp_features = extract_lbp_features(image)

            if glcm_features is not None and lbp_features is not None:
                features_glcm.append(glcm_features)
                labels_glcm.append(label)

                features_lbp.append(lbp_features)
                labels_lbp.append(label)

    max_len_glcm = max(len(x) for x in features_glcm)
    features_glcm = np.array([np.pad(x, (0, max_len_glcm - len(x)), mode='constant') for x in features_glcm])

    max_len_lbp = max(len(x) for x in features_lbp)
    features_lbp = np.array([np.pad(x, (0, max_len_lbp - len(x)), mode='constant') for x in features_lbp])

    return np.array(features_glcm), np.array(labels_glcm), np.array(features_lbp), np.array(labels_lbp), original_images

# Load dataset
X_glcm, y_glcm, X_lbp, y_lbp, original_images = load_dataset()

scaler_lbp = StandardScaler()
X_lbp = scaler_lbp.fit_transform(X_lbp)

# Create train-test split while keeping track of original indices
X_train_glcm, X_test_glcm, y_train_glcm, y_test_glcm, train_indices, test_indices = train_test_split(
    X_glcm, y_glcm, np.arange(len(y_glcm)), test_size=0.3, random_state=42
)

X_train_lbp, X_test_lbp, y_train_lbp, y_test_lbp = train_test_split(
    X_lbp, y_lbp, test_size=0.3, random_state=42
)

# Apply augmentation only to training images
X_train_glcm_aug, y_train_glcm_aug = list(X_train_glcm), list(y_train_glcm)
X_train_lbp_aug, y_train_lbp_aug = list(X_train_lbp), list(y_train_lbp)

# 🔹 Corrected: Use indices from the original dataset correctly
for idx, original_idx in enumerate(train_indices):
    image = original_images[original_idx]  # Retrieve correct original image
    augmented_images = augment_image(image)

    for aug_img in augmented_images:
        glcm_features = extract_glcm_features(aug_img)
        lbp_features = extract_lbp_features(aug_img)

        if glcm_features is not None and lbp_features is not None:
            X_train_glcm_aug.append(glcm_features)
            y_train_glcm_aug.append(y_train_glcm[idx])  # ✅ Use correct training label

            X_train_lbp_aug.append(lbp_features)
            y_train_lbp_aug.append(y_train_lbp[idx])  # ✅ Use correct training label

# Convert augmented dataset back to numpy arrays
X_train_glcm = np.array(X_train_glcm_aug)
y_train_glcm = np.array(y_train_glcm_aug)
X_train_lbp = np.array(X_train_lbp_aug)
y_train_lbp = np.array(y_train_lbp_aug)


# Train classifiers
svm_glcm = SVC(kernel='linear')
svm_glcm.fit(X_train_glcm, y_train_glcm)
knn_glcm = KNeighborsClassifier(n_neighbors=3)
knn_glcm.fit(X_train_glcm, y_train_glcm)

svm_lbp = SVC(kernel='linear')
svm_lbp.fit(X_train_lbp, y_train_lbp)
knn_lbp = KNeighborsClassifier(n_neighbors=5, metric='manhattan')
knn_lbp.fit(X_train_lbp, y_train_lbp)


# Evaluate models
print("SVM GLCM Performance:\n", classification_report(y_test_glcm, svm_glcm.predict(X_test_glcm)))
print("KNN GLCM Performance:\n", classification_report(y_test_glcm, knn_glcm.predict(X_test_glcm)))
print("SVM LBP Performance:\n", classification_report(y_test_lbp, svm_lbp.predict(X_test_lbp)))
print("KNN LBP Performance:\n", classification_report(y_test_lbp, knn_lbp.predict(X_test_lbp)))


def classify_texture(image, algorithm):
    image = preprocess_image(image)
    
    if algorithm == "SVM (GLCM)":
        features = extract_glcm_features(image).reshape(1, -1)
        prediction = svm_glcm.predict(features)
    elif algorithm == "KNN (GLCM)":
        features = extract_glcm_features(image).reshape(1, -1)
        prediction = knn_glcm.predict(features)
    elif algorithm == "SVM (LBP)":
        features = extract_lbp_features(image).reshape(1, -1)
        features = scaler_lbp.transform(features)
        prediction = svm_lbp.predict(features)
    elif algorithm == "KNN (LBP)":
        features = extract_lbp_features(image).reshape(1, -1)
        features = scaler_lbp.transform(features)
        prediction = knn_lbp.predict(features)
    
    return CATEGORIES[prediction[0]]


interface = gr.Interface(
    fn=classify_texture,
    inputs=[
        gr.Image(type="numpy"),
        gr.Radio(["SVM (GLCM)", "KNN (GLCM)", "SVM (LBP)", "KNN (LBP)"], label="Select Algorithm")
    ],
    outputs=gr.Label(label="Predicted Texture"),
    title="Texture Classification",
    description="Upload an image of a texture and choose an algorithm to classify it as stone, brick, or wood."
)

interface.launch()