Detecção e Classificação de Semáforos com OpenCV e Machine Learning

Arnaldo Gualberto
Ensina.AI
Published in
8 min readJul 3, 2018

Em agosto de 2016, fui contratado como freelancer para um projeto de visão computacional pela Vsoft Tecnologia. Além de ser meu primeiro trabalho como freelancer, esse projeto também representou meu primeiro projeto real com Machine Learning. Entre captar imagens, gabaritar semáforos, treinar o modelo e fazer o deploy em um Raspberry Pi, o tempo total do projeto foi de 3 meses. O resultado final pode ser conferido no vídeo abaixo:

Quer saber como eu fiz o projeto? É só acompanhar o restante do post.

O início do projeto

Desde o começo, eu sabia que o objetivo final do projeto era ter o sistema rodando em um Raspberry Pi 3, de preferência em tempo real. Apesar de o Raspberry Pi ser um bom computador para o que ele é proposto, o seu poder de processamento é bastante limitado. Na época, eu já vinha estudando sobre Deep Learning e, apesar de saber que seria uma ótima opção, o Raspberry Pi não ia suportar o processamento por mais leve que fosse a arquitetura. Hoje em dia, já temos arquiteturas de Deep Learning capazes de rodar em dispositivos embarcados — como a MobileNet — , mas na época o Deep Learning não era opção.

Como sempre gosto de fazer ao iniciar um projeto novo, passei os primeiros dias procurando trabalhos na literatura sobre detecção de semáforos em tempo real. Um dos trabalhos que encontrei foi uma dissertação de Mestrado da UFES justamente sobre esse tema. Você pode conferir o sistema rodando nesse vídeo:

Quando li a dissertação, vi que o sistema fazia parte de um projeto de carro autônomo da UFES, chamado IARA. Esse carro autônomo era equipado com GPS, duas câmeras de alta resolução que permitiam fazer o mapeamento 3D da cena e o processamento era feito por um computador com alto poder de processamento e diversos clusters armazenado no porta-malas do carro. Além disso, a dissertação estava muito bem escrita, com bastantes detalhes da metodologia, o que permitia a reprodutibilidade do método. Os resultados tanto de detecção quanto de classificação também eram excelentes. Porém, a dúvida principal era: será que o sistema rodaria em um Raspberry Pi? Eu sabia que não…

Porém, antes de pensar em processamento, eu devia pensar nos dados que eu iria utilizar para treinar o meu modelo. E é aí que os problemas começaram…

A construção da base de dados

Na própria dissertação, havia um link para o banco de dados que o autor usou para treinar o modelo dele. Lembro que fiquei bastante animado quando vi o link, pois quem trabalha com Machine Learning sabe que os dados são a parte mais importante de um projeto. Entretanto, quando cliquei no link…

Ainda procurei por outros bancos na internet, mas vi que nenhum seria bom o suficiente. Naquele momento percebi que eu teria que fazer o meu próprio banco. Nos 4 dias seguintes, peguei meu carro, grudei uma webcam Logitech C525 atrás do retrovisor com uma fita 3M e dirigi pelas ruas da minha cidade gravando vídeos dos semáforos. Ao todo, gravei 23 vídeos em diferentes condições de iluminação (manhã/tarde), clima (sol/chuva) e com diferentes modelos de semáforo.

Depois que gravei os vídeos, comecei o que muitos consideram (inclusive eu) a parte mais chata, porém necessária, de um treinamento em um banco novo: o gabarito. Primeiramente, utilizei o FFmpeg para extrair frames dos vídeos que gravei. Logo após, desenvolvi meu próprio programa para marcar semáforos nas imagens e indicar o seu estado. Esse programa, na verdade, é bem parecido com alternativas que a gente encontra na internet (como o LabelImg), só que adapatado para as necessidades do projeto — como ser capaz de rotular o estado do semáforo à medida que marco os semáforos. Perdi 1 ou 2 dias fazendo o programa em C# e 1 semana gabaritando os frames. Ao final desse processo, eu tinha gabaritado aproximadamente 5300 semáforos. Você pode conferir alguns dessas anotações nas imagens abaixo:

Exemplo de anotações das imagen de semáforos. Repare nas diferentes condições de iluminação, tamanhos, poses, modelos e a presença de obstruções em alguns casos.

Quando estava fazendo o gabarito, eu observei duas coisas bastante interessantes que valem a pena destacar. São elas:

  • Semáforos piscam! Tanto na imagem acima quanto no vídeo do início desse artigo, você pode perceber que as luzes dos semáforos piscam com certa frequência. Isso acontece por que a frequência de captação da câmera é diferente da frequência do semáforo (provavelmente 60Hz). Tentei resolver esse problema mudando algumas configurações da câmera e inclusive utilizando outras câmeras (como de celulares), mas não fizeram muito efeito. No fim das contas, considerei isso como um 4º estado do semáforo (indefinido) e desenvolvi todo o restante do sistema com isso em mente.
  • Semáforos verdes são em geral maiores que os vermelhos. Você sabe dizer por que, caro leitor?

