Robô seguidor de linha com Raspberry Pi Zero W e OpenCV 22

A Raspberry PI Zero W é a mais nova placa da série das Raspberry. Seus maiores atrativos são o impressionante tamanho físico (é somente um pouco maior que um Pendrive), sua alta conectividade (possui, já nativo, conectividade WI-FI e Bluetooth) e seu preço reduzido em relação às demais Raspberries, tornando assim uma interessantíssima opção para quem quer desenvolver projetos utilizando Linux Embarcado. Aproveitando o recente lançamento desta placa aqui na loja, será mostrado nesse post um projeto bem bacana aos que gostam de robótica e visão computacional: como construir um robô seguidor de linha utilizando visão computacional (OpenCV) e a Raspberry Pi Zero W.

Material necessário para você montar o seu robô seguidor de linha

Para fazer esse projeto de robô seguidor de linha, você irá precisar de:

Considerações

Este tutorial de robô seguidor de linha considera que você já está com a Raspberry PI Zero W operante (inclusive com acesso à Internet), ou seja, com o Raspbian instalado em um catão na Raspberry PI Zero W. O procedimento para isso é idêntico ao feito na Raspberry PI 3, por exemplo. Portanto, basta baixar a imagem do Raspbian mais recente e instalar!

Dica: configure também o acesso VNC Raspberry PI. Aqui, também o processo é idêntico ao feito na Raspberry PI 3.

Compilação e instalação do OpenCV

O OpenCV é um biblioteca Open-Source de visão computacional e machine learning. Além de muito popular, ela é excelente, permitindo o desenvolvimento de soluções realmente muito boas e profissionais. Um dos grandes méritos da Raspberry PI é a fácil integração com o OpenCV, aumentando ainda mais o leque de opções de projetos possíveis de serem feitos nela.

O mais recomendado, por motivos de otimização de desempenho, é compilar o OpenCV na plataforma-alvo (no nosso caso, a Raspberry PI Zero W). Este processo envolve baixar fontes e bibliotecas e compilar de fato o OpenCV , portanto é um processo que exige conexão com a Internet. Além disso, trata-se de um processo que exige todo o processamento da Raspberry PI Zero W, portanto é extremamente recomendável iniciar o processo de compilação e “esquecer” a placa compilando por algumas horas.

Para baixar todas as bibliotecas necessárias, código-fonte do OpenCV e fazer sua compilação, siga o procedimento abaixo:

  1. No terminal Linux (na Raspberry PI Zero W), utilize o seguinte comando:
    nano InstalaOpenCV.sh
  2. No editor de textos nano, cole o seguinte conteúdo:
    #!/bin/bash
    
    sudo apt-get install -y build-essential cmake pkg-config
    sudo apt-get install -y libjpeg-dev libtiff5-dev libjasper-dev libpng12-dev
    sudo apt-get install -y libavcodec-dev libavformat-dev libswscale-dev libv4l-dev
    sudo apt-get install -y libxvidcore-dev libx264-dev
    sudo apt-get install -y libgtk2.0-dev
    sudo apt-get install -y libatlas-base-dev gfortran
    sudo apt-get install -y python2.7-dev python3-dev
    
    cd ~
    wget -O opencv.zip https://github.com/Itseez/opencv/archive/3.1.0.zip
    unzip opencv.zip
    
    wget -O opencv_contrib.zip https://github.com/Itseez/opencv_contrib/archive/3.1.0.zip
    unzip opencv_contrib.zip
    
    pip install numpy
    
    cd ~/opencv-3.1.0/
    mkdir build
    cd build
    cmake -D CMAKE_BUILD_TYPE=RELEASE \
        -D CMAKE_INSTALL_PREFIX=/usr/local \
        -D INSTALL_PYTHON_EXAMPLES=ON \
        -D OPENCV_EXTRA_MODULES_PATH=~/opencv_contrib-3.1.0/modules \
        -D BUILD_EXAMPLES=ON ..
    
    
    make
    sudo make install
    sudo ldconfig
    
  3. Saia do editor nano pressionando Ctrl+X e depois Y
  4. Digite o seguinte comando:
    sudo chmod +x InstalaOpenCV.sh
  5. Feito isso, inicie o processo com o comando abaixo:
    sudo ./InstalaOpenCV.sh

