Integração QT e OpenCV

Estou trabalhando em um projeto de visão computacional em que preciso fornecer ao usuário uma interface gráfica para realizar algumas configurações. Isso é bastante comum e é normal ter dúvidas de qual o melhor caminho a seguir, por isso decidi criar esse post para visioncompy falando sobre o assunto.

O OpenCV já possui alguns recursos de interface gráfica, porém esse recurso foram inseridos para facilitar o trabalho dos desenvolvedores. Caso queremos algo mais formidável, vamos precisar adicionar ao nosso projeto uma biblioteca de interface gráfica.

QT é um framework multiplataforma para desenvolvimento de interfaces gráficas em C++ criado pela empresa norueguesa Trolltech. O QT tem ganhado bastante destaque pelo seu designer inovador.

Uma das grandes vantagens do QT é sua capacidade de integração em diferentes plataformas. Veja como o mesmo código pode ter comportamentos diferentes.

Janelas construida no Mac, windows 10 e Ubuntu

Pode parecer apenas um pequeno detalhe, mais normalmente não encontramos essa integração dinâmica em framework híbridos.

Ao procurar por biblioteca do Qt para o Python vai descobrir que existe duas implementações, PyQt5 e PySide2. Qual a diferença entre eles?

Na verdade, elas são muitos parecidas. A principal diferença esta na licença. O PySide2 é disponibilizado sobre a licença LGPL. Já o PyQt5 usa GPL e comercial. Isso significa que podemos usar ambas para construir e distribuir aplicação de código aberto. Porém, para código fechado o PyQt5 exigem uma licença.

Em suma, se sua aplicação é comercial e não pretende pagar licença, use PySide2. Caso contrário pode escolher qualquer uma. Por outro lado, as dua comunidade são bastante ativa, porém o PyQt5 tem sido mais adotado pelos programadores.

Aplicação

Vamos dividir nossa aplicação em duas partes (OpenCV como back-end e PyQt5 como Front-end). Vamos criar dois arquivos. Eu dei o nome de process.py e main.py.

O process.py implementaremos uma Classe para processar o video. Esse será uma Thread que executara individualmente uma camada baixo.

Para comunicarmos com a Thread principal, vamos usar sinais e solts do Qt. Passaremos os frames do back-end para o front-end.

Pré-requisito

Para executar essa aplicação você deve instalar os seguintes pacotes.

  • Python3
  • PyQt5
  • OpenCV (opencv-python-headless)
  • PyQt5designer

Todos os pacotes podem ser instalados pelo gerenciador pip.

Aqui vai um detalhe importante, existem 4 pacotes do OpenCV.

  • opencv-python
  • opencv-contrib-python
  • opencv-python-headless
  • opencv-contrib-python-headless

Os dois últimos são equivalentes aos primeiro exceto por não possuir suporte a interface gráfica. Como vamos usar o Qt, devemos instalar um dos pacotes terminado em headless. Caso contrário vamos ter conflito entre o OpenCV e o Qt.

Processamento

Para demonstração vamos apenas abrir a webcam mostrar na tela. Essa classe pode receber implementação de qualquer rotina de processamento, como um algoritmo de detecção de movimento, segmentação, ou um modelo de deep learning.

import cv2
from PyQt5.QtGui import QImage
from PyQt5.QtCore import pyqtSignal,QThread

class Video(QThread):
    
    change_pixmap = pyqtSignal(QImage)

    def __init__(self):
        super(Video, self).__init__()
        self.stop=False
   
    def run(self):
        cap=cv2.VideoCapture(0)
        while cap.isOpened():
            ret,frame=cap.read()
            if not ret:
                print("sem frame")
                cap.release()
                break
            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            h, w, ch = frame.shape

            convertToQtFormat = QImage(frame, w, h, ch*w, QImage.Format_RGB888)

            self.change_pixmap.emit(convertToQtFormat)
            if self.stop:
                cap.release()
                break

if __name__ == "__main__":
    cv=Video()
    cv.start()

Na linha 5, criamos uma classe Video que herda da classe QTheard. Essa classe precisa ser uma Thread porque dentro do método run temos um loop que prende a execução. Caso não fosse uma thread a tela congelaria.

Construindo a Janela

Uma das vantagens de criar interface com Qt é essa ferramenta gráfica. Com ela basta clicar nos widiget e arrastar para nossa área. Com o PyQt5designer instalado basta executar no terminal designer

