Update app.py
Browse files
app.py
CHANGED
|
@@ -26,6 +26,7 @@ TARGET_SIZE = (30, 30)
|
|
| 26 |
distances = [1]
|
| 27 |
angles = [0]
|
| 28 |
|
|
|
|
| 29 |
def load_and_convert_images(directory):
|
| 30 |
"""Load images from a specified directory using glob and convert them to grayscale.
|
| 31 |
|
|
@@ -36,49 +37,23 @@ def load_and_convert_images(directory):
|
|
| 36 |
list: A list of resized grayscale images.
|
| 37 |
"""
|
| 38 |
dataset = []
|
| 39 |
-
# Use glob to find image files in the directory
|
| 40 |
for img_path in glob.glob(f"{directory}/*.*"):
|
| 41 |
if img_path.endswith((".jpg", ".png", ".jpeg")):
|
| 42 |
img = cv2.imread(img_path) # Read the image
|
| 43 |
if img is not None:
|
| 44 |
# Convert to grayscale and resize the image
|
| 45 |
gray_image = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
| 46 |
-
resized_image = cv2.resize(
|
|
|
|
|
|
|
| 47 |
dataset.append(resized_image)
|
| 48 |
return dataset
|
| 49 |
|
| 50 |
-
# Ensure that both datasets contain 2D arrays (grayscale images)
|
| 51 |
-
def check_and_reshape(dataset):
|
| 52 |
-
"""Ensure all images in the dataset are 2D arrays.
|
| 53 |
-
|
| 54 |
-
Args:
|
| 55 |
-
dataset (list): A list of images.
|
| 56 |
-
|
| 57 |
-
Returns:
|
| 58 |
-
list: A list of reshaped images to ensure they are all 2D.
|
| 59 |
-
"""
|
| 60 |
-
reshaped_dataset = []
|
| 61 |
-
for img in dataset:
|
| 62 |
-
if img.ndim == 3: # If the image has 3 dimensions (like RGB), convert it to grayscale
|
| 63 |
-
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
| 64 |
-
reshaped_dataset.append(gray_img)
|
| 65 |
-
elif img.ndim == 2: # If it's already grayscale (2D), just append it
|
| 66 |
-
reshaped_dataset.append(img)
|
| 67 |
-
else:
|
| 68 |
-
raise ValueError("Unexpected image dimension: {}".format(img.ndim))
|
| 69 |
-
return reshaped_dataset
|
| 70 |
|
| 71 |
# Load datasets using glob
|
| 72 |
grass_dataset = load_and_convert_images(grass_dir)
|
| 73 |
wood_dataset = load_and_convert_images(wood_dir)
|
| 74 |
|
| 75 |
-
# Apply the reshaping to both datasets
|
| 76 |
-
grass_dataset = check_and_reshape(grass_dataset)
|
| 77 |
-
wood_dataset = check_and_reshape(wood_dataset)
|
| 78 |
-
|
| 79 |
-
# Now concatenate the datasets
|
| 80 |
-
X = np.concatenate([grass_dataset, wood_dataset])
|
| 81 |
-
y = np.concatenate([np.zeros(len(grass_dataset)), np.ones(len(wood_dataset))]) # Labels: 0 for Grass, 1 for Wood
|
| 82 |
|
| 83 |
def calc_glcm_features(images):
|
| 84 |
"""Calculate GLCM features for a list of images.
|
|
@@ -90,48 +65,181 @@ def calc_glcm_features(images):
|
|
| 90 |
list: A list of GLCM features for each image.
|
| 91 |
"""
|
| 92 |
features = []
|
| 93 |
-
for
|
| 94 |
-
glcm = graycomatrix(
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
|
|
|
|
|
|
|
|
|
| 100 |
features.append([contrast, dissimilarity, homogeneity, energy, correlation])
|
| 101 |
return features
|
| 102 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
def extract_lbp_features(images):
|
| 104 |
-
"""Extract LBP features
|
| 105 |
|
| 106 |
Args:
|
| 107 |
images (list): A list of grayscale images.
|
| 108 |
|
| 109 |
Returns:
|
| 110 |
-
list: A list of LBP
|
| 111 |
"""
|
| 112 |
-
|
| 113 |
for image in images:
|
| 114 |
lbp = local_binary_pattern(image, N_POINTS, RADIUS, method="uniform")
|
| 115 |
-
|
| 116 |
-
lbp_hist =
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
|
| 121 |
-
#
|
| 122 |
-
|
| 123 |
-
|
|
|
|
|
|
|
| 124 |
|
| 125 |
-
#
|
| 126 |
-
|
| 127 |
|
| 128 |
-
#
|
| 129 |
-
|
| 130 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
|
| 132 |
-
# Example: KNN classifier for LBP features
|
| 133 |
-
knn_lbp = KNeighborsClassifier(n_neighbors=3)
|
| 134 |
-
knn_lbp.fit(lbp_features, y)
|
| 135 |
|
| 136 |
def classify_texture(image, method):
|
| 137 |
"""Classify the texture of the uploaded image as grass or wood using the selected method.
|
|
@@ -150,10 +258,10 @@ def classify_texture(image, method):
|
|
| 150 |
# Extract features based on the selected method
|
| 151 |
if method == "GLCM":
|
| 152 |
feature = calc_glcm_features([resized_image])
|
| 153 |
-
prediction =
|
| 154 |
elif method == "LBP":
|
| 155 |
feature = extract_lbp_features([resized_image])
|
| 156 |
-
prediction =
|
| 157 |
else:
|
| 158 |
raise ValueError("The method is not recognized")
|
| 159 |
|
|
@@ -162,7 +270,9 @@ def classify_texture(image, method):
|
|
| 162 |
|
| 163 |
# Highlight the image based on the classification
|
| 164 |
if result == "Grass":
|
| 165 |
-
highlighted_image = cv2.cvtColor(
|
|
|
|
|
|
|
| 166 |
highlighted_image[:] = [0, 255, 0] # Highlight in green
|
| 167 |
else:
|
| 168 |
highlighted_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
|
@@ -170,6 +280,7 @@ def classify_texture(image, method):
|
|
| 170 |
|
| 171 |
return result, highlighted_image
|
| 172 |
|
|
|
|
| 173 |
# Gradio interface setup with a dropdown for method selection
|
| 174 |
iface = gr.Interface(
|
| 175 |
fn=classify_texture,
|
|
|
|
| 26 |
distances = [1]
|
| 27 |
angles = [0]
|
| 28 |
|
| 29 |
+
|
| 30 |
def load_and_convert_images(directory):
|
| 31 |
"""Load images from a specified directory using glob and convert them to grayscale.
|
| 32 |
|
|
|
|
| 37 |
list: A list of resized grayscale images.
|
| 38 |
"""
|
| 39 |
dataset = []
|
|
|
|
| 40 |
for img_path in glob.glob(f"{directory}/*.*"):
|
| 41 |
if img_path.endswith((".jpg", ".png", ".jpeg")):
|
| 42 |
img = cv2.imread(img_path) # Read the image
|
| 43 |
if img is not None:
|
| 44 |
# Convert to grayscale and resize the image
|
| 45 |
gray_image = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
| 46 |
+
resized_image = cv2.resize(
|
| 47 |
+
gray_image, TARGET_SIZE, interpolation=cv2.INTER_AREA
|
| 48 |
+
)
|
| 49 |
dataset.append(resized_image)
|
| 50 |
return dataset
|
| 51 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
|
| 53 |
# Load datasets using glob
|
| 54 |
grass_dataset = load_and_convert_images(grass_dir)
|
| 55 |
wood_dataset = load_and_convert_images(wood_dir)
|
| 56 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
def calc_glcm_features(images):
|
| 59 |
"""Calculate GLCM features for a list of images.
|
|
|
|
| 65 |
list: A list of GLCM features for each image.
|
| 66 |
"""
|
| 67 |
features = []
|
| 68 |
+
for img in images:
|
| 69 |
+
glcm = graycomatrix(img, distances, angles, symmetric=True, normed=True)
|
| 70 |
+
|
| 71 |
+
# Calculate GLCM properties
|
| 72 |
+
contrast = graycoprops(glcm, "contrast")[0, 0]
|
| 73 |
+
dissimilarity = graycoprops(glcm, "dissimilarity")[0, 0]
|
| 74 |
+
homogeneity = graycoprops(glcm, "homogeneity")[0, 0]
|
| 75 |
+
energy = graycoprops(glcm, "energy")[0, 0]
|
| 76 |
+
correlation = graycoprops(glcm, "correlation")[0, 0]
|
| 77 |
+
|
| 78 |
features.append([contrast, dissimilarity, homogeneity, energy, correlation])
|
| 79 |
return features
|
| 80 |
|
| 81 |
+
|
| 82 |
+
# Calculate GLCM features for grass and wood datasets
|
| 83 |
+
grass_glcm_features = calc_glcm_features(grass_dataset)
|
| 84 |
+
wood_glcm_features = calc_glcm_features(wood_dataset)
|
| 85 |
+
|
| 86 |
+
|
| 87 |
def extract_lbp_features(images):
|
| 88 |
+
"""Extract LBP features from a list of images.
|
| 89 |
|
| 90 |
Args:
|
| 91 |
images (list): A list of grayscale images.
|
| 92 |
|
| 93 |
Returns:
|
| 94 |
+
list: A list of LBP histograms for each image.
|
| 95 |
"""
|
| 96 |
+
lbp_features = []
|
| 97 |
for image in images:
|
| 98 |
lbp = local_binary_pattern(image, N_POINTS, RADIUS, method="uniform")
|
| 99 |
+
n_bins = int(lbp.max() + 1)
|
| 100 |
+
lbp_hist, _ = np.histogram(lbp, bins=n_bins, range=(0, n_bins), density=True)
|
| 101 |
+
lbp_features.append(lbp_hist)
|
| 102 |
+
return lbp_features
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
# Extract LBP features for grass and wood datasets
|
| 106 |
+
grass_lbp_features = extract_lbp_features(grass_dataset)
|
| 107 |
+
wood_lbp_features = extract_lbp_features(wood_dataset)
|
| 108 |
+
|
| 109 |
+
# Create labels (0 for grass, 1 for wood)
|
| 110 |
+
grass_labels = [0] * len(grass_dataset) # Label all grass images as 0
|
| 111 |
+
wood_labels = [1] * len(wood_dataset) # Label all wood images as 1
|
| 112 |
+
|
| 113 |
+
# Combine features and labels for GLCM classifier
|
| 114 |
+
glcm_features = np.array(grass_glcm_features + wood_glcm_features)
|
| 115 |
+
glcm_labels = grass_labels + wood_labels
|
| 116 |
+
|
| 117 |
+
# Prepare labels and features for LBP classifier
|
| 118 |
+
lbp_features = np.array(grass_lbp_features + wood_lbp_features)
|
| 119 |
+
lbp_labels = grass_labels + wood_labels
|
| 120 |
+
|
| 121 |
+
# Number of images
|
| 122 |
+
num_grass = len(grass_dataset)
|
| 123 |
+
num_wood = len(wood_dataset)
|
| 124 |
+
|
| 125 |
+
# Define labels for classification
|
| 126 |
+
y = np.array([0] * num_grass + [1] * num_wood)
|
| 127 |
+
|
| 128 |
+
# Define KFold cross-validation
|
| 129 |
+
k = 5
|
| 130 |
+
kf = KFold(n_splits=k, shuffle=True, random_state=42)
|
| 131 |
|
| 132 |
+
# Store results for GLCM and LBP classifiers
|
| 133 |
+
glcm_metrics = {"accuracy": [], "precision": [], "recall": [], "f1_score": []}
|
| 134 |
+
lbp_metrics = {"accuracy": [], "precision": [], "recall": [], "f1_score": []}
|
| 135 |
+
y_true_glcm, y_true_lbp = [], []
|
| 136 |
+
y_pred_glcm, y_pred_lbp = [], []
|
| 137 |
|
| 138 |
+
# Parameter tuning using GridSearchCV for KNN classifier
|
| 139 |
+
param_grid = {"n_neighbors": [3, 5, 7], "p": [1, 2]}
|
| 140 |
|
| 141 |
+
# GLCM Classifier Training and Evaluation
|
| 142 |
+
glcm_knn = KNeighborsClassifier()
|
| 143 |
+
glcm_grid_search = GridSearchCV(glcm_knn, param_grid, cv=kf)
|
| 144 |
+
glcm_grid_search.fit(glcm_features, y)
|
| 145 |
+
|
| 146 |
+
# Perform cross-validation and evaluate GLCM classifier
|
| 147 |
+
for train_index, test_index in kf.split(glcm_features):
|
| 148 |
+
x_train, x_test = glcm_features[train_index], glcm_features[test_index]
|
| 149 |
+
y_train, y_test = y[train_index], y[test_index]
|
| 150 |
+
|
| 151 |
+
glcm_classifier = KNeighborsClassifier(
|
| 152 |
+
n_neighbors=glcm_grid_search.best_params_["n_neighbors"]
|
| 153 |
+
)
|
| 154 |
+
glcm_classifier.fit(x_train, y_train)
|
| 155 |
+
y_pred = glcm_classifier.predict(x_test)
|
| 156 |
+
|
| 157 |
+
# Collect true and predicted labels
|
| 158 |
+
y_true_glcm.extend(y_test)
|
| 159 |
+
y_pred_glcm.extend(y_pred)
|
| 160 |
+
|
| 161 |
+
# Calculate and store metrics
|
| 162 |
+
accuracy = accuracy_score(y_test, y_pred)
|
| 163 |
+
precision = precision_score(y_test, y_pred, average="macro")
|
| 164 |
+
recall = recall_score(y_test, y_pred, average="macro")
|
| 165 |
+
f1 = f1_score(y_test, y_pred, average="macro")
|
| 166 |
+
|
| 167 |
+
glcm_metrics["accuracy"].append(accuracy)
|
| 168 |
+
glcm_metrics["precision"].append(precision)
|
| 169 |
+
glcm_metrics["recall"].append(recall)
|
| 170 |
+
glcm_metrics["f1_score"].append(f1)
|
| 171 |
+
|
| 172 |
+
# Print metrics for this fold
|
| 173 |
+
print(
|
| 174 |
+
f"GLCM Fold Metrics: Accuracy={accuracy:.2f}, Precision={precision:.2f}, Recall={recall:.2f}, F1-Score={f1:.2f}"
|
| 175 |
+
)
|
| 176 |
+
|
| 177 |
+
# Print average metrics for GLCM classifier
|
| 178 |
+
print("\nAverage GLCM Classifier Metrics:")
|
| 179 |
+
for metric in glcm_metrics:
|
| 180 |
+
avg_metric = np.mean(glcm_metrics[metric])
|
| 181 |
+
print(f"{metric.capitalize()}: {avg_metric:.2f}")
|
| 182 |
+
|
| 183 |
+
# Confusion Matrix for GLCM
|
| 184 |
+
glcm_conf_matrix = confusion_matrix(y_true_glcm, y_pred_glcm)
|
| 185 |
+
print("\nConfusion Matrix for GLCM Classifier:")
|
| 186 |
+
print(glcm_conf_matrix)
|
| 187 |
+
|
| 188 |
+
# Classification Report for GLCM
|
| 189 |
+
print("\nClassification Report for GLCM Classifier:")
|
| 190 |
+
print(classification_report(y_true_glcm, y_pred_glcm))
|
| 191 |
+
|
| 192 |
+
# LBP Classifier Training and Evaluation
|
| 193 |
+
lbp_knn = KNeighborsClassifier()
|
| 194 |
+
lbp_grid_search = GridSearchCV(lbp_knn, param_grid, cv=kf)
|
| 195 |
+
lbp_grid_search.fit(lbp_features, y)
|
| 196 |
+
|
| 197 |
+
# Perform cross-validation and evaluate LBP classifier
|
| 198 |
+
for train_index, test_index in kf.split(lbp_features):
|
| 199 |
+
x_train, x_test = lbp_features[train_index], lbp_features[test_index]
|
| 200 |
+
y_train, y_test = y[train_index], y[test_index]
|
| 201 |
+
|
| 202 |
+
lbp_classifier = KNeighborsClassifier(
|
| 203 |
+
n_neighbors=lbp_grid_search.best_params_["n_neighbors"]
|
| 204 |
+
)
|
| 205 |
+
lbp_classifier.fit(x_train, y_train)
|
| 206 |
+
y_pred = lbp_classifier.predict(x_test)
|
| 207 |
+
|
| 208 |
+
# Collect true and predicted labels
|
| 209 |
+
y_true_lbp.extend(y_test)
|
| 210 |
+
y_pred_lbp.extend(y_pred)
|
| 211 |
+
|
| 212 |
+
# Calculate and store metrics
|
| 213 |
+
accuracy = accuracy_score(y_test, y_pred)
|
| 214 |
+
precision = precision_score(y_test, y_pred, average="macro")
|
| 215 |
+
recall = recall_score(y_test, y_pred, average="macro")
|
| 216 |
+
f1 = f1_score(y_test, y_pred, average="macro")
|
| 217 |
+
|
| 218 |
+
lbp_metrics["accuracy"].append(accuracy)
|
| 219 |
+
lbp_metrics["precision"].append(precision)
|
| 220 |
+
lbp_metrics["recall"].append(recall)
|
| 221 |
+
lbp_metrics["f1_score"].append(f1)
|
| 222 |
+
|
| 223 |
+
# Print metrics for this fold
|
| 224 |
+
print(
|
| 225 |
+
f"LBP Fold Metrics: Accuracy={accuracy:.2f}, Precision={precision:.2f}, Recall={recall:.2f}, F1-Score={f1:.2f}"
|
| 226 |
+
)
|
| 227 |
+
|
| 228 |
+
# Print average metrics for LBP classifier
|
| 229 |
+
print("\nAverage LBP Classifier Metrics:")
|
| 230 |
+
for metric in lbp_metrics:
|
| 231 |
+
avg_metric = np.mean(lbp_metrics[metric])
|
| 232 |
+
print(f"{metric.capitalize()}: {avg_metric:.2f}")
|
| 233 |
+
|
| 234 |
+
# Confusion Matrix for LBP
|
| 235 |
+
lbp_conf_matrix = confusion_matrix(y_true_lbp, y_pred_lbp)
|
| 236 |
+
print("\nConfusion Matrix for LBP Classifier:")
|
| 237 |
+
print(lbp_conf_matrix)
|
| 238 |
+
|
| 239 |
+
# Classification Report for LBP
|
| 240 |
+
print("\nClassification Report for LBP Classifier:")
|
| 241 |
+
print(classification_report(y_true_lbp, y_pred_lbp))
|
| 242 |
|
|
|
|
|
|
|
|
|
|
| 243 |
|
| 244 |
def classify_texture(image, method):
|
| 245 |
"""Classify the texture of the uploaded image as grass or wood using the selected method.
|
|
|
|
| 258 |
# Extract features based on the selected method
|
| 259 |
if method == "GLCM":
|
| 260 |
feature = calc_glcm_features([resized_image])
|
| 261 |
+
prediction = glcm_classifier.predict(feature)
|
| 262 |
elif method == "LBP":
|
| 263 |
feature = extract_lbp_features([resized_image])
|
| 264 |
+
prediction = lbp_classifier.predict(feature)
|
| 265 |
else:
|
| 266 |
raise ValueError("The method is not recognized")
|
| 267 |
|
|
|
|
| 270 |
|
| 271 |
# Highlight the image based on the classification
|
| 272 |
if result == "Grass":
|
| 273 |
+
highlighted_image = cv2.cvtColor(
|
| 274 |
+
image, cv2.COLOR_BGR2RGB
|
| 275 |
+
) # Convert to RGB for displaying
|
| 276 |
highlighted_image[:] = [0, 255, 0] # Highlight in green
|
| 277 |
else:
|
| 278 |
highlighted_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
|
|
|
| 280 |
|
| 281 |
return result, highlighted_image
|
| 282 |
|
| 283 |
+
|
| 284 |
# Gradio interface setup with a dropdown for method selection
|
| 285 |
iface = gr.Interface(
|
| 286 |
fn=classify_texture,
|