Índice:

Carro de controle de pista autônomo usando Raspberry Pi e OpenCV: 7 etapas (com fotos)
Carro de controle de pista autônomo usando Raspberry Pi e OpenCV: 7 etapas (com fotos)

Vídeo: Carro de controle de pista autônomo usando Raspberry Pi e OpenCV: 7 etapas (com fotos)

Vídeo: Carro de controle de pista autônomo usando Raspberry Pi e OpenCV: 7 etapas (com fotos)
Vídeo: Android Things, Machine Learning e Máquinas de Doces - Alvaro Viebrantz 2024, Novembro
Anonim
Carro de controle de pista autônomo usando Raspberry Pi e OpenCV
Carro de controle de pista autônomo usando Raspberry Pi e OpenCV

Nesses instrutíveis, um robô autônomo que mantém a faixa será implementado e passará pelas seguintes etapas:

  • Reunindo peças
  • Pré-requisitos de instalação de software
  • Montagem de Hardware
  • Primeiro teste
  • Detectando linhas de faixa e exibindo a linha guia usando openCV
  • Implementando um controlador PD
  • Resultados

Etapa 1: reunir componentes

Coletando Componentes
Coletando Componentes
Coletando Componentes
Coletando Componentes
Coletando Componentes
Coletando Componentes
Coletando Componentes
Coletando Componentes

As imagens acima mostram todos os componentes usados neste projeto:

  • Carro RC: comprei o meu numa loja local no meu país. É equipado com 3 motores (2 para regulagem e 1 para direção). A principal desvantagem deste carro é que a direção é limitada entre "sem direção" e "direção total". Em outras palavras, ele não pode virar em um ângulo específico, ao contrário dos carros RC com direção servo. Você pode encontrar um kit veicular semelhante projetado especialmente para pi framboesa aqui.
  • Raspberry pi 3 model b +: este é o cérebro do carro que irá realizar várias etapas de processamento. Ele é baseado em um processador quad core de 64 bits com clock de 1,4 GHz. Eu peguei o meu daqui.
  • Módulo de câmera Raspberry pi 5 mp: suporta gravação 1080p a 30 fps, 720p a 60 fps e 640x480p 60/90. Ele também suporta interface serial que pode ser conectada diretamente no raspberry pi. Não é a melhor opção para aplicativos de processamento de imagens, mas é suficiente para este projeto e também é muito barato. Eu peguei o meu daqui.
  • Motor Driver: É usado para controlar as direções e velocidades dos motores DC. Ele suporta o controle de motores de 2 cc em 1 placa e pode suportar 1,5 A.
  • Banco de força (opcional): usei um banco de força (avaliado em 5 V, 3 A) para ligar o raspberry pi separadamente. Um conversor redutor (conversor buck: corrente de saída 3A) deve ser usado para ligar o raspberry pi de uma fonte.
  • Bateria LiPo 3s (12 V): As baterias de polímero de lítio são conhecidas por seu excelente desempenho no campo da robótica. É usado para alimentar o driver do motor. Eu comprei o meu aqui.
  • Fios de jumper macho para macho e fêmea para fêmea.
  • Fita dupla-face: usada para montar os componentes no carro RC.
  • Fita azul: Este é um componente muito importante deste projeto, é usado para fazer as duas faixas de rodagem entre as quais o carro passará. Você pode escolher a cor que quiser, mas eu recomendo escolher cores diferentes das do ambiente ao redor.
  • Fechos de correr e barras de madeira.
  • Chave de fenda.

Etapa 2: Instalação do OpenCV no Raspberry Pi e configuração do visor remoto

Instalação do OpenCV no Raspberry Pi e configuração do display remoto
Instalação do OpenCV no Raspberry Pi e configuração do display remoto

Esta etapa é um pouco chata e levará algum tempo.

OpenCV (Open source Computer Vision) é uma visão computacional de código aberto e biblioteca de software de aprendizado de máquina. A biblioteca possui mais de 2500 algoritmos otimizados. Siga ESTE guia muito simples para instalar o openCV em seu raspberry pi, bem como instalar o sistema operacional raspberry pi (se ainda não o fez). Observe que o processo de construção do openCV pode levar cerca de 1,5 horas em uma sala bem resfriada (já que a temperatura do processador ficará muito alta!), Portanto, tome um pouco de chá e espere pacientemente: D.

