Indice

Introduzione

I sistemi informatici basati sull’intelligenza artificiale si stanno diffondendo notevolmente e numerosi sviluppatori creano e addestrano modelli capaci di essere utilizzati da parte di programmatori terzi nelle proprie applicazioni. Per cui anche noi oggi sfrutteremo un modello preesistente per dare vita ad un sistema ad hoc per il riconoscimento facciale (utile per la prevenzione dalle intrusioni), con un’accuratezza pari al 99.38 %. L’applicativo in questione, scritto in Python, linguaggio con ampia disponibilità di libreria, si interfaccerà con uno scritto in C++ e destinato ad Arduino che comanderà l’accensione di un led verde, nel caso in cui vi sia la corrispondenza tra volto memorizzato e rilevato tramite web cam, oppure, in caso contrario, quella di un led rosso con annessa emissione di suono (tramite buzzer).

Prerequisiti e Componenti

L’installazione dei prerequisiti è uno degli aspetti fondamentali del progetto affinché si possa utilizzare il modello basato sull’AI, senza eccezioni a runtime e senza errori durante la fase di building delle dipendenze. Si precisa, inoltre, fondamentale l’ordine di esecuzione delle seguenti operazioni:

  1. Installazione dell’ultima versione di Python (https://www.python.org/downloads)
  2. Download di “CMake” (https://cmake.org/download) e conseguenziale installazione assicurandosi di avere la spunta “Add CMake to the PATH environment variable”.
  1. Recarsi nella console (terminale) e digitare a cascata i seguenti comandi:
    pip install dlib
    pip install face_recognition
    pip install numpy==1.26.4 (L’ultima versione 2.0.0 non è ancora compatibile con l’ultima versione di dlib)
    pip install pyserial
    pip install opencv-python
    pip install setuptools
  2. Installazione dell’ultima versione di Arduino IDE (https://www.arduino.cc/en/software)

In seguito, si provvede a dotarsi dei seguenti componenti elettronici:

  • Arduino (UNO, Nano…)
  • Breadboard
  • Cavi Jumper
  • Buzzer
  • Led Rosso
  • Led Verde
  • 2 Resistenze da 220 Ω (Ohm)

Sarà, infine, necessario collegare al pc su cui sarà in esecuzione il programma Python una Web Cam, verificando che sia correttamente rilevata dal sistema operativo della macchina.

Setup Arduino

Hardware

Per comprendere in maniera immediata il corretto utilizzo dei componenti sopra elencati, si riporta, di seguito, lo schema elettrico di riferimento del sistema elettronico antintrusione.

Software

Per comprendere appieno il seguente codice è necessario avere una conoscenza base del linguaggio C.

enum {
  GREEN = 2,
  RED,
  BUZZER
};

void setup() {
  Serial.begin(115200);
  pinMode(GREEN, OUTPUT);
  pinMode(RED, OUTPUT);
  pinMode(BUZZER, OUTPUT);
}

void loop() {
if (Serial.available() > 0) {
    bool isRecognized = Serial.readString().toInt(); // Leggi l'intero inviato dalla porta seriale

    if (isRecognized) {
      digitalWrite(GREEN, HIGH);
      delay(2000);
      digitalWrite(GREEN, LOW);
    }
    else {
      digitalWrite(RED, HIGH);
      tone(BUZZER, 1000);
      delay(2000);
      noTone(BUZZER);
      digitalWrite(RED, LOW);
    }


    // Attendere un po' per assicurarsi che tutti i byte siano arrivati
    delay(100);
  }
}

Analizziamolo nel dettaglio. Innanzitutto, impostiamo il numero dei pin digitali che utilizzeremo per comandare l’accensione e lo spegnimento dei led e l’emissione del suono, tramite il buzzer. Successivamente, in fase di setup, inizializziamo la connessione seriale scegliendo la baud rate, cioè il numero di bit massimi trasferibili in un secondo. Si precisa che tale connessione consentirà ad Arduino di comunicare con il programma Python per la ricezione dei dati da quest’ultimo. Inoltre, affinché ciò avvenga è necessario che Arduino sia connesso tramite porta USB alla macchina di sviluppo o comunque quella su cui sarà in esecuzione il programma Python. Un’altra soluzione, per consentire lo scambio dati tra i due programmi, consiste nello sfruttare una rete locale: in tal caso Arduino dovrà essere dotato di una scheda di rete integrata o esterna (shield Ethernet e/o Wi-Fi). Il prossimo passo è impostare la modalità dei pin digitali, cioè, indicare se li si utilizza come input o output. La prossima funzione contiene il codice che realmente eseguirà quanto progettato: in primis si verifica la presenza di dati in arrivo dal programma Python, tramite la porta seriale, diretti ad Arduino, i quali verranno letti e castati (convertiti da tipo “A” a tipo “B”) a Bool. Se il valore è true, allora si accende il led verde per due secondi e poi lo si spegne (ciò indica che il programma Python ha trovato corrispondenza tra il volto rilevato e quello memorizzato nel file immagine), altrimenti si accende il rosso e si emette un suono con frequenza pari a 1000 hz per altrettanti secondi (in tal caso il programma Python non ha trovato corrispondenza tra i volti).

Programma Python

In quest’ultima fase è necessario comprendere come opera a livello concettuale la libreria per il rilevamento dei volti e successivo loro confronto, svincolandosi così, almeno in prima battuta, dal formalismo richiesto dal codice. Vi è da sottolineare, innanzitutto, di selezionare le foto dei soggetti autorizzati all’accesso. Tali immagini dovranno essere in formato JPEG o PNG. Successivamente, per ciascuna di queste immagini, si deve verificare la presenza di un volto ed eseguirne la relativa codifica: si trasforma l’immagine stessa in un array numpy costituito da valori numerici che consentono di rappresentare coerentemente il volto da un punto di vista matematico. Le operazioni sopra menzionate vengono eseguite anche per il volto rilevato in tempo reale attraverso la web cam. In seguito, si confrontano i due volti mediante un’apposita funzione della libreria che mette in relazione  i due vettori numpy (quello rappresentante il volto rilevato in tempo reale e quello presente nell’immagine precaricata) determinando la cosiddetta distanza. La distanza è un valore, oscillante tra 0.0 e 1.0, che indica nella concretezza la somiglianza tra i due volti rilevati e viene determinato mediante la distanza euclidea. Per chi non la conoscesse è la misura della distanza tra due punti, note le coordinate. In definitiva maggiore è questo valore, minore è la somiglianza tra i volti e viceversa. A causa delle impostazioni predefinite della libreria un valore minore o uguale a 0.6, in merito alla distanza, suggerisce la congruità dei due volti confrontati, cioè, si tratta della stessa persona. Tale setting può essere ovviamente modificato. Avendo ora una panoramica generale è possibile presentare il codice nel dettagliando.

import face_recognition
import cv2
import numpy as np
import serial
import time

# Colori primari codifica BGR (opencv)
RED = (0, 0, 255)
BLUE = (255, 0, 0)
GREEN = (0, 255, 0)

# Carica l'immagine del soggetto autorizzato e la codifica
secure_image = face_recognition.load_image_file("Recognize/dg.jpg")
secure_encoding = face_recognition.face_encodings(secure_image)[0]

if len(secure_encoding) == 0:
    exit
    
secure_encoding_np = np.array(secure_encoding)

# Inizializza la connessione Arduino
arduino = serial.Serial(port='COM3', baudrate=115200, timeout=.1)
time.sleep(2)  # Attendi 2 secondi per stabilire la connessione

# Apre la webcam
cap = cv2.VideoCapture(0)

while cap.isOpened():
    success, frame = cap.read()
    if not success:
        break
    
    # Trova i volti
    actual_encoding = face_recognition.face_encodings(frame)
    
    # Verifica presenza volti
    if len(actual_encoding) == 0:
        message = "Volti non rilevati!"
    else:
        # Converti le liste in array numpy
        actual_encoding_np = np.array(actual_encoding)
        
        # Trova le coordinate dei volti e i punti di riferimento nell'immagine del frame
        face_xy = face_recognition.face_locations(frame)[0]
        face_landmarks = face_recognition.face_landmarks(frame)[0] if len(face_recognition.face_landmarks(frame)) > 0 else []

        # Confronto volti
        face_distance = face_recognition.face_distance(secure_encoding_np, actual_encoding_np)
        match = face_distance[0] <= 0.6
        message = f"Match: {"Accesso Consentito!" if match else "Accesso Negato!"}"
        square_color = GREEN if match else RED
    
        # Invia i dati arduino
        arduino.write(bytes(str(int(match)), 'utf-8'))
    
    # Visualizza il messaggio di stato
    cv2.putText(frame, message, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, BLUE, 2, cv2.LINE_AA) 
    
    try:
        # Disegna cerchio per ciascun punto di riferimento del volto e li unisce con una polilinea chiusa
        if len(face_landmarks) > 0:
            for feature, points in face_landmarks.items():
                for point in points:
                    cv2.circle(frame, point, 2, RED, -1)
                np_points = np.array(points)  
                cv2.polylines(frame, [np_points], isClosed=True, color=RED)
                
        # Disegna un quadrato intorno al volto rilevato dalla videocamera
        if len(face_xy) > 0:
            top, right, bottom, left = face_xy
            cv2.rectangle(frame, (left, top), (right, bottom), square_color, 2) 
    except NameError: 
        print("Face not detected")
        
    # Mostra l'immagine elaborata
    cv2.imshow('Riconoscimento Facciale', frame)

    # Interrompi il ciclo se viene premuto Esc
    if cv2.waitKey(5) & 0xFF == 27:
        break

# Rilascia le risorse
cap.release()
cv2.destroyAllWindows()
arduino.close() 

Oltre alle necessarie importazioni si definiscono tre costanti, ciascuna delle quali memorizza la codifica dei tre colori primari dell’RGB. In realtà, osservando attentamente, è facilmente comprensibile che ci si trova dinanzi ad una codifica BGR nella quale per l’appunto si ha l’inversione dei numeri corrispondenti alle intensità dei colori Red e Blue. Tale codifica è quella adottata dalla libreria “opencv” per la gestione della GUI e delle relative immagini mostrate all’utente, tramite una finestra applicativa. In seguito, si carica e codifica l’immagine del soggetto autorizzato e non ritenuto quindi un intruso. Il prossimo passo è inizializzare la connessione seriale con Arduino, indicando la porta utilizzata e soprattutto la baud rate che deve essere uguale a quella impostata precedentemente nell’istruzione: Serial.begin(115200). In seguito, si preleva il riferimento allo stream di input della Web Cam, la si rende, cioè, disponibile in fase di lettura per il programma Python, specificando qual è quella di interesse (in tal caso “0” indica la prima rilevata dal sistema operativo del PC su cui verrà eseguito lo script Python). Il while impone l’iterazione delle istruzioni sino a quando la Web Cam risulta aperta: si preleva il frame attuale, si ricercano volti al suo interno e in caso affermativo si procede alla sola codifica del primo rilevato, all’individuazione delle coordinate che delimitano il volto e dei punti di riferimento per ciascuna feature, cioè parte del volto (naso, bocca, occhi…). Successivamente, si effettua il confronto e sulla base di tale operazione si determina il messaggio da stampare in output e il colore del rettangolo che delimiterà il volto nella GUI. Si trasmette il risultato ad Arduino tramite la seriale. In seguito, per ciascuna feature, si disegnano dei cerchi per tutti i punti di riferimento presi in esame dalla libreria e li si uniscono tra loro sfruttando una polilinea chiusa. Si procede a disegnare il rettangolo di cui precedentemente si è selezionato il colore e si mostra la finestra applicativa a video. Infine, si rilasciano le risorse associata all’applicazione quando viene chiusa con l’uso di “Esc” da parte dell’utente.

Si precisa che questo codice sorgente è stato realizzato per analizzare una singola foto precaricata, dotata di un unico volto. Nel caso in cui ci siano più volti il primo rilevato sarà oggetto del confronto. Stesso discorso vale per il rilevamento dei volti del frame, ottenuto tramite la Web Cam.