Spaces:
Sleeping
Sleeping
| import cv2 | |
| import numpy as np | |
| import gradio as gr | |
| def process_image(image, calibration_factor): | |
| """ | |
| Processes the input image to measure the hair diameter. | |
| Args: | |
| image (numpy array): Uploaded image in RGB format. | |
| calibration_factor (float): Conversion factor from pixels to micrometers. | |
| Returns: | |
| tuple: A result text and the annotated image. | |
| """ | |
| if image is None: | |
| return "No image provided.", None | |
| # Convert image from RGB (Gradio default) to BGR (OpenCV default) | |
| image_bgr = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) | |
| # Convert to grayscale | |
| gray = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2GRAY) | |
| # Apply Gaussian blur to reduce noise | |
| blurred = cv2.GaussianBlur(gray, (5, 5), 0) | |
| # Apply thresholding (using Otsu's method) to create a binary image | |
| ret, thresh = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) | |
| # Find contours in the thresholded image | |
| contours, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) | |
| if not contours: | |
| return "No hair contour found.", image | |
| # Assume the hair strand is the largest contour | |
| hair_contour = max(contours, key=cv2.contourArea) | |
| # Fit a minimum area rectangle to the contour | |
| rect = cv2.minAreaRect(hair_contour) | |
| (center, (width, height), angle) = rect | |
| # The smaller dimension is considered the hair's diameter in pixels | |
| diameter_pixels = min(width, height) | |
| diameter_real = diameter_pixels * calibration_factor # Convert to micrometers | |
| # Draw the fitted rectangle on the image for visualization | |
| box = cv2.boxPoints(rect) | |
| box = np.int0(box) | |
| annotated = image_bgr.copy() | |
| cv2.drawContours(annotated, [box], 0, (0, 255, 0), 2) | |
| # Convert annotated image back to RGB for Gradio display | |
| annotated_rgb = cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB) | |
| result_text = f"Estimated Hair Diameter: {diameter_real:.2f} micrometers" | |
| return result_text, annotated_rgb | |
| # Build the Gradio interface | |
| iface = gr.Interface( | |
| fn=process_image, | |
| inputs=[ | |
| gr.Image(type="numpy", label="Upload Hair Image"), | |
| gr.Number(value=0.05, label="Calibration Factor (micrometers per pixel)") | |
| ], | |
| outputs=[ | |
| gr.Textbox(label="Result"), | |
| gr.Image(label="Annotated Image") | |
| ], | |
| title="Hair Diameter Measurement Tool", | |
| description=( | |
| "Upload a high-resolution image of a hair sample (captured with a dermascope) " | |
| "and provide a calibration factor based on a reference object included in the image. " | |
| "This tool processes the image to estimate the hair diameter using image processing techniques." | |
| ) | |
| ) | |
| if __name__ == '__main__': | |
| iface.launch() |