O treinamento do detector

Com os dados em mãos, o próximo passo agora era treinar o detector de semáforos. Na tal dissertação, o método utilizado para treinar o detector foi um utilitário da OpenCV que ajuda a treinar detectores baseado em métodos de cascata. Você pode conferir mais detalhes na própria documentação. Talvez, no futuro, eu até possa publicar um artigo aqui no Medium ensinando a treinar detectores e com dicas para o treinamento.

Depois de ler a documentação sobre os utilitários e também outros artigos na internet sobre detecção com esse utilitário, dei início ao treinamento do meu próprio detector. Lembro que testei diversas combinações de parâmetros como: o tamanho mínimo/máximo de detecção, o aspect ratio dos semáforos, diferentes tipos de features, a proporção de positivos/negativos, etc... Ao todo, treinei e avaliei mais 110 detectores diferentes!

Apesar de todo esse treinamento, lembro que meu detector tinha uma f-measure de aproximadamente 80%. Para quem não sabe, a f-measure é uma medida muito utilizada para detectores que balanceia as taxas de precisão e revocação (recall) de detectores. Em poucas a palavras, o recall analisa "quantos semáforos do seu banco foram detectados?" Enquanto a precisão responde "quantas detecções são realmente um semáforo?" Boa parte da minha f-measure era afetada pela precisão, ou seja, o meu sistema detectava bem os semáforos, mas ele retornava muitos falsos positivos.

É mais fácil aumentar a precisão de um detector com um pós-processamento do que aumentar o recall sem treinar um novo detector!

O lado bom disso é que eu poderia treinar um classificador para avaliar as detecções do meu sistema e indicar se aquela detecção representava realmente um semáforo. Para isso, treinei um SVM com praticamente o mesmo banco que utilizei para treinar o detector. Com isso, aumentamos a nossa f-measure de pouco mais de 80% para 98%!

O treinamento dos classificadores

Com o detector finalizado, faltava agora saber o estado desse semáforo (verde, amarelo, vermelho ou indefinido). Para isso, implementei o método descrito naquela dissertação: primeiramente, as imagens eram pré-processadas para destacar as bandas R e G de uma imagem RGB; depois, atributo eram extraídos utilizando Local Binary Patterns (LBP) e um SVM foi treinado para fazer a classificação do estado do semáforo. Utilizando esse método, nosso reconhecedor de semáforos alcançou uma acurácia de 98% também!

Embarcando no Raspberry Pi

Após 2 meses e meio de desenvolvimento, o código do sistema estava pronto e organizado. O último passo agora era portar todo esse código para o Raspberry Pi.

O primeiro passo nessa etapa, seria buildar a OpenCV no próprio Raspberry Pi. Tentei diversos tutoriais na internet e, ao menos para mim, esse aqui foi o único que realmente funcionou.

Se você precisar buildar a OpenCV no Raspberry Pi, eu aconselho que desligue a hibernação do computador e vá fazer outra coisa, pois esse processo demora bastante…

Bastava agora compilar esse código pro Raspberry Pi. Para minha sorte, o próprio Visual Studio permite que você compile um código-fonte remotamente. Tudo o que você precisa é do usuário, senha e ip do seu Raspberry. Primeiramente, com o VS aberto, vá em Tools ➡️ Options ➡️ Cross Platform ➡️ Connection Manager e adicione o seu Raspberry Pi a sua lista de dispositivos remotos, como na figura abaixo:

Depois, mude o seu tipo de build de x64/x86 ➡️ ARM e a opção Local Windows Debugger ➡️ Local Remote Debugger, ambas na barra de ferramentas do Visual Studio. Agora, é só executar o seu projeto normalmente. Detalhes adicionais podem ser encontrados nesse link.

Ao final de todo o processo, tive meu projeto rodando num Raspberry Pi à uma taxa entre 4 e 5 fps. A fins de comparação, o meu notebook é capaz de executar entre 16–20 fps. Ou seja, o Raspberry Pi teve um poder de processamento de 4x a 5x menor.

Considerações Finais

Em comparação com a dissertação que mencionei, o trabalho que desenvolvi obteve um resultado bastante parecido, porém com muito menos recurso que um projeto de carro autônomo. Nosso sistema utiliza uma webcam de baixo custo, roda a 16 fps em um desktop barato e ainda é capaz de rodar em um Raspberry Pi 3 a 4/5 fps sem nenhuma otimização de código. A f-measure do nosso detector é de 98,4% e nosso sistema acerta o estado do semáforo com 98,49% de precisão.

Em relação a todo o processo em si, na minha opinião, a parte mais difícil foi conseguir compilar o projeto no Raspberry Pi. Instalar a OpenCV, configurar o projeto e o Visual Studio para as especificações do Raspberry Pi demandaram um certo tempo. Construir o banco foi um pouco demorado também e, de certa forma, chato. Entretanto, boa parte do sucesso do projeto é devido ao banco construído.

--

--