DEV Community

Maria Eduarda de Azevedo Silva for OpenDevUFCG

Posted on

Script para abrir webcam com Python utilizando OpenCV

Já pensou em desenvolver algum projetinho que precisa de uma captura de imagem em tempo real, mas não sabe por onde começar? Nesse post vamos trazer uma solução simples de como fazer um script utilizando Python3 e OpenCV para abrir a sua webcam!

Um pouco sobre a biblioteca OpenCV

A OpenCV é uma biblioteca de código aberto desenvolvida pela Intel nos anos 2000, inicialmente nas linguagens C e C++, com o objetivo de melhorar o aparato disponível para o desenvolvimento de aplicações, assim como nos estudos e pesquisas na área de Visão Computacional. Hoje em dia ela ainda é referência e se tornou multiplataforma e multilinguagem, isto é, podemos utilizá-la em aplicações nos mais diversos sistemas operacionais e linguagens de programação.

OpenCV + Python == "PERFEIÇÃO"

O OpenCV não foi apenas desenvolvido para melhorar a experiência em questão de otimização das técnicas e dos algoritmos de processamento de imagem, mas também para fácil utilização da biblioteca pelos interessados.
Quando unimos isso ao Python temos um ambiente perfeito para explorar a criatividade utilizando imagens: biblioteca simples e linguagem de sintaxe simples!
Quer provas? Continua lendo este post!

Instalando o OpenCV na sua máquina

Para iniciar, precisaremos instalar o OpenCV na nossa máquina. Para isso, você pode instalar o pacote via pip com o comando abaixo:



pip install opencv-python


Enter fullscreen mode Exit fullscreen mode

ou



pip3 install opencv-python


Enter fullscreen mode Exit fullscreen mode

Lembrando que é sempre bom considerar a criação de um ambiente virtual para seus projetos! Assim, caso você queira compartilhar e receber contribuições, irá ajudar bastante na configuração local do projeto na máquina do contribuidor, além de evitar que alguns erros se tornem incapazes de serem resolvidos. Caso não saiba como fazê-lo, consulte este link da documentação do Python para saber mais.

Mão na massa!

Com o OpenCV instalado na máquina podemos partir para o código! Crie um arquivo .py, abra o editor de sua preferência e vamos começar!

Antes de tudo, vamos importar a nossa biblioteca:



import cv2 as cv


Enter fullscreen mode Exit fullscreen mode

Com o pacote devidamente importado, poderemos partir para a captura da imagem pela webcam do seu computador!

Abrindo a webcam com código!

Abrir a webcam com um script Python é mais fácil do que você imagina! Vamos utilizar OpenCV para capturar imagens em tempo real.

Passo 1:

O primeiro passo é instanciar um objeto VideoCapture. Essa classe do OpenCV é responsável por realizar a captura de frames em um vídeo, o qual passamos o caminho como parâmetro na criação do objeto. No caso da leitura da webcam, precisamos passar o seu ID de referência (se você usa um laptop, o ID da sua câmera primária é 0):



camera = cv.VideoCapture(0)


Enter fullscreen mode Exit fullscreen mode

Passo 2:

Para iniciar o segundo passo, é importante que você entenda o que é um vídeo. De uma forma bem genérica, um vídeo é uma sucessão de imagens que imprimem uma sensação de movimento. A cada uma dessas imagens damos o nome de quadros (ou frames).

Fazendo o paralelo disso com lógica de programação, qual a estrututa que utilizamos para fazer uma repetição sucessiva de alguma coisa?

Se você respondeu laço, está mais que correto! Mas, qual laço escolher, for ou while?

Vamos pensar: estamos fazendo uma captura de vídeo em tempo real com a nossa webcam, então não sabemos quando a captura irá parar, desse modo enquanto estiver rodando nosso script eu capturo um frame novo. Matamos a charada e vamos de loop while!

Passando essa ideia para Python, temos algo do tipo:



rodando = True

while rodando:
    # Captura um frame
    pass


Enter fullscreen mode Exit fullscreen mode

Passo 3:

Agora podemos partir para todo o código dentro do loop!

Para fazer a captura do frame da vez, usaremos o método read() no nosso objeto VideoCapture. Esse método retorna uma tupla com dois valores: um booleano referente ao status de captura e o próprio frame no formato de um array NumPy.

