Saltar al contenido principal

1 - Finch Robot Machine Learning Model

Objetivo

En este laboratorio reforzaremos la integración de hardware para la ciencia de datos, ya has trabajado con android y ahora podrás trabajar con un el robot mantarraya (Finch).

A través de varios pasos construirás un modelo de machine learning como el algoritmo del vecino más cercano (nearest-neighbor).

  • Entrena tu modelo con dibujos muestra.
  • Cuantifica la diferencia entre dibujos.
  • Predice la clase de un nuevo boceto encontrando cuál de las muestras está más cerca de él, lo que se conoce como su vecino más cercano.
  • Usa tu predicción para controlar tu robot Finch.

Pre-requisitos

  • Python 3.x
  • Tkinter (para la interfaz gráfica)
  • NumPy (para manipulación de datos numéricos)

Instrucciones

Sigue los pasos descritos en la siguiente práctica, si tienes algún problema no olvides que tus profesores están para apoyarte.

Laboratorio

Paso 1: Configuración del Entorno

Instala Tkinter y NumPy (si no están ya instalados):

pip install tk
pip install numpy

Paso 2: Configuración inicial

Crea un archivo Python llamado sketch_recognition_app.py e importa los módulos:

import tkinter as tk
import numpy as np

tkinter es el módulo para crear interfaces gráficas de usuario (GUI).

numpy se utiliza para operaciones numéricas, como el manejo de arrays y cálculos de distancias.

Agrega las variables globales a utilizar

line_id = None
line_points = np.empty((0, 2))
line_data = []
arrays_of_points = {}
line_options = {}

Explicación:

line_id mantiene el identificador de la línea actual en el lienzo.

line_points es un array que almacena los puntos de la línea.

line_data guarda las coordenadas de la línea en forma de lista.

arrays_of_points es un diccionario para almacenar muestras de líneas.

line_options permite ajustar opciones de dibujo para la línea (e.g., color, grosor).

Paso 3: Configura la interfaz gráfica

Crea una ventana principal y un lienzo para dibujar.

root = tk.Tk()

canvas = tk.Canvas(root, width=600, height=400)
canvas.pack()

Asocia eventos de ratón al lienzo para manejar el inicio del dibujo, el dibujo en sí y la finalización.

canvas.bind('<Button-1>', set_start)
canvas.bind('<B1-Motion>', draw_line)
canvas.bind('<ButtonRelease-1>', end_line)

Añade botones para limpiar el lienzo y guardar muestras.

frame = tk.Frame(root)
frame.pack(side=tk.BOTTOM)

tk.Button(frame, text="Clear", font="comicsans 12 bold",
command=clearCanvas).pack(side=tk.RIGHT, padx=6, pady=6)

tk.Button(frame, text="Save Sample Up", font="comicsans 12 bold",
command=saveSampleArrowUp).pack(side=tk.BOTTOM, padx=6, pady=6)

tk.Button(frame, text="Save Sample Down", font="comicsans 12 bold",
command=saveSampleArrowDown).pack(side=tk.BOTTOM, padx=6, pady=6)

Inicia el bucle principal de la aplicación.

root.mainloop()

Si intentaste correr tu código te darás cuenta que no compila define los métodos que se utilizan en los botones, más adelante les daremos su uso.

Por lo pronto, una vez que tu código compile deberás ver lo siguiente:

lab_8

Paso 4: Funciones para dibujar una línea

Crea la función set_start

Esta función se llama cuando se hace clic en el lienzo. Inicia el proceso de dibujo al guardar la posición inicial de la línea y limpiar el lienzo.

def set_start(event):
global line_points
clearCanvas()
line_data.extend((event.x, event.y))
line_points = np.empty((0, 2))
line_points = np.append(line_points, [[event.x, event.y]], axis=0)

Crea la función dibujar línea

Esta función se llama cuando se arrastra el ratón. Actualiza la línea en el lienzo con las nuevas coordenadas.

def draw_line(event):
global line_id, line_points
line_data.extend((event.x, event.y))
line_points = np.append(line_points, [[event.x, event.y]], axis=0)

if line_id is not None:
canvas.delete(line_id)
line_id = canvas.create_line(line_data, **line_options)

Hasta ahora dibujamos una línea pero necesitamos obtener puntos equidistantes para ayudar a la reconstrucción al algoritmo del vecino más cercano.

Agrega la siguiente función que calcula la distancia euclidiana entre dos puntos.

def calculate_distance(p1, p2):
return np.sqrt((p2[0] - p1[0])**2 + (p2[1] - p1[1])**2)

La siguiente función toma una lista de puntos y genera una cantidad especificada de puntos equidistantes a lo largo de la línea:

def generate_evenly_spaced_points(points, num_points):
total_length = sum(calculate_distance(points[i], points[i+1]) for i in range(len(points) - 1))
segment_length = total_length / (num_points - 1)

result_points = [points[0]]
accumulated_length = 0

for i in range(1, len(points)):
p1 = points[i - 1]
p2 = points[i]
current_segment_length = calculate_distance(p1, p2)

while accumulated_length + current_segment_length >= segment_length:
remaining_length = segment_length - accumulated_length
t = remaining_length / current_segment_length
new_point = (p1[0] + t * (p2[0] - p1[0]), p1[1] + t * (p2[1] - p1[1]))
result_points.append(new_point)
accumulated_length = 0
p1 = new_point
current_segment_length = calculate_distance(p1, p2)

accumulated_length += current_segment_length

if len(result_points) < num_points:
result_points.append(points[-1])

return result_points

Crea la función para finalizar la línea

Finaliza la línea y calcula puntos equidistantes para la visualización.

def end_line(event=None):
global line_id, line_points

evenly_spaced_points = generate_evenly_spaced_points(line_points, 64)

count = 1
for point in evenly_spaced_points:
print(count, " - (", float(point[0]), " , ", float(point[1]), ")")
canvas.create_oval(point[0], point[1], point[0], point[1], width = 3, fill = 'white')
count += 1

line_points = evenly_spaced_points

Excelente ahora debes ver lo siguiente: lab_8

Antes de pasar a la siguiente sección vamos usar nuestro botón de clear que limpie nuestro lienzo cada vez que iniciamos un nuevo dibujo.

Tu función de clearCanvas debe quedar de la siguiente manera, la cual, elimina todos los elementos del lienzo y reinicia las variables asociadas a las líneas y puntos:

def clearCanvas(e=""):
global created_element_info, canvas, created, new, line_id, line_points
canvas.delete("all")
created_element_info = []
created = []
new = []

array_of_points = {}
line_points = line_points[:0]
line_data.clear()
line_id = None

Paso 5: Cuantifica la diferencia entre dibujos.

Ya que tenemos nuestro lienzo y podemos dibujar en el, procederemos a usar nuestros botones de Save Sample Down y Save Sample Up

Las funciones deben quedar de la siguiente manera, las cuales, guardan la línea actual en el diccionario arrays_of_points bajo las claves 'up' y 'down', respectivamente, y luego limpian el lienzo:

def saveSampleArrowUp(e=""):
arrays_of_points['up'] = line_points
clearCanvas()

def saveSampleArrowDown(e=""):
arrays_of_points['down'] = line_points
clearCanvas()

Perfecto, para continuar lo único que necesitamos es una función que calcula la diferencia total entre dos conjuntos de puntos, sumando las distancias punto a punto.

def differenceBetween(list1, list2):
totalDifference = 0

for i in range(0, len(arrays_of_points["up"])):
totalDifference += calculate_distance(list1[i], list2[i])

return totalDifference

Paso 6: Predice la clase de un nuevo boceto

Excelente para seguir avanzando vamos a predecir nuestro nuevo lienzo por lo que necesitamos una función que compara la línea actual con las muestras guardadas y determina cuál es más similar

def getPrediction():
match_text = "up"
match = arrays_of_points["up"]
minDifference = differenceBetween(line_points, match)

# for each item in samples
difference = differenceBetween(line_points, arrays_of_points["down"])

if difference < minDifference:
match = arrays_of_points["down"]
match_text = "down"
minDifference = difference

print("match")
print(match_text)

Para probarlo actualiza tu método end_line y agrega el siguiente código el final del método

if ('up' in arrays_of_points and bool(arrays_of_points['up'])) and ('down' in arrays_of_points and bool(arrays_of_points['down'])):
getPrediction()

Excelente ahora compruebalo tu mismo! Cuando dibujes algo parecido a uno de los samples guardados.

Dibuja una flecha hacia arriba de la siguiente manera y guardala en Save Sample Up lab_8

Dibuja una flecha hacia abajo de la siguiente manera y guardala en Save Sample Down lab_8

Por último dibuja flechas parecidas y ver el resultado de match_text que debe ser up | down dependiendo de quien sea su vecino más cercano.

Paso 7: Controlar tu robot Finch.

Lo primero es que debemos configurar nuestro código para usar el Finch, descarga esta [librería] y ponla en la misma ubicación donde se encuentra tu archivo de sketch recognition.

  1. Ahora en tu script de sketch recognition importa la librería. Verifica que tu archivo de la librería descargado se llame BirdBrain.py
from BirdBrain import Finch
  1. En tus variables globales, agrega la instancia de tu robot
myFinch = Finch()
  1. Por último modifica tu código de getPrediction() y agrega al final el siguiente código
# 4. Mueve tu finch
if match_text == 'up':
# Direction, cm, velocity
myFinch.setMove('F', 10, 50)
else:
myFinch.setMove('B', 10, 50)

Para comenzar a utilizar el robot Finch, tenemos que descargar un software para conectarlo vía Bluetooth

Descarga blue bird connector que está disponible para MacOS y Windows

Enciende el finch de la parte de abajo se encuentra un botón negro en medio de las ruedas, manténlo presionado hasta que enciendan las luces.

Ahora ejecuta el software y verás que se encuentra buscando el finch.

Da clic en el finch que encontro y corre tu proyecto de python para ver avanzando a tu finch.

Paso 8: Reto

¿El código se puede mejorar no crees?

  1. Mejora el código y la interfaz para que puede recibir n samples
  2. Agrega las flechas de derecha e izquierda y que el finch gire y avance en la dirección correspondiente
  3. Crea al menos dos samples diferentes que le diga al finch que genere una rutina como un baile o el uso de los sensores.

Aquí puedes consultar los Finch Class Methods