Nossa interface terá uma label, onde vamos colocar o vídeo e dois botões para iniciar e para a execução.

Para tratar e acessar os elementos gráficos, fazemos referência ao nome que atribuímos aos objetos. Essa opção aparece no lado direito da tela quando o objeto está selecionado.

  • pushButton_stop: Nome do botão de parada.
  • pushButton_start: Nome do botão iniciar.
  • label_video: Nome da label que vamos mostrar o vídeo.

Após construir nossa janela, podemos geral o código python rodando o seguinte comando.

pyuic5 interface.ui > py.py

Onde, interface.ui é o arquivo gerado pela Qtdesigner e py.py é nosso arquivo de destino..

Porém, existe uma forma mais elegante de carregar a interface. A função loadUi, carrega diretamente o arquivo interface.ui.

from PyQt5.uic import loadUi
from PyQt5.QtWidgets import  QMainWindow,QApplication
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtGui import QImage, QPixmap
from process import Video

class Janela(QMainWindow):
    def __init__(self):
        super(Janela,self).__init__()
        loadUi("interface.ui", self)
        self.vs=Video()
        self.vs.change_pixmap.connect(self.frame_update)

    @pyqtSlot()
    def on_pushButton_start_clicked(self):
        if not self.vs.isRunning():
            self.vs.stop=False
            self.vs.start()

    @pyqtSlot()
    def on_pushButton_stop_clicked(self):
        self.vs.stop=True

    @pyqtSlot(QImage)
    def frame_update(self,image):
        self.label_video.setPixmap(QPixmap.fromImage(image))

if __name__ == "__main__":
    import sys
    app=QApplication(sys.argv)
    my_janela=Janela()
    my_janela.show()
    app.exec_()

Os métodos com o decorador @pyqtSlot são os slots ligado aos sinais que queremos tratar.

Integração

Para ligar os componentes, o QT utiliza sinais e slots. Sinais é um evento que um determinado componente pode emitir como, por exemplo, um clique de um botão. Slots são as funções chamadas devido à ocorrência de uma sinal.

QPixmap é uma propriedade do objeto label que permite carregar uma imagem. Assim quando houver um frame disponível, o back-end vai emitir um sinal que esta ligado ao método frame_update no front-end que atualiza o QPixmap da label.

Na linha 7 do process.py criamos um sinal que recebe um parâmetro QImage.

change_pixmap = pyqtSignal(QImage)

Na linha 12 de main.py fazemos a conexão. Toda vez que o sinal for emitido o método frame_update vai ser chamado e recebera o parâmetro passado para o sinal.

self.vs.change_pixmap.connect(self.frame_update)
    @pyqtSlot(QImage)
    def frame_update(self,image):
        self.label_video.setPixmap(QPixmap.fromImage(image))

Seguindo algumas diretrizes, o QT nos ajuda em algumas tarefas, por exemplo. Se definirmos um novo método com nome on_NOME_MEU_BOTAO_clicked, o QT fará a ligação automática do sinal do evento clicked para o novo método.

    @pyqtSlot()
    def on_pushButton_start_clicked(self):

Já no caso da label que nós executamos a emissão do sinal, temos que fazer a ligação manualmente.

Podemos colocar um botão na tela e não implementar um slot para um determinado sinal que ele possa emitir. Mais se declaramos um novo sinal ele obrigatoriamente deve ter um solt ligado a ele.

Caso tenha seguido todos os passos, deve ter um resultado parecido com isso.

Erros comuns

  • QObject::moveToThread: Current thread (qt.qpa.plugin: Could not load the Qt platform plugin “xcb” in “): desistale o opencv e instale uma versão teminada em headless .
  • Multiplas chamdas de Slot: Ao clicar em um botão você pode ter mais de uma chamada do Slot. Verifique se adicionou o decorador @pyqtslot(), ou se não conectou manualmente um slot que o QT faz automático.

Caso enfrente mais algum erro não listado aqui ou não consiga executar, fique a vontade para nos contar nos comentários a baixo.

Conclusão

Existem algumas razões para utilizar Qt em nossas aplicações, a mais notável é capacidade da framework em rodar em diferentes plataformas, incluindo sistemas embarcados. Além disso, o QT tem uma comunidade bastante ativa e recebe atualizações com frequência. Muitos projetos vêm sendo desenvolvido em QT e alguns estão migrando.

7 Replies to “Integração QT e OpenCV”

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *