Manipulação de imagens em tempo real.

Nesse artigo, simularemos o funcionamento dos filtros do Instagram demonstrando como é possível aplicar filtros em tempo real utilizando apenas Python, OpenCV e uma webcam. Para não estendermos demais esse post, não detalharemos o algoritmo por trás de cada filtro.

Manipulação de imagens em tempo real.

Nesse artigo, simularemos o funcionamento dos filtros do Instagram demonstrando como é possível aplicar filtros em tempo real utilizando  apenas Python, OpenCV e uma webcam. Para não estendermos demais esse post, não detalharemos o algoritmo por trás de cada filtro. Utilizaremos a classe filters.py que discutimos neste artigo.

Nosso algoritmo será composto de 3 etapas principais:

  1. Inicialização: nessa etapa configuramos as dependências e inicializamos o streaming
  2. Execução: etapa principal do nosso projeto, que será responsável por aplicar o filtro selecionado nos frames do streaming iniciado na etapa 1.
  3. Finalização: nessa etapa encerramos o streaming e liberamos os processos que foram inicializados na etapa 1.

Após conhecer o fluxo da nossa aplicação.... hora do código.

Hora do Código

Como de costume, começamos importando os pacotes que iremos utilizar, e definimos algumas constantes importantes (para evitar números mágicos no meio do código):

# importar pacotes
import os

from imutils.video import VideoStream
import imutils
import numpy as np
import cv2
import time
from os.path import dirname, join
import os

# importamos nossos filtros
from filters import grayscale, original, sketch, sepia, blur, canny

# constantes
WEBCAM = os.environ.get('WEBCAM', 1)

Lembrando que também precisamos instalar as dependências:

pip install imutils numpy opencv-python

Note que nossa constante WEBCAM é um dado do tipo inteiro e foi obtida a partir das variáveis de ambiente do sistema operacional. Veremos mais adiante que essa constante será responsável por indicar à nossa aplicação qual webcam deve ser utilizada.

"Quero te ver" - Inicializando o streaming

Nossa proposta é manipular imagens em tempo real, por isso não podemos simplesmente enviar uma imagem como fizemos em artigos anteriores, precisamos iniciar um streaming de vídeo .

Um Streaming de Vídeo nada mais é do que a transmissão de um vídeo (ou uma sequência de imagens - frames) em tempo real, nesse caso, de uma webcam para o computador que está executando nossa aplicação.

Para nos ajudar nessa tarefa utilizaremos a classe VideoStream da biblioteca imutils que torna esse processo tão simples quanto executar um comando:

def main():
    print('[INFO] starting video stream')
    vs = VideoStream(src=WEBCAM).start()
    time.sleep(2.0)

    filters = {
        '0': original,
        '1': grayscale,
        '2': sketch,
        '3': sepia,
        '4': blur,
        '5': canny,
        '6': None,
        '7': None
    }

    print("""Press any of the following keys to:
        0: Original Image
        1: Grayscale
        2: Sketch
        3: Sepia
        4: Blur
        5: Canny
        6: Face detection
        7: Blur face
        q: Quit""")

    # initial_filter
    selected_filter = '0'

No código acima estamos definindo uma função main que abrigará todo nosso código.

Em seguida instanciamos e inicializamos o streaming de vídeo utilizando como fonte da transmissão a webcam , que foi definida anteriormente, para evitar possíveis erros, indicamos ao nosso algoritmo para aguardar 2 segundos antes de prosseguirmos a execução time.sleep(2.0).

Como é desejável que o usuário seja capaz de alterar o filtro em tempo de execução, criamos um dicionário filters que traduzirá o filtro selecionado para a função correspondente.

Para finalizar esse etapa mostramos um texto com os filtros disponíveis e inicializamos o filtro inicial para "0" - imagem original.

"Vamos mudar" - Aplicando filtros no streaming

Com o streaming iniciado podemos agora ler os frames e aplicar as transformações que o usuário selecionar. Para termos o efeito de vídeo, iremos colocar todo o processamento em um loop:

 while True:
        # ler frames
        frame = vs.read()
        frame = imutils.resize(frame, width=400)

        # pegar filtro selecionado
        filter = filters.get(selected_filter)
        if filter is not None:
            # aplicar filtro no frame
            frame = filter(frame)

        # exibir frame na tela
        cv2.imshow("Frame", frame)
        key = cv2.waitKey(1) & 0xFF
        if key == ord('q'):
            break

        if key in [ord(k) for k in filters.keys()]:
            selected_filter = chr(key)

Dentro do loop fazemos a leitura do frame capturado vs.read() redimensionamos a imagem mutils.resize(frame, width=400), em seguida pegamos o filtro a partir do dicionário que configuramos na etapa anterior filter = filter.get(selected_filter) e o aplicamos à imagem frame = filter(frame) , nesse momento nossa variável frame está com o filtro aplicado.

Por fim utilizamos o OpenCV para exibir a imagem cv2.imshow("Frame", frame).

Como todo nosso código está sendo executado em um loop infinito é importante configurarmos uma condição de parada, nesse caso utilizamos o waitKey do OpenCV.

O waitKey ficará "escutando" e, quando alguma tecla for pressionada, ela será armazenada na variável key. Em nosso exemplo realizamos 2 testes:

  • O primeiro verifica se a tecla é a letra "q", em caso positivo, interrompe a execução do loop.
  • O segundo verifica se a tecla existe no dicionário de filtros, em caso positivo, aplica o filtro selecionado.

"Ao sair, apague a luz" - Finalizando a aplicação

Quando interrompemos o loop, precisamos destruir as janelas abertas pelo imshow do OpenCV e encerrar o stream:

 # destruir as janelas e interromper o stream
 cv2.destroyAllWindows()
 vs.stop()

Da maneira que construímos, para aplicarmos qualquer outro filtro, basta que ele seja programado - no nosso caso os filtros localizados em filters.py - e incluído no dicionário filters.

Bônus - Deep Learning para Detecção de Rostos

Para irmos um pouco além, como etapa bônus, utilizaremos um modelo de Deep Learning pré-treinado e discutido neste artigo.

Iremos implementar a funcionalidade de detecção e censura de rostos da mesma maneira dos filtros, ou seja, o usuário poderá selecioná-los através das teclas.

Como utilizaremos Deep Learning e um modelo já treinado, precisamos, primeiro carregar os arquivos, informamos o nível de confiança desejado (nesse caso, 70% ou 0.7) e carregamos o modelo:

PROTOTXT = join(dirname(__file__), "deploy.prototxt.txt")
MODEL = join(dirname(__file__), "res10_300x300_ssd_iter_140000.caffemodel")
CONFIDENCE_THRESHOLD = 0.7

# carregar o modelo
net = cv2.dnn.readNetFromCaffe(PROTOTXT, MODEL)

Nota: utilizamos as funções join e dirname(__file__) , ao invés de simplesmente informar o nome do arquivo, para melhor compatibilidade entre sistemas operacionais.

Com nosso modelo carregado, iremos incluir as funções de detecção e censura de rostos no dicionário (opções 6 e 7) e dentro do nosso loop:

    h, w = frame.shape[:2]
    
    blob = cv2.dnn.blobFromImage(frame, 1, (300, 300), (104.0, 177.0, 123.0))
    net.setInput(blob)
    detections = net.forward()
     
    # iterar ao longo das deteccoes
    for i in range(0, detections.shape[2]):
        # exemplo de intervalo de confianca
        confidence = detections[0, 0, i, 2]
        
        # selecionar apenas intervalos acima do threshold
        if confidence > CONFIDENCE_THRESHOLD:
            # label da confiança
            text = "{:.2f}%".format(confidence * 100)
            
            # calcular o bounding box
            box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
            (startX, startY, endX, endY) = box.astype('int')
            
            if selected_filter == '6':
                frame = cv2.rectangle(frame, (startX, startY), (endX, endY), (0, 255, 0), 1)
                
            if selected_filter == '7':
                face = frame[startY:endY, startX:endX]
                blured = cv2.GaussianBlur(face, (33, 33), 0)
                frame[startY:endY, startX:endX] = blured

Primeiramente extraímos o tamanho (altura e largura) do frame que estamos analisando.

Como estamos trabalhando com deep learning, precisamos realizar alguns pré-processamentos na imagen antes de passá-la através da rede. Para nos auxiliar nesse processo, o OpenCV possui a função cv2.dnn.blobFromImage, que executa os seguintes passos:

  1. Subtração da média
  2. Escala
  3. Opcionalmente troca de canais (alterar R[ed] com B[lue])

Mais detalhes sobre esse recurso podem ser encontrados aqui.

Para a subtração da média utilizamos os valores descritos no benchmark do próprio OpenCV.

Definimos, então, o blob gerado como input da rede neural net.setInput(blob) e a executamos net.forward() esse processo resultará nas detecções realizadas. Como a rede irá retornar mais de uma detecção e seus respectivos graus de confiança, iremos iterar sobre elas procurando pelo primeiro resultado que esteja acima do limiar de 70% que definimos anteriormente.

Quando encontrado, iremos extrair os pontos que delimitam o rosto detectado formando uma "caixa" (bounding box):

box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
(startX, startY, endX, endY) = box.astype('int')

Com os pontos da bounding box - que delimitam a área de rosto detectado -, podemos aplicar os filtros desejados.

Em nosso exemplo nosso filtro 6 (detecção de rosto) irá exibir um retângulo verde ao redor do rosto frame = cv2.rectangle(frame, (startX, startY), (endX, endY), (0, 255, 0), 1). Enquanto que nosso filtro 7 (censura) irá aplicar o GaussianBlur para embaçar e censurar o rosto.

face = frame[startY:endY, startX:endX]
blured = cv2.GaussianBlur(face, (33, 33), 0)
frame[startY:endY, startX:endX] = blured

Demo

A versão final do projeto pode ser conferida no link abaixo

Projeto

virb30/realtime_face_filters
Contribute to virb30/realtime_face_filters development by creating an account on GitHub.

Referências

Face detection with OpenCV and deep learning - PyImageSearch
Learn how to perform face detection in images and face detection in video streams using OpenCV, Python, and deep learning.
opencv/opencv
Open Source Computer Vision Library. Contribute to opencv/opencv development by creating an account on GitHub.
Deep learning: How OpenCV’s blobFromImage works - PyImageSearch
Today’s blog post is inspired by a number of PyImageSearch readers who have commented on previous deep learning tutorials wanting to understand what exactly OpenCV’s blobFromImage function is doing under the hood. You see, to obtain (correct) predictions from deep…