Agora basta esperar. Devido à Raspberry PI Zero W ser single-core (ao contrário da Raspberry PI 3 que é quad-core), o processo de compilação é bem lento. No meu caso, o processo todo demorou entre 6 e 7 horas. Portanto, uma boa ideia é deixar compilando durante a noite.

Importante: pelo fato da compilação do OpenCV ser algo que exige todo o processamento da Raspberry PI (e por muito tempo), a tendência é esta esquentar consideravelmente. Portanto, recomendo fortemente não fazer este processo com a Raspberry PI Zero W em cases fechados (quanto mais ventilado for pra ela, melhor).

Circuito esquemático

O circuito esquemático do projeto robô seguidor de linha pode ser visto na figura abaixo:

 

Esquemático robô segue-linha

 

Movimentação do robô

A movimentação do robô seguidor de linha é feito conforme mostra a figura abaixo:

 

Movimentação do robô seguidor de linha

Ou seja:

  • Para ir para a direita, aciona-se o motor da esquerda e para-se o motor da direita (figura 2.a)
  • Para ir para a esquerda, aciona-se o motor da direta e para-se o motor da esquerda (figura 2.b)
  • Para ir para frente, aciona-se ambos os motores (figura 2.c)

Funcionamento do projeto

Compilado e instalado o OpenCV, estamos prontos para prosseguir no projeto! Este projeto consiste em um robô seguidor de linha, porém ao invés de utilizar sensores reflexivos infravermelhos ou sensores semelhantes, será utilizada visão computacional (com OpenCV) para detecção da linha. O robô, com base nos resultados obtidos do processamento de imagem, funcionará da seguinte forma:

  • Se o processamento de imagens indicou que o robô está a direita da linha, aciona-se o motor da roda da direita (desta forma, levando o robô para a esquerda)
  • Se o processamento de imagens indicou que o robô está a esquerda da linha, aciona-se o motor da roda da esquerda (desta forma, levando o robô para a direita)
  • Se nenhuma linha for detectada, o robô irá parar (será considerado fim de curso)

Em suma, enquanto alguma linha foi “enxergada” pelo robô, este se movimenta seguindo-a. A partir do momento que nenhuma linha é mais “vista” pelo robô, é considerado fim de curso e este irá parar.

Processamento de imagens – detalhes

O processamento de imagens desse projeto possui as seguintes etapas:

  1. Aquisição de um quadro/frame da webcam: cada frame obtido é processado individualmente pelas etapas seguintes. É importante ressaltar aqui que, devido à baixa capacidade de processamento da Raspberry PI Zero W (em relação às outras da linha), uma boa resolução de imagem (ou seja, que permite um bom tempo de processamento de imagem) é 320×240 pixels.
  2. Desenho de uma linha vertical, no centro da imagem: esta linha servirá como referência: se a linha do chão / linha- guia cruzar esta linha, o robô está andando em cima da linha-guia. Observe a figura abaixo:
    Figura 3 - linha de referência no frame
    Figura 3 – linha de referência no frame

    Caso contrário, precisará ir para a esquerda ou direita,dependendo do posicionamento relativo entre as linhas. Observe a figura abaixo:

    Figura 4 - posicionamento da linha-guia em relação à linha-referência
    Figura 4 – posicionamento da linha-guia em relação à linha-referência

     

  3. Transformação do frame para escala de cinza: a escala de cinza é normalmente a primeira etapa de um processamento de imagens pois, por conter muito menos informações que uma imagem colorida, torna seu processamento muito mais rápido nas etapas posteriores.
  4. Gaussian Blur: Este filtro faz com que contornos fiquem mais suaves (efeito smooth), fazendo com que a linha-guia fique mais “uniforme”. isso significa que variações bruscas de cor / reflexos, etc. serão suavizados e ficarão com a cor mais parecida com a da linha-guia.
  5. Binarização: a binarização consiste em reconfigurar as cores de todos os pixels da imagem, seguindo a lógica: Se a cor do pixel (em escala de cinza) for menor que um dado limiar, esse pixel tem sua cor substituída pela cor branca. Se a cor do pixel (em escala de cinza) for maior que um dado limiar, esse pixel tem sua cor substituída pela cor preta. Portanto, a imagem ficará em preto-e-branco, com mais ou menos detalhes dependendo do limiar de binarização (limiar de cor escolhido).
    Importante: a determinação deste limiar é empírica, ou seja, varia de caso para caso e deve ser determinado na prática (até a qualidade da imagem obtida da câmera pode influenciar). Portanto o limiar de binarização aqui utilizado pode ser diferente no seu caso.
  6.  Dilatação: após a binarização, a linha (objeto que desejamos realçar / detectar) pode conter “buracos” dentro de si, o que nas etapas posteriores podem ser considerados objetos/contornos à parte. Estes “buracos” são péssimos para o processamento de imagens pois, além e uma falsa detecção de um contorno/objeto, estes farão com que o processamento de imagem fique mais pesado (pois serão processados contornos que tem relevância nenhuma para este projeto). Para eliminar estes “buracos”, é feito um processo chamado de dilatação. Ao fim deste, o objeto (no caso, a linha-guia) virará uma “massa de pixels” única, sem “buracos”.
  7. Inversão de cores: neste ponto, a linha detectada deve estar na cor preta e o restante da imagem na cor branca. Como a etapa seguinte (busca e processamento de contornos) considera uma massa de pixels branca como contorno fechado, é realizada aqui a inversão de cores da imagem (desta forma, a linha passará a ser branca e, portanto, detectável como contorno).
  8. Busca e processamento de contornos: esta é a etapa definitiva do processamento de imagens, pois aqui determina-se onde a linha-guia está. Espera-se que o único contorno encontrado (de área maior que um determinado limite) seja a linha no chão. Em seguida, obtêm-se o centro de uma área retangular capaz de “englobar” todo o contorno da linha que foi detectado(este centro recebe o nome de centróide).

