| 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) |