OpenCV e Streamlit: creare un’app di photo editing

Manipolare le immagini è un task che è molto utile in diversi campi applicativi. OpenCV, una libreria Python, permette facilmente di modificare le immagini a seconda delle nostre esigenze. In questo tutorial scopriamo come costruire una semplice web app mediante Streamlit per applicare alcuni effetti alle nostre foto.

Share

Reading time: 7 minutes

Ognuno di noi ha la necessità prima o poi di modificare delle immagini. Esistono diversi software, sia gratuiti che a pagamento, che permettono di ottenere risultati strabilianti. Da quelli più professionali come Photoshop fino a quelli gratuiti che si trovano online. L’idea di questi ultimi a volte è molto semplice. Caricate l’immagini, scegliete l’effetto che volete applicare e, infine scaricate il risultato finale. Ma come fanno? In realtà molti effetti non sono difficili da realizzare se si usano le librerie giuste. OpenCV, una libreria di Computer Vision di Python, permette di manipolare le immagini in real-time. È possibile, ad esempio, trasformare una foto in uno schizzo o convertirla in scala di grigi. Ovviamente, OpenCV permette questo e molto altro.

Ultimamente abbiamo esplorato anche Streamlit, un framework open-source interamente in Python che consente di costruire rapidamente applicazioni web e dashboard orientati ai dati senza la necessità di avere competenze di sviluppo web front-end. Potete leggere gli articoli Streamlit: costruire una Web App in pochi minutiStreamlit: come migliorare l’esperienza utente di una web app per avere un’idea di cosa si riesce a fare in pochissimo tempo e concentrandosi prevalentemente sui dati che vogliamo gestire e/o analizzare.

In questo articolo useremo Streamlit per costruire un’app che permetta di caricare una foto e scegliere l’effetto da applicare mediante la libreria OpenCV.

Prerequisiti

È necessario, prima di cominciare a sviluppare, installare Streamlit e OpenCV. Per l’installazione di Streamlit potete fare riferimento al tutorial Streamlit: costruire una Web App in pochi minuti. Per installare OpenCV potete seguire le istruzioni che trovate nella documentazione ufficiale. Se usate Anaconda e l’environment creato nei precedenti tutorial, è sufficiente aprire il Terminale dell’ambiente e installate  OpenCV con il seguente comando.

pip install opencv-python 

Create nella cartella del vostro progetto un file photo_converter.py dove inserirete il codice che illustreremo di seguito. A questo punto lanciate Streamlit con il seguente comando.

streamlit run photo_converter.py 

Dal momento che il file inizialmente è vuoto la pagina del browser avrà solo il menù in alto a destra. Ricordatevi di selezionare nel menù Settings la voce ‘Run on save’ in modo che ogni volta che si effettua una modifica al codice questa si rifletterà automaticamente nella web app.

Iniziamo anche ad importare le librerie necessarie inserendo il seguente codice.

#Import libraries
import streamlit as st
import numpy as np
import cv2
from  PIL import Image, ImageEnhance 

Personalizzare il layout dell’app

Nell’interfaccia principale dell’applicazione, iniziamo ad aggiungere un’intestazione usando st.markdown() e anche un logo. Il motivo per cui scegliamo st.markdown() invece di st.title() è che possiamo usare i CSS per stilizzarlo e renderlo più accattivante.

image = Image.open(r'...\Insights_Bees_logo.png') #Brand logo image (optional)

#Create two columns with different width
col1, col2 = st.columns( [0.8, 0.2])
with col1:               # To display the header text using css style
    st.markdown(""" <style> .font {
    font-size:35px ; font-family: 'Cooper Black'; color: #FF9633;} 
    </style> """, unsafe_allow_html=True)
    st.markdown('<p class="font">Upload your photo here...</p>', unsafe_allow_html=True)
    
with col2:               # To display brand logo
    st.image(image,  width=150) 

Aggiungiamo anche un’intestazione e un espansore nella barra laterale per fornire maggiori informazioni sull’applicazione.

#Add a header and expander in side bar
st.sidebar.markdown('<p class="font">My First Photo Converter App</p>', unsafe_allow_html=True)
with st.sidebar.expander("About the App"):
     st.write("""
        Use this simple app to convert your favorite photo to a pencil sketch, a grayscale image or an image with blurring effect.  \n  \nThis app was created by Sharone Li as a side project to learn Streamlit and computer vision. Hope you enjoy!
     """) 

Caricare le immagini