Feito isso, as decisões abaixo são tomadas, conforme explicado no tópico “Funcionamento deste projeto” deste artigo:

  • Se o centróide do contorno da linha-guia estiver à esquerda da linha azul desenhada no frame (conforme passo 2 do processamento de imagens), o robô deve se mover para a esquerda.
  • Se o centróide do contorno da linha-guia estiver à direita da linha azul desenhada no frame (conforme passo 2 do processamento de imagens), o robô deve se mover para a direita.
  • Se nenhuma linha for “vista”, considera-se que é fim de curso e o robô para de se mover.

Código-fonte do projeto

O código-fonte do projeto robô seguidor de linha pode ser visto abaixo:

import cv2
import numpy as np
import RPi.GPIO as GPIO

LimiarBinarizacao = 125       #este valor eh empirico. Ajuste-o conforme sua necessidade 
AreaContornoLimiteMin = 5000  #este valor eh empirico. Ajuste-o conforme sua necessidade 

#GPIOs utilizados:
GPIOMotor1 = 18 #Broadcom pi2067n 18 (P1 pin 12)
GPIOMotor2 = 17 #Broadcom pin 17 (P1 pin 11)


#Funcao: trata imagem e retorna se o robo seguidor de linha deve ir para a esqueda ou direita
#Parametros: frame capturado da webcam e primeiro frame capturado
#Retorno: < 0: robo deve ir para a direita
#         > 0: robo deve ir para a esquerda
#         0:   nada deve ser feito
def TrataImagem(img):
    #obtencao das dimensoes da imagem
    height = np.size(img,0)
    width= np.size(img,1)
    QtdeContornos = 0
    DirecaoASerTomada = 0
    
    #tratamento da imagem
    gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    gray = cv2.GaussianBlur(gray, (21, 21), 0)
    FrameBinarizado = cv2.threshold(gray,LimiarBinarizacao,255,cv2.THRESH_BINARY)[1]
    FrameBinarizado = cv2.dilate(FrameBinarizado,None,iterations=2)
    FrameBinarizado = cv2.bitwise_not(FrameBinarizado)
    
    #descomente as linhas abaixo se quiser ver o frame apos binarizacao, dilatacao e inversao de cores
    #cv2.imshow('F.B.',FrameBinarizado)
    #cv2.waitKey(10)

    _, cnts, _ = cv2.findContours(FrameBinarizado.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cv2.drawContours(img,cnts,-1,(255,0,255),3)

    for c in cnts:
	    #se a area do contorno capturado for pequena, nada acontece
	    if cv2.contourArea(c) < AreaContornoLimiteMin:
        	continue
            
            QtdeContornos = QtdeContornos + 1

	    #obtem coordenadas do contorno (na verdade, de um retangulo que consegue abrangir todo ocontorno) e
	    #realca o contorno com um retangulo.
	    (x, y, w, h) = cv2.boundingRect(c)   #x e y: coordenadas do vertice superior esquerdo
	                                         #w e h: respectivamente largura e altura do retangulo

            cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)
	
	    #determina o ponto central do contorno e desenha um circulo para indicar
	    CoordenadaXCentroContorno = (x+x+w)/2
	    CoordenadaYCentroContorno = (y+y+h)/2
	    PontoCentralContorno = (CoordenadaXCentroContorno,CoordenadaYCentroContorno)
	    cv2.circle(img, PontoCentralContorno, 1, (0, 0, 0), 5)
        
    	    DirecaoASerTomada = CoordenadaXCentroContorno - (width/2)   #em relacao a linha central
     
    #output da imagem
    #linha em azul: linha central / referencia
    #linha em verde: linha que mostra distancia entre linha e a referencia
    cv2.line(img,(width/2,0),(width/2,height),(255,0,0),2)
    
    if (QtdeContornos > 0):
        cv2.line(img,PontoCentralContorno,(width/2,CoordenadaYCentroContorno),(0,255,0),1)
    
    cv2.imshow('Analise de rota',img)
    cv2.waitKey(10)
    return DirecaoASerTomada, QtdeContornos


