| | import streamlit as st |
| | from diffusers import StableDiffusionControlNetPipeline, ControlNetModel |
| | from diffusers import UniPCMultistepScheduler |
| | import torch |
| | from PIL import Image |
| | import numpy as np |
| | import cv2 |
| | import time |
| |
|
| | |
| | st.set_page_config( |
| | page_title="AI Image Generator with ControlNet", |
| | page_icon="🎨", |
| | layout="wide", |
| | initial_sidebar_state="expanded" |
| | ) |
| |
|
| | |
| | st.markdown(""" |
| | <style> |
| | .main { |
| | background-color: #f5f5f5; |
| | } |
| | .stButton>button { |
| | background-color: #4CAF50; |
| | color: white; |
| | border-radius: 8px; |
| | padding: 10px 24px; |
| | font-weight: bold; |
| | } |
| | .stButton>button:hover { |
| | background-color: #45a049; |
| | } |
| | .stSelectbox, .stSlider, .stTextInput { |
| | margin-bottom: 20px; |
| | } |
| | .header { |
| | color: #4CAF50; |
| | text-align: center; |
| | } |
| | .footer { |
| | text-align: center; |
| | margin-top: 30px; |
| | color: #777; |
| | font-size: 0.9em; |
| | } |
| | .image-container { |
| | display: flex; |
| | justify-content: space-around; |
| | flex-wrap: wrap; |
| | gap: 20px; |
| | margin-top: 20px; |
| | } |
| | .image-card { |
| | border-radius: 10px; |
| | box-shadow: 0 4px 8px rgba(0,0,0,0.1); |
| | padding: 15px; |
| | background: white; |
| | } |
| | </style> |
| | """, unsafe_allow_html=True) |
| |
|
| | |
| | st.markdown("<h1 class='header'>🎨 AI Image Generator with ControlNet</h1>", unsafe_allow_html=True) |
| | st.markdown("Generate stunning images guided by Stable Diffusion and ControlNet. Upload a reference image or use edge detection to control the output.") |
| |
|
| | |
| | with st.sidebar: |
| | st.image("https://huggingface.co/front/assets/huggingface_logo-noborder.svg", width=200) |
| | st.markdown("### Configuration") |
| | |
| | |
| | model_choice = st.selectbox( |
| | "Select ControlNet Type", |
| | ("Canny Edge", "Depth Map", "OpenPose (Human Pose)"), |
| | index=0 |
| | ) |
| | |
| | |
| | prompt = st.text_area("Prompt", "a beautiful landscape with mountains and lake, highly detailed, digital art") |
| | negative_prompt = st.text_area("Negative Prompt", "blurry, low quality, distorted") |
| | num_images = st.slider("Number of images to generate", 1, 4, 1) |
| | steps = st.slider("Number of inference steps", 20, 100, 50) |
| | guidance_scale = st.slider("Guidance scale", 1.0, 20.0, 7.5) |
| | seed = st.number_input("Seed", value=42, min_value=0, max_value=1000000) |
| | |
| | |
| | uploaded_file = st.file_uploader("Upload control image", type=["jpg", "png", "jpeg"]) |
| | |
| | |
| | with st.expander("Advanced Options"): |
| | strength = st.slider("Control strength", 0.1, 2.0, 1.0) |
| | low_threshold = st.slider("Canny low threshold", 1, 255, 100) |
| | high_threshold = st.slider("Canny high threshold", 1, 255, 200) |
| |
|
| | |
| | @st.cache_resource |
| | def load_models(model_type): |
| | if model_type == "Canny Edge": |
| | controlnet = ControlNetModel.from_pretrained( |
| | "lllyasviel/sd-controlnet-canny", |
| | torch_dtype=torch.float16 |
| | ) |
| | elif model_type == "Depth Map": |
| | controlnet = ControlNetModel.from_pretrained( |
| | "lllyasviel/sd-controlnet-depth", |
| | torch_dtype=torch.float16 |
| | ) |
| | else: |
| | controlnet = ControlNetModel.from_pretrained( |
| | "lllyasviel/sd-controlnet-openpose", |
| | torch_dtype=torch.float16 |
| | ) |
| | |
| | pipe = StableDiffusionControlNetPipeline.from_pretrained( |
| | "runwayml/stable-diffusion-v1-5", |
| | controlnet=controlnet, |
| | torch_dtype=torch.float16, |
| | safety_checker=None |
| | ).to("cuda") |
| | |
| | pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config) |
| | pipe.enable_model_cpu_offload() |
| | return pipe |
| |
|
| | |
| | def process_control_image(image, model_type): |
| | image = np.array(image) |
| | |
| | if model_type == "Canny Edge": |
| | image = cv2.Canny(image, low_threshold, high_threshold) |
| | image = image[:, :, None] |
| | image = np.concatenate([image, image, image], axis=2) |
| | elif model_type == "Depth Map": |
| | |
| | |
| | image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) |
| | image = np.stack([image]*3, axis=-1) |
| | else: |
| | |
| | image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) |
| | |
| | return Image.fromarray(image) |
| |
|
| | |
| | col1, col2 = st.columns([1, 1]) |
| |
|
| | with col1: |
| | st.markdown("### Control Image") |
| | if uploaded_file is not None: |
| | control_image = Image.open(uploaded_file) |
| | processed_image = process_control_image(control_image, model_choice) |
| | st.image(processed_image, caption="Processed Control Image", use_column_width=True) |
| | else: |
| | st.info("Please upload an image to use as control") |
| |
|
| | with col2: |
| | st.markdown("### Generated Images") |
| | if st.button("Generate Images"): |
| | if uploaded_file is None: |
| | st.warning("Please upload a control image first") |
| | else: |
| | with st.spinner("Generating images... Please wait"): |
| | start_time = time.time() |
| | |
| | |
| | pipe = load_models(model_choice) |
| | |
| | |
| | generator = torch.Generator(device="cuda").manual_seed(seed) |
| | |
| | |
| | images = pipe( |
| | [prompt] * num_images, |
| | negative_prompt=[negative_prompt] * num_images, |
| | image=processed_image, |
| | num_inference_steps=steps, |
| | generator=generator, |
| | guidance_scale=guidance_scale, |
| | controlnet_conditioning_scale=strength |
| | ).images |
| | |
| | |
| | st.markdown(f"<div class='image-container'>", unsafe_allow_html=True) |
| | for i, img in enumerate(images): |
| | st.image(img, caption=f"Image {i+1}", use_column_width=True) |
| | st.markdown("</div>", unsafe_allow_html=True) |
| | |
| | |
| | end_time = time.time() |
| | st.success(f"Generated {num_images} images in {end_time - start_time:.2f} seconds") |
| |
|
| | |
| | st.markdown(""" |
| | <div class='footer'> |
| | <p>Powered by Stable Diffusion and ControlNet | Deployed on Hugging Face Spaces</p> |
| | </div> |
| | """, unsafe_allow_html=True) |