Material / app.py
tejani's picture
Update app.py
1e71b17 verified
import gradio as gr
import cv2
import numpy as np
from PIL import Image
import noise # Requires `pynoise` package: `pip install noise`
# Advanced Normal Map with multi-scale gradients and color influence
def generate_normal_map(image, strength=1.0, blur_size=5, use_bilateral=False, color_influence=0.3):
img = np.array(image)
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
# Noise reduction
if use_bilateral:
gray = cv2.bilateralFilter(gray, 9, 75, 75)
else:
gray = cv2.GaussianBlur(gray, (blur_size, blur_size), 0)
# Multi-scale gradient computation using Laplacian pyramid
levels = 3
normal_map = np.zeros((gray.shape[0], gray.shape[1], 3), dtype=np.float32)
for i in range(levels):
scale = 1 / (2 ** i)
resized = cv2.resize(gray, None, fx=scale, fy=scale, interpolation=cv2.INTER_AREA)
sobel_x = cv2.Scharr(resized, cv2.CV_64F, 1, 0)
sobel_y = cv2.Scharr(resized, cv2.CV_64F, 0, 1)
sobel_x = cv2.resize(sobel_x, (gray.shape[1], gray.shape[0]), interpolation=cv2.INTER_LINEAR)
sobel_y = cv2.resize(sobel_y, (gray.shape[1], gray.shape[0]), interpolation=cv2.INTER_LINEAR)
normal_map[..., 0] += sobel_x * (1.0 / levels)
normal_map[..., 1] += sobel_y * (1.0 / levels)
# Normalize with strength
normal_map[..., 0] = cv2.normalize(normal_map[..., 0], None, -strength, strength, cv2.NORM_MINMAX)
normal_map[..., 1] = cv2.normalize(normal_map[..., 1], None, -strength, strength, cv2.NORM_MINMAX)
normal_map[..., 2] = 1.0
# Add color influence
color_factor = color_influence * strength
normal_map[..., 0] += (img[..., 0] / 255.0 - 0.5) * color_factor
normal_map[..., 1] += (img[..., 1] / 255.0 - 0.5) * color_factor
# Normalize to unit vectors
norm = np.linalg.norm(normal_map, axis=2, keepdims=True)
normal_map = np.divide(normal_map, norm, out=np.zeros_like(normal_map), where=norm != 0)
normal_map = (normal_map + 1) * 127.5
normal_map = np.clip(normal_map, 0, 255).astype(np.uint8)
return Image.fromarray(normal_map)
# Advanced Displacement Map with fixed type handling
def generate_displacement_map(image, contrast=1.0, add_noise=False, noise_scale=0.1, edge_boost=1.0):
img = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2GRAY)
# Adaptive histogram equalization
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
img = clahe.apply(img)
# Adjust contrast
img = cv2.convertScaleAbs(img, alpha=contrast, beta=0)
# Edge enhancement with Laplacian
laplacian = cv2.Laplacian(img, cv2.CV_64F)
# Convert Laplacian to uint8 after scaling and clipping
laplacian = cv2.convertScaleAbs(laplacian, alpha=edge_boost, beta=0)
# Ensure both arrays are uint8 before blending
img = cv2.addWeighted(img, 1.0, laplacian, 0.5 * edge_boost, 0) # Reduced weight for stability
# Optional Perlin noise
if add_noise:
height, width = img.shape
noise_map = np.zeros((height, width), dtype=np.float32)
for y in range(height):
for x in range(width):
noise_map[y, x] = noise.pnoise2(x / 50.0, y / 50.0, octaves=6) * noise_scale * 255
img = cv2.add(img, noise_map.astype(np.uint8))
return Image.fromarray(img)
# Advanced Roughness Map with frequency separation
def generate_roughness_map(image, invert=True, sharpness=1.0, detail_boost=0.5, frequency_weight=0.5):
img = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2GRAY)
# Frequency separation
low_freq = cv2.bilateralFilter(img, 9, 75, 75)
high_freq = cv2.subtract(img, low_freq)
# Combine with weight
img = cv2.addWeighted(low_freq, 1.0 - frequency_weight, high_freq, frequency_weight, 0)
# Optional inversion
if invert:
img = 255 - img
# Sharpening and detail boost
blurred = cv2.GaussianBlur(img, (5, 5), 0)
img = cv2.addWeighted(img, 1.0 + sharpness, blurred, -sharpness, 0)
img = cv2.addWeighted(img, 1.0 + detail_boost, blurred, -detail_boost, 0)
return Image.fromarray(img)
# Process function
def process_image(input_image, normal_strength=1.0, normal_blur=5, normal_bilateral=False, normal_color=0.3,
disp_contrast=1.0, disp_noise=False, disp_noise_scale=0.1, disp_edge=1.0,
rough_invert=True, rough_sharpness=1.0, rough_detail=0.5, rough_freq=0.5):
normal_map = generate_normal_map(input_image, strength=normal_strength, blur_size=normal_blur,
use_bilateral=normal_bilateral, color_influence=normal_color)
displacement_map = generate_displacement_map(input_image, contrast=disp_contrast, add_noise=disp_noise,
noise_scale=disp_noise_scale, edge_boost=disp_edge)
roughness_map = generate_roughness_map(input_image, invert=rough_invert, sharpness=rough_sharpness,
detail_boost=rough_detail, frequency_weight=rough_freq)
return normal_map, displacement_map, roughness_map
# Gradio Interface
interface = gr.Interface(
fn=process_image,
inputs=[
gr.Image(type="pil", label="Upload Image"),
# Normal Map Controls
gr.Slider(minimum=0.1, maximum=5.0, step=0.1, value=1.0, label="Normal Map Strength"),
gr.Slider(minimum=3, maximum=15, step=2, value=5, label="Normal Map Blur Size (odd numbers)"),
gr.Checkbox(value=False, label="Use Bilateral Filter for Normal Map"),
gr.Slider(minimum=0.0, maximum=1.0, step=0.1, value=0.3, label="Normal Map Color Influence"),
# Displacement Map Controls
gr.Slider(minimum=0.5, maximum=2.0, step=0.1, value=1.0, label="Displacement Map Contrast"),
gr.Checkbox(value=False, label="Add Noise to Displacement Map"),
gr.Slider(minimum=0.05, maximum=0.5, step=0.05, value=0.1, label="Displacement Noise Scale"),
gr.Slider(minimum=0.0, maximum=2.0, step=0.1, value=1.0, label="Displacement Edge Boost"),
# Roughness Map Controls
gr.Checkbox(value=True, label="Invert Roughness Map"),
gr.Slider(minimum=0.0, maximum=2.0, step=0.1, value=1.0, label="Roughness Sharpness"),
gr.Slider(minimum=0.0, maximum=1.0, step=0.1, value=0.5, label="Roughness Detail Boost"),
gr.Slider(minimum=0.0, maximum=1.0, step=0.1, value=0.5, label="Roughness Frequency Weight")
],
outputs=[
gr.Image(type="pil", label="Normal Map"),
gr.Image(type="pil", label="Displacement Map"),
gr.Image(type="pil", label="Roughness Map")
],
title="Advanced Material Map Generator",
description="Upload an image to generate highly accurate Normal, Displacement, and Roughness maps with advanced controls."
)
interface.launch()