Spaces:
Sleeping
Sleeping
import cv2 | |
import numpy as np | |
from matplotlib import pyplot as plt | |
import gradio as gr | |
def load_image(image_path): | |
image = cv2.imread(image_path) | |
if image is None: | |
print(f"Error: Unable to load image at {image_path}") | |
return None | |
return image | |
def filter_sky_contours(mask, image_height, min_area=8000, max_area=None, height_ratio=0.2): | |
""" | |
Filter contours that are likely to be the sky based on area and position. | |
Parameters: | |
- mask: Binary mask where the sky is white and the rest is black. | |
- image_height: Height of the original image. | |
- min_area: Minimum area of a contour to be considered as sky. | |
- max_area: Maximum area of a contour to be considered as sky. | |
- height_ratio: The ratio of the image height where we expect the sky to be located. | |
Returns: | |
- sky_mask: Refined binary mask with filtered sky regions. | |
""" | |
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) | |
sky_mask = np.zeros_like(mask) | |
for contour in contours: | |
area = cv2.contourArea(contour) | |
x, y, w, h = cv2.boundingRect(contour) | |
aspect_ratio = w / float(h) | |
convex_hull_area = cv2.contourArea(cv2.convexHull(contour)) | |
if convex_hull_area > 0: # Ensure the denominator is not zero | |
smoothness = area / convex_hull_area | |
else: | |
smoothness = 1 # Or some other default value that makes sense for your application | |
if (area > min_area) and (max_area is None or area < max_area) and (y < image_height * height_ratio): | |
if aspect_ratio > 1 and smoothness > 0.5: # Adjust thresholds as necessary | |
cv2.drawContours(sky_mask, [contour], -1, (255), thickness=cv2.FILLED) | |
return sky_mask | |
def detect_edges(image): | |
# Convert to grayscale | |
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) | |
# Apply bilateral filter to preserve edges while reducing noise | |
bilateral = cv2.bilateralFilter(gray, d=9, sigmaColor=75, sigmaSpace=75) | |
# Use a combination of Sobel filters to find gradients | |
sobelx = cv2.Sobel(bilateral, cv2.CV_64F, 1, 0, ksize=3) | |
sobely = cv2.Sobel(bilateral, cv2.CV_64F, 0, 1, ksize=3) | |
# Combine the gradients | |
gradient_magnitude = cv2.magnitude(sobelx, sobely) | |
gradient_magnitude = np.uint8(gradient_magnitude) | |
# Apply thresholding to get binary result | |
_, edge_binary = cv2.threshold(gradient_magnitude, 20, 255, cv2.THRESH_BINARY) | |
# Optionally, you can apply dilation followed by erosion to close gaps | |
kernel = np.ones((3,3), np.uint8) | |
edge_dilated = cv2.dilate(edge_binary, kernel, iterations=1) | |
edge_processed = cv2.erode(edge_dilated, kernel, iterations=1) | |
return edge_processed | |
def adaptive_threshold_sky(image): | |
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) | |
return cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, | |
cv2.THRESH_BINARY,21,2) | |
def refine_mask(mask): | |
kernel = np.ones((35,35), np.uint8) | |
return cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) | |
def segment_sky(image): | |
# Detect edges. | |
edges = detect_edges(image) | |
# Invert the edges. | |
edges_inv = cv2.bitwise_not(edges) | |
# Create a color mask using adaptive thresholding. | |
color_mask = adaptive_threshold_sky(image) | |
# Combine the color mask with the inverted edges to get an initial sky mask. | |
combined_mask = cv2.bitwise_and(color_mask, edges_inv) | |
# Filter contours that are likely to be the sky and refine the mask. | |
refined_mask_contour = filter_sky_contours(combined_mask, image.shape[0]) | |
# Optionally, you can further refine the mask with morphological operations if needed. | |
refined_mask_morph = refine_mask(refined_mask_contour) | |
# Apply the final refined mask to segment the sky. | |
segmented_sky = cv2.bitwise_and(image, image, mask=refined_mask_morph) | |
contours, _ = cv2.findContours(refined_mask_morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) | |
return segmented_sky, contours | |
def display_results_with_contours(original, segmented, edges, contours): | |
# Create a copy of the original image to draw contours on. | |
image_with_contours = original.copy() | |
cv2.drawContours(image_with_contours, contours, -1, (0, 255, 0), 3) | |
plt.figure(figsize=(12, 8)) | |
plt.subplot(1, 4, 1) | |
plt.imshow(cv2.cvtColor(original, cv2.COLOR_BGR2RGB)) | |
plt.title('Original Image') | |
plt.subplot(1, 4, 2) | |
plt.imshow(edges, cmap='gray') | |
plt.title('Edges') | |
plt.subplot(1, 4, 3) | |
plt.imshow(cv2.cvtColor(segmented, cv2.COLOR_BGR2RGB)) | |
plt.title('Segmented Sky') | |
plt.subplot(1, 4, 4) | |
plt.imshow(cv2.cvtColor(image_with_contours, cv2.COLOR_BGR2RGB)) | |
plt.title('Contours') | |
plt.show() | |
def process_and_display(image): | |
# Convert the uploaded PIL image to an OpenCV format | |
image = np.array(image) | |
image = image[:, :, :3] # Ensure the image is in RGB format | |
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) | |
# Segment the sky | |
segmented_sky, _ = segment_sky(image) | |
# Prepare the segmented sky image for display in Gradio | |
segmented_rgb = cv2.cvtColor(segmented_sky, cv2.COLOR_BGR2RGB) | |
return segmented_rgb | |
# Set up the Gradio interface | |
iface = gr.Interface( | |
fn=process_and_display, | |
inputs=gr.Image(), | |
outputs=gr.Image(label="Segmented Sky"), | |
title="Sky Pixel Identification", | |
description="Upload an image to see the segmented sky." | |
) | |
# Run the app | |
iface.launch() | |