O valor do status é muito útil para quando estamos lendo um vídeo passado por um caminho, visto que, quando chegamos no último frame do mesmo, o próximo não poderá ser capturado, o retorno desse valor será False e poderemos parar a execução do loop sem o lançamento de uma exceção. Além disso, pode haver algum problema na captura da webcam e isso ser indicado por esse retorno, por isso, vamos utilizá-lo.

Já o frame é essencial, já que é o que mais nos interessa! A imagem retornada será o alvo dos processamentos feitos durante a iteração do loop e também será jogada na saída.

Faremos isso da seguinte forma, dissociando os valores retornados em duas variáveis:



#...
while rodando:
    status, frame = camera.read()


Enter fullscreen mode Exit fullscreen mode

Passo 4:

Chegou o momento de falarmos das condições de parada.

Queremos parar a captura em dois casos:

  1. Em casos de erro
  2. Caso eu apenas queira parar de executar o script

Vamos unir essas duas condições de parada em apenas uma condição:



#...
    if not status or cv.waitKey(1) & 0xff == ord('q'):
        rodando = False


Enter fullscreen mode Exit fullscreen mode

Parece estranho, mas não é difícil de entender. O que estamos querendo dizer é basicamente "se deu algo errado na captura da imagem ou o usuário apertou a tecla 'q', então pare o script!".

A função cv.waitKey(1) espera que um evento de tecla acionada aconteça e, caso ocorra, ela retorna o valor da tecla pressionada segundo o padrão UTF-8. Para comparar se a tecla pressionada foi a 'q', combinamos o retorno dessa função com a constante hexadecimal 0xff e utilizamos a função padrão de python ord('q'), que retorna o valor de uma string passada como parâmetro em UTF-8.

Caso ao menos uma dessas condições seja satisfeita, a nossa variável rodando passa a receber False e na próxima iteração há a parada do loop.

Passo 5:

Nesse estágio já temos o objeto que captura nossa imagem da webcam, a imagem capturada devidamente lida e também uma condição de parada do nosso loop. Só nos resta uma coisa: jogar essa imagem na saída!

Para isso utilizaremos a função cv.imshow() do OpenCV. Essa função cria recebe dois parâmetros: uma string referente ao nome da janela e o array que se refere a imagem que queremos mostrar. Temos tudo que precisamos!

Para finalmente enxergarmos a captura em tempo real, escrevemos a seguinte linha dentro do loop:



#...
    cv.imshow("Camera", frame)


Enter fullscreen mode Exit fullscreen mode

Finalmente, o script!

Tudo isso resulta em um script de apenas 8 linhas!



import cv2 as cv 

camera = cv.VideoCapture(0)
rodando = True

while rodando:

    status, frame = camera.read()

    if not status or cv.waitKey(1) & 0xff == ord('q'):
        rodando = False

    cv.imshow("Camera", frame)


Enter fullscreen mode Exit fullscreen mode

Com isso temos tudo que você precisa para capturar imagens em tempo real utilizando um script simples em Python junto com OpenCV!

E o que fazer com esse script?

Existem várias possibilidades de aproveitar essa base, que vão desde detectores até registros de imagens para alguma aplicação ou sistema feito em Python.

Muito obrigada por ter lido até o fim! Se esse post te inspirou a criar alguma coisa, não se esquece de postar nas tuas redes e me marcar, que eu quero ver ~e quem sabe eu volte aqui com mais coisas legais para fazer com OpenCV~!

Minhas redes:
LinkedIn: Maria Eduarda de Azevedo Silva
Twitter: @ddt_azevedo
GitHub: MariaEduardaDeAzevedo

Top comments (3)

Collapse
 
juliobguedes profile image
Júlio Guedes

Já fazendo a conexão: esse post é uma ótima porta de entrada pra fazer coisas mais complicadas e legais. Ontem, na Python Nordeste, foram mostrados exemplos de detecção de rosto, nariz, pose, etc. E tudo isso usava como base o código daqui!

Collapse
 
matheus_moura_07f7dc13937 profile image
matheus moura

Estou enfrentando problemas com um software que estou desenvolvendo, acontece que ao abrir a função de cadastro de visitantes em meu programa de controle de acesso acaba sendo que demora uma eternidade para a camera capturar a foto, o código ta logo abaixo:

Função para verificar a disponibilidade de câmeras ao iniciar o programa

