Quando eu estava aprendendo Visão Computacional na universidade, eu frequentemente tinha que construir aplicações para processar imagens de câmeras em tempo real. Para essas aplicações, eu utilizava o OpenCV para processar as imagens e o HighGUI (a biblioteca de interface gráfica do OpenCV) para exibir os resultados.
A imagem abaixo mostra um exemplo simples de como transmitir um feed de câmera usando OpenCV e HighGUI.
Uma aplicação simples de streaming de webcam usando OpenCV e HighGUI.
Código...
import cv2
cap = cv2.VideoCapture(0)
while True:
ret, frame = cap.read()
if not ret:
break
cv2.imshow('Camera Stream', frame)
if cv2.waitKey(1) == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
No entanto, o HighGUI não é muito amigável ao usuário além de não ser fácil de personalizar a interface. Como eu decidi aprender Streamlit recentemente, eu achei que seria uma boa ideia tentar construir uma aplicação semelhante as que eu construía na universidade, mas agora combinando Streamlit e OpenCV.
Por padrão, o Streamlit tem apenas st.camera_input
, usado para capturar imagens da webcam. No entanto, eu queria construir uma aplicação que pudesse processar e exibir o feed da câmera em tempo real.
Depois de algumas pesquisas, descobri dois componentes feitos pela comunidade do Streamlit que poderiam me ajudar a construir a aplicação que eu queria: streamlit-camera-input-live e streamlit_webrtc.
Durante alguns experimentos, notei que streamlit-camera-input-live
era visivelmente mais lento do que streamlit_webrtc
. Então, decidi usar streamlit_webrtc
para construir a aplicação.
Sem mais delongas, vamos ver o resultado final da aplicação.
Como ficou a aplicação
A aplicação em si é bastante simples. Ela tem um player de vídeo que mostra o feed da câmera e alguns botões para controlar quais filtros são aplicados ao vídeo. A imagem abaixo mostra o resultado final.
O resultado final.
Amostra o código!
Para criar esta aplicação, usei o seguinte arquivo requirements.txt.
streamlit>=1.30
streamlit-webrtc>=0.47
opencv-python>=4.9
numpy>=1.26
Para instalar os módulos em seu ambiente, você pode usar:
pip install -r requirements.txt
Agora, vamos entender como a aplicação foi construída em três passos.
Passo 1. Transmitindo o feed da câmera para o streamlit
Este primeiro trecho mostra como transmitir o feed da câmera para o Streamlit usando streamlit_webrtc
. O parâmetro sendback_audio
é definido como False
porque não precisamos de áudio nesta aplicação. Ao defini-lo como True
, você também ouviria o áudio da câmera.
import streamlit as st
from streamlit_webrtc import webrtc_streamer
st.title("OpenCV Filters on Video Stream")
webrtc_streamer(key="streamer", sendback_audio=False)
Para executar este código, você pode usar o seguinte comando no seu terminal (supondo que o código está em um arquivo chamado app.py
):
streamlit run app.py
Passo 2. Criando botões para controlar os filtros
Agora podemos adicionar botões para controlar os filtros. Para deixá-los mais apresentáveis, usei a função st.columns
para criar uma grade de botões e colocar cada filtro em uma coluna (ou célula).
Neste ponto, os filtros ainda não estão implementados, mas toda vez que você clicar em um botão, a variável filter
será atualizada com o nome do filtro que você clicou.
import streamlit as st
from streamlit_webrtc import webrtc_streamer
st.title("OpenCV Filters on Video Stream")
filter = "none"
col1, col2, col3, col4, col5, col6 = st.columns([1, 1, 1, 1, 1, 1])
with col1:
if st.button("None"):
filter = "none"
with col2:
if st.button("Blur"):
filter = "blur"
with col3:
if st.button("Grayscale"):
filter = "grayscale"
with col4:
if st.button("Sepia"):
filter = "sepia"
with col5:
if st.button("Canny"):
filter = "canny"
with col6:
if st.button("Invert"):
filter = "invert"
webrtc_streamer(key="streamer", sendback_audio=False)
Passo 3. Aplicando os filtros
No último passo, precisamos aplicar os filtros ao feed de vídeo. Para fazer isso, usaremos o módulo de visão computacional OpenCV e os módulos auxiliares numpy e av.
A função transform
é responsável por aplicar os filtros ao feed de vídeo. Usamos o argumento video_frame_callback
para dizer ao webrtc_streamer
para usar esta função para processar os quadros de vídeo. Fazendo isso, webrtc_streamer
chamará a função transform
toda vez que um novo quadro estiver disponível.
A função transform
recebe um quadro do feed de vídeo e, com base no filtro selecionado, aplica o filtro correspondente ao quadro. Se você quiser aprender mais sobre os filtros, pode verificar algumas referências abaixo:
O código final ficou assim:
import cv2
import streamlit as st
from streamlit_webrtc import webrtc_streamer, VideoHTMLAttributes
import numpy as np
import av
st.title("OpenCV Filters on Video Stream")
filter = "none"
def transform(frame: av.VideoFrame):
img = frame.to_ndarray(format="bgr24")
if filter == "blur":
img = cv2.GaussianBlur(img, (21, 21), 0)
elif filter == "canny":
img = cv2.cvtColor(cv2.Canny(img, 100, 200), cv2.COLOR_GRAY2BGR)
elif filter == "grayscale":
# We convert the image twice because the first conversion returns a 2D array.
# the second conversion turns it back to a 3D array.
img = cv2.cvtColor(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), cv2.COLOR_GRAY2BGR)
elif filter == "sepia":
kernel = np.array(
[[0.272, 0.534, 0.131], [0.349, 0.686, 0.168], [0.393, 0.769, 0.189]]
)
img = cv2.transform(img, kernel)
elif filter == "invert":
img = cv2.bitwise_not(img)
elif filter == "none":
pass
return av.VideoFrame.from_ndarray(img, format="bgr24")
col1, col2, col3, col4, col5, col6 = st.columns([1, 1, 1, 1, 1, 1])
with col1:
if st.button("None"):
filter = "none"
with col2:
if st.button("Blur"):
filter = "blur"
with col3:
if st.button("Grayscale"):
filter = "grayscale"
with col4:
if st.button("Sepia"):
filter = "sepia"
with col5:
if st.button("Canny"):
filter = "canny"
with col6:
if st.button("Invert"):
filter = "invert"
webrtc_streamer(
key="streamer",
video_frame_callback=transform,
sendback_audio=False
)
Conclusão
Neste post, mostrei como construir uma aplicação simples de streaming de webcam usando Streamlit e OpenCV. Espero que você tenha gostado e que possa usar esta aplicação como ponto de partida para construir aplicativos mais complexos.
Se você tiver alguma dúvida ou sugestão, sinta-se à vontade para deixar um comentário abaixo. Até o próximo post! 👋