#Programa principal

#Setup dos GPIOs:
GPIO.setmode(GPIO.BCM) 
GPIO.setup(GPIOMotor1, GPIO.OUT)
GPIO.setup(GPIOMotor2, GPIO.OUT)
GPIO.output(GPIOMotor1, GPIO.LOW)
GPIO.output(GPIOMotor2, GPIO.LOW)

camera = cv2.VideoCapture(0)
camera.set(3,320)
camera.set(4,240)

#faz algumas leituras de frames antes de consierar a analise
#motivo: algumas camera podem demorar mais para se "acosumar a luminosidade" quando ligam, capturando frames consecutivos com muita variacao de luminosidade. Para nao levar este efeito ao processamento de imagem, capturas sucessivas sao feitas fora do processamento da imagem, dando tempo para a camera "se acostumar" a luminosidade do ambiente
for i in range(0,20):
    (grabbed, Frame) = camera.read()

while True:
    try:
      (grabbed, Frame) = camera.read()
    
      if (grabbed):
          Direcao,QtdeLinhas = TrataImagem(Frame)
          if (QtdeLinhas == 0):
             print "Nenhuma linha encontrada. O robo ira parar."
             GPIO.output(GPIOMotor1, GPIO.LOW)
             GPIO.output(GPIOMotor2, GPIO.LOW) 
             continue
        
          if (Direcao > 0):
              print "Distancia da linha de referencia: "+str(abs(Direcao))+" pixels a direita"
              GPIO.output(GPIOMotor1, GPIO.HIGH)
              GPIO.output(GPIOMotor2, GPIO.LOW)
          if (Direcao < 0):
              print "Distancia da linha de referencia: "+str(abs(Direcao))+" pixels a esquerda"      
              GPIO.output(GPIOMotor1, GPIO.LOW)
              GPIO.output(GPIOMotor2, GPIO.HIGH)
          if (Direcao == 0):
              print "Exatamente na linha de referencia!"      
              GPIO.output(GPIOMotor1, GPIO.HIGH)
              GPIO.output(GPIOMotor2, GPIO.HIGH)
    except (KeyboardInterrupt):
        GPIO.output(GPIOMotor1, GPIO.LOW)
        GPIO.output(GPIOMotor2, GPIO.LOW)
	exit(1)   

Copie-o e salve-o na Rasberry PI Zero W como RoboLinha.py. Para executá-lo, utilize o comando abaixo:

python RoboLinha.py

Preparação do local para o robô rodar

O chão que o robô rodará deverá ser o mais claro possível e a linha a mais escuro possível. Idealmente, chão branco e linha preta. O motivo disso é que, quanto mais alto o contraste de cor entre linha e chão, mais fácil é para a linha ser detectada por visão computacional.

Agora é só colocar uma linha no chão e colocar o robô pra rodar!

Gostou do projeto do Robô seguidor de linha com Raspberry Pi Zero W e OpenCV? Deixe seu comentário logo abaixo.