Dobbiamo aggiungere un uploader di file, nell’interfaccia principale dell’app, in modo che gli utenti possano caricare le loro foto sia con il drag-and-drop che sfogliando il contenuto del loro hard disk. Usiamo, pertanto, il widget st.file_uploader() specificando i tipi di immagine che sono accettati (ad esempio, JPG, PNG, JPEG). In questo modo si evita che un utente possa caricare file non supportati dall’applicazione.

#Add file uploader to allow users to upload photos
uploaded_file = st.file_uploader("", type=['jpg','png','jpeg']) 

Visualizzare le immagini prima e dopo la trasformazione

Una volta che l’utente carica una foto, desideriamo mostrare l’immagine originale e quella convertita una vicina all’altra. Pertanto, creiamo due colonne con la stessa larghezza sotto l’uploader del file, una per ‘prima’ e l’altra per ‘dopo’. Assicuratevi di mettere tutto all’interno dell’istruzione ‘if uploaded_file is not None:’. Diversamente, l’applicazione visualizzerà un messaggio di errore se non troverà alcuna immagine da convertire.

#Add 'before' and 'after' columns
if uploaded_file is not None:
    image = Image.open(uploaded_file)
    
    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(image,width=300)  

    with col2:
        st.markdown('<p style="text-align: center;">After</p>',unsafe_allow_html=True) 

L’applicazione si mostrerà come segue.

Convertire le foto con OpenCV

Fino a questo momento l’applicazione permette solo di caricare l’immagine e mostrala.

Ora vogliamo visualizzare nella seconda colonna col2 l’immagine convertita in base alla scelta fatta dall’utente. Come possiamo ottenere ciò?

Abbiamo bisogno, quindi, di creare un filtro o una casella di selezione singola per permettere agli utenti di specificare la conversione che vogliono applicare. Per mantenere l’interfaccia principale pulita e focalizzata sull’immagine, aggiungiamo questo filtro alla barra laterale. È sufficiente aggiungere la seguente linea di codice all’interno del ramo ‘if uploaded_file is not None’. Se volete che il filtro appaia sempre anche quando un’immagine non è stata caricata basta inserirlo prima dell’istruzione if.

filter = st.sidebar.radio('Covert your photo to:', ['Original','Gray Image', 'Black and White', 'Pencil Sketch', 'Blur Effect']) 

Per processare la scelta fatta dall’utente, dobbiamo indicare all’interno del codice dove passare i valori dei filtri e quale azione innescare. Abbiamo, quindi, bisogno di aggiungere alcune dichiarazioni condizionali sotto col2 per ottenere ciò.

Nel codice che segue, usiamo dichiarazioni condizionali if-else per convertire le immagini in diversi formati selezionati dall’utente. Per ogni scelta useremo funzioni diverse di OpenCV per convertire l’immagine nel formato desiderato e visualizzeremo il risultato usando st.image().

#Add conditional statements to take the user input values
    with col2:
        st.markdown('<p style="text-align: center;">After</p>',unsafe_allow_html=True)
        filter = st.sidebar.radio('Covert your photo to:', ['Original','Gray Image','Black and White', 'Pencil Sketch', 'Blur Effect'])
        if filter == 'Gray Image':
                converted_img = np.array(image.convert('RGB'))
                gray_scale = cv2.cvtColor(converted_img, cv2.COLOR_RGB2GRAY)
                st.image(gray_scale, width=300)
        elif filter == 'Black and White':
                converted_img = np.array(image.convert('RGB'))
                gray_scale = cv2.cvtColor(converted_img, cv2.COLOR_RGB2GRAY)
                slider = st.sidebar.slider('Adjust the intensity', 1, 255, 127, step=1)
                (thresh, blackAndWhiteImage) = cv2.threshold(gray_scale, slider, 255, cv2.THRESH_BINARY)
                st.image(blackAndWhiteImage, width=300)
        elif filter == 'Pencil Sketch':
                converted_img = np.array(image.convert('RGB')) 
                gray_scale = cv2.cvtColor(converted_img, cv2.COLOR_RGB2GRAY)
                inv_gray = 255 - gray_scale
                slider = st.sidebar.slider('Adjust the intensity', 25, 255, 125, step=2)
                blur_image = cv2.GaussianBlur(inv_gray, (slider,slider), 0, 0)
                sketch = cv2.divide(gray_scale, 255 - blur_image, scale=256)
                st.image(sketch, width=300) 
        elif filter == 'Blur Effect':
                converted_img = np.array(image.convert('RGB'))
                slider = st.sidebar.slider('Adjust the intensity', 5, 81, 33, step=2)
                converted_img = cv2.cvtColor(converted_img, cv2.COLOR_RGB2BGR)
                blur_image = cv2.GaussianBlur(converted_img, (slider,slider), 0, 0)
                st.image(blur_image, channels='BGR', width=300) 
        else: 
                st.image(image, width=300) 

