Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -5,38 +5,36 @@ import cv2 # Using OpenCV for fast resizing and filtering
|
|
| 5 |
import requests
|
| 6 |
import os
|
| 7 |
|
| 8 |
-
# ---
|
| 9 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
haar_cascade_url = "https://raw.githubusercontent.com/opencv/opencv/master/data/haarcascades/haarcascade_frontalface_default.xml"
|
| 11 |
haar_cascade_filename = "haarcascade_frontalface_default.xml"
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
try:
|
| 15 |
-
r = requests.get(haar_cascade_url, timeout=10)
|
| 16 |
-
r.raise_for_status() # Raise an exception for bad status codes
|
| 17 |
-
with open(haar_cascade_filename, 'wb') as f:
|
| 18 |
-
f.write(r.content)
|
| 19 |
-
print("Download complete.")
|
| 20 |
-
face_cascade = cv2.CascadeClassifier(haar_cascade_filename)
|
| 21 |
-
except requests.exceptions.RequestException as e:
|
| 22 |
-
print(f"Error downloading Haar Cascade model: {e}")
|
| 23 |
-
face_cascade = None
|
| 24 |
-
else:
|
| 25 |
face_cascade = cv2.CascadeClassifier(haar_cascade_filename)
|
| 26 |
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
draw.ellipse((20, 20, 130, 80), fill='black')
|
| 34 |
-
draw.ellipse((170, 20, 280, 80), fill='black')
|
| 35 |
-
# Bridge
|
| 36 |
-
draw.line((130, 50, 170, 50), fill='black', width=10)
|
| 37 |
-
return sunglasses
|
| 38 |
-
|
| 39 |
-
sunglasses_img = create_sunglasses_mask()
|
| 40 |
|
| 41 |
|
| 42 |
# --- Optimized Filter Functions ---
|
|
@@ -91,13 +89,11 @@ def apply_sharpen(img_np):
|
|
| 91 |
return cv2.filter2D(img_np, -1, kernel)
|
| 92 |
|
| 93 |
def apply_sunglasses(img_np):
|
| 94 |
-
"""Detects faces and overlays sunglasses."""
|
| 95 |
if img_np is None or face_cascade is None: return img_np
|
| 96 |
-
|
| 97 |
pil_image = Image.fromarray(img_np)
|
| 98 |
gray = cv2.cvtColor(img_np, cv2.COLOR_RGB2GRAY)
|
| 99 |
faces = face_cascade.detectMultiScale(gray, 1.3, 5)
|
| 100 |
-
|
| 101 |
for (x, y, w, h) in faces:
|
| 102 |
sunglasses_width = int(w * 0.9)
|
| 103 |
sunglasses_height = int(sunglasses_width * sunglasses_img.height / sunglasses_img.width)
|
|
@@ -105,73 +101,115 @@ def apply_sunglasses(img_np):
|
|
| 105 |
pos_y = y + int(h * 0.2)
|
| 106 |
resized_sunglasses = sunglasses_img.resize((sunglasses_width, sunglasses_height))
|
| 107 |
pil_image.paste(resized_sunglasses, (pos_x, pos_y), resized_sunglasses)
|
| 108 |
-
|
| 109 |
return np.array(pil_image)
|
| 110 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
|
| 112 |
# --- Main Processing Functions ---
|
| 113 |
-
def
|
| 114 |
if image is None: return None
|
| 115 |
-
img_np = np.array(image.convert("RGB"))
|
| 116 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
filter_map = {
|
| 118 |
"Grayscale": apply_grayscale, "Sepia": apply_sepia, "Invert": apply_invert,
|
| 119 |
"Posterize": apply_posterize, "Solarize": apply_solarize, "Vignette": apply_vignette,
|
| 120 |
"Contour": apply_contour, "Sharpen": apply_sharpen, "Sunglasses": apply_sunglasses,
|
| 121 |
-
"None": lambda img: img
|
| 122 |
}
|
| 123 |
|
| 124 |
filter_function = filter_map.get(filter_name, lambda img: img)
|
| 125 |
-
|
| 126 |
-
return processed_np
|
| 127 |
|
| 128 |
def process_live_frame(frame, filter_name):
|
| 129 |
if frame is None: return None
|
| 130 |
resized_frame = cv2.resize(frame, (640, 480), interpolation=cv2.INTER_AREA)
|
| 131 |
-
return
|
| 132 |
-
|
| 133 |
|
| 134 |
# --- Gradio UI ---
|
| 135 |
css = """
|
| 136 |
#title { text-align: center; color: #1d1e22; font-size: 2.8em; font-weight: 700; }
|
| 137 |
#subtitle { text-align: center; color: #57606a; font-size: 1.2em; }
|
| 138 |
-
.gradio-container { max-width:
|
| 139 |
"""
|
| 140 |
|
| 141 |
with gr.Blocks(css=css, theme=gr.themes.Soft()) as demo:
|
| 142 |
gr.Markdown("# Image Filters: Old, New & Face Effects", elem_id="title")
|
| 143 |
gr.Markdown("Apply classic and modern filters to your images or live webcam feed.", elem_id="subtitle")
|
| 144 |
|
| 145 |
-
filter_choices = [
|
| 146 |
-
"None",
|
| 147 |
-
"--- Old School ---", "Grayscale", "Sepia", "Invert", "Posterize", "Solarize",
|
| 148 |
-
"--- New School ---", "Vignette", "Contour", "Sharpen",
|
| 149 |
-
"--- Face Effects ---", "Sunglasses"
|
| 150 |
-
]
|
| 151 |
-
|
| 152 |
with gr.Tabs():
|
| 153 |
with gr.TabItem("Live Webcam"):
|
| 154 |
with gr.Row(equal_height=True):
|
| 155 |
-
with gr.Column():
|
| 156 |
webcam_input = gr.Image(sources=["webcam"], streaming=True, label="Webcam Input")
|
| 157 |
-
with gr.Column():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
webcam_output = gr.Image(label="Filtered Output")
|
| 159 |
-
webcam_filter = gr.Radio(filter_choices, value="None", label="Select Filter")
|
| 160 |
|
| 161 |
-
|
| 162 |
-
|
|
|
|
|
|
|
|
|
|
| 163 |
|
| 164 |
with gr.TabItem("Image File"):
|
| 165 |
with gr.Row(equal_height=True):
|
| 166 |
-
with gr.Column():
|
| 167 |
upload_input = gr.Image(type="pil", sources=["upload"], label="Upload an Image")
|
| 168 |
-
with gr.Column():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
upload_output = gr.Image(label="Filtered Output")
|
| 170 |
-
upload_filter = gr.Radio(filter_choices, value="None", label="Select Filter")
|
| 171 |
-
|
| 172 |
-
upload_input.change(process_static_image, [upload_input, upload_filter], upload_output)
|
| 173 |
-
upload_filter.change(process_static_image, [upload_input, upload_filter], upload_output)
|
| 174 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
|
| 176 |
if __name__ == "__main__":
|
| 177 |
demo.launch(debug=True)
|
|
|
|
| 5 |
import requests
|
| 6 |
import os
|
| 7 |
|
| 8 |
+
# --- Asset Setup ---
|
| 9 |
+
# Function to download a file from a URL
|
| 10 |
+
def download_file(url, filename):
|
| 11 |
+
if not os.path.exists(filename):
|
| 12 |
+
print(f"Downloading {filename}...")
|
| 13 |
+
try:
|
| 14 |
+
r = requests.get(url, timeout=10)
|
| 15 |
+
r.raise_for_status()
|
| 16 |
+
with open(filename, 'wb') as f:
|
| 17 |
+
f.write(r.content)
|
| 18 |
+
print("Download complete.")
|
| 19 |
+
return True
|
| 20 |
+
except requests.exceptions.RequestException as e:
|
| 21 |
+
print(f"Error downloading {filename}: {e}")
|
| 22 |
+
return False
|
| 23 |
+
return True
|
| 24 |
+
|
| 25 |
+
# Download Haar Cascade for face detection
|
| 26 |
haar_cascade_url = "https://raw.githubusercontent.com/opencv/opencv/master/data/haarcascades/haarcascade_frontalface_default.xml"
|
| 27 |
haar_cascade_filename = "haarcascade_frontalface_default.xml"
|
| 28 |
+
face_cascade = None
|
| 29 |
+
if download_file(haar_cascade_url, haar_cascade_filename):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
face_cascade = cv2.CascadeClassifier(haar_cascade_filename)
|
| 31 |
|
| 32 |
+
# Download Metamask logo for face effect
|
| 33 |
+
metamask_url = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/36/MetaMask_Fox.svg/1024px-MetaMask_Fox.svg.png"
|
| 34 |
+
metamask_filename = "metamask_logo.png"
|
| 35 |
+
metamask_img = None
|
| 36 |
+
if download_file(metamask_url, metamask_filename):
|
| 37 |
+
metamask_img = Image.open(metamask_filename).convert("RGBA")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
|
| 39 |
|
| 40 |
# --- Optimized Filter Functions ---
|
|
|
|
| 89 |
return cv2.filter2D(img_np, -1, kernel)
|
| 90 |
|
| 91 |
def apply_sunglasses(img_np):
|
|
|
|
| 92 |
if img_np is None or face_cascade is None: return img_np
|
| 93 |
+
sunglasses_img = create_sunglasses_mask()
|
| 94 |
pil_image = Image.fromarray(img_np)
|
| 95 |
gray = cv2.cvtColor(img_np, cv2.COLOR_RGB2GRAY)
|
| 96 |
faces = face_cascade.detectMultiScale(gray, 1.3, 5)
|
|
|
|
| 97 |
for (x, y, w, h) in faces:
|
| 98 |
sunglasses_width = int(w * 0.9)
|
| 99 |
sunglasses_height = int(sunglasses_width * sunglasses_img.height / sunglasses_img.width)
|
|
|
|
| 101 |
pos_y = y + int(h * 0.2)
|
| 102 |
resized_sunglasses = sunglasses_img.resize((sunglasses_width, sunglasses_height))
|
| 103 |
pil_image.paste(resized_sunglasses, (pos_x, pos_y), resized_sunglasses)
|
|
|
|
| 104 |
return np.array(pil_image)
|
| 105 |
|
| 106 |
+
def apply_metamask_head(img_np):
|
| 107 |
+
"""Detects faces and overlays a Metamask logo."""
|
| 108 |
+
if img_np is None or face_cascade is None or metamask_img is None: return img_np
|
| 109 |
+
pil_image = Image.fromarray(img_np)
|
| 110 |
+
gray = cv2.cvtColor(img_np, cv2.COLOR_RGB2GRAY)
|
| 111 |
+
faces = face_cascade.detectMultiScale(gray, 1.3, 5)
|
| 112 |
+
for (x, y, w, h) in faces:
|
| 113 |
+
# Make the logo slightly larger than the face and position it
|
| 114 |
+
logo_width = int(w * 1.4)
|
| 115 |
+
logo_height = int(logo_width * metamask_img.height / metamask_img.width)
|
| 116 |
+
pos_x = x - int((logo_width - w) / 2)
|
| 117 |
+
pos_y = y - int(logo_height * 0.2) # Position it slightly higher
|
| 118 |
+
resized_logo = metamask_img.resize((logo_width, logo_height))
|
| 119 |
+
pil_image.paste(resized_logo, (pos_x, pos_y), resized_logo)
|
| 120 |
+
return np.array(pil_image)
|
| 121 |
+
|
| 122 |
+
# Helper for sunglasses
|
| 123 |
+
def create_sunglasses_mask():
|
| 124 |
+
sunglasses = Image.new('RGBA', (300, 100), (0, 0, 0, 0))
|
| 125 |
+
draw = ImageDraw.Draw(sunglasses)
|
| 126 |
+
draw.ellipse((20, 20, 130, 80), fill='black')
|
| 127 |
+
draw.ellipse((170, 20, 280, 80), fill='black')
|
| 128 |
+
draw.line((130, 50, 170, 50), fill='black', width=10)
|
| 129 |
+
return sunglasses
|
| 130 |
|
| 131 |
# --- Main Processing Functions ---
|
| 132 |
+
def process_image(image, filter_name):
|
| 133 |
if image is None: return None
|
|
|
|
| 134 |
|
| 135 |
+
# Convert PIL Image to NumPy array for processing
|
| 136 |
+
if isinstance(image, Image.Image):
|
| 137 |
+
img_np = np.array(image.convert("RGB"))
|
| 138 |
+
else: # It's already a numpy array from the webcam
|
| 139 |
+
img_np = image
|
| 140 |
+
|
| 141 |
filter_map = {
|
| 142 |
"Grayscale": apply_grayscale, "Sepia": apply_sepia, "Invert": apply_invert,
|
| 143 |
"Posterize": apply_posterize, "Solarize": apply_solarize, "Vignette": apply_vignette,
|
| 144 |
"Contour": apply_contour, "Sharpen": apply_sharpen, "Sunglasses": apply_sunglasses,
|
| 145 |
+
"Metamask Head": apply_metamask_head, "None": lambda img: img
|
| 146 |
}
|
| 147 |
|
| 148 |
filter_function = filter_map.get(filter_name, lambda img: img)
|
| 149 |
+
return filter_function(img_np)
|
|
|
|
| 150 |
|
| 151 |
def process_live_frame(frame, filter_name):
|
| 152 |
if frame is None: return None
|
| 153 |
resized_frame = cv2.resize(frame, (640, 480), interpolation=cv2.INTER_AREA)
|
| 154 |
+
return process_image(resized_frame, filter_name)
|
|
|
|
| 155 |
|
| 156 |
# --- Gradio UI ---
|
| 157 |
css = """
|
| 158 |
#title { text-align: center; color: #1d1e22; font-size: 2.8em; font-weight: 700; }
|
| 159 |
#subtitle { text-align: center; color: #57606a; font-size: 1.2em; }
|
| 160 |
+
.gradio-container { max-width: 1080px !important; margin: auto !important; }
|
| 161 |
"""
|
| 162 |
|
| 163 |
with gr.Blocks(css=css, theme=gr.themes.Soft()) as demo:
|
| 164 |
gr.Markdown("# Image Filters: Old, New & Face Effects", elem_id="title")
|
| 165 |
gr.Markdown("Apply classic and modern filters to your images or live webcam feed.", elem_id="subtitle")
|
| 166 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
with gr.Tabs():
|
| 168 |
with gr.TabItem("Live Webcam"):
|
| 169 |
with gr.Row(equal_height=True):
|
| 170 |
+
with gr.Column(scale=2):
|
| 171 |
webcam_input = gr.Image(sources=["webcam"], streaming=True, label="Webcam Input")
|
| 172 |
+
with gr.Column(scale=1):
|
| 173 |
+
gr.Markdown("### Filter Controls")
|
| 174 |
+
with gr.Accordion("Old School", open=True):
|
| 175 |
+
webcam_radio_old = gr.Radio(["None", "Grayscale", "Sepia", "Invert", "Posterize", "Solarize"], label="Filter", value="None")
|
| 176 |
+
with gr.Accordion("New School", open=True):
|
| 177 |
+
webcam_radio_new = gr.Radio(["None", "Vignette", "Contour", "Sharpen"], label="Filter", value="None")
|
| 178 |
+
with gr.Accordion("Face Effects", open=True):
|
| 179 |
+
webcam_radio_face = gr.Radio(["None", "Sunglasses", "Metamask Head"], label="Filter", value="None")
|
| 180 |
+
with gr.Column(scale=2):
|
| 181 |
webcam_output = gr.Image(label="Filtered Output")
|
|
|
|
| 182 |
|
| 183 |
+
# Link radio buttons to the stream
|
| 184 |
+
webcam_radio_old.change(process_live_frame, [webcam_input, webcam_radio_old], webcam_output).then(lambda: "None", None, webcam_radio_new).then(lambda: "None", None, webcam_radio_face)
|
| 185 |
+
webcam_radio_new.change(process_live_frame, [webcam_input, webcam_radio_new], webcam_output).then(lambda: "None", None, webcam_radio_old).then(lambda: "None", None, webcam_radio_face)
|
| 186 |
+
webcam_radio_face.change(process_live_frame, [webcam_input, webcam_radio_face], webcam_output).then(lambda: "None", None, webcam_radio_old).then(lambda: "None", None, webcam_radio_new)
|
| 187 |
+
webcam_input.stream(lambda img, r1, r2, r3: process_live_frame(img, next(f for f in (r1,r2,r3) if f != "None")), [webcam_input, webcam_radio_old, webcam_radio_new, webcam_radio_face], webcam_output)
|
| 188 |
|
| 189 |
with gr.TabItem("Image File"):
|
| 190 |
with gr.Row(equal_height=True):
|
| 191 |
+
with gr.Column(scale=2):
|
| 192 |
upload_input = gr.Image(type="pil", sources=["upload"], label="Upload an Image")
|
| 193 |
+
with gr.Column(scale=1):
|
| 194 |
+
gr.Markdown("### Filter Controls")
|
| 195 |
+
with gr.Accordion("Old School", open=True):
|
| 196 |
+
upload_radio_old = gr.Radio(["None", "Grayscale", "Sepia", "Invert", "Posterize", "Solarize"], label="Filter", value="None")
|
| 197 |
+
with gr.Accordion("New School", open=True):
|
| 198 |
+
upload_radio_new = gr.Radio(["None", "Vignette", "Contour", "Sharpen"], label="Filter", value="None")
|
| 199 |
+
with gr.Accordion("Face Effects", open=True):
|
| 200 |
+
upload_radio_face = gr.Radio(["None", "Sunglasses", "Metamask Head"], label="Filter", value="None")
|
| 201 |
+
with gr.Column(scale=2):
|
| 202 |
upload_output = gr.Image(label="Filtered Output")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 203 |
|
| 204 |
+
# Link radio buttons to the static image processor
|
| 205 |
+
def upload_filter_change(img, r1, r2, r3):
|
| 206 |
+
active_filter = next((f for f in (r1, r2, r3) if f != "None"), "None")
|
| 207 |
+
return process_image(img, active_filter)
|
| 208 |
+
|
| 209 |
+
upload_radio_old.change(upload_filter_change, [upload_input, upload_radio_old, upload_radio_new, upload_radio_face], upload_output).then(lambda: "None", None, upload_radio_new).then(lambda: "None", None, upload_radio_face)
|
| 210 |
+
upload_radio_new.change(upload_filter_change, [upload_input, upload_radio_old, upload_radio_new, upload_radio_face], upload_output).then(lambda: "None", None, upload_radio_old).then(lambda: "None", None, upload_radio_face)
|
| 211 |
+
upload_radio_face.change(upload_filter_change, [upload_input, upload_radio_old, upload_radio_new, upload_radio_face], upload_output).then(lambda: "None", None, upload_radio_old).then(lambda: "None", None, upload_radio_new)
|
| 212 |
+
upload_input.change(upload_filter_change, [upload_input, upload_radio_old, upload_radio_new, upload_radio_face], upload_output)
|
| 213 |
|
| 214 |
if __name__ == "__main__":
|
| 215 |
demo.launch(debug=True)
|