Para o display remoto, siga também ESTE guia para configurar o acesso remoto ao seu raspberry pi a partir do seu dispositivo Windows / Mac.

Etapa 3: conectando as peças

Conectando as peças
Conectando as peças
Conectando as peças
Conectando as peças
Conectando as peças
Conectando as peças

As imagens acima mostram as conexões entre o raspberry pi, o módulo da câmera e o driver do motor. Observe que os motores que usei absorvem 0,35 A a 9 V cada, o que torna seguro o acionador do motor operar 3 motores ao mesmo tempo. E como quero controlar a velocidade de 2 motores de estrangulamento (1 traseiro e 1 dianteiro) exatamente da mesma forma, conectei-os à mesma porta. Montei o motor do lado direito do carro usando fita dupla. Quanto ao módulo da câmera, coloquei uma tira zip entre os orifícios dos parafusos, como mostra a imagem acima. Então, eu encaixo a câmera em uma barra de madeira para que eu possa ajustar a posição da câmera como eu quiser. Tente instalar a câmera no meio do carro o máximo possível. Recomendo colocar a câmera pelo menos 20 cm acima do solo para que o campo de visão na frente do carro fique melhor. O esquema do Fritzing está anexado abaixo.

Etapa 4: primeiro teste

Primeiro teste
Primeiro teste
Primeiro teste
Primeiro teste

Teste de câmera:

Assim que a câmera estiver instalada e a biblioteca openCV construída, é hora de testar nossa primeira imagem! Vamos tirar uma foto do pi cam e salvá-la como "original.jpg". Isso pode ser feito de 2 maneiras:

1. Usando comandos de terminal:

Abra uma nova janela de terminal e digite o seguinte comando:

raspistill -o original.jpg

Isso pegará uma imagem estática e a salvará no diretório "/pi/original.jpg".

2. Usando qualquer IDE python (eu uso IDLE):

Abra um novo esboço e escreva o seguinte código:

import cv2

video = cv2. VideoCapture (0) enquanto True: ret, frame = video.read () frame = cv2.flip (frame, -1) # usado para virar a imagem verticalmente cv2.imshow ('original', frame) cv2. imwrite ('original.jpg', frame) key = cv2.waitKey (1) if key == 27: break video.release () cv2.destroyAllWindows ()

Vamos ver o que aconteceu neste código. A primeira linha é importar nossa biblioteca openCV para usar todas as suas funções. a função VideoCapture (0) começa a transmitir um vídeo ao vivo da fonte determinada por esta função, neste caso é 0 que significa câmera raspi. se você tiver várias câmeras, devem ser colocados números diferentes. video.read () irá ler cada quadro que vem da câmera e salvá-lo em uma variável chamada "quadro". A função flip () inverte a imagem em relação ao eixo y (verticalmente), pois estou montando minha câmera inversamente. imshow () exibirá nossas molduras encabeçadas pela palavra "original" e imwrite () salvará nossa foto como original.jpg. waitKey (1) irá aguardar 1 ms para que qualquer botão do teclado seja pressionado e retorna seu código ASCII. se o botão escape (esc) for pressionado, um valor decimal de 27 será retornado e interromperá o loop de acordo. video.release () irá parar a gravação e destroyAllWindows () irá fechar todas as imagens abertas pela função imshow ().

Recomendo testar sua foto com o segundo método para se familiarizar com as funções openCV. A imagem é salva no diretório "/pi/original.jpg". A foto original que minha câmera tirou é mostrada acima.

Testando motores:

