Spaces:
Sleeping
Sleeping
File size: 7,778 Bytes
a2c2f9c c245bb5 a2c2f9c 62e6699 a2c2f9c |
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 |
#Hannah Nguyen
#CS5330 - Lab 1: Sky Pixel Identification
import gradio as gr
import cv2
import numpy as np
# Function to calculate the threshold values for sky detection
# based on the top region of the image, which is assumed to contain the sky.
def calculate_thresholds_from_top(hsv_image, top_fraction=0.25):
# Define the top region of the image for sampling sky pixels
top_region = hsv_image[:int(hsv_image.shape[0] * top_fraction), :, :]
# Calculate the average hue, saturation, and value in the top region
# to determine the characteristic colors of the sky in the image.
avg_hue = np.mean(top_region[:, :, 0])
avg_sat = np.mean(top_region[:, :, 1])
avg_val = np.mean(top_region[:, :, 2])
# Initialize the limits to cover a broad range of sky colors.
# Adjust the ranges based on the averages calculated above.
lower_limit = np.array([0, 0, min(180, avg_val - 30)])
upper_limit = np.array([180, max(255, avg_sat + 30), 255])
# Predefined hue ranges for typical sky colors.
blue_hue_range = (100, 140)
orange_hue_range = (10, 50)
# Adjust the hue range based on whether the average hue corresponds to
# typical blue skies or orange sunset/sunrise skies.
if avg_hue > blue_hue_range[0] and avg_hue < blue_hue_range[1]:
lower_limit[0] = blue_hue_range[0]
upper_limit[0] = blue_hue_range[1]
elif avg_hue > orange_hue_range[0] and avg_hue < orange_hue_range[1]:
lower_limit[0] = orange_hue_range[0]
upper_limit[0] = orange_hue_range[1]
# Extend the saturation limits to include clouds, which have high brightness
# and low saturation.
lower_limit[1] = 0 # Include low saturation values for cloud detection.
upper_limit[1] = 255 # High saturation for clear skies or sunsets
# Extend the upper value limit to ensure we capture the brightness of the sky and clouds.
upper_limit[2] = 255
return lower_limit, upper_limit
# Function to validate the sky detection mask and adjust thresholds if necessary.
# This ensures that the amount of sky detected is within reasonable bounds.
def validate_thresholds(mask, hsv_image, initial_lower_limit, initial_upper_limit):
height, width = mask.shape
total_pixels = height * width
sky_pixels = cv2.countNonZero(mask)
# Calculate the percentage of the image identified as sky.
sky_percentage = sky_pixels / total_pixels
# Define target bounds for the percentage of sky in the image.
target_min_sky_percentage = 0.2
target_max_sky_percentage = 0.95
# If the sky percentage is within the target bounds, return the current mask.
if target_min_sky_percentage <= sky_percentage <= target_max_sky_percentage:
return mask
# If the sky percentage is outside the target bounds, adjust thresholds (1e-6 to handle edge case and avoid zero devision).
adjustment_ratio = target_min_sky_percentage / (sky_percentage + 1e-6) if sky_percentage < target_min_sky_percentage else target_max_sky_percentage / (sky_percentage + 1e-6)
# Adjust the hue and saturation ranges based on the deviation from the target sky percentage.
hue_adjustment = (initial_upper_limit[0] - initial_lower_limit[0]) * (adjustment_ratio - 1)
sat_adjustment = (initial_upper_limit[1] - initial_lower_limit[1]) * (adjustment_ratio - 1)
# Apply the adjusted limits to create a new mask.
new_lower_limit = np.array([
max(0, initial_lower_limit[0] - hue_adjustment),
max(0, initial_lower_limit[1] - sat_adjustment),
initial_lower_limit[2]
])
new_upper_limit = np.array([
min(180, initial_upper_limit[0] + hue_adjustment),
min(255, initial_upper_limit[1] + sat_adjustment),
initial_upper_limit[2]
])
# Ensure new limits are type uint8 for mask creation.
new_lower_limit = np.array(new_lower_limit, dtype=np.uint8)
new_upper_limit = np.array(new_upper_limit, dtype=np.uint8)
adjusted_mask = cv2.inRange(hsv_image, new_lower_limit, new_upper_limit)
return adjusted_mask
# Function to find the most probable horizon line using the Hough Line Transform.
# It filters out significantly vertical lines and assumes the horizon is the highest
def find_horizon_line(edges):
lines = cv2.HoughLinesP(edges, 1, np.pi / 180, threshold=50, minLineLength=80, maxLineGap=10)
if lines is not None:
lines = [l[0] for l in lines if abs(l[0][0] - l[0][2]) > abs(l[0][1] - l[0][3])]
# Sort lines by their midpoint y-coordinate
lines.sort(key=lambda x: (x[1]+x[3])/2)
# The horizon line is the one with the smallest y-coordinate midpoint
horizon_line = lines[0]
return horizon_line
return None
# Function to create a mask that excludes everything below the detected horizon line.
# This helps to differentiate between sky and non-sky regions, particularly in cases
# where reflections or similar colors appear below the horizon.
def mask_below_horizon(image, horizon_line):
mask = np.zeros(image.shape[:2], dtype=np.uint8)
cv2.line(mask, (horizon_line[0], horizon_line[1]), (horizon_line[2], horizon_line[3]), 255, thickness=5)
# Fill below the line to exclude anything below it
cv2.floodFill(mask, None, seedPoint=(0, horizon_line[1] + 1), newVal=255)
return mask
# The main function that processes the image for Gradio.
# It applies the steps for sky detection and outputs an image
# showing the original and sky-detected regions side by side.
def process_image_for_gradio(image):
# Convert to HSV color space
hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
# Calculate thresholds from the top part of the image
lower_limit, upper_limit = calculate_thresholds_from_top(hsv_image, top_fraction=0.2)
# Ensure the limits are uint8 before using them in cv2.inRange
lower_limit = np.array(lower_limit, dtype=np.uint8)
upper_limit = np.array(upper_limit, dtype=np.uint8)
# Initial mask based on the initially calculated thresholds
initial_mask = cv2.inRange(hsv_image, lower_limit, upper_limit)
# Validate and adjust the mask if necessary
mask = validate_thresholds(initial_mask, hsv_image, lower_limit, upper_limit)
# Apply morphological operations to remove small noise
kernel_size = 5
kernel = np.ones((kernel_size, kernel_size), np.uint8)
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
# Apply edge detection to help find the horizon line
edges = cv2.Canny(mask, 50, 150)
# Find the horizon line
horizon_line = find_horizon_line(edges)
# Create a mask to exclude everything below the horizon
if horizon_line is not None:
horizon_mask = mask_below_horizon(image, horizon_line)
# Combine the sky mask with the horizon mask
mask = cv2.bitwise_and(mask, horizon_mask)
# Bitwise-AND mask and original image to extract sky
sky = cv2.bitwise_and(image, image, mask=mask)
# Stack the original image and the result side by side
result = np.hstack((image, sky))
return result
# Gradio function that wraps around process_image_for_gradio
def gradio_interface(image):
output_image = process_image_for_gradio(image)
return output_image
# Define the Gradio interface
iface = gr.Interface(
fn=gradio_interface,
inputs=gr.components.Image(type="numpy"), # Updated input definition
outputs=gr.components.Image(type="numpy"), # Updated output definition
title="Sky Pixel Identification",
description="Upload an image to identify sky pixels. The result will show the original image and sky pixels side by side."
)
# Launch the interface
iface.launch() |