def verificar_cameras_disponiveis():
cameras_disponiveis = []
for i in range(5): # Testa os primeiros 5 índices de câmeras, a letra i aqui funciona como uma variável
#que vai receber o valor entre 1 a 5, de acordo com o número de câmeras disponíveis pra uso.
cam = cv2.VideoCapture(i)
if cam.isOpened():
cameras_disponiveis.append(i)
cam.release()

    else:
        cam.release()
        print(f"Câmeras disponíveis: {cameras_disponiveis}")
Enter fullscreen mode Exit fullscreen mode

Função para iniciar a verificação de câmeras em uma thread separada

def iniciar_verificacao_cameras():
thread = threading.Thread(target=verificar_cameras_disponiveis)
thread.daemon = True # Garante que a thread será finalizada quando a aplicação principal for fechada.
thread.start()

Função para ser chamada quando a janela principal for carregada

def ao_iniciar():
iniciar_verificacao_cameras()

Criando forma class da função cadstro_visitantes():

class Cadastrovisitantes:
def init(self, janela, tree_visitantes, lbl_last_photo, lbl_last_data):
self.janela = janela
self.tree_visitantes = tree_visitantes
self.lbl_last_photo = lbl_last_photo
self.lbl_last_data = lbl_last_data
self.foto_capturada = None
self.timer_running = False
self.lbl_img_cameras = [None] * 3
self.camera_index = tk.IntVar(value=0)
# Incializar lista de fotos redimensionadas
self.fotos_redimensionadas = []
self.campos = {}
self.cam = None
def criar_label_entrada(self, janela, texto, linha, coluna):
tk.Label(janela, text = texto, font=("Arial", 12), bg="#FFFFFF").grid(row=linha, column=coluna, padx=10, pady=10, sticky="W")
entrada = tk.Entry(janela, font=("Arial", 12), borderwidth=4, relief="groove")
entrada.grid(row=linha, column=coluna + 1, columnspan=2, padx=10, pady=10, sticky="W")
return entrada