Esta etapa é essencial para determinar o sentido de rotação de cada motor. Primeiro, vamos fazer uma breve introdução sobre o princípio de funcionamento de um driver de motor. A imagem acima mostra a pinagem do driver do motor. A habilitação A, a entrada 1 e a entrada 2 estão associadas ao controle do motor A. A habilitação B, a entrada 3 e a entrada 4 estão associadas ao controle do motor B. O controle de direção é estabelecido pela parte "Entrada" e o controle de velocidade é estabelecido pela parte "Ativar". Para controlar a direção do motor A, por exemplo, defina a Entrada 1 para ALTO (3,3 V neste caso, uma vez que estamos usando uma framboesa pi) e defina a Entrada 2 para BAIXO, o motor girará em uma direção específica e configurando os valores opostos para a entrada 1 e a entrada 2, o motor girará na direção oposta. Se Entrada 1 = Entrada 2 = (ALTO ou BAIXO), o motor não gira. Os pinos de habilitação recebem um sinal de entrada de modulação por largura de pulso (PWM) do raspberry (0 a 3,3 V) e acionam os motores de acordo. Por exemplo, um sinal 100% PWM significa que estamos trabalhando na velocidade máxima e 0% sinal PWM significa que o motor não está girando. O código a seguir é usado para determinar as direções dos motores e testar suas velocidades.

tempo de importação

import RPi. GPIO as GPIO GPIO.setwarnings (False) # Pinos do motor de direção Steering_enable = 22 # Pino Físico 15 in1 = 17 # Pino Físico 11 in2 = 27 # Pino Físico 13 # Pinos dos motores do throttle throttle_enable = 25 # Pino Físico 22 in3 = 23 # Pino físico 16 in4 = 24 # Pino físico 18 GPIO.setmode (GPIO. BCM) # Use a numeração GPIO em vez da numeração física GPIO.setup (in1, GPIO.out) GPIO.setup (in2, GPIO.out) GPIO. configuração (in3, GPIO.out) GPIO.setup (in4, GPIO.out) GPIO.setup (throttle_enable, GPIO.out) GPIO.setup (direction_enable, GPIO.out) # Steering Motor Control GPIO.output (in1, GPIO. HIGH) GPIO.output (in2, GPIO. LOW) direção = GPIO. PWM (direção_ativar, 1000) # definir a frequência de comutação para 1000 Hz direction.stop () # Throttle Motors Control GPIO.output (in3, GPIO. HIGH) GPIO.output (in4, GPIO. LOW) throttle = GPIO. PWM (throttle_enable, 1000) # definir a frequência de comutação para 1000 Hz throttle.stop () time.sleep (1) throttle.start (25) # inicia o motor a 25 % Sinal PWM-> (0,25 * tensão da bateria) - driver's perda de direção.início (100) # dá partida no motor em 100% do sinal PWM-> (1 * Tensão da bateria) - perda de tempo do motorista. sono (3) aceleração.parada () direção.parada ()

Este código acionará os motores de estrangulamento e o motor de direção por 3 segundos e, em seguida, os parará. A (perda do driver) pode ser determinada usando um voltímetro. Por exemplo, sabemos que um sinal 100% PWM deve fornecer a tensão total da bateria no terminal do motor. Mas, ao definir PWM para 100%, descobri que o driver está causando uma queda de 3 V e o motor está obtendo 9 V em vez de 12 V (exatamente o que eu preciso!). A perda não é linear, ou seja, a perda em 100% é muito diferente da perda em 25%. Depois de executar o código acima, meus resultados foram os seguintes:

Resultados de estrangulamento: se in3 = HIGH e in4 = LOW, os motores de estrangulamento terão uma rotação no sentido horário (CW), ou seja, o carro se moverá para frente. Caso contrário, o carro se moverá para trás.

Resultados da direção: se in1 = HIGH e in2 = LOW, o motor de direção girará no máximo à esquerda, ou seja, o carro irá virar para a esquerda. Caso contrário, o carro irá virar para a direita. Depois de alguns experimentos, descobri que o motor de direção não gira se o sinal PWM não for 100% (ou seja, o motor vai virar totalmente para a direita ou totalmente para a esquerda).

Etapa 5: Detectando Linhas de Pista e Calculando a Linha de Rumo

Detectando Linhas de Pista e Calculando a Linha de Rumo
Detectando Linhas de Pista e Calculando a Linha de Rumo
Detectando linhas da pista e calculando a linha de direção
Detectando linhas da pista e calculando a linha de direção
Detectando Linhas de Pista e Calculando a Linha de Rumo
Detectando Linhas de Pista e Calculando a Linha de Rumo

