229 lines
8.7 KiB
Python
229 lines
8.7 KiB
Python
import cv2
|
|
import numpy as np
|
|
from tkinter import Tk, Button, filedialog, Label, messagebox, Label, messagebox, StringVar, OptionMenu
|
|
from PIL import Image, ImageTk
|
|
import os
|
|
import cv2
|
|
import numpy as np
|
|
from sklearn.mixture import GaussianMixture
|
|
import matplotlib.pyplot as plt
|
|
|
|
def preprocess_image(image_path):
|
|
image = cv2.imread(image_path)
|
|
if image is None:
|
|
raise FileNotFoundError(f"Image not found or unable to read: {image_path}")
|
|
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
|
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
|
|
return blurred, image
|
|
|
|
def segment_crystal(blurred_image):
|
|
_, binary = cv2.threshold(blurred_image, 30, 255, cv2.THRESH_BINARY)
|
|
contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
|
if len(contours) == 0:
|
|
raise ValueError("No crystal detected in the image.")
|
|
crystal_contour = max(contours, key=cv2.contourArea)
|
|
mask = np.zeros_like(blurred_image)
|
|
cv2.drawContours(mask, [crystal_contour], -1, 255, thickness=cv2.FILLED)
|
|
segmented_crystal = cv2.bitwise_and(blurred_image, blurred_image, mask=mask)
|
|
return segmented_crystal, crystal_contour, binary
|
|
|
|
def detect_defects_GMM(segmented_crystal, crystal_contour):
|
|
# Extract the region of interest (ROI) using the crystal contour
|
|
mask = np.zeros_like(segmented_crystal)
|
|
cv2.drawContours(mask, [crystal_contour], -1, 255, thickness=cv2.FILLED)
|
|
roi = cv2.bitwise_and(segmented_crystal, segmented_crystal, mask=mask)
|
|
|
|
# Flatten the ROI to a 1D array of pixel intensities
|
|
pixel_intensities = roi.flatten()
|
|
pixel_intensities = pixel_intensities[pixel_intensities > 0] # Remove background pixels
|
|
|
|
if len(pixel_intensities) < 10:
|
|
raise ValueError("Not enough data points to fit Gaussian Mixture Model.")
|
|
|
|
# Reshape for GMM
|
|
X = pixel_intensities.reshape(-1, 1)
|
|
|
|
# Fit a Gaussian Mixture Model with two components
|
|
gmm = GaussianMixture(n_components=2, random_state=0).fit(X)
|
|
|
|
# Get the means and covariances of the fitted Gaussians
|
|
means = gmm.means_.flatten()
|
|
covars = gmm.covariances_.flatten()
|
|
|
|
# Determine which component corresponds to high brightness
|
|
high_brightness_mean_index = np.argmax(means)
|
|
high_brightness_mean = means[high_brightness_mean_index]
|
|
high_brightness_covar = covars[high_brightness_mean_index]
|
|
|
|
# Calculate the probability density function (PDF) values for each pixel intensity
|
|
pdf_values = gmm.score_samples(X)
|
|
|
|
# Set a threshold to identify high brightness regions
|
|
threshold = np.percentile(pdf_values, 98) # Adjust this threshold as needed
|
|
|
|
# Identify high brightness pixels
|
|
high_brightness_pixels = X[pdf_values >= threshold].flatten()
|
|
|
|
# Find contours corresponding to high brightness regions
|
|
_, binary_high_brightness = cv2.threshold(roi, int(high_brightness_mean), 255, cv2.THRESH_BINARY)
|
|
contours, _ = cv2.findContours(binary_high_brightness, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
|
|
|
defects = []
|
|
for contour in contours:
|
|
perimeter = cv2.arcLength(contour, True)
|
|
if perimeter > 5:
|
|
defects.append(contour)
|
|
|
|
# Create a black image with the same shape as the original image
|
|
binary_defects = np.zeros_like(segmented_crystal, dtype=np.uint8)
|
|
|
|
# Draw high brightness regions on the binary defects image
|
|
for y in range(segmented_crystal.shape[0]):
|
|
for x in range(segmented_crystal.shape[1]):
|
|
if mask[y, x] != 0 and segmented_crystal[y, x] >= high_brightness_mean:
|
|
binary_defects[y, x] = 255
|
|
|
|
# Generate a GMM plot image
|
|
fig, ax = plt.subplots(figsize=(4.8, 3.6))
|
|
ax.hist(pixel_intensities, bins=50, density=True, alpha=0.5, color='gray', edgecolor='black')
|
|
|
|
x_vals = np.linspace(0, 255, 1000).reshape(-1, 1)
|
|
log_prob = gmm.score_samples(x_vals)
|
|
responsibilities = gmm.predict_proba(x_vals)
|
|
|
|
# Convert the plot to an image
|
|
fig.canvas.draw()
|
|
gmm_plot_img = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8)
|
|
gmm_plot_img = gmm_plot_img.reshape(fig.canvas.get_width_height()[::-1] + (3,))
|
|
plt.close(fig)
|
|
|
|
cv2.imshow("gmm",gmm_plot_img)
|
|
cv2.waitKey(10)
|
|
|
|
return defects, binary_defects
|
|
|
|
def detect_defects(segmented_crystal, crystal_contour):
|
|
edges = cv2.Canny(segmented_crystal, 30, 90)
|
|
mask = np.zeros_like(edges)
|
|
cv2.drawContours(mask, [crystal_contour], -1, 255, thickness=cv2.FILLED)
|
|
inverted_mask = mask#cv2.bitwise_not(mask)
|
|
defects_edges = cv2.bitwise_and(edges, inverted_mask)
|
|
contours, _ = cv2.findContours(defects_edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
|
defects = []
|
|
for contour in contours:
|
|
perimeter = cv2.arcLength(contour, True)
|
|
if perimeter > 5:
|
|
defects.append(contour)
|
|
return defects,defects_edges
|
|
|
|
def calculate_total_area(crystal_contour):
|
|
total_area = cv2.contourArea(crystal_contour)
|
|
return total_area
|
|
|
|
def score_defects(defects, total_area):
|
|
defect_area = sum(cv2.contourArea(defect) for defect in defects)
|
|
score = (defect_area / total_area) * 100
|
|
return score
|
|
|
|
def resize_image_for_display(image, width=480, height=360):
|
|
return cv2.resize(image, (width, height))
|
|
|
|
def process_image():
|
|
global original_image, binary_image, image_with_defects, segmented_crystal, crystal_contour, defects, total_area, score
|
|
|
|
# Get the directory of the current script
|
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
# Open file dialog starting from the script's directory
|
|
image_path = filedialog.askopenfilename(initialdir=script_dir, title="Select an Image",
|
|
filetypes=(("JPEG files", "*.jpg *.jpeg"), ("PNG files", "*.png"), ("All files", "*.*")))
|
|
|
|
if not image_path:
|
|
return
|
|
|
|
try:
|
|
blurred_image, original_image = preprocess_image(image_path)
|
|
segmented_crystal, crystal_contour, binary_image = segment_crystal(blurred_image)
|
|
selected_algorithm = algorithm_var.get()
|
|
if selected_algorithm == '1':
|
|
defects,binary_image = detect_defects(segmented_crystal, crystal_contour)
|
|
elif selected_algorithm == '2':
|
|
defects,binary_image = detect_defects_GMM(segmented_crystal, crystal_contour)
|
|
|
|
total_area = calculate_total_area(crystal_contour)
|
|
score = score_defects(defects, total_area)
|
|
|
|
result_text = (
|
|
f"Detected {len(defects)} defects.\n"
|
|
f"Total Defect Area: {sum(cv2.contourArea(defect) for defect in defects):.2f} pixels\n"
|
|
f"Total Crystal Area: {total_area} pixels\n"
|
|
f"Defect Score (% of Total Area): {score:.2f}%"
|
|
)
|
|
result_label.config(text=result_text)
|
|
|
|
print(result_text)
|
|
|
|
image_with_defects = cv2.drawContours(original_image.copy(), defects, -1, (0, 255, 0), 2)
|
|
|
|
update_images()
|
|
except Exception as e:
|
|
messagebox.showerror("Error", str(e))
|
|
|
|
def update_images():
|
|
images = [
|
|
("Original Image", resize_image_for_display(original_image)),
|
|
("Binary Defect Image", resize_image_for_display(binary_image)),
|
|
("Image with Defects", resize_image_for_display(image_with_defects)),
|
|
("Segmented Crystal", resize_image_for_display(segmented_crystal))
|
|
]
|
|
|
|
for i, (label_text, img) in enumerate(images):
|
|
label = labels[i]
|
|
label.config(text=label_text)
|
|
|
|
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
|
img_pil = Image.fromarray(img_rgb)
|
|
img_tk = ImageTk.PhotoImage(img_pil)
|
|
|
|
image_labels[i].config(image=img_tk)
|
|
image_labels[i].image = img_tk
|
|
|
|
root = Tk()
|
|
root.title("Crystal Defect Detection")
|
|
|
|
original_image = None
|
|
binary_image = None
|
|
image_with_defects = None
|
|
segmented_crystal = None
|
|
crystal_contour = None
|
|
defects = []
|
|
total_area = 0
|
|
score = 0
|
|
|
|
labels = [Label(root, text=f"Image {i+1}") for i in range(4)]
|
|
image_labels = [Label(root) for i in range(4)]
|
|
|
|
# Add a label to display the detection results
|
|
result_label = Label(root, text="", justify='left')
|
|
|
|
button_open = Button(root, text="Open Image", command=process_image)
|
|
|
|
# Algorithm selection dropdown menu
|
|
algorithm_var = StringVar(value='1') # Default to algorithm 1
|
|
algorithm_menu = OptionMenu(root, algorithm_var, '1', '2')
|
|
algorithm_menu.config(width=10)
|
|
|
|
# Arrange labels and images in a 2x2 grid
|
|
for i in range(4):
|
|
row = i // 2
|
|
col = i % 2
|
|
labels[i].grid(row=row*2, column=col*2, padx=10, pady=5)
|
|
image_labels[i].grid(row=row*2+1, column=col*2, padx=10, pady=5)
|
|
|
|
# Place the result label below the images
|
|
result_label.grid(row=4, column=0, columnspan=2, padx=10, pady=10)
|
|
|
|
button_open.grid(row=5, column=0, columnspan=2, padx=10, pady=10)
|
|
algorithm_menu.grid(row=5, column=1, columnspan=2, padx=30, pady=10)
|
|
|
|
root.mainloop() |