Faça seu comentário

Acesse sua conta e participe

22 Comentários

  1. Olá, é possivel no lugar de uma webcam usar a própria camera que conecta no rasp? (rasp modelo 4b)

  2. Olá, teria como fazer esse projeto só que com arduino?

    Maria Eduarda Clementino Aires
    1. Olá Maria.

      Temos outro post que aborda o projeto com Arduino: https://www.makerhero.com/blog/projeto-robo-seguidor-de-linha-arduino/

      Att.
      Vitor Mattos.
      Suporte Técnico MakerHero.

  3. Boa tarde, meu nome é Renato. Fiz a compilação na Raspberry pi 3B. No final apareceu essa mensagem de erro no terminal.

    compilation terminated.
    make[2]: *** [modules/core/CMakeFiles/pch_Generate_opencv_core.dir/build.make:64: modules/core/precomp.hpp.gch/opencv_core_RELEASE.gch] Error 1
    make[1]: *** [CMakeFiles/Makefile2:4118: modules/core/CMakeFiles/pch_Generate_opencv_core.dir/all] Error 2
    make: *** [Makefile:163: all] Error 2

    Após isso testei o código do robô no aplicativo Thonny e retornou esse erro:
    ModuleNotFoundError: No module named ‘cv2’

  4. eu queria uma atualização desse projeto, seria bem legal ^_^

  5. O circuito pode ser usado como ponte H, para outros projetos?

    1. Pode. Desde que respeite a corrente máxima no coletor do BC337. De acordo com o data sheet é 500mA contínua ou 1A alternada. Mas você acha módulos na internet por 15 reais que suportam correntes bem maiores, como por exemplo o L298N, que suporta até 6A em cada canal.

  6. Vocês tem algum vídeo do carro funcionando?

  7. O meu código fica dando erro de indentação

  8. Bom dia meu nome e Robinson Rafael , gostaria de saber se tem como usar um raspberry pi 3 b, usar neste robo agv

    1. Robinson, bom dia.

      Sim, você pode utilizar a Raspberry Pi 3b neste projeto.

      Atenciosamente,
      Pedro Bertoleti

  9. Minha compilação da erro quando tenta conectar com o serviço raspbian.raspberrypi.org

    1. Bruno, bom dia.

      Poderia colocar aqui as mensagens de erro? Pergunto pois, pela descrição dada está parecendo que sua Raspberry não está com conectada à Internet, porém com as mensagens de erro conseguirei verificar melhor o que está ocorrendo.

      Atenciosamente,
      Pedro Bertoleti

      1. eu consegui, obrigado!

  10. Ola boa noite. Primeiramente muito obrigado pelo blog e pelo excelente trabalho. Gostaria de saber posso substituir a câmera por um sensor de presença? e se essa configuração se aplica também ao raspberry pi b+?

    1. acredito que não, pois o opencv trabalha com imagem

  11. Olá, após rodar algumas vezes, quando detecta somente uma linha o programa da o seguinte erro

    CoordenadaXCentroContorno = (x + x + w) / 2
    UnboundLocalError: local variable ‘x’ referenced before assignment

    Sabe de alguma coisa relacionada a isso ? estou tentando reproduzir usando uma raspberry pi 3 desde já agradeço;

  12. Muito bom, obrigado por compartilhar o conhecimento!
    Mas uma pergunta, consigo instalar o OpenCV no Raspberry Pi 3 usando este tutorial?

    1. Renan, bom dia.

      Primeiramente, muito obrigado pela leitura e elogio!
      Sobre sua pergunta, sim, é possível instalar o OpenCV na Raspberry PI 3 usando este tutorial.

      Atenciosamente,
      Pedro Bertoleti

  13. Uhum, mostra o video do robo andando please ??

Trackbacks and Pingbacks

  1. […] juntamente com a Raspberry Pi podemos criar diversos tipos de aplicações, desde câmeras IP até robôs com processamento de imagem usando OpenCV. Neste post vamos mostrar a nossa nova câmera compatível com Raspberry Pi, uma câmera com […]

  2. […] PI Zero W é uma placa e tanto! Já vimos aqui no blog uma série de projetos com ela, como robô que utiliza visão computacional, campainha IoT, streaming de vídeo e por aí vai. Mas esta placa pode ir além, podendo ser […]