Manipulação de Imagens com Python e OpenCV - parte 2

Dando sequência na série de manipulação de imagens com Python e OpenCV, dessa vez vamos aplicar os conceitos descritos na parte 1 para construir uma aplicação utilizando, além de Python e OpenCV, o Streamlit.

Manipulação de Imagens com Python e OpenCV - parte 2

Dando sequência a esta série de manipulação de imagens com Python e OpenCV, dessa vez vamos aplicar os conceitos descritos na parte 1 para construir uma aplicação utilizando, além de Python e OpenCV, o Streamlit.

Nota: nesse artigo iremos nos concentrar na utilização do Streamlit e como podemos utilizá-lo para aplicar os conceitos da primeira parte desta série. Portanto não iremos nos preocupar, com a arquitetura do software.

Streamlit

O Streamlit é uma biblioteca Python que nos permite transformar scripts de dados em aplicações web de maneira fácil e rápida. Para utilizá-lo precisamos apenas instalar e importar em nosso script.

Desenvolvimento da aplicação

Como não iremos utilizar o Google Colab dessa vez, precisamos, antes de iniciarmos o desenvolvimento da nossa aplicação, instalar as dependências que iremos utilizar.

pip install streamlit opencv-python numpy

Com as dependências instaladas, já podemos dar inicio ao desenvolvimento da aplicação. Primeiro criamos um arquivo chamado main.py e nele vamos importar as bibliotecas necessárias:

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

Com os pacotes importados iremos criar nossa função principal main() que executará nosso código exibindo o título da página e da barra lateral utilizando o streamlit:

def main():
    st.title("Aplicação de Filtros em Imagens")
    st.sidebar.title("Escolha o filtro")
    
if __name__ == '__main__':
    main()

O if é uma função de segurança que só executa a função main se o script for invocado intencionalmente.

Para iniciarmos nosso servidor streamlit utilizamos o comando:

streamlit run main.py

Por padrão o streamlit estará disponível pelo endereço http://localhost:8051

Nessa altura você deve ter uma tela semelhante a essa:

É uma tela simples mas suficiente para entendermos como o streamlit funciona, na primeira linha de nosso script, definimos o titulo da página principal com o comando st.title("Aplicação de Filtros em Imagens") e, logo em seguida, definimos também o título da sidebar st.sidebar.title("Escolha o filtro").

Podemos perceber, portanto que, todo conteúdo que queremos alocar na sidebar deverá ser precedido de st.sidebar. A maioria dos componentes que estão disponíveis para a página principal, tem sua versão sidebar. Mais detalhes podem ser vistos na documentação.

Em seguida iremos adicionar um componente para fazer o upload de uma imagem:

image_file = st.file_uploader("Carregue uma foto e aplique um filtro no menu lateral", type=['jpg', 'jpeg', 'png'])

if image_file:
    user_image = Image.open(image_file)
    st.sidebar.text("Imagem Original")
    st.sidebar.image(user_image, width=150)
else:
    user_image = Image.open('empty.jpg')

Nesse trecho, inserimos um componente de upload de arquivos st.file_uploader e indicamos seu label e quais os formatos aceitos.

Em seguida verificamos se o arquivo foi enviado. Em caso positivo, carregamos a imagem utilizando o Pillow (PIL) Image.open(image_file) , precisamos carregar a imagem dessa forma (e não utilizando o OpenCV diretamente) para que o streamlit consiga utilizá-la corretamente.

Com a imagem carregada mostramos o texto "Imagem Original" e uma miniatura da imagem carregada na sidebar st.sidebar.text("Imagem Original") e st.sidebar.image(user_image, width=150).

Para finalizar, caso a imagem não tenha sido carregada exibimos uma imagem estática user_image = Image.open("empty.jpg").

Com a imagem carregada podemos começar aplicando nossos filtros.

Para este exemplo optamos por criar um novo arquivo filters.py onde escreveremos nossa classe que terá os métodos necessários para a aplicação dos filtros:

# importar pacotes
import cv2
import numpy as np
from PIL import ImageEnhance

# definição do nome dos filtros
FILTER_NAMES = {
    'Desenho': 'sketch'
}

# definição do tamanho padrão de saída
OUTPUT_WIDTH = 500

class Filters:
    def __init__(self, st, original_image):
        self.st = st
        self.original_image = original_image
        self.selected_filter = st.sidebar.radio("Filtros", [key for key in FILTER_NAMES.keys()]
        
    def __call__(self, width=OUTPUT_WIDTH):
        run = getattr(self, FILTER_NAMES.get(self.selected_filter))
        result_image = run()
        self.st.image(result_image, width=width)
        
    def grayscale(self):
        converted_image = np.array(self.original_image)
        gray_image = cv2.cvtColor(converted_image, cv2.COLOR_RGB2GRAY)
        return gray_image

Explicando o que fazemos em cada bloco. Primeiro importamos os pacotes e definimos algumas constantes. A constante FILTER_NAMES é um dicionário que tem o nome do filtro (que será exibido na tela) e o nome da função correspondente na classe.

A primeira função da nossa classe será invocada quando a classe for instanciada e irá receber o objeto do streamlit e a imagem que foi enviada. Ainda como parte da inicialização da classe também criamos a lista de filtros disponíveis na sidebar como botões do tipo radio, também estamos armazenando a opção de filtro selecionado na variável self.selected_filter.

A segunda função que criamos é responsável por chamar o filtro selecionado a partir da variável definida anteriormente. Iremos carregar a imagem resultante de uma das funções diretamente na página utilizando o streamlit self.st.image(result_image, width=width).

Por fim nossa última função contém a execução do filtro (grayscale). Nessa função primeiramente convertemos a imagem carregada em um array do numpy np.array(self.original_image), em seguida utilizamos o OpenCV para convertê-la para escala de cinza cv2.cvtColor(converted_image, cv2.COLOR_RGB2GRAY).

Atenção! Utilizamos cv2.COLOR_RGB2GRAY nesse caso pois nossa imagem foi carregada pelo Pillow, e não pelo OpenCV.

Após a conversão, retornamos a imagem para que seja exibida na tela.

Agora em nosso main.py precisamos importar e instanciar nossa classe para utilizarmos os filtros.

# importar a classe
from filters import Filters

...

# instanciar a classe
filter = Filters(st, user_image)

# executar a classe (chama __call__)
filter()

Como todas as funções estão encapsuladas em nossa classe. Após instanciada, precisamos apenas executá-la como se fosse uma função.

Conclusão

Nesse exemplo mostramos como é simples utilizar o Streamlit para transformar scripts ou notebooks em aplicações web prontas para serem disponibilizadas.

Esse e os demais filtros podem ser conferidos no repositório do projeto disponível neste link.

Você pode conferir uma demo da aplicação nesse link.