Vediamo un pò più nel dettaglio come funziona OpenCV nei diversi casi.

Per convertire un’immagine in scala di grigi è sufficiente usare la funzione cvtColor() e specificare che il codice di conversione dello spazio colore sia cv2.COLOR_BGR2GRAY.

Diversamente, se l’immagine deve essere in bianco e nero, dobbiamo dapprima convertirla in scala di grigi e successivamente definire una soglia per assegnare ad ogni pixel il colore bianco (valore 255 se è spora soglia) o nero (valore 0 se è sottosoglia). Per lasciare all’utente la facoltà di scegliere questa soglia creiamo un widget a scorrimento.

Al fine di ottenere uno schizzo a matita, prima convertiamo l’immagine in scala di grigi e poi fondamentalmente la invertiamo, facendo diventare i toni neri bianchi e viceversa. Infine applichiamo il filtro di sfocatura usando la funzione Gaussian Blur (cv2.GaussianBlur). Per personalizzare lo schizzo a matita, inseriamo anche in questo caso un widget a scorrimento per selezionare la dimensione del kernel gaussiamo e regolare di conseguenza la sfocatura. I valori predefiniti sono (125,125) sia per l’altezza che la larghezza. E’ possibile anche usare valori differenti per i due parametri. Infine, usiamo la funzione cv2.divide per dividere i pixel dell’immagine grigia con quelli di 255-blur_image. Questo restituisce un’immagine che sembra un disegno a matita.

Infine, per sfocare un’immagine, la convertiamo prima in scala di grigi e poi aggiungiamo il filtro di sfocatura usando la funzione Gaussian Blur (cv2.GaussianBlur). Anche in questo caso aggiungiamo un widget st.siderbar.slider() per permettere all’utente di regolare l’intensità della sfocatura.

Richiedere un feedback

Concludendo, rendiamo più “social” l’applicazione aggiungendo una sezione di feedback nella barra laterale per raccogliere le valutazioni e i commenti degli utenti. Usiamo il widget st.text_input() per permettere agli utenti di inviare commenti e il widget st.slider() per assegnare un punteggio su una scala da 1 a 5. Possiamo poi usare st.form_submit_button() e st.form() per inserire in batch i widget insieme e inviare i valori dei widget con il clic di un pulsante, il che innescherà solo una singola ripetizione dell’intera app.

#Add a feedback section in the sidebar
st.sidebar.title(' ') #Used to create some space between the filter widget and the comments section
st.sidebar.markdown(' ') #Used to create some space between the filter widget and the comments section
st.sidebar.subheader('Please help us improve!')
with st.sidebar.form(key='columns_in_form',clear_on_submit=True): #set clear_on_submit=True so that the form will be reset/cleared once it's submitted
    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) 

In questo caso il feedback verrà visualizzato nella barra laterale. Mediante altre librerie Python potete inviarlo via email oppure salvarli dove ritenute più opportuno.

Tutto il codice è disponibile nel repository github. Se volete usare l’app potete collegarvi a Streamlit Cloud.

Letture consigliate

More To Explore

Intelligenza artificiale

Gradio: applicazioni web in python per AI [parte2]

Gradio è una libraria python che ci permette di creare applicazioni web in modo veloce e intuitivo per i nostri modelli di machine learning e AI. Le nostre applicazioni richiedono sempre un’interazione con l’utente e una personalizzazione del layout. Scopriamo, mediante degli esempi, come migliorare le nostre applicazioni.

Intelligenza artificiale

Gradio: applicazioni web in python per AI [parte1]

Scrivere applicazioni web per i nostri modelli di machine learning e/o di intelligenza artificiale può richiedere molto tempo e competenze che non sono in nostro possesso. Per snellire e velocizzare questo compito ci viene in aiuto Gradio, una libreria Python pensata per creare applicazioni web con poche righe di codice. Scopriamo le sue funzionalità base con alcuni esempi.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Design with MongoDB

Design with MongoDB!!!

Buy the new book that will help you to use MongoDB correctly for your applications. Available now on Amazon!