|
|
|
import numpy as np |
|
import streamlit as st |
|
from PIL import ExifTags, Image |
|
from Segmentation.segmentation import get_mask, replace_sofa |
|
from StyleTransfer.styleTransfer import ( |
|
StyleFAST, |
|
StyleTransformer, |
|
styleProjection, |
|
) |
|
|
|
PAGE_CONFIG = { |
|
"page_title": "SofaStyler.io", |
|
"page_icon": ":art:", |
|
"layout": "centered", |
|
} |
|
st.set_page_config(**PAGE_CONFIG) |
|
|
|
|
|
def fix_orient(img: Image.Image) -> Image.Image: |
|
""" |
|
This function fix the orientation of input images. |
|
This is especially usefull in the context of images from a mobile phone. |
|
|
|
Parameters: |
|
img = input image |
|
Return: |
|
img = img with correct orientation |
|
""" |
|
flag = False |
|
for orientation in ExifTags.TAGS.keys(): |
|
if ExifTags.TAGS[orientation] == "Orientation": |
|
flag = True |
|
break |
|
|
|
info = img.getexif() |
|
if len(info) & flag: |
|
info = dict(info.items()) |
|
if orientation in info.keys(): |
|
orientation = info[orientation] |
|
if (orientation == 1) | (orientation == 2): |
|
img = img |
|
if (orientation == 3) | (orientation == 4): |
|
img = img.rotate(180, expand=True) |
|
if (orientation == 5) | (orientation == 6): |
|
img = img.rotate(270, expand=True) |
|
if (orientation == 7) | (orientation == 8): |
|
img = img.rotate(90, expand=True) |
|
return img |
|
|
|
|
|
def resize_sofa(img: Image.Image) -> Image.Image: |
|
""" |
|
This function adds padding to make the original image square and 640by640. |
|
It also returns the original ratio of the image, such that it can be reverted later. |
|
Parameters: |
|
img = original image |
|
Return: |
|
img_square = squared image |
|
box = parameters to later crop the image to it original ratio |
|
""" |
|
width, height = img.size |
|
idx = np.argmin([width, height]) |
|
newsize = (640, 640) |
|
|
|
if idx == 0: |
|
img_square = Image.new(img.mode, (height, height), (255, 255, 255)) |
|
img_square.paste(img, ((height - width) // 2, 0)) |
|
box = ( |
|
newsize[0] * (1 - width / height) // 2, |
|
0, |
|
newsize[0] - newsize[0] * (1 - width / height) // 2, |
|
newsize[1], |
|
) |
|
else: |
|
img_square = Image.new(img.mode, (width, width), (255, 255, 255)) |
|
img_square.paste(img, (0, (width - height) // 2)) |
|
box = ( |
|
0, |
|
newsize[1] * (1 - height / width) // 2, |
|
newsize[0], |
|
newsize[1] - newsize[1] * (1 - height / width) // 2, |
|
) |
|
img_square = img_square.resize(newsize) |
|
return img_square, box |
|
|
|
|
|
def resize_style(img: Image.Image) -> Image.Image: |
|
""" |
|
This function generates a zoomed out version of |
|
the style image and resizes it to a 640by640 square. |
|
Parameters: |
|
img = image containing the style/pattern |
|
Return: |
|
dst = a zoomed-out and resized version of the pattern |
|
""" |
|
width, height = img.size |
|
idx = np.argmin([width, height]) |
|
|
|
|
|
if idx == 0: |
|
top = (height - width) // 2 |
|
bottom = height - (height - width) // 2 |
|
left = 0 |
|
right = width |
|
else: |
|
left = (width - height) // 2 |
|
right = width - (width - height) // 2 |
|
top = 0 |
|
bottom = height |
|
newsize = (640, 640) |
|
img = img.crop((left, top, right, bottom)) |
|
|
|
|
|
copies = 8 |
|
resize = (newsize[0] // copies, newsize[1] // copies) |
|
img_zoomed_out = Image.new("RGB", (resize[0] * copies, resize[1] * copies)) |
|
img = img.resize((resize)) |
|
for row in range(copies): |
|
img = img.transpose(Image.FLIP_LEFT_RIGHT) |
|
for column in range(copies): |
|
img = img.transpose(Image.FLIP_TOP_BOTTOM) |
|
img_zoomed_out.paste(img, (resize[0] * row, resize[1] * column)) |
|
img_zoomed_out = img_zoomed_out.resize((newsize)) |
|
return img_zoomed_out |
|
|
|
|
|
image = Image.open("figures/logo.png") |
|
options = [ |
|
"Style Transformer", |
|
"StyleFAST", |
|
"Style Projection", |
|
] |
|
|
|
|
|
col1, col2 = st.columns([0.8, 0.2]) |
|
with col1: |
|
st.markdown( |
|
""" |
|
<style> |
|
@import url('https://fonts.googleapis.com/css2?family=Arvo&display=swap'); |
|
</style> |
|
""", |
|
unsafe_allow_html=True, |
|
) |
|
st.markdown( |
|
""" |
|
<style> |
|
html, body, [class*="css"] { |
|
font-family: 'Arvo'; |
|
} |
|
</style> |
|
""", |
|
unsafe_allow_html=True, |
|
) |
|
st.markdown( |
|
""" <style> .font { |
|
font-size:30px ; font-family: 'Arvo'; color: #04b188; |
|
src:url("https://fonts.googleapis.com/css2?family=Arvo&display=swap");} |
|
</style> """, |
|
unsafe_allow_html=True, |
|
) |
|
st.markdown( |
|
'<p class="font">Upload your photos here...</p>', unsafe_allow_html=True |
|
) |
|
with col2: |
|
st.image(image, width=150) |
|
|
|
|
|
st.sidebar.markdown('<center class="font">π </center>', unsafe_allow_html=True) |
|
st.sidebar.markdown( |
|
'<center class="font">A sofastyler App</center>', unsafe_allow_html=True |
|
) |
|
st.sidebar.markdown("") |
|
with st.sidebar.text("About the App"): |
|
st.write( |
|
""" |
|
Customize your sofa to your wildest dreams π!\ |
|
\nProvide a picture of your sofa, a desired pattern and\ |
|
choose one of the algorithms below. |
|
\nOr just look at an example. |
|
""" |
|
) |
|
st.sidebar.title("") |
|
with st.sidebar.expander("References"): |
|
st.write( |
|
"[1. The data that was used to train the segmentation model.]" |
|
+ "(https://tianchi.aliyun.com/specials/promotion/alibaba-3d-future)" |
|
+ "\n\n" |
|
+ "[2. Github repository used to train a segmentation model with transfer " |
|
+ "learning.]" |
|
+ "(https://github.com/qubvel/segmentation_models)" |
|
+ "\n\n" |
|
+ "[3. The github repository that is used for the style transformer.]" |
|
+ "(https://github.com/diyiiyiii/StyTR-2)" |
|
+ "\n\n" |
|
+ "[4. A tensorflow model for fast arbitrary image style transfer.]" |
|
+ "(https://tfhub.dev/google/magenta/arbitrary-image-stylization-v1-256/2)" |
|
+ "\n\n" |
|
+ "[5. A paddleHub model for parameter free style transfer.]" |
|
+ "(https://github.com/PaddlePaddle/PaddleHub/tree/release/v2.2/modules/" |
|
+ "image/Image_gan/style_transfer/stylepro_artistic)" |
|
) |
|
|
|
|
|
uploaded_content = st.file_uploader( |
|
label="Image with sofa", type=["jpg", "png", "jpeg"] |
|
) |
|
uploaded_style = st.file_uploader( |
|
label="Image with pattern", type=["jpg", "png", "jpeg"] |
|
) |
|
|
|
|
|
checkbox = st.checkbox("Show example") |
|
if checkbox: |
|
filter = st.radio("Style your sofa with:", options) |
|
col1, col2 = st.columns([0.5, 0.5]) |
|
|
|
if filter == "Style Transformer": |
|
content = Image.open("figures/sofa_example1.jpg") |
|
style = Image.open("figures/style_example1.jpg") |
|
output = "figures/0.png" |
|
with col1: |
|
st.markdown( |
|
'<p style="text-align: center;">Before</p>', unsafe_allow_html=True |
|
) |
|
st.image(content, width=300) |
|
st.image(style, width=300) |
|
with col2: |
|
st.markdown( |
|
'<p style="text-align: center;">After</p>', unsafe_allow_html=True |
|
) |
|
st.image(output, width=300) |
|
elif filter == "StyleFAST": |
|
content = Image.open("figures/sofa_example3.jpg") |
|
style = Image.open("figures/style_example10.jpg") |
|
output = "figures/1.png" |
|
with col1: |
|
st.markdown( |
|
'<p style="text-align: center;">Before</p>', unsafe_allow_html=True |
|
) |
|
st.image(content, width=300) |
|
st.image(style, width=300) |
|
with col2: |
|
st.markdown( |
|
'<p style="text-align: center;">After</p>', unsafe_allow_html=True |
|
) |
|
st.image(output, width=300) |
|
elif filter == "Style Projection": |
|
content = Image.open("figures/sofa_example2.jpg") |
|
style = Image.open("figures/style_example6.jpg") |
|
output = "figures/2.png" |
|
with col1: |
|
st.markdown( |
|
'<p style="text-align: center;">Before</p>', unsafe_allow_html=True |
|
) |
|
st.image(content, width=300) |
|
st.image(style, width=300) |
|
with col2: |
|
st.markdown( |
|
'<p style="text-align: center;">After</p>', unsafe_allow_html=True |
|
) |
|
st.image(output, width=300) |
|
|
|
|
|
elif (uploaded_content is not None) & (uploaded_style is not None): |
|
content = fix_orient(Image.open(uploaded_content)) |
|
style = fix_orient(Image.open(uploaded_style)) |
|
filter = st.radio("Style your sofa with:", options) |
|
ETA = "Unknown" |
|
if filter == "Style Transformer": |
|
ETA = "50s with CPU, 9s with GPU" |
|
elif filter == "StyleFAST": |
|
ETA = "15s with CPU, 3s with GPU" |
|
elif filter == "Style Projection": |
|
alpha = st.slider( |
|
"Adjust the weight of the image vs style", 0.0, 1.0, 0.8, step=0.1 |
|
) |
|
ETA = "20s with CPU, 10s with GPU" |
|
st.info("Estimated processing time: " + ETA) |
|
button = st.button("Style my sofa") |
|
|
|
col1, col2 = st.columns([0.5, 0.5]) |
|
with col1: |
|
st.markdown('<p style="text-align: center;">Before</p>', unsafe_allow_html=True) |
|
st.image(content, width=300) |
|
st.image(style, width=300) |
|
|
|
if button: |
|
with col2: |
|
st.markdown( |
|
'<p style="text-align: center;">After</p>', unsafe_allow_html=True |
|
) |
|
with st.spinner("Preprocessing images..."): |
|
|
|
|
|
resized_img, box = resize_sofa(content) |
|
resized_style = resize_style(style) |
|
|
|
|
|
with st.spinner("generating mask..."): |
|
mask = get_mask(resized_img) |
|
|
|
if filter == "Style Transformer": |
|
|
|
with st.spinner("Styling sofa..."): |
|
styled_sofa = StyleTransformer(resized_img, resized_style) |
|
|
|
with st.spinner("Replacing sofa..."): |
|
new_sofa = replace_sofa(resized_img, mask, styled_sofa) |
|
new_sofa = new_sofa.crop(box) |
|
st.balloons() |
|
st.image(new_sofa, width=300) |
|
elif filter == "StyleFAST": |
|
|
|
with st.spinner("Styling sofa..."): |
|
styled_sofa = StyleFAST(resized_img, resized_style) |
|
|
|
with st.spinner("Replacing sofa..."): |
|
new_sofa = replace_sofa(resized_img, mask, styled_sofa) |
|
new_sofa = new_sofa.crop(box) |
|
st.balloons() |
|
st.image(new_sofa, width=300) |
|
elif filter == "Style Projection": |
|
|
|
with st.spinner("Styling sofa..."): |
|
styled_sofa = styleProjection(resized_img, resized_style, alpha) |
|
|
|
with st.spinner("Replacing sofa..."): |
|
new_sofa = replace_sofa(resized_img, mask, styled_sofa) |
|
new_sofa = new_sofa.crop(box) |
|
st.balloons() |
|
st.image(new_sofa, width=300) |
|
else: |
|
st.image(image, width=300) |
|
|
|
|
|
st.sidebar.title(" ") |
|
st.sidebar.markdown(" ") |
|
st.sidebar.subheader("Please help us improve!") |
|
with st.sidebar.form(key="columns_in_form", clear_on_submit=True): |
|
rating = st.slider( |
|
"Please rate the app", |
|
min_value=1, |
|
max_value=5, |
|
value=3, |
|
help="Drag the slider to rate the app." |
|
+ "This is a 1-5 rating scale where 5 is the highest rating", |
|
) |
|
text = st.text_input(label="Please leave your feedback here") |
|
submitted = st.form_submit_button("Submit") |
|
if submitted: |
|
st.write("Thanks for your feedback!") |
|
st.markdown("Your Rating:") |
|
st.markdown(rating) |
|
st.markdown("Your Feedback:") |
|
st.markdown(text) |
|
|