|
|
|
|
|
|
|
|
|
|
|
from io import BytesIO |
|
|
import base64 |
|
|
|
|
|
import numpy as np |
|
|
import torch |
|
|
import torch.nn as nn |
|
|
import torch.optim as optim |
|
|
from torchvision import transforms, models |
|
|
from PIL import Image |
|
|
import gradio as gr |
|
|
|
|
|
|
|
|
DRIVE_ROOT_PATH = "Data" |
|
|
|
|
|
background_image_paths = [ |
|
|
|
|
|
"/AdobeColorFunko/Outfits/DummyDress1.png", |
|
|
"/AdobeColorFunko/Outfits/GlassesDummy.png", |
|
|
"/AdobeColorFunko/Outfits/DummyDress3.png" |
|
|
] |
|
|
|
|
|
|
|
|
|
|
|
class BeardClassifier: |
|
|
def __init__(self, model_path, class_names): |
|
|
self.model = models.resnet18(pretrained=False) |
|
|
num_ftrs = self.model.fc.in_features |
|
|
self.model.fc = nn.Linear(num_ftrs, len(class_names)) |
|
|
self.load_model(model_path) |
|
|
self.model.eval() |
|
|
self.data_transforms = transforms.Compose([ |
|
|
transforms.Resize((224, 224)), |
|
|
transforms.ToTensor(), |
|
|
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) |
|
|
]) |
|
|
self.class_names = class_names |
|
|
|
|
|
def preprocess_image(self, image_path): |
|
|
image = Image.open(image_path).convert("RGB") |
|
|
image = self.data_transforms(image) |
|
|
image = image.unsqueeze(0) |
|
|
return image |
|
|
|
|
|
def load_model(self, model_path): |
|
|
if torch.cuda.is_available(): |
|
|
|
|
|
self.model.load_state_dict(torch.load(model_path)) |
|
|
else: |
|
|
|
|
|
self.model.load_state_dict(torch.load(model_path, map_location=torch.device('cpu'))) |
|
|
|
|
|
def classify_beard(self, image_path): |
|
|
input_image = self.preprocess_image(image_path) |
|
|
|
|
|
with torch.no_grad(): |
|
|
predictions = self.model(input_image) |
|
|
|
|
|
probabilities = torch.nn.functional.softmax(predictions[0], dim=0) |
|
|
predicted_class = torch.argmax(probabilities).item() |
|
|
predicted_label = self.class_names[predicted_class] |
|
|
|
|
|
return predicted_label |
|
|
|
|
|
|
|
|
class BeardColorClassifier: |
|
|
def __init__(self, model_path, class_names): |
|
|
self.model = models.resnet18(pretrained=False) |
|
|
num_ftrs = self.model.fc.in_features |
|
|
self.model.fc = nn.Linear(num_ftrs, len(class_names)) |
|
|
self.load_model(model_path) |
|
|
self.model.eval() |
|
|
self.data_transforms = transforms.Compose([ |
|
|
transforms.Resize((224, 224)), |
|
|
transforms.ToTensor(), |
|
|
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) |
|
|
]) |
|
|
self.class_names = class_names |
|
|
|
|
|
def preprocess_image(self, image_path): |
|
|
image = Image.open(image_path).convert("RGB") |
|
|
image = self.data_transforms(image) |
|
|
image = image.unsqueeze(0) |
|
|
return image |
|
|
|
|
|
def load_model(self, model_path): |
|
|
if torch.cuda.is_available(): |
|
|
|
|
|
self.model.load_state_dict(torch.load(model_path)) |
|
|
else: |
|
|
|
|
|
self.model.load_state_dict(torch.load(model_path, map_location=torch.device('cpu'))) |
|
|
|
|
|
def classify_beard_color(self, image_path): |
|
|
input_image = self.preprocess_image(image_path) |
|
|
|
|
|
with torch.no_grad(): |
|
|
predictions = self.model(input_image) |
|
|
|
|
|
probabilities = torch.nn.functional.softmax(predictions[0], dim=0) |
|
|
predicted_class = torch.argmax(probabilities).item() |
|
|
predicted_label = self.class_names[predicted_class] |
|
|
|
|
|
return predicted_label |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def predict_beard_style(image_path): |
|
|
|
|
|
|
|
|
model_path = DRIVE_ROOT_PATH + '/FunkoSavedModels/FunkoResnet18Style.pt' |
|
|
class_names = ['Bandholz', 'FullGoatee', 'Moustache', 'RapIndustryStandards', 'ShortBeard'] |
|
|
|
|
|
beard_classifier = BeardClassifier(model_path, class_names) |
|
|
|
|
|
input_image = beard_classifier.preprocess_image(image_path) |
|
|
|
|
|
with torch.no_grad(): |
|
|
predictions = beard_classifier.model(input_image) |
|
|
|
|
|
probabilities = torch.nn.functional.softmax(predictions[0], dim=0) |
|
|
predicted_class = torch.argmax(probabilities).item() |
|
|
|
|
|
predicted_style_label = beard_classifier.class_names[predicted_class] |
|
|
|
|
|
print(f"The predicted beard style is: {predicted_style_label}") |
|
|
|
|
|
return predicted_style_label |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def predict_beard_color(image_path): |
|
|
|
|
|
|
|
|
color_model_path = DRIVE_ROOT_PATH + '/FunkoSavedModels/FunkoResnet18Color.pt' |
|
|
class_names = ['Black', 'DarkBrown', 'Ginger', 'LightBrown', 'SaltAndPepper', 'White'] |
|
|
|
|
|
beard_color_classifier = BeardColorClassifier(color_model_path, class_names) |
|
|
|
|
|
input_image = beard_color_classifier.preprocess_image(image_path) |
|
|
|
|
|
with torch.no_grad(): |
|
|
predictions = beard_color_classifier.model(input_image) |
|
|
|
|
|
probabilities = torch.nn.functional.softmax(predictions[0], dim=0) |
|
|
predicted_class = torch.argmax(probabilities).item() |
|
|
|
|
|
predicted_color_label = beard_color_classifier.class_names[predicted_class] |
|
|
|
|
|
print(f"The predicted beard color is: {predicted_color_label}") |
|
|
|
|
|
return predicted_color_label |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def dummy_eye(background_image, x,y, placeholder_image_path, x_coordinate, y_coordinate): |
|
|
placeholder_image = Image.open(placeholder_image_path) |
|
|
target_size = (x, y) |
|
|
placeholder_image = placeholder_image.resize(target_size, Image.LANCZOS) |
|
|
|
|
|
placeholder_width, placeholder_height = placeholder_image.size |
|
|
region_box = (x_coordinate, y_coordinate, x_coordinate + placeholder_width, y_coordinate + placeholder_height) |
|
|
placeholder_mask = placeholder_image.split()[3] if placeholder_image.mode == 'RGBA' else None |
|
|
background_image.paste(placeholder_image, region_box, mask=placeholder_mask) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return background_image |
|
|
|
|
|
|
|
|
def process_image_Beard(background_image, x, placeholder_image_path, x_coordinate, y_coordinate): |
|
|
placeholder_image = Image.open(placeholder_image_path) |
|
|
target_size = (x, x) |
|
|
placeholder_image = placeholder_image.resize(target_size, Image.LANCZOS) |
|
|
|
|
|
placeholder_width, placeholder_height = placeholder_image.size |
|
|
region_box = (x_coordinate, y_coordinate, x_coordinate + placeholder_width, y_coordinate + placeholder_height) |
|
|
placeholder_mask = placeholder_image.split()[3] if placeholder_image.mode == 'RGBA' else None |
|
|
background_image.paste(placeholder_image, region_box, mask=placeholder_mask) |
|
|
background_array = np.array(background_image) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return background_array |
|
|
|
|
|
|
|
|
def getDummyBackgroundImage(background_image): |
|
|
|
|
|
background_image = Image.open(DRIVE_ROOT_PATH + background_image) |
|
|
|
|
|
placeholder_image_eyebro = Image.open(DRIVE_ROOT_PATH + '/AdobeColorFunko/EyezBrowz/Eyebrow.png') |
|
|
placeholder_image_eyebro = placeholder_image_eyebro.resize((200,200),Image.LANCZOS) |
|
|
|
|
|
|
|
|
x_coordinate = 115 |
|
|
y_coordinate = 80 |
|
|
|
|
|
placeholder_width, placeholder_height = placeholder_image_eyebro.size |
|
|
|
|
|
region_box = (x_coordinate, y_coordinate, x_coordinate + placeholder_width, y_coordinate + placeholder_height) |
|
|
placeholder_mask = placeholder_image_eyebro.split()[3] if placeholder_image_eyebro.mode == 'RGBA' else None |
|
|
|
|
|
background_image.paste(placeholder_image_eyebro, region_box, mask=placeholder_mask) |
|
|
|
|
|
|
|
|
return background_image |
|
|
|
|
|
|
|
|
def setEyesOnTheDummy(background_image): |
|
|
|
|
|
predicted_gender = 'Male' |
|
|
image_with_eyes = None |
|
|
|
|
|
|
|
|
if predicted_gender == 'Male': |
|
|
x=245 |
|
|
y=345 |
|
|
placeholder_image_path = DRIVE_ROOT_PATH + f'/AdobeColorFunko/EyezBrowz/{predicted_gender}Eye.png' |
|
|
x_coordinate = 90 |
|
|
y_coordinate = 50 |
|
|
|
|
|
print("++++",type(background_image)) |
|
|
image_with_eyes = dummy_eye(background_image,x,y, placeholder_image_path, x_coordinate, y_coordinate) |
|
|
|
|
|
return image_with_eyes |
|
|
|
|
|
def setGlassesonDummy(background_image): |
|
|
|
|
|
placeholder_image_glasses = Image.open(DRIVE_ROOT_PATH + '/AdobeColorFunko/Glasses/Glasses.png') |
|
|
|
|
|
placeholder_image_glasses = placeholder_image_glasses.resize((280,380),Image.LANCZOS) |
|
|
placeholder_array_glasses = np.array(placeholder_image_glasses) |
|
|
|
|
|
x_coordinate = 72 |
|
|
y_coordinate = 30 |
|
|
|
|
|
placeholder_width, placeholder_height = placeholder_image_glasses.size |
|
|
|
|
|
region_box = (x_coordinate, y_coordinate, x_coordinate + placeholder_width, y_coordinate + placeholder_height) |
|
|
placeholder_mask = placeholder_image_glasses.split()[3] if placeholder_image_glasses.mode == 'RGBA' else None |
|
|
|
|
|
print(">>>>>",type(background_image)) |
|
|
background_image.paste(placeholder_image_glasses, region_box, mask=placeholder_mask) |
|
|
background_array = np.array(background_image) |
|
|
|
|
|
return background_image |
|
|
|
|
|
def generatePopFigure(background_image, predicted_style_label, predicted_color_label): |
|
|
|
|
|
match predicted_style_label: |
|
|
case "Bandholz": |
|
|
x=320 |
|
|
placeholder_image_path = DRIVE_ROOT_PATH + f'/AdobeColorFunko/Bandholz/{predicted_color_label}.png' |
|
|
x_coordinate = 50 |
|
|
y_coordinate = 132 |
|
|
|
|
|
case "ShortBeard": |
|
|
x=300 |
|
|
placeholder_image_path = DRIVE_ROOT_PATH + f'/AdobeColorFunko/ShortBeard/{predicted_color_label}.png' |
|
|
x_coordinate = 62 |
|
|
y_coordinate = 118 |
|
|
|
|
|
case "FullGoatee": |
|
|
x=230 |
|
|
placeholder_image_path = DRIVE_ROOT_PATH + f'/AdobeColorFunko/Goatee/{predicted_color_label}.png' |
|
|
x_coordinate = 96 |
|
|
y_coordinate = 162 |
|
|
|
|
|
case "RapIndustryStandards": |
|
|
x=290 |
|
|
placeholder_image_path = DRIVE_ROOT_PATH + f'/AdobeColorFunko/RapIndustry/{predicted_color_label}.png' |
|
|
x_coordinate = 67 |
|
|
y_coordinate = 120 |
|
|
|
|
|
case "Moustache": |
|
|
x=220 |
|
|
placeholder_image_path = DRIVE_ROOT_PATH + f'/AdobeColorFunko/Moustache/{predicted_color_label}.png' |
|
|
x_coordinate = 100 |
|
|
y_coordinate = 160 |
|
|
case _: |
|
|
print("Sorry, I still don't know how to recognize this!") |
|
|
|
|
|
final_pop_image = process_image_Beard(background_image.copy(),x, placeholder_image_path, x_coordinate, y_coordinate) |
|
|
|
|
|
return final_pop_image |
|
|
|
|
|
|
|
|
def getFunkoPOPFigure(image): |
|
|
|
|
|
print("Input Image: ", image) |
|
|
beard_style = predict_beard_style(image) |
|
|
beard_color = predict_beard_color(image) |
|
|
|
|
|
final_img_list = [] |
|
|
|
|
|
|
|
|
for dummy in background_image_paths: |
|
|
print("bg_img_path: ", dummy) |
|
|
funkoPopDummy = getDummyBackgroundImage(dummy) |
|
|
|
|
|
|
|
|
funkoPopDummyWithEyes = setEyesOnTheDummy(funkoPopDummy) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pop_image = generatePopFigure(funkoPopDummyWithEyes, beard_style, beard_color) |
|
|
final_img_list.append(pop_image) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
print("final result: ", final_img_list) |
|
|
return final_img_list |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
|
|
theme = gr.themes.Base().set( |
|
|
body_background_fill="linear-gradient(180deg,#0e5c99,#017cec)", |
|
|
body_background_fill_dark="linear-gradient(180deg,#0e5c99,#017cec)", |
|
|
body_text_color="white", |
|
|
body_text_color_dark="white", |
|
|
button_primary_background_fill="#fbc051", |
|
|
button_primary_background_fill_dark="#fbc051", |
|
|
button_primary_text_color="black", |
|
|
button_primary_text_color_dark="black" |
|
|
) |
|
|
|
|
|
imageComponent = gr.Image(type="filepath") |
|
|
with gr.Blocks(theme=theme, title="POP! Yourself", css="footer {visibility: hidden}") as demo: |
|
|
|
|
|
gr.Markdown(""" |
|
|
<img src="https://funko.com/on/demandware.static/Sites-FunkoUS-Site/-/en_US/v1693988598802/lib/img/pop-yourself-logo.7f7a42c2.svg" width="80" alt="logo"> |
|
|
|
|
|
### Get your Funko Pop Today. Get started with our Pop Figure generator tool & generate your Funko avatar quickly by just uploading your image. |
|
|
---""") |
|
|
gr.Interface( |
|
|
fn=getFunkoPOPFigure, |
|
|
inputs=imageComponent, |
|
|
outputs=["image", "image", "image"], |
|
|
title="The new Generative AI powered POP! Generator", |
|
|
description="Now you very own personalized figurine is just one click away. Upload a clear image with the clear background which shows your face completely. The image should not be blurry.", |
|
|
allow_flagging="never", |
|
|
|
|
|
) |
|
|
|
|
|
demo.launch() |