Agora, cursos muito populares sobre a criação de pilotos automáticos para carros. Esse
nano-grau da Udacity é provavelmente a opção mais conhecida.
Muitas pessoas aprendem com isso e postam suas decisões. Eu também não podia passar e me deixar levar.
A diferença é que o curso envolve o desenvolvimento de um algoritmo com base nos dados fornecidos, e eu fiz tudo pelo
meu robô .
A primeira tarefa que os alunos do curso enfrentam ao estudar a visão por computador é seguir a linha na estrada. Muitos artigos foram escritos sobre este tópico, eis alguns dos mais detalhados:
Todos eles são bastante simples e o esquema de trabalho é reduzido a vários pontos:
Colei fita branca no chão e comecei a trabalhar.

Nos trabalhos mencionados acima, sua tarefa era encontrar a linha amarela, para que funcionassem com as cores HLS e HSV. Como minha linha era apenas branca, decidi não me incomodar com isso e me limitar a um filtro preto e branco.
Geometria
Os problemas com a geometria começaram imediatamente. Para os alunos das figuras, a faixa com a seta entra no horizonte. E ainda, muitas linhas são detectadas nele, que os autores tiveram que combinar. No entanto, suas linhas eram bem direcionadas e não havia detritos nas fotos.
Eu tenho uma imagem completamente diferente. A geometria da fita isolante estava longe de ser reta. O brilho no chão gerou ruído.
Depois de aplicar o Canny, foi o que aconteceu:

E as linhas de Hough eram assim:

Fortalecendo os critérios, foi possível excluir o lixo, mas quase todas as linhas encontradas na faixa desapareceram. Confiar em segmentos tão minúsculos seria tolice.

Em geral, os resultados foram extremamente instáveis, e me ocorreu tentar uma abordagem diferente.
Em vez de linhas, comecei a procurar contornos. Tendo assumido que o maior circuito é a fita isolante, conseguimos nos livrar do lixo. (Depois, descobriu-se que o grande rodapé branco ocupava mais espaço na moldura do que a fita isolante. Eu tive que cobri-lo com uma almofada de sofá).
Se pegarmos o retângulo mínimo que delimita o contorno, a linha longitudinal média é muito adequada para o papel do vetor de movimento.

A luz
O segundo problema foi a iluminação. Coloquei um lado da pista com sucesso na sombra do sofá e era completamente impossível processar fotos de toda a faixa com as mesmas configurações. Como resultado, tive que implementar o corte dinâmico em um filtro preto e branco. O algoritmo é o seguinte: se após a aplicação do filtro na imagem houver muito branco (mais de 10%), o limite deverá ser aumentado. Se muito pouco (menos de 3%) - omita. A prática mostrou que, em média, para 3-4 iterações, é possível encontrar o ponto de corte ideal.
Os números mágicos são colocados em uma configuração separada (veja abaixo), você pode brincar com eles em busca do melhor.
def balance_pic(image): global T ret = None direction = 0 for i in range(0, tconf.th_iterations): rc, gray = cv.threshold(image, T, 255, 0) crop = Roi.crop_roi(gray) nwh = cv.countNonZero(crop) perc = int(100 * nwh / Roi.get_area()) logging.debug(("balance attempt", i, T, perc)) if perc > tconf.white_max: if T > tconf.threshold_max: break if direction == -1: ret = crop break T += 10 direction = 1 elif perc < tconf.white_min: if T < tconf.threshold_min: break if direction == 1: ret = crop break T -= 10 direction = -1 else: ret = crop break return ret
Tendo ajustado a visão da máquina, foi possível avançar para o movimento real. O algoritmo foi o seguinte:
- 0,5 segundos de carro em linha reta
- tire uma foto
- encontre o vetor
- se o início do vetor for deslocado em relação ao centro da imagem, taxiaremos levemente na direção certa
- se o ângulo de inclinação do vetor se desviar da vertical mais do que o necessário - nós dirigimos na direção certa
- se de repente aconteceu que a faixa desapareceu do quadro, assumimos que fizemos uma curva e começamos a girar na direção da última direção ou inclinação do vetor na etapa anterior
Versão
abreviada do código (Full - on
Github ):
def check_shift_turn(angle, shift): turn_state = 0 if angle < tconf.turn_angle or angle > 180 - tconf.turn_angle: turn_state = np.sign(90 - angle) shift_state = 0 if abs(shift) > tconf.shift_max: shift_state = np.sign(shift) return turn_state, shift_state def get_turn(turn_state, shift_state): turn_dir = 0 turn_val = 0 if shift_state != 0: turn_dir = shift_state turn_val = tconf.shift_step if shift_state != turn_state else tconf.turn_step elif turn_state != 0: turn_dir = turn_state turn_val = tconf.turn_step return turn_dir, turn_val def follow(iterations): tanq.set_motors("ff") try: last_turn = 0 last_angle = 0 for i in range(0, iterations): a, shift = get_vector() if a is None: if last_turn != 0: a, shift = find_line(last_turn) if a is None: break elif last_angle != 0: logging.debug(("Looking for line by angle", last_angle)) turn(np.sign(90 - last_angle), tconf.turn_step) continue else: break turn_state, shift_state = check_shift_turn(a, shift) turn_dir, turn_val = get_turn(turn_state, shift_state) if turn_dir != 0: turn(turn_dir, turn_val) last_turn = turn_dir else: time.sleep(tconf.straight_run) last_turn = 0 last_angle = a finally: tanq.set_motors("ss")
Resultados
Desigualmente, mas com confiança, o tanque está rastejando ao longo da trajetória:

E aqui está um GIF dos gráficos de depuração:

Configurações de algoritmo
Código do
Github .