def abrir_janela_cadastro(self):
    janela_cadastro_visitantes = tk.Toplevel(self.janela)
    janela_cadastro_visitantes.title("Cadastro de Visitantes/Condominos/Moradores")
    janela_cadastro_visitantes.configure(bg="#F5F5F5")
    largura_janela = 800
    altura_janela = 800
    largura_tela = janela_cadastro_visitantes.winfo_screenwidth()
    altura_tela = janela_cadastro_visitantes.winfo_screenheight()
    pos_X = (largura_tela // 2) - (largura_janela // 2)
    pos_Y = (altura_tela // 2) - (altura_janela // 2)
    janela_cadastro_visitantes.geometry(f"{largura_janela}x{altura_janela}+{pos_X}+{pos_Y}")
    janela_cadastro_visitantes.resizable(False, False)

    #Campos de entrada e labels

    campos = [
        ("Nome do Condomino/Visitante/Moradores", 'registro_nome'),
        ("Documento do condômino/visitante/morador", 'registro_documento'),
        ("Motivo da visita", 'Motivo_Visita'),
        ("Endereço (Gleba)", 'registro_endereco_gleba'),
        ("Endereço (Número)",'registro_endereco_numero'),
        ("Tempo de Acesso", 'registro_tempo_acesso'),
        ("Modelo do Veículo", 'registro_veiculo')
    ]



    for i, (texto, campo) in enumerate(campos, start=1):
        self.campos[campo] = self.criar_label_entrada(janela_cadastro_visitantes, texto, i, 0)


    # Campos de exibição de imagens das câmeras.
    self.lbl_img_cameras[0] = tk.Label(janela_cadastro_visitantes, bg="#D3D3D3")
    self.lbl_img_cameras[0].grid(row=8, column=0, columnspan=1, padx=20, pady=10, stick="NSEW")

    # Botões para capturar fotos das câmeras individualmente
    tk.Button(janela_cadastro_visitantes, text="Capturar Foto da Câmera 1", font="Arial 14", command=lambda: self.funcao_capturar_foto(0)).grid(row=10, column=0, columnspan=1, padx=10, pady=10, stick="NSEW")
    self.lbl_img = tk.Label(janela_cadastro_visitantes, bg="#D3D3D3")
    self.lbl_img.grid(row=8, column=1, columnspan=2, padx=20, pady=10, sticky="NSEW")

    tk.Label(janela_cadastro_visitantes, text="Selecione a câmera", font=("Arial", 12), bg="#FFFFFF").grid(row=9, column=0, padx=10, pady=10, stick="W")
    tk.OptionMenu(janela_cadastro_visitantes, self.camera_index, 0, 1, 2).grid(row=9, column=1, columnspan=2, padx=10, pady=10, stick="W")

    # Campo de seleção da tabela
    tabelas = ["Condominos", "Moradores", "Visitantes"]
    self.tabela_var = tk.StringVar(janela_cadastro_visitantes)
    self.tabela_var.set(tabelas[0])
    tk.Label(janela_cadastro_visitantes, text="Selecione a tabela", font=("Arial", 12), bg="#FFFFFF").grid(row=0, column=0, padx=10, pady=10, stick="W")
    tk.OptionMenu(janela_cadastro_visitantes, self.tabela_var, *tabelas).grid(row=0, column=1, columnspan=2, padx=10, pady=10, stick="W")

    # Campo de botões

    tk.Button(janela_cadastro_visitantes, text="Cadastrar", font="Arial 14", command=self.registrar_visitantes).grid(row=18, column=0, columnspan=1, padx=10, pady=10, stick="NSEW")
    tk.Button(janela_cadastro_visitantes, text="Sair", font="Arial 14", command=janela_cadastro_visitantes.destroy).grid(row=18, column=1, columnspan=1, padx=10, pady=10, stick="NSEW")

def valida_entrada(self):
    campos_obrigatorios = ['registro_nome', 'registro_documento', 'Motivo_Visita', 'registro_veiculo', 'registro_endereco_gleba', 'registro_endereco_numero', 'registro_tempo_acesso']
    for campo in campos_obrigatorios:
        if not self.campos[campo].get().strip():
            raise ValueError(f"O campo {campo} está vazio.")
    try:
        float(self.campos['registro_tempo_acesso'].get())
    except ValueError:
        raise ValueError("O campo Tempo de Acesso deve ser um número válido.")

def obter_dados_campos(self):
    return {campo: self.campos[campo].get() for campo in self.campos}

def registrar_visitantes(self):
    try:
        self.valida_entrada()
        dados = self.obter_dados_campos()
        tempo = float(dados['registro_tempo_acesso'])
        image_bin = None
        if self.foto_capturada is not None:
            _, img_encoded = cv2.imencode('.png', self.foto_capturada)
            image_bin = img_encoded.tobytes()


        with sqlite3.connect('BancoDeDadosSeguranca.db') as conn:
            cursor = conn.cursor()
            cursor.execute("INSERT INTO Visitantes (Nome, Documento, MotivodaVisita, Modelo_do_Veiculo, Endereco_gleba, Endereco_numero, tempo, foto_capturada) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", 
                           (dados['registro_nome'], dados['registro_documento'], dados['Motivo_Visita'], dados['Modelo_do_Veiculo'], dados['registro_endereco_gleba'], dados['registro_endereco_numero'], tempo, image_bin))
            conn.commit()

        messagebox.showinfo("Sucesso", "O visitante foi registrado com sucesso!")

        if len(self.tree_visitantes.get_children()) >= 10:
            self.tree_visitantes.delete(self.tree_visitantes.get_children()[0])

        hora_atual = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        self.tree_visitantes.insert("", tk.END, values=(dados['registro_nome'], dados['registro_documento'], dados['Modelo_do_Veiculo'], dados['registro_endereco_gleba'], dados['registro_endereco_numero'], hora_atual))

        # Atualizar a foto e dados do último visitante
        if self.foto_capturada is not None:
            frame_rgb = cv2.cvtColor(self.foto_capturada, cv2.COLOR_BGR2RGB)
            image_pil = Image.fromarray(frame_rgb)
            imagem_tk = ImageTk.PhotoImage(image_pil)
            self.lbl_last_photo.configure(image=imagem_tk)
            self.lbl_last_photo.image = imagem_tk

        dados_atualizados = f"Nome: {dados['registro_nome']}\nDocumento: {dados['registro_documento']}\nVeículo: {dados['Modelo_do_Veiculo']}"
        self.lbl_last_data.config(text=dados_atualizados)


    except ValueError as e:
        messagebox.showerror("Erro de Validação", str(e))
    except Exception as e:
        messagebox.showerror("Erro", f"Ocorreu um erro ao registrar os dados: {str(e)}")











def funcao_capturar_foto(self, camera_index):
    def capturar():
        try:

            cam = cv2.VideioCapture(camera_index)
            if not self.cam.isOpened():
                messagebox.showerror("Erro", f"A câmera {camera_index} não pôde ser acessada.")
                return

            ret, frame = self.cam.read()

            cam.release()
            cv2.destroyAllWindows()

            # Esperar até que a câmera esteja fornecendo frames corretamente
            #max_attempts = 10
            #for attempt in range(max_attempts):
                #ret, frame = self.cam.read()
               # if ret:
               #     break
               # time.sleep(0.05)  # Pequeno atraso entre as tentativas



            if not ret:
                messagebox.showerror("Erro", f"Falha ao capturar imagem da câmera {camera_index}")
            else:

                img_name = f"foto-capturada-camera-{camera_index}.png"
                cv2.imwrite(img_name, frame)

                #Redimensionar a imagem para se encaixar no widget e na Treeview
                frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                image_pil = Image.fromarray(frame_rgb)
                #AJUSTA O TAMANNHO DA FOTO CONFORME O NECESSÁRIO
                imagem_widget = image_pil.resize((200, 150), Image.LANCZOS) #Tamanho maior de imagem para o widget
                imagem_treeview = image_pil.resize((50, 50),
Enter fullscreen mode Exit fullscreen mode

Image.LANCZOS) #Tamanho menor para a treeview
imagem_tk_widget = ImageTk.PhotoImage(imagem_widget)
imagem_tk_treeview = ImageTk.PhotoImage(imagem_treeview)

                #Atualizar a imagem no widget na thread principal
def atualizar_imagem():
if self.janela.winfo_exists():
self.lbl_img_cameras[camera_index].configure(image=imagem_tk_widget)
self.lbl_img_cameras[camera_index].image = imagem_tk_widget
self.foto_capturada = frame
                    #Adicionar a foto redimensionada à lista de fotos para a treeview

                    self.fotos_redimensionadas.append(imagem_tk_treeview)



            self.janela.after(0, atualizar_imagem)



            messagebox.showinfo("Sucesso", f"Foto da câmera {camera_index} capturada com sucesso!")


    except Exception as e:
        print(f"Erro ao tentar capturar a foto: {str(e)}")
        messagebox.showerror("Erro", f"Erro ao acessar a câmera: {str(e)}")


thread = threading.Thread(target=capturar, daemon=True)
thread.start
Enter fullscreen mode Exit fullscreen mode

def fechar_camera(self):
if self.cam is not None and self.cam.isOpened():
self.cam.release()
self.cam= None
cv2.destroyAllWindows()

def del(self):
self.fechar_camera()

Enter fullscreen mode Exit fullscreen mode




Configurações da janela principal

janela = tk.Tk()
janela.title("Cadastro de Acessos")
janela.geometry("800x600")
janela.configure(bg="#DCEBF5")

menu_barra = tk.Menu(janela)
janela.configure(menu=menu_barra)

Instanciar a classe de cadastro de visitantes

cadastro = Cadastrovisitantes(janela, tree_visitantes, lbl_last_photo, lbl_last_data)

Botão para abrir a janela de cadastro de visitantes

btn_abrir_cadastro = tk.Button(janela, text="Abrir Cadastro de Visitantes", command=cadastro.abrir_janela_cadastro)
btn_abrir_cadastro.pack(padx=10, pady=5, side = "bottom")

Menu principal

menu_menu = tk.Menu(menu_barra, tearoff=0)
menu_barra.add_cascade(label="Menu", menu=menu_menu)
menu_menu.add_command(label="Sair", command=janela.destroy)
Onde será que to errando, ja fiz milhares de versões disso e ainda to enfrentando problemas com lentidão. O software tem as funções de consulta de registros, ia para reconhecimento facial e de placas de carro, mas tudo que diz respeito a camera sempre da essa lentidão.

Collapse
 
veniciocosta profile image
veniciocosta

Quando modifico no código para uma webcam externa (camera = cv.VideoCapture(1)) o processo demora muuuito. Sabem o que posso fazer fazer para reduzir esse tempo?