Nesta etapa, será explicado o algoritmo que controlará o movimento do carro. A primeira imagem mostra todo o processo. A entrada do sistema são imagens, a saída é theta (ângulo de direção em graus). Observe que o processamento é feito em 1 imagem e será repetido em todos os quadros.

Câmera:

A câmera começará a gravar um vídeo com resolução (320 x 240). Eu recomendo diminuir a resolução para que você possa obter uma melhor taxa de quadros (fps), uma vez que a queda de fps ocorrerá após a aplicação de técnicas de processamento a cada quadro. O código abaixo será o loop principal do programa e adicionará cada etapa a este código.

import cv2

importar numpy como np video = cv2. VideoCapture (0) video.set (cv2. CAP_PROP_FRAME_WIDTH, 320) # definir a largura para 320 p video.set (cv2. CAP_PROP_FRAME_HEIGHT, 240) # definir a altura para 240 p # O loop enquanto Verdadeiro: ret, frame = video.read () frame = cv2.flip (frame, -1) cv2.imshow ("original", frame) key = cv2.waitKey (1) if key == 27: break video.release () cv2.destroyAllWindows ()

O código aqui irá mostrar a imagem original obtida no passo 4 e é mostrado nas imagens acima.

Converter para HSV Color Space:

Agora, depois de fazer a gravação de vídeo como quadros da câmera, a próxima etapa é converter cada quadro em espaço de cor de Matiz, Saturação e Valor (HSV). A principal vantagem de fazer isso é poder diferenciar as cores por seu nível de luminância. E aqui está uma boa explicação do espaço de cores HSV. A conversão para HSV é feita por meio da seguinte função:

def convert_to_HSV (frame):

hsv = cv2.cvtColor (frame, cv2. COLOR_BGR2HSV) cv2.imshow ("HSV", hsv) return hsv

Esta função será chamada a partir do loop principal e retornará o quadro no espaço de cores HSV. O quadro obtido por mim no espaço de cores HSV é mostrado acima.

Detectar cor azul e bordas:

Depois de converter a imagem em espaço de cores HSV, é hora de detectar apenas a cor na qual estamos interessados (ou seja, a cor azul, uma vez que é a cor das linhas da pista). Para extrair a cor azul de um quadro HSV, uma faixa de matiz, saturação e valor deve ser especificado. consulte aqui para ter uma ideia melhor sobre os valores do HSV. Após alguns experimentos, os limites superior e inferior da cor azul são mostrados no código abaixo. E para reduzir a distorção geral em cada quadro, as bordas são detectadas apenas usando detector de bordas astutas. Mais sobre o astuto Edge pode ser encontrado aqui. Uma regra geral é selecionar os parâmetros da função Canny () com uma proporção de 1: 2 ou 1: 3.

def detect_edges (frame):

lower_blue = np.array ([90, 120, 0], dtype = "uint8") # limite inferior da cor azul upper_blue = np.array ([150, 255, 255], dtype = "uint8") # limite superior de máscara de cor azul = cv2.inRange (hsv, lower_blue, upper_blue) # esta máscara irá filtrar tudo, exceto azul # detectar bordas arestas = cv2. Canny (máscara, 50, 100) cv2.imshow ("bordas", bordas) retornar bordas

Esta função também será chamada a partir do loop principal que toma como parâmetro o quadro do espaço de cores HSV e retorna o quadro com bordas. A moldura afiada que obtive encontra-se acima.

Selecione a região de interesse (ROI):

Selecionar a região de interesse é crucial para focar apenas em uma região do quadro. Nesse caso, não quero que o carro veja muitos itens no ambiente. Eu só quero que o carro se concentre nas linhas da pista e ignore tudo o mais. P. S: o sistema de coordenadas (eixos xey) começa no canto superior esquerdo. Em outras palavras, o ponto (0, 0) começa no canto superior esquerdo. o eixo y sendo a altura e o eixo x sendo a largura. O código abaixo seleciona a região de interesse para focar apenas na metade inferior do quadro.

def region_of_interest (bordas):

altura, largura = bordas.shape # extrai a altura e largura das bordas máscara máscara = np.zeros_like (bordas) # faz uma matriz vazia com as mesmas dimensões da moldura de bordas # focaliza apenas a metade inferior da tela # especifica as coordenadas de 4 pontos (inferior esquerdo, superior esquerdo, superior direito, inferior direito) polygon = np.array (

Esta função tomará como parâmetro a moldura afiada e desenhará um polígono com 4 pontos predefinidos. Ele vai focar apenas no que está dentro do polígono e ignorar tudo que está fora dele. O quadro da minha região de interesse é mostrado acima.

Detectar segmentos de linha:

A transformação de Hough é usada para detectar segmentos de linha de um quadro com arestas. A transformada de Hough é uma técnica para detectar qualquer forma matemática. Ele pode detectar quase qualquer objeto, mesmo que seja distorcido de acordo com algum número de votos. uma ótima referência para a transformação de Hough é mostrada aqui. Para esta aplicação, a função cv2. HoughLinesP () é usada para detectar linhas em cada quadro. Os parâmetros importantes que esta função assume são:

cv2. HoughLinesP (frame, rho, theta, min_threshold, minLineLength, maxLineGap)

  • Quadro: é o quadro em que queremos detectar as linhas.
  • rho: é a precisão da distância em pixels (geralmente é = 1)
  • theta: precisão angular em radianos (sempre = np.pi / 180 ~ 1 grau)
  • min_threshold: voto mínimo que deve obter para ser considerado como uma linha
  • minLineLength: comprimento mínimo da linha em pixels. Qualquer linha menor que este número não é considerada uma linha.
  • maxLineGap: intervalo máximo em pixels entre 2 linhas a ser tratado como 1 linha. (Não é usado no meu caso, uma vez que as linhas de faixa que estou usando não têm nenhuma lacuna).

Esta função retorna os pontos finais de uma linha. A função a seguir é chamada de meu loop principal para detectar linhas usando a transformação de Hough:

def detect_line_segments (cropped_edges):

rho = 1 theta = np.pi / 180 min_threshold = 10 line_segments = cv2. HoughLinesP (cropped_edges, rho, theta, min_threshold, np.array (), minLineLength = 5, maxLineGap = 0) retornar line_segments

Inclinação média e interceptação (m, b):

lembre-se de que a equação da reta é dada por y = mx + b. Onde m é a inclinação da linha eb é a interceptação y. Nesta parte, a média de inclinações e interceptações de segmentos de linha detectados usando a transformada de Hough serão calculados. Antes de fazer isso, vamos dar uma olhada na foto do quadro original mostrada acima. A pista da esquerda parece estar subindo, então tem uma inclinação negativa (lembra do ponto inicial do sistema de coordenadas?). Em outras palavras, a linha da pista da esquerda tem x1 <x2 e y2 x1 e y2> y1, o que dará uma inclinação positiva. Portanto, todas as linhas com inclinação positiva são consideradas pontos da pista direita. No caso de linhas verticais (x1 = x2), a inclinação será infinita. Nesse caso, ignoraremos todas as linhas verticais para evitar um erro. Para adicionar mais precisão a essa detecção, cada quadro é dividido em duas regiões (direita e esquerda) por meio de 2 linhas de limite. Todos os pontos de largura (pontos do eixo x) maiores que a linha limite direita estão associados ao cálculo da faixa direita. E se todos os pontos de largura forem menores que a linha limite esquerda, eles serão associados ao cálculo da faixa esquerda. A função a seguir pega o quadro em processamento e segmentos de pista detectados usando a transformada de Hough e retorna a inclinação média e a interceptação de duas linhas de pista.

def average_slope_intercept (frame, line_segments):

lane_lines = se line_segments for None: print ("nenhum segmento de linha detectado") retornar lane_lines height, width, _ = frame.shape left_fit = right_fit = boundary = left_region_boundary = width * (1 - boundary) right_region_boundary = largura * limite para line_segment em line_segments: para x1, y1, x2, y2 em line_segment: if x1 == x2: print ("pulando linhas verticais (inclinação = infinito)") continue fit = np.polyfit ((x1, x2), (y1, y2), 1) inclinação = (y2 - y1) / (x2 - x1) interceptar = y1 - (inclinação * x1) se inclinação <0: se x1 <limite_esquerda_região e x2 limite_região_direita e x2> limite_região_direita: direito_fino. append ((inclinação, interceptação)) left_fit_average = np.average (left_fit, axis = 0) se len (left_fit)> 0: lane_lines.append (make_points (frame, left_fit_average)) right_fit_average = np.average (right_fit, axis = 0) if len (right_fit)> 0: lane_lines.append (make_points (frame, right_fit_average)) # lane_lines é uma matriz 2-D que consiste nas coordenadas das linhas da pista direita e esquerda # por exemplo: lan e_lines =

make_points () é uma função auxiliar para a função average_slope_intercept () que retornará as coordenadas limitadas das linhas da pista (do fundo ao meio do quadro).

def make_points (frame, line):

altura, largura, _ = frame.shape declive, intercept = linha y1 = altura # parte inferior do frame y2 = int (y1 / 2) # fazer pontos do meio do frame para baixo se slope == 0: slope = 0.1 x1 = int ((y1 - interceptar) / inclinação) x2 = int ((y2 - interceptar) / inclinação) retornar

Para evitar a divisão por 0, uma condição é apresentada. Se declive = 0, que significa y1 = y2 (linha horizontal), dê ao declive um valor próximo a 0. Isso não afetará o desempenho do algoritmo e também evitará casos impossíveis (dividindo por 0).

Para exibir as linhas da pista nos quadros, a seguinte função é usada:

def display_lines (frame, lines, line_color = (0, 255, 0), line_width = 6): # line color color (B, G, R)

line_image = np.zeros_like (frame) se lines não for None: for line in lines: for x1, y1, x2, y2 in line: cv2.line (line_image, (x1, y1), (x2, y2), line_color, line_width) line_image = cv2.addWeighted (frame, 0.8, line_image, 1, 1) return line_image

A função cv2.addWeighted () assume os seguintes parâmetros e é usada para combinar duas imagens, mas dando um peso a cada uma.

cv2.addWeighted (imagem1, alfa, imagem2, beta, gama)

E calcula a imagem de saída usando a seguinte equação:

output = alpha * image1 + beta * image2 + gamma

Mais informações sobre a função cv2.addWeighted () são derivadas aqui.

Calcular e exibir a linha de título:

Esta é a etapa final antes de aplicarmos velocidades aos nossos motores. A linha de direção é responsável por dar ao motor de direção a direção na qual ele deve girar e dar aos motores de estrangulamento a velocidade na qual eles irão operar. O cálculo da linha de direção é trigonometria pura, as funções trigonométricas tan e atan (tan ^ -1) são usadas. Alguns casos extremos são quando a câmera detecta apenas uma linha de faixa ou quando não detecta nenhuma linha. Todos esses casos são mostrados na seguinte função:

def get_steering_angle (frame, lane_lines):

altura, largura, _ = frame.shape if len (lane_lines) == 2: # se duas linhas de faixa forem detectadas _, _, left_x2, _ = lane_lines [0] [0] # extrair x2 esquerdo da matriz lane_lines _, _, right_x2, _ = lane_lines [1] [0] # extrair x2 direito da matriz lane_lines mid = int (largura / 2) x_offset = (left_x2 + right_x2) / 2 - mid y_offset = int (height / 2) elif len (lane_lines) == 1: # se apenas uma linha for detectada x1, _, x2, _ = lane_lines [0] [0] x_offset = x2 - x1 y_offset = int (altura / 2) elif len (lane_lines) == 0: # se nenhuma linha for detectada x_offset = 0 y_offset = int (altura / 2) ângulo_para_mid_radian = math.atan (x_offset / y_offset) ângulo_para_mid_deg = int (ângulo_para_mid_radian * 180.0 / math.pi) direção_angulo = ângulo_para_mid_deg + 90 retorno direção

x_offset no primeiro caso é o quanto a média ((x2 direito + x2 esquerdo) / 2) difere do meio da tela. y_offset é sempre considerado como height / 2. A última imagem acima mostra um exemplo de linha de título. angle_to_mid_radians é o mesmo que "theta" mostrado na última imagem acima. Se ângulo_direcção = 90, significa que o carro tem uma linha de direção perpendicular à linha "altura / 2" e o carro se moverá para frente sem virar. Se ângulo_direcção> 90, o carro deve virar para a direita, caso contrário, deve virar para a esquerda. Para exibir a linha de título, a seguinte função é usada:

def display_heading_line (quadro, direção_angulo, line_color = (0, 0, 255), line_width = 5)

imagem_cabeçalho = np.zeros_like (quadro) altura, largura, _ = quadro.shape direção_angulo_radiano = ângulo_direcionamento / 180,0 * math.pi x1 = int (largura / 2) y1 = altura x2 = int (x1 - altura / 2 / math.tan (direção_angulo_radiano)) y2 = int (altura / 2) cv2.line (cabeçalho_imagem, (x1, y1), (x2, y2), linha_cor, linha_largura) cabeçalho_imagem = cv2.addWeighted (quadro, 0,8, cabeçalho_imagem, 1, 1) retornar cabeçalho_imagem

A função acima usa o quadro no qual a linha de direção será desenhada e o ângulo de direção como entrada. Ele retorna a imagem da linha de título. O quadro da linha de título obtido no meu caso é mostrado na imagem acima.

Combinando todos os códigos juntos:

O código agora está pronto para ser montado. O código a seguir mostra o loop principal do programa que chama cada função:

import cv2

importar numpy como np video = cv2. VideoCapture (0) video.set (cv2. CAP_PROP_FRAME_WIDTH, 320) video.set (cv2. CAP_PROP_FRAME_HEIGHT, 240) enquanto True: ret, frame = video.read () frame = cv2.flip (frame, -1) #Chamando as funções hsv = convert_to_HSV (frame) bordas = detect_edges (hsv) roi = region_of_interest (bordas) line_segments = detect_line_segments (roi) lane_lines = average_slope_intercept (frame, line_segments) lane_lines_image = display_lines (frame, lane_image) = get_steering_angle (frame, lane_lines) header_image = display_heading_line (lane_lines_image, Steering_angle) key = cv2.waitKey (1) if key == 27: break video.release () cv2.destroyAllWindows ()

Etapa 6: Aplicação do controle PD

Aplicando Controle PD
Aplicando Controle PD

Agora temos nosso ângulo de direção pronto para alimentar os motores. Conforme mencionado anteriormente, se o ângulo de direção for maior que 90, o carro deve virar à direita, caso contrário, deve virar à esquerda. Eu apliquei um código simples que vira o motor de direção para a direita se o ângulo estiver acima de 90 e vira para a esquerda se o ângulo de direção for menor que 90 a uma velocidade de estrangulamento constante de (10% PWM), mas recebi muitos erros. O principal erro que tenho é quando o carro se aproxima de qualquer curva, o motor de direção atua diretamente, mas os motores de estrangulamento ficam presos. Tentei aumentar a velocidade de aceleração para (20% PWM) nas curvas, mas acabei com o robô saindo das pistas. Eu precisava de algo que aumente muito a velocidade de aceleração se o ângulo de direção for muito grande e aumente um pouco se o ângulo de direção não for tão grande, então reduza a velocidade para um valor inicial conforme o carro se aproxima de 90 graus (movendo-se em linha reta). A solução foi usar um controlador PD.

O controlador PID significa controlador proporcional, integral e derivativo. Este tipo de controlador linear é amplamente utilizado em aplicações de robótica. A imagem acima mostra o circuito de controle de feedback PID típico. O objetivo deste controlador é atingir o "setpoint" da maneira mais eficiente, ao contrário dos controladores "on-off" que ligam ou desligam a planta de acordo com algumas condições. Algumas palavras-chave devem ser conhecidas:

  • Ponto de ajuste: é o valor desejado que você deseja que seu sistema alcance.
  • Valor real: é o valor real detectado pelo sensor.
  • Erro: é a diferença entre o setpoint e o valor real (erro = Setpoint - Valor real).
  • Variável controlada: a partir de seu nome, a variável que você deseja controlar.
  • Kp: Constante proporcional.
  • Ki: Constante integral.
  • Kd: Constante derivada.

Em suma, o circuito do sistema de controle PID funciona da seguinte forma:

  • O usuário define o ponto de ajuste necessário para o sistema atingir.
  • O erro é calculado (erro = valor nominal - real).
  • O controlador P gera uma ação proporcional ao valor do erro. (o erro aumenta, a ação P também aumenta)
  • O controlador I irá integrar o erro ao longo do tempo, o que elimina o erro de estado estacionário do sistema, mas aumenta seu overshoot.
  • O controlador D é simplesmente a derivada de tempo para o erro. Em outras palavras, é a inclinação do erro. Ele faz uma ação proporcional à derivada do erro. Este controlador aumenta a estabilidade do sistema.
  • A saída do controlador será a soma dos três controladores. A saída do controlador será 0 se o erro for 0.

Uma ótima explicação sobre o controlador PID pode ser encontrada aqui.

Voltando ao carro de manutenção da pista, minha variável controlada foi a velocidade de aceleração (já que a direção tem apenas dois estados, à direita ou à esquerda). Um controlador PD é usado para este propósito, uma vez que a ação D aumenta muito a velocidade de estrangulamento se a alteração do erro for muito grande (ou seja, grande desvio) e diminui a velocidade do carro se essa alteração do erro se aproximar de 0. Eu segui os seguintes passos para implementar um PD controlador:

  • Defina o ponto de ajuste para 90 graus (sempre quero que o carro se mova em linha reta)
  • Calculou o ângulo de desvio do meio
  • O desvio fornece duas informações: quão grande é o erro (magnitude do desvio) e qual direção o motor de direção deve tomar (sinal de desvio). Se o desvio for positivo, o carro deve virar para a direita, caso contrário, ele deve virar para a esquerda.
  • Como o desvio é negativo ou positivo, uma variável de "erro" é definida e sempre igual ao valor absoluto do desvio.
  • O erro é multiplicado por um Kp constante.
  • O erro sofre diferenciação no tempo e é multiplicado por um Kd constante.
  • A velocidade dos motores é atualizada e o loop começa novamente.

O código a seguir é usado no loop principal para controlar a velocidade dos motores de estrangulamento:

velocidade = 10 # velocidade de operação em% PWM

# Variáveis a serem atualizadas a cada loop lastTime = 0 lastError = 0 # Constantes PD Kp = 0,4 Kd = Kp * 0,65 Enquanto True: now = time.time () # variável de tempo atual dt = now - lastTime desvio = ângulo_de_iranque - 90 # equivalente para a variável angle_to_mid_deg erro = abs (desvio) se desvio -5: # não orientar se houver um intervalo de erro de 10 graus desvio = 0 erro = 0 GPIO.output (in1, GPIO. LOW) GPIO.output (in2, GPIO. LOW) direction.stop () elif deviation> 5: # orientar para a direita se o desvio for positivo GPIO.output (in1, GPIO. LOW) GPIO.output (in2, GPIO. HIGH) direction.start (100) elif desvio < -5: # orientar para a esquerda se o desvio for negativo GPIO.output (in1, GPIO. HIGH) GPIO.output (in2, GPIO. LOW) direction.start (100) derivative = kd * (error - lastError) / dt proporcional = kp * erro PD = int (velocidade + derivada + proporcional) spd = abs (PD) se spd> 25: spd = 25 throttle.start (spd) lastError = erro lastTime = time.time ()

Se o erro for muito grande (o desvio do meio é alto), as ações proporcionais e derivativas são altas, resultando em alta velocidade de estrangulamento. Quando o erro se aproxima de 0 (o desvio do meio é baixo), a ação derivativa atua inversamente (a inclinação é negativa) e a velocidade de estrangulamento fica baixa para manter a estabilidade do sistema. O código completo está anexado abaixo.

Etapa 7: Resultados

Os vídeos acima mostram os resultados que obtive. Ele precisa de mais afinação e ajustes adicionais. Eu estava conectando o raspberry pi ao meu monitor LCD porque o vídeo transmitido pela minha rede tinha alta latência e era muito frustrante de trabalhar, por isso há fios conectados ao raspberry pi no vídeo. Usei placas de espuma para desenhar a pista.

Aguardo suas recomendações para melhorar este projeto! Como espero que este instructables tenha sido bom o suficiente para lhe fornecer algumas informações novas.

Recomendado: