Nous allons voir comment créer une application React Native permettant la communication Bluetooth entre un appareil Android et un ESP32. Nous utilisons React Native pour développer un terminal Bluetooth sur Android permettant la communication avec un NodeMCU ESP32. Le NodeMCU permet de tester notre application avec un objet connecté mais l’application peut fonctionner avec n’importe quel appareil Bluetooth.
Matériel
Un ordinateur avec installation de React Native et Node.js
Un appareil Android avec Bluetooth
Un câble USB pour relier l’ordinateur à l’appareil
#include "BluetoothSerial.h"
#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
#error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
#endif
BluetoothSerial SerialBT;
void callback(esp_spp_cb_event_t event, esp_spp_cb_param_t *param) {
if (event == ESP_SPP_SRV_OPEN_EVT) {
Serial.println("Client Connected");
}
if (event == ESP_SPP_CLOSE_EVT ) {
Serial.println("Client disconnected");
//SerialBT.flush();
//SerialBT.disconnect();
//SerialBT.end();
//SerialBT.begin("ESP32BT");
ESP.restart(); // needed to be able to reconnect
}
}
void setup() {
Serial.begin(115200);
SerialBT.register_callback(callback);
SerialBT.begin("ESP32BT"); //Bluetooth device name
Serial.println("The device started, now you can pair it with bluetooth!");
}
String msg = "";
void loop() {
/*if (Serial.available()) {
SerialBT.write(Serial.read());
}*/
readSerialPort();
//Send data to slave
if (msg != "") {
Serial.println(msg);
SerialBT.println(msg);
msg = "";
}
if (SerialBT.available()) {
Serial.write(SerialBT.read());
}
delay(20);
}
void readSerialPort() {
while (Serial.available()) {
delay(10);
if (Serial.available() > 0) {
char c = Serial.read(); //gets one byte from serial buffer
msg += c; //add to String
}
}
Serial.flush(); //clean buffer
}
Nous rajoutons la fonction callback à la gestion du Bluetooth pour détecter la déconnexion et redémarrer l’ESP32
SerialBT.register_callback(callback);
N.B.: le reset de l’ESP32 est nécessaire à la reconnexion du Bluetooth car, visiblement, le BluetoothSocket ne se ferme pas à la deconnexion
Application React Native pour la gestion du Bluetooth
Pour gérer la communciation Bluetooth (classic) sur l’appareil Android, nous utilisons la librairie react-native-bluetooth-classic
npm install react-native-bluetooth-classic --save
Pour mettre en place le projet de l’application, suivez le tutoriel précédent.
Dans le fichier App.tsx, pour utiliser la bibliothèque nous l’importons à l’aide de la commande
import RNBluetoothClassic, {BluetoothDevice,} from 'react-native-bluetooth-classic';
Nous créons un composant fonctionnel qui contiendra les éléments nous permettant de gérer la communication Bluetooth
Dans le fichier App.tsx, nous créons la fonction requestBluetoothPermission()
async function requestBluetoothPermission(){
try {
const grantedScan = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
{
title: 'Bluetooth Scan Permission',
message: 'This app needs Bluetooth Scan permission to discover devices.',
buttonPositive: 'OK',
buttonNegative: 'Cancel',
}
);
const grantedConnect = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
{
title: 'Bluetooth Connect Permission',
message: 'This app needs Bluetooth Connect permission to connect to devices.',
buttonPositive: 'OK',
buttonNegative: 'Cancel',
}
);
const grantedLocation = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
{
title: 'Fine Location Permission',
message: 'This app needs to know location of device.',
buttonPositive: 'OK',
buttonNegative: 'Cancel',
}
);
if (
grantedScan === PermissionsAndroid.RESULTS.GRANTED &&
grantedConnect === PermissionsAndroid.RESULTS.GRANTED &&
grantedLocation === PermissionsAndroid.RESULTS.GRANTED
) {
console.log('Bluetooth permissions granted');
// Vous pouvez maintenant commencer la découverte et la connexion Bluetooth ici.
} else {
console.log('Bluetooth permissions denied');
}
} catch (err) {
console.warn(err);
}
}
Fonction de gestion du Bluetooth
Les fonctions permettant la gestion du Bluetooth sont les suivantes:
découvrir des appareils bluetooth startDeviceDiscovery() (j’utilise les appareils apairés)
se connecter à l’appareil connectToDevice()
se déconnecter disconnect()
envoyer des messages sendMessage()
lire les messages provenant de la communication readData()
N.B.: la documentation de la librairie mentionne l’utilisation d’un listener onDataReceived que je n’ai pas réussi à utiliser. J’introduis donc la fonction readData et un Interval pour récupérer les données.
Comme l’appairage n’est pas gérer par l’application, il faut appairer l’ESP32 avant l’utilisation de l’application. Une fois le code chargé sur l’ESP32, vous pouvez lancer l’application sur le téléphone à l’aide de la commande
Nous allons voir dans ce tutoriel comment obtenir des streams vidéo synchronisés avec Python et OpenCV. Une des problématiques du streaming vidéo est d’émettre et d’acquérir des signaux vidéo de qualité et si possible avec le moins de délai possible. La capacité de synchroniser des flux vidéo est de pouvoir traité leurs données simultanément, comme pour la reconnaissance d’objets.
Pour ce tutoriel, j’utilise deux Orange Pi Zero, qui génère les streams vidéo à partir de caméras USB (ArduCam), connectés sur le réseau de l’ordinateur via un switch Ethernet.
Émission des streams
Pour créer le stream à partir du flux vidéo de la caméra, nous utilisons FFMPEG. Nous utilisons le protocole UDP dans lequel nous spécifions l’adresse IP de l’ordinateur et le port sur lequel se trouve le streaming.
Dans le tutoriel, nous utiliserons le filtre drawtext qui permet de rajouter du texte sur la vidéo. Cela nous permet d’afficher l’heure et d’observer facilement le délai.
Enfin nous affichons l’heure locale de l’ordinateur pour comparaisons
Hori = cv2.putText(Hori, date_time,(10, 100),font, 1,(210, 155, 155), 4, cv2.LINE_4)
Voici le code complet pour la capture des streams vidéo non-synchronisés
N.B.: Ce code fonctionne une fois que les commandes ffmpeg ont été lancé sur chaque machine
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import numpy as np
import cv2
import datetime
#<add code to run ffmpeg command via ssh>
cap0 = cv2.VideoCapture("udp://127.0.0.1:8553")
cap1 = cv2.VideoCapture("udp://127.0.0.1:8554")
while cap0.isOpened() and cap1.isOpened():
# Capture frame-by-frame
ret0, frame0 = cap0.read()
ret1, frame1 = cap1.read()
# Get current date and time
now=datetime.datetime.now()
date_time = now.strftime("%H:%M:%S")
font = cv2.FONT_HERSHEY_SIMPLEX
# write the date time in the video frame
#frame0 = cv2.putText(frame0, date_time,(10, 100),font, 1,(210, 155, 155), 4, cv2.LINE_4)
#frame1 = cv2.putText(frame1, date_time,(10, 100),font, 1,(210, 155, 155), 4, cv2.LINE_4)
#if (ret0):
# # Display the resulting frame
# cv2.imshow('Cam 0', frame0)
#if (ret1):
# # Display the resulting frame
# cv2.imshow('Cam 1', frame1)
if (ret0 and ret1):
frame0 =cv2.resize(frame0, (640,480))
frame1 =cv2.resize(frame1, (640,480))
Hori = np.concatenate((frame0, frame1), axis=1)
Hori = cv2.putText(Hori, date_time,(10, 100),font, 1,(210, 155, 155), 4, cv2.LINE_4)
cv2.imshow('Cam 0&1', Hori)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
#release captures
cap0.release()
cap1.release()
cv2.destroyAllWindows()
Nous observons un retard d’une seconde entre les deux flux vidéo. Ceci n’est pas acceptable si nous souhaitons traité les images de manière synchronisée.
Capture de streams avec Multithreading
La solution la plus simple est de dédier des threads à la capture des images. Pour cela nous utilisons le paquet threading. Nous allons créer une classe VideoStream qui va gérer la lecture du stream dans son propre thread
Nous pouvons ensuite instancier nos deux objets cap0 et cap1
cap0 = VideoStream("udp://127.0.0.1:8553").start()
cap1 = VideoStream("udp://127.0.0.1:8554").start()
while cap0.isOpened() and cap1.isOpened():
ret0, frame0 = cap0.read()
ret1, frame1 = cap1.read()
# Get current date and time
#date_time = str(datetime.datetime.now())
now=datetime.datetime.now()
date_time = now.strftime("%H:%M:%S")
font = cv2.FONT_HERSHEY_SIMPLEX
if ret0 and ret1:
frame0 =cv2.resize(frame0, (640,480))
frame1 =cv2.resize(frame1, (640,480))
Hori = np.concatenate((frame0, frame1), axis=1)
Hori = cv2.putText(Hori, date_time,(10, 100),font, 1,(210, 155, 155), 4, cv2.LINE_4)
cv2.imshow('Cam 0&1', Hori)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
#release capture
cap0.release()
cap1.release()
cv2.destroyAllWindows()
Les deux flux vidéos sont maintenant synchronisés et il est possible de les enregistrer dans un fichier vidéo ou de faire du traitement d’image sur les deux streams en même temps.
Code complet la réception de streams vidéo synchronisés
Le protocole SSH (Secure Socket Shell) est très utilisé pour se connecter à un serveur, ou une machine, distant connectée à un réseau. Il permet d’échanger des fichiers, de créer, modifier ou lancer des scripts sur des machines distantes.
SSH est généralement installé de base dans les distribution Linux ou Windows.
Description du protocole SSH
Le protocole SSH (Secure Shell) est un protocole de communication cryptographique utilisé pour établir des connexions sécurisées et confidentielles entre deux systèmes informatiques via un réseau. Son principe repose sur l’utilisation de méthodes de chiffrement asymétrique et symétrique pour protéger les données transitant entre les systèmes. L’authentification des utilisateurs se fait généralement à l’aide de paires de clés cryptographiques, comprenant une clé publique et une clé privée, ce qui garantit une vérification sécurisée de l’identité de l’utilisateur. L’utilité principale du protocole SSH réside dans son rôle de sécurisation des accès à distance aux serveurs, de transfert de fichiers sécurisés et de gestion à distance des systèmes, offrant une protection robuste contre les menaces potentielles, notamment les attaques de type « man-in-the-middle » et les interceptions de données sensibles.
Ouvrir une session SSH
Pour se connecter à un appareil
ssh <host>@<serveur>
exemple pour un raspberry pi
ssh pi@192.168.1.2
Une fois la connexion établie, le moniteur vous demandera le mot de passe pour accéder au terminal distant.
Si votre machine distante le permet, le tag -X permet d’afficher les fenêtres de votre machine
ssh -X <host>@<serveur>
Copier un fichier distant avec le protocole SSH
La commande à utiliser pour copier un fichier d’une machine distante à une autre est la commande scp
scp <source> <destination>
scp root@192.168.1.33:script.py C:\Users\ADMIN
Un mot de passe sera demander pour valider la copie.
Se connecter avec un mot de passe
Sur linux, il est possible d’installer et d’utiliser SSH
sudo apt-get install sshpass
sshpass -p {password} ssh {user}@{ipaddress}
Sur Windows, Putty peut se charger de la gestion du mot de passe
Installer PuTTy puis ajouter le dossier de l’exécutable dans la variable d’environnement Path (C:\Program Files\PuTTY)
putty -ssh "root@192.168.1.32" -pw root #pour ouvrir une connexion en directe
Pour envoyer des commandes en direct, nous utilsons plink
Les Technologies de l’Information est un domaine en constante évolution qui joue un rôle essentiel dans notre société moderne. Les cours d’informatique, regroupés sous le terme Technologies de l’Information, offrent une formation précieuse et ouvrent la voie à une multitude de carrières passionnantes.
La Formation en Informatique
Les cours d’informatique, proposent une formation technique destinée à équiper les étudiants des compétences essentielles pour naviguer dans le paysage technologique complexe d’aujourd’hui. Ces programmes éducatifs englobent un éventail diversifié de domaines, tels que la programmation informatique, l’administration de systèmes, la conception de bases de données, la sécurité informatique, la virtualisation, la gestion des réseaux, et l’analyse de données.
La Programmation
Au cœur de l’informatique se trouvent la maîtrise des langages de programmation tels que Java, Python, C++, JavaScript et Ruby. Ces langages servent de fondement pour le développement de logiciels et d’applications. Les étudiants acquièrent une compréhension profonde des structures de données, des algorithmes, et des méthodologies de développement, tout en étudiant des concepts avancés tels que la programmation orientée objet (POO) et la conception logicielle.
L’administration de système
Le volet dédié à l’administration de systèmes se penche sur la gestion des infrastructures informatiques. Les étudiants explorent les systèmes d’exploitation, tels que Linux et Windows. Ils apprennent à configurer des serveurs, à administrer des bases de données, et à assurer la disponibilité des services informatiques. Ils se familiarisent également avec la virtualisation, un élément clé de la gestion des ressources informatiques dans les environnements d’entreprise.
La Cybersécurité
La sécurité informatique est un domaine critique, où les étudiants découvrent comment protéger les systèmes et les réseaux contre les menaces potentielles. Ils explorent les concepts de pare-feu, d’authentification, de chiffrement, et de gestion des vulnérabilités pour renforcer la sécurité des données et des applications. Les formations en sécurité informatique abordent également les aspects de la gestion des incidents de sécurité, de la réponse aux cyberattaques, et de la conformité aux réglementations.
La gestion de réseaux
La gestion des réseaux est un pilier essentiel de la formation en informatique. Les étudiants apprennent à concevoir, à configurer, et à maintenir des réseaux informatiques, en abordant des technologies telles que le routage, la commutation, et les protocoles de communication. Ils étudient également les réseaux sans fil, les réseaux d’entreprise, et les architectures de réseau évolutives.
L’analyse de données
Enfin, l’analyse de données occupe une place de choix dans la formation en informatique. Les étudiants acquièrent des compétences en collecte, en traitement, et en analyse de données à l’aide d’outils et de langages tels que R, Python, et les bases de données NoSQL. Ils explorent des techniques d’apprentissage automatique (machine learning) et d’intelligence artificielle (IA) pour extraire des informations précieuses à partir de vastes ensembles de données.
En conclusion, les cours d’informatique forment les étudiants à devenir des acteurs essentiels dans l’ère numérique en constante évolution. Cette formation approfondie les prépare à des carrières stimulantes dans des rôles tels que développeur de logiciels, administrateur système, analyste en sécurité informatique, architecte cloud, ingénieur réseau, data scientist, consultant en technologie de l’information, et gestionnaire de projet informatique.
Métiers en Informatique
Les étudiants qui suivent des cours d’informatique ont accès à une grande variété de métiers. Voici quelques-unes des carrières les plus courantes dans le domaine des Technologies de l’Information :
Développeur de Logiciels : Les développeurs créent des applications, des sites web et des logiciels en utilisant différents langages de programmation. Ils sont responsables de la conception, du codage, du test et de la maintenance de ces programmes.
Administrateur Système : Les administrateurs système gèrent les réseaux informatiques, les serveurs et les bases de données d’une organisation. Ils veillent à ce que tous les systèmes fonctionnent de manière fluide et en toute sécurité.
Analyste en Sécurité Informatique : les analystes en sécurité informatique sont chargés de protéger les systèmes informatiques contre les attaques et les vulnérabilités.
Architecte Cloud : Les architectes cloud conçoivent et mettent en place des infrastructures cloud pour les entreprises. Ils sont responsables de la migration vers le cloud et de l’optimisation des ressources cloud.
Ingénieur en Réseau : Ils s’occupent de la conception, de la mise en place et de la maintenance des réseaux informatiques. Ils assurent une connectivité fiable au sein d’une organisation.
Data Scientist : Les data scientists analysent de vastes ensembles de données pour en extraire des informations utiles. Ils sont essentiels pour la prise de décision basée sur les données dans de nombreuses industries.
Consultant en Technologie de l’Information : Les consultants en IT travaillent généralement de manière indépendante ou pour des entreprises de conseil. Ils aident les clients à résoudre des problèmes informatiques complexes et à améliorer leur efficacité opérationnelle.
Gestionnaire de Projet Informatique : ces ingénieurs supervisent le développement de projets informatiques, veillant à ce qu’ils soient livrés à temps et dans le respect du budget.
Perspectives de Carrière et Demande Croissante
Les métiers de l’IT offrent de nombreuses opportunités de carrière passionnantes et bien rémunérées. Avec la numérisation croissante de tous les secteurs, la demande de professionnels de l’informatique devrait rester élevée. En outre, le domaine évolue rapidement, ce qui signifie que les professionnels de l’informatique doivent se tenir constamment à jour avec les dernières technologies et tendances.
Les cours d’informatique et les carrières qui en découlent sont au cœur de la révolution numérique. Que vous soyez passionné par la programmation, la sécurité informatique, l’analyse de données ou la gestion de projets, il existe une multitude de voies à explorer dans le domaine de l’Information Technology. Une formation solide en informatique peut ouvrir les portes à des opportunités professionnelles stimulantes et enrichissantes dans un monde de plus en plus connecté.
L’Auto-Apprentissage des Technologies de l’Information
Il est important de noter que de nombreuses personnes choisissent de se former en informatique de manière autodidacte. En effet, l’auto-formation est devenue plus accessible que jamais grâce à une multitude de ressources en lignes. Des plateformes telles que Coursera, Udemy et Codecademy ou des sites comme ce blog proposent des ressources et cours en ligne sur une variété de sujets informatiques, allant de la programmation à la sécurité informatique.
Les projets pratiques sont également un moyen efficace d’apprendre par soi-même. Par exemple, en se lançant dans la création d’un site web, en développant une application mobile ou en participant à des projets open source, les apprenants peuvent acquérir une expérience et développer leurs compétences. Les forums de programmation, comme StackOverflow, sont une ressource précieuse pour poser des questions et résoudre des problèmes rencontrés.
Le succès de l’auto-apprentissage dépend souvent de la discipline personnelle et de la capacité à rester motivé. Cependant, de nombreux professionnels de l’informatique ont débuté leur carrière de cette manière. Le fait de construire des projets personnels ou collaboratifs permet de se créer une expérience conséquente. Cette voie d’auto-formation peut être particulièrement adaptée à ceux qui cherchent à changer de carrière ou à acquérir des compétences spécifiques dans le domaine des technologies de l’information.
En conclusion, l’apprentissage des technologies de l’information n’est pas nécessairement limité aux salles de classe traditionnelles. Les ressources en ligne abondent de projets pratiques et des communautés en ligne (Stackoverflow, Github, etc.). Ce qui fait de l’auto-apprentissage une option viable pour ceux qui cherchent à acquérir des compétences informatiques. Que l’on opte pour une formation académique ou un auto-apprentissage, les opportunités dans le domaine de l’Information Technology restent prometteuses, avec une demande constante de professionnels qualifiés pour relever les défis technologiques du 21e siècle.
Pour améliorer les performances sur Raspberry Pi vous pouvez utiliser le langage C++ ainsi que des librairies optimisées afin d’accélérer la vitesse de calcul des modèles de détection d’objets. C’est ce que propose TensorFlow Lite.
Une bonne référence pour commencer sur le sujet est le site QEngineering.
Matériel
Raspberry Pi 4
Écran+souris+clavier
Carte SD avec OS Raspbian 64bits
Configuration
Pour de meilleures performances, il vous faudra installer la version 64bits de Raspberry Pi OS. Cette version est disponible dans le logiciel Raspberry Pi Imager dans le menu Raspberry Pi OS (others)
Installation de Code::Blocks
Code blocks IDE est un logiciel comme Thonny ou Geany qui vous permettra de compiler et de lancer des codes écrits en C/C++
Installation de TensorFlow Lite sur Raspberry Pi OS 64bits
Pour de meilleures performances sur Raspberry Pi, une solution est d’utiliser la version Lite de TensorFlow. Veuillez suivre la procédure décrite sur le site QEngineering pour installer TensorFlow Lite sur Raspberry Pi OS 64bits
N.B.: n’oubliez pas de redémarrer le Raspberry Pi après l’installation de tensorflow
On construit le modèle à partir du fichier tflite. Dans ce projet, nous utilisons MobileNet V1
std::unique_ptr<tflite::FlatBufferModel> model = tflite::FlatBufferModel::BuildFromFile("detect.tflite");
Puis on ouvre un flux vidéo (où un fichier vidéo ou une image)
VideoCapture cap(0);
On exécute ensuite le modèle sur chaque image
interpreter->Invoke();
Enfin nous traçons les résultats de la détections sur l’image et l’affichons
#include <stdio.h>
#include <opencv2/opencv.hpp>
#include <opencv2/dnn.hpp>
#include <opencv2/highgui.hpp>
#include <fstream>
#include <iostream>
#include <opencv2/core/ocl.hpp>
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/kernels/register.h"
#include "tensorflow/lite/string_util.h"
#include "tensorflow/lite/model.h"
#include <cmath>
using namespace cv;
using namespace std;
const size_t width = 300;
const size_t height = 300;
std::vector<std::string> Labels;
std::unique_ptr<tflite::Interpreter> interpreter;
static bool getFileContent(std::string fileName)
{
// Open the File
std::ifstream in(fileName.c_str());
// Check if object is valid
if(!in.is_open()) return false;
std::string str;
// Read the next line from File untill it reaches the end.
while (std::getline(in, str))
{
// Line contains string of length > 0 then save it in vector
if(str.size()>0) Labels.push_back(str);
}
// Close The File
in.close();
return true;
}
void detect_from_video(Mat &src)
{
Mat image;
int cam_width =src.cols;
int cam_height=src.rows;
// copy image to input as input tensor
cv::resize(src, image, Size(300,300));
memcpy(interpreter->typed_input_tensor<uchar>(0), image.data, image.total() * image.elemSize());
interpreter->SetAllowFp16PrecisionForFp32(true);
interpreter->SetNumThreads(4); //quad core
// cout << "tensors size: " << interpreter->tensors_size() << "\n";
// cout << "nodes size: " << interpreter->nodes_size() << "\n";
// cout << "inputs: " << interpreter->inputs().size() << "\n";
// cout << "input(0) name: " << interpreter->GetInputName(0) << "\n";
// cout << "outputs: " << interpreter->outputs().size() << "\n";
interpreter->Invoke(); // run your model
const float* detection_locations = interpreter->tensor(interpreter->outputs()[0])->data.f;
const float* detection_classes=interpreter->tensor(interpreter->outputs()[1])->data.f;
const float* detection_scores = interpreter->tensor(interpreter->outputs()[2])->data.f;
const int num_detections = *interpreter->tensor(interpreter->outputs()[3])->data.f;
//there are ALWAYS 10 detections no matter how many objects are detectable
// cout << "number of detections: " << num_detections << "\n";
const float confidence_threshold = 0.5;
for(int i = 0; i < num_detections; i++){
if(detection_scores[i] > confidence_threshold){
int det_index = (int)detection_classes[i]+1;
float y1=detection_locations[4*i ]*cam_height;
float x1=detection_locations[4*i+1]*cam_width;
float y2=detection_locations[4*i+2]*cam_height;
float x2=detection_locations[4*i+3]*cam_width;
Rect rec((int)x1, (int)y1, (int)(x2 - x1), (int)(y2 - y1));
rectangle(src,rec, Scalar(0, 0, 255), 1, 8, 0);
putText(src, format("%s", Labels[det_index].c_str()), Point(x1, y1-5) ,FONT_HERSHEY_SIMPLEX,0.5, Scalar(0, 0, 255), 1, 8, 0);
}
}
}
int main(int argc,char ** argv)
{
float f;
float FPS[16];
int i, Fcnt=0;
Mat frame;
chrono::steady_clock::time_point Tbegin, Tend;
for(i=0;i<16;i++) FPS[i]=0.0;
// Load model
std::unique_ptr<tflite::FlatBufferModel> model = tflite::FlatBufferModel::BuildFromFile("detect.tflite");
// Build the interpreter
tflite::ops::builtin::BuiltinOpResolver resolver;
tflite::InterpreterBuilder(*model.get(), resolver)(&interpreter);
interpreter->AllocateTensors();
// Get the names
bool result = getFileContent("COCO_labels.txt");
if(!result)
{
cout << "loading labels failed";
exit(-1);
}
VideoCapture cap("James.mp4");
if (!cap.isOpened()) {
cerr << "ERROR: Unable to open the camera" << endl;
return 0;
}
cout << "Start grabbing, press ESC on Live window to terminate" << endl;
while(1){
// frame=imread("Traffic.jpg"); //need to refresh frame before dnn class detection
cap >> frame;
if (frame.empty()) {
cerr << "ERROR: Unable to grab from the camera" << endl;
break;
}
Tbegin = chrono::steady_clock::now();
detect_from_video(frame);
Tend = chrono::steady_clock::now();
//calculate frame rate
f = chrono::duration_cast <chrono::milliseconds> (Tend - Tbegin).count();
if(f>0.0) FPS[((Fcnt++)&0x0F)]=1000.0/f;
for(f=0.0, i=0;i<16;i++){ f+=FPS[i]; }
putText(frame, format("FPS %0.2f", f/16),Point(10,20),FONT_HERSHEY_SIMPLEX,0.6, Scalar(0, 0, 255));
//show output
// cout << "FPS" << f/16 << endl;
imshow("RPi 4 - 1,9 GHz - 2 Mb RAM", frame);
char esc = waitKey(5);
if(esc == 27) break;
}
cout << "Closing the camera" << endl;
destroyAllWindows();
cout << "Bye!" << endl;
return 0;
}
Les résultats de ce code montre une détection d’objet à une vitesse entre 10 et 20 FPS mais avec une faible précision sur cet exemple.
Une étude avec d’autres exemples et modèles doit être mené mais ce projet montre l’intérêt de C++ et Tensorflow Lite pour la détection d’objet sur Raspberry Pi
Il est possible d’embarquer des modèles de reconnaissance d’objets, comme Yolo, sur un Raspberry Pi. Bien sûr de par ses faibles performances comparer à des ordinateurs, les performances sont moindre en terme de reconnaissance en temps réel. Il est par contre tout à fait possible de de développer des algorithmes utilisant la reconnaissance d’objet pour des applications ne nécessitant pas de temps réel.
Matériel
Raspberry Pi 4
Écran+souris+clavier
Carte SD avec OS Raspbian 64bits
Configuration
Pour utiliser la Yolo, il vous faudra installer la version 64bits de Raspberry Pi OS. Cette version est disponible dans le logiciel Raspberry Pi Imager dans le menu Raspberry Pi OS (others)
Les versions de Python compatibles sont les versions >=3.9
Le code pour la détection d’objet avec Yolo est le même que sur un ordinateur
On initialise le modèle Yolo (le fichier de poids .pt est télécharger au début de programme)
model = YOLO("yolov5nu.pt")
Puis on ouvre un flux vidéo (où un fichier vidéo ou une image)
vs = VideoStream(src=0, resolution=(640, 640)).start()
#video_cap = cv2.VideoCapture(0)
On exécute ensuite le modèle sur chaque image
detections = model(frame)[0]
Enfin nous traçons les résultats de la détections sur l’image et l’affichons
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
import datetime
from ultralytics import YOLO
import cv2
from imutils.video import VideoStream
import screeninfo
# define some constants
CONFIDENCE_THRESHOLD = 0.7
GREEN = (0, 255, 0)
model = YOLO("yolov5nu.pt")
#model = YOLO("./runs/detect/yolov8n_v8_50e2/weights/best.pt") # test trained model
print(model.names)
# initialize the video capture object
vs = VideoStream(src=0, resolution=(640, 640)).start()
#video_cap = cv2.VideoCapture(0)
#video_cap = cv2.VideoCapture("datasets\\Splash - 23011.mp4")
while True:
# start time to compute the fps
start = datetime.datetime.now()
frame = vs.read(); ret=True
#ret, frame = video_cap.read()
# if there are no more frames to process, break out of the loop
if not ret:
break
# run the YOLO model on the frame
detections = model(frame)[0]
# loop over the detections
#for data in detections.boxes.data.tolist():
for box in detections.boxes:
#extract the label name
label=model.names.get(box.cls.item())
# extract the confidence associated with the detection
data=box.data.tolist()[0]
confidence = data[4]
# filter out weak detections
if float(confidence) < CONFIDENCE_THRESHOLD:
continue
# draw the bounding box on the frame
xmin, ymin, xmax, ymax = int(data[0]), int(data[1]), int(data[2]), int(data[3])
cv2.rectangle(frame, (xmin, ymin) , (xmax, ymax), GREEN, 2)
#draw confidence and label
y = ymin - 15 if ymin - 15 > 15 else ymin + 15
cv2.putText(frame, "{} {:.1f}%".format(label,float(confidence*100)), (xmin, y), cv2.FONT_HERSHEY_SIMPLEX, 0.5, GREEN, 2)
# end time to compute the fps
end = datetime.datetime.now()
# show the time it took to process 1 frame
total = (end - start).total_seconds()
print(f"Time to process 1 frame: {total * 1000:.0f} milliseconds")
# calculate the frame per second and draw it on the frame
fps = f"FPS: {1 / total:.2f}"
cv2.putText(frame, fps, (50, 50),
cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 0, 255), 2)
# show the frame to our screen
cv2.imshow("Frame", frame)
#press key Q to exit
if cv2.waitKey(1) == ord("q"):
break
#video_cap.release()
vs.stop()
cv2.destroyAllWindows()
Les résultats de ce code montre une détection d’objet à une vitesse avoisinant une image par seconde environ (1 FPS, comparé à 10FPS pour un ordinateur)
Amélioration de la rapidité
Faire tourner un modèle de reconnaissance d’objet sur un Raspberry Pi est un véritable défi. Il existe des solutions pour améliorer ces résultats.
Notre résultat de base est obtenu avec le modèle yolov5nu avec une détection à 1 image par seconde (1 FPS)
Changer de carte
Si vous souhaitez des résultat performant mieux vaut vous tourner vers des microordinateurs prévus pour travailler avec l’intelligence artificielle. C’est cartes possèdent entre autre des microprocesseurs adaptés à ces volumes de calculs (ex; Nvidia Jetson)
Changer de langage
Le code proposé ici est écrit en Python, il existe des versions lite écrite en C++ qui permette d’augmenter considérablement la vitesse d’exécution.
Réduire le nombre d’image traité
Il est possible de réduire la charge de calcul en sautant des images.
Dans ce tutoriel, nous allons apprendre comment activer et gérer le Bluetooth Low Energy(BLE) sur un ESP32 en utilisant le langage de programmation Arduino.
Le Bluetooth Low Energy est une version du bluetooth à faible énergie qui permet d’envoyer des petits paquets de données à intervalle régulier.
Matériels
Un module ESP32 (Bluetooth+Wifi embarqué)
Un ordinateur avec Python installé ou smartphone
Câble USB pour la connexion ESP32-ordinateur
Environnement et Configuration de l’IDE
Pour programmer votre ESP32 avec l’IDE Arduino, vous pouvez suivre ce tutoriel précédent.
Communication Série via BLE
La communication BLE doit être configuré avec un certains nombre d’adresses (UIID) qui sont comme des registres mémoires dans lesquels nous allons pouvoir lire et/ou écrire. Dans l’exemple, nous définissons un service avec une caractéristique disponible en lecture et en écriture. Nous pourrons donc écrire et lire cette valeur en adressant directement l’uiid CHARACTERISTIC_UUID.
//https://github.com/espressif/arduino-esp32/tree/master/libraries/BLE
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
class MyCallbacks: public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pCharacteristic) {
std::string value = pCharacteristic->getValue();
if (value.length() > 0) {
Serial.println("*********");
Serial.print("New value: ");
for (int i = 0; i < value.length(); i++)
Serial.print(value[i]);
Serial.println();
Serial.println("*********");
}
}
};
void setup() {
Serial.begin(115200);
Serial.println("1- Download and install an BLE Terminal FREE");
Serial.println("2- Scan for BLE devices in the app");
Serial.println("3- Connect to ESP32BLE");
Serial.println("4- Go to CUSTOM CHARACTERISTIC in CUSTOM SERVICE and write something");
BLEDevice::init("ESP32BLE");
BLEServer *pServer = BLEDevice::createServer();
BLEService *pService = pServer->createService(SERVICE_UUID);
BLECharacteristic *pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE
);
pCharacteristic->setCallbacks(new MyCallbacks());
pCharacteristic->setValue("Hello World");
pService->start();
BLEAdvertising *pAdvertising = pServer->getAdvertising();
pAdvertising->start();
Serial.print("Server address:");
Serial.println(BLEDevice::getAddress().toString().c_str());
}
void loop() {
// put your main code here, to run repeatedly:
delay(2000);
}
N.B.: il est possible de récupérer l’adresse MAC de l’ESP32 avec la fonction BLEDevice::getAddress().toString().c_str()
Appairage
Une fois la configuration du module effectuée comme vous le désirez, vous pouvez appairé l’ESP32 avec le système de votre choix comme n’importe quel périphérique Bluetooth. Sélectionnez le nom dans la liste des périphériques détectés (nom ESP32BLE)
Test de la communication BLE à l’aide de BLE Terminal
Nous allons tester la communication BLE à l’aide de l’application BLE Terminal
Le message est bien échangé entre le téléphone et l’ESP32 via Bluetooth LE
Communication bidirectionnel entre appareil et ESP32BLE
Voici le code permettant de modifier la valeur de la caractéristique à l’aide du moniteur série. Ainsi l’appareil connecté et le moniteur série viennent modifier la valeur de la caractéristique.
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
BLECharacteristic *pCharacteristic = NULL;
std::string msg;
class MyCallbacks: public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pCharacteristic) {
std::string value = pCharacteristic->getValue();
if (value.length() > 0) {
Serial.println("*********");
Serial.print("New value: ");
for (int i = 0; i < value.length(); i++)
Serial.print(value[i]);
Serial.println();
Serial.println("*********");
}
}
};
void setup() {
Serial.begin(115200);
Serial.println("1- Download and install an BLE Terminal Free");
Serial.println("2- Scan for BLE devices in the app");
Serial.println("3- Connect to ESP32BLE");
Serial.println("4- Go to CUSTOM CHARACTERISTIC in CUSTOM SERVICE and write something");
BLEDevice::init("ESP32BLE");
BLEServer *pServer = BLEDevice::createServer();
BLEService *pService = pServer->createService(SERVICE_UUID);
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE
);
pCharacteristic->setCallbacks(new MyCallbacks());
pCharacteristic->setValue("Hello World");
pService->start();
BLEAdvertising *pAdvertising = pServer->getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(true);
pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue
pAdvertising->setMinPreferred(0x12);
pAdvertising->start();
Serial.print("Server address:");
Serial.println(BLEDevice::getAddress().toString().c_str());
}
void loop() {
readSerialPort();
//Send data to slave
if(msg!=""){
pCharacteristic->setValue(msg);
msg="";
}
delay(2000);
}
void readSerialPort(){
while (Serial.available()) {
delay(10);
if (Serial.available() >0) {
char c = Serial.read(); //gets one byte from serial buffer
msg += c; //add to String
}
}
Serial.flush(); //clean buffer
}
En pratique, il sera certainement préférable d’utiliser un service pour l’écriture et un service pour la lecture.
Communiquer entre ESP32 et Python via BLE
Vous pouvez gérer la communication Bluetooth Low Energy depuis votre PC.
import asyncio
from bleak import BleakScanner
async def main():
target_name = "ESP32BLE"
target_address = None
devices = await BleakScanner.discover()
for d in devices:
print(d)
if target_name == d.name:
target_address = d.address
print("found target {} bluetooth device with address {} ".format(target_name,target_address))
break
asyncio.run(main())
Output
C1:2E:C6:8E:47:E8: None
3C:61:05:31:5F:12: ESP32BLE
found target ESP32BLE bluetooth device with address 3C:61:05:31:5F:12
Se connecter et communiquer avec l’ESP32
Voici un script Python pour se connecter automatiquement à l’appareil BLE ESP32 depuis un PC. Pour communiquer avec l’appareil BLE, il est important de connaître les uiid des différents services. Nous définissons les UUID comme ceux définis dans le code de l’ESP32
import asyncio
from bleak import BleakScanner
from bleak import BleakClient
async def main():
target_name = "ESP32BLE"
target_address = None
SERVICE_UUID= "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
CHARACTERISTIC_UUID= "beb5483e-36e1-4688-b7f5-ea07361b26a8"
devices = await BleakScanner.discover()
for d in devices:
print(d)
if target_name == d.name:
target_address = d.address
print("found target {} bluetooth device with address {} ".format(target_name,target_address))
break
if target_address is not None:
async with BleakClient(target_address) as client:
print(f"Connected: {client.is_connected}")
while 1:
text = input()
if text == "quit":
break
await client.write_gatt_char(CHARACTERISTIC_UUID, bytes(text, 'UTF-8'), response=True)
try:
data = await client.read_gatt_char(CHARACTERISTIC_UUID)
data = data.decode('utf-8') #convert byte to str
print("data: {}".format(data))
except Exception:
pass
else:
print("could not find target bluetooth device nearby")
asyncio.run(main())
Créer des caractéristiques, descripteurs et services
Le principe du BLE repose sur l’envoie de petits paquets de données contrairement au Bluetooth classique qui permet l’envoie de données en continu. En ce sens, vous pouvez créer des services et/ou des caractéristiques différentes en fonctions des données échangées. Si, par exemple, vous avez plusieurs capteurs branchés à l’ESP32, vous pouvez créer une caractéristique par capteur.
Chaque caractéristique est identifiée par UUID (16 bytes Universally Unique IDentifier) et gérée par un service.
Chaque service peut gérer par défaut 7 caractéristiques (15 handles au total, deux pour une caractéristique et un pour un descripteur)
Il est possible d’augmenter ce nombre à la création du service
BLEService* pService = pServer->createService(BLEUUID(SERVICE_UUID), 32); // 32 available handles
Nous créons donc les variables et fonctions nécessaires pour la gestions de 3 caractéristiques supplémentaires
Points (pPoints, PtsCallbacks)
IP (pIP, IpCallbacks)
EEPROM (pEeprom, EeCallbacks)
Pour définir les UUD, nous utilisons le site uuid-generator
/*
Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleWrite.cpp
Ported to Arduino ESP32 by Evandro Copercini
*/
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
#define POINTS_UUID "ff0a1ece-8a3a-4dc8-bfb8-eae1371dc3fb"
#define IP_UUID "ff673592-0b83-4151-a871-7d222c9dab3c"
#define EEPROM_UUID "5056c1b6-f123-4ade-aa77-9b7f44c717eb"
BLECharacteristic *pCharacteristic = NULL;
BLECharacteristic* pPoints = NULL;
BLECharacteristic* pIP = NULL;
BLECharacteristic* pEeprom = NULL;
std::string msg;
bool deviceConnected = false;
bool oldDeviceConnected = false;
class MyCallbacks: public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pCharacteristic) {
std::string value = pCharacteristic->getValue();
if (value.length() > 0) {
Serial.print("Charac value: ");
for (int i = 0; i < value.length(); i++)
Serial.print(value[i]);
Serial.println();
}
}
};
class PtsCallbacks: public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pPoints) {
std::string value = pPoints->getValue();
if (value.length() > 0) {
Serial.print("Points value: ");
for (int i = 0; i < value.length(); i++)
Serial.print(value[i]);
Serial.println();
}
}
};
class IpCallbacks: public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pIP) {
std::string value = pIP->getValue();
if (value.length() > 0) {
Serial.print("IP value: ");
for (int i = 0; i < value.length(); i++)
Serial.print(value[i]);
Serial.println();
}
}
};
class EeCallbacks: public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pEeprom) {
std::string value = pEeprom->getValue();
if (value.length() > 0) {
Serial.print("EEPROM value: ");
for (int i = 0; i < value.length(); i++)
Serial.print(value[i]);
Serial.println();
}
}
};
class MyServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
Serial.println("Client connected");
}
void onDisconnect(BLEServer* pServer) {
Serial.println("Client disconnected");
BLEDevice::startAdvertising(); // needed for reconnection
}
};
void setup() {
Serial.begin(115200);
Serial.println("ESP32BLE server ready");
BLEDevice::init("ESP32BLE");
BLEServer *pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
BLEService *pService = pServer->createService(SERVICE_UUID);
//create BLE characteristics
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE
);
pCharacteristic->setCallbacks(new MyCallbacks());
pCharacteristic->setValue("Hello World");
// Create a BLE Characteristic
pPoints = pService->createCharacteristic(
POINTS_UUID,
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE);
pPoints->setCallbacks(new PtsCallbacks());
pPoints->setValue("0");
pIP = pService->createCharacteristic(
IP_UUID,
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE);
pIP->setCallbacks(new IpCallbacks());
pIP->setValue("0");
pEeprom = pService->createCharacteristic(
EEPROM_UUID,
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE);
pEeprom->setCallbacks(new EeCallbacks());
pEeprom->setValue("0");
//start servers
pService->start();
BLEAdvertising *pAdvertising = pServer->getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(true);
pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue
pAdvertising->setMinPreferred(0x12);
pAdvertising->start();
Serial.print("Server address:");
Serial.println(BLEDevice::getAddress().toString().c_str());
}
void loop() {
readSerialPort();
//Send data to slave
if(msg!=""){
pCharacteristic->setValue(msg);
msg="";
}
}
void readSerialPort(){
while (Serial.available()) {
delay(10);
if (Serial.available() >0) {
char c = Serial.read(); //gets one byte from serial buffer
msg += c; //add to String
}
}
Serial.flush(); //clean buffer
}
N.B.: lorsque vous modifier les services et caractéristiques de l’appareil, vous devez l’appairer de nouveau
Dans ce tutoriel, nous allons apprendre comment activer, gérer et tester le Bluetooth sur un ESP32 en utilisant le langage de programmation Arduino. Le Bluetooth est une technologie sans fil largement utilisée pour la communication entre dispositifs électroniques. Elle permet très rapidement de transformer votre système en objet connecté.
Matériels
Un module ESP32 (Bluetooth+Wifi embarqué)
Un ordinateur avec Python installé ou smartphone
Câble USB pour la connexion ESP32-ordinateur
Environnement et Configuration de l’IDE
Pour programmer votre ESP32 avec l’IDE Arduino, vous pouvez suivre ce tutoriel précédent.
Récupérer l’adresse MAC
Cette information n’est pas forcément nécessaire mais il est toujours bon de savoir comment récupérer l’adresse MAC sur l’ESP32
#include "esp_bt_main.h"
#include "esp_bt_device.h"
#include "BluetoothSerial.h"
BluetoothSerial SerialBT;
void printDeviceAddress() {
const uint8_t* point = esp_bt_dev_get_address();
for (int i = 0; i < 6; i++) {
char str[3];
sprintf(str, "%02X", (int)point[i]);
Serial.print(str);
if (i < 5){
Serial.print(":");
}
}
}
void setup() {
Serial.begin(115200);
SerialBT.begin("ESP32BT");
Serial.print("MaxAddr : ");
printDeviceAddress();
}
void loop() {}
Output
14:42:43.448 -> MaxAddr : 3C:61:05:31:5F:12
Communication Série via Bluetooth
La communication Bluetooth s’active comme la communication série. La méthode est similaire pour le module HC-06
#include "BluetoothSerial.h"
#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
#error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
#endif
BluetoothSerial SerialBT;
void setup() {
Serial.begin(115200);
SerialBT.begin("ESP32BT"); //Bluetooth device name
Serial.println("The device started, now you can pair it with bluetooth!");
}
void loop() {
if (Serial.available()) {
SerialBT.write(Serial.read());
}
if (SerialBT.available()) {
Serial.write(SerialBT.read());
}
delay(20);
}
N.B.: Il semble qu’il y ait un moyen d’ajouter un code PIN mais impossible de le faire marcher.
SerialBT.setPin(pin);
SerialBT.begin("ESP32BT", true); //BT with PIN
Appairage
Une fois la configuration du module effectuée comme vous le désirez, vous pouvez appairé l’ESP32 avec le système de votre choix comme n’importe quel périphérique Bluetooth. Sélectionnez le nom dans la liste des périphériques détectés (nom ESP32BT)
Test de la communication Bluetooth à l’aide de Serial Bluetooth Terminal
Le message est bien échangé entre le téléphone et l’ESP32 via Bluetooth
Code pour récupérer la commande complète
Dans le code précédent, on faisait une copie byte par byte du message pour le renvoyer au moniteur. Ici nous allons enregistrer la commande complète dans un String msg. Cela vous permettra d’analyser la commande et de définir l’action correspondante (ex: allumer/éteindre une LED)
#include "BluetoothSerial.h"
#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
#error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
#endif
String msg;
BluetoothSerial SerialBT;
const char *pin = "1234";
void setup() {
Serial.begin(115200);
SerialBT.begin("ESP32BT"); //Bluetooth device name
Serial.println("ESP32BT device started, now you can pair it!");
}
void loop(){
readSerialPort();
// Send answer to master
if(msg!=""){
Serial.print("Master sent : " );
Serial.println(msg);
SerialBT.print("received : "+msg);
msg="";
}
}
void readSerialPort(){
while (SerialBT.available()) {
delay(10);
if (SerialBT.available() >0) {
char c = SerialBT.read(); //gets one byte from serial buffer
msg += c; //makes the string readString
}
}
SerialBT.flush();
}
Communiquer entre ESP32 et Python via Bluetooth
Vous pouvez gérer la communication Bluetooth depuis votre PC.
Pour cela installer le paquets PyBluez
python -m pip install pybluez
Detect bluetooth devices
import bluetooth
target_name = "ESP32BT"
target_address = None
nearby_devices = bluetooth.discover_devices(lookup_names=True,lookup_class=True)
print(nearby_devices)
for btaddr, btname, btclass in nearby_devices:
if target_name == btname:
target_address = btaddr
break
if target_address is not None:
print("found target {} bluetooth device with address {} ".format(target_name,target_address))
else:
print("could not find target bluetooth device nearby")
Output
[('88:C6:26:91:30:84', 'UE\xa0BOOM\xa02', 2360344), ('88:C6:26:7E:F2:7A', 'UE\xa0BOOM\xa02', 2360344), ('4C:EA:AE:D6:92:08', 'OPPO A94 5G', 5898764), ('41:42:DD:1F:45:69', 'MX_light', 2360344), ('3C:61:05:31:5F:12', 'ESP32BT', 7936)]
found target ESP32BT bluetooth device with address 3C:61:05:31:5F:12
Se connecter et communiquer avec l’ESP32
Voici un script Python pour se connecter automatiquement à l’appareil Bluetooth ESP32 depuis un PC
import bluetooth
import socket
target_name = "ESP32BT"
target_address = None
nearby_devices = bluetooth.discover_devices(lookup_names=True,lookup_class=True)
print(nearby_devices)
for btaddr, btname, btclass in nearby_devices:
if target_name == btname:
target_address = btaddr
break
if target_address is not None:
print("found target {} bluetooth device with address {} ".format(target_name,target_address))
""" # With PyBluez NOT WORKING
serverMACAddress = target_address
port = 1
s = bluetooth.BluetoothSocket(bluetooth.RFCOMM)
s.connect((serverMACAddress, port))
while 1:
text = raw_input() # Note change to the old (Python 2) raw_input
if text == "quit":
break
s.send(text)
data = s.recv(1024)
if data:
print(data)
sock.close()"""
serverMACAddress = target_address
port = 1
s = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_STREAM, socket.BTPROTO_RFCOMM)
s.connect((serverMACAddress,port))
print("connected to {}".format(target_name))
while 1:
text = input()
if text == "quit":
break
s.send(bytes(text, 'UTF-8'))
data = s.recv(1024)
if data:
print(data)
s.close()
else:
print("could not find target bluetooth device nearby")
N.B.: Seul la libraire socket fonctionne pour la communication Bluetooth. Il semble y avoir un soucis de maintenance sur la librairie PyBluez
Dans ce tutoriel, nous allons entrainer un modèle MobileNetV2 TensorFlow avec Keras pour qu’il s’applique à notre problématique. Nous allons ensuite pouvoir l’utiliser ne temps réel pour classifier de nouvelles images.
Pour ce tutoriel, nous supposons que vous avez suivit les tutoriel précédent: utilisation d’un modèle TensorFlow et préparation d’une base de données pour l’entrainement.
N.B.: je n’ai pas trouvé la bonne méthode pour entrainer le model ssd mobilenetV2, tel quel, avec tensorflow. Je suis donc passé sous Yolo. Si vous avez la bonne méthode n’hésitez pas à laisser un commentaire.
Pour entrainer le modèle, vous pouvez utiliser le script suivant qui va:
charger et augmenter la base de données
créer un modèle (model) à partir du modèle MobileNetV2(base_model)
entrainer les nouveaux gains du model
entrainer finement les gains du base_model
import matplotlib.pyplot as plt
import numpy as np
import os
import tensorflow as tf
#_URL = 'https://storage.googleapis.com/mledu-datasets/cats_and_dogs_filtered.zip'
#path_to_zip = tf.keras.utils.get_file('cats_and_dogs.zip', origin=_URL, extract=True)
#PATH = os.path.join(os.path.dirname(path_to_zip), 'cats_and_dogs_filtered')
PATH="./data/cats_and_dogs_filtered"
train_dir = os.path.join(PATH, 'train')
validation_dir = os.path.join(PATH, 'validation')
BATCH_SIZE = 32
IMG_SIZE = (160, 160)
#create train and validation sets
train_dataset = tf.keras.utils.image_dataset_from_directory(train_dir,
shuffle=True,
batch_size=BATCH_SIZE,
image_size=IMG_SIZE)
validation_dataset = tf.keras.utils.image_dataset_from_directory(validation_dir,
shuffle=True,
batch_size=BATCH_SIZE,
image_size=IMG_SIZE)
class_names = train_dataset.class_names
plt.figure(figsize=(10, 10))
for images, labels in train_dataset.take(1):
for i in range(9):
ax = plt.subplot(3, 3, i + 1)
plt.imshow(images[i].numpy().astype("uint8"))
plt.title(class_names[labels[i]])
plt.axis("off")
val_batches = tf.data.experimental.cardinality(validation_dataset)
test_dataset = validation_dataset.take(val_batches // 5)
validation_dataset = validation_dataset.skip(val_batches // 5)
print('Number of validation batches: %d' % tf.data.experimental.cardinality(validation_dataset))
print('Number of test batches: %d' % tf.data.experimental.cardinality(test_dataset))
#configure performance
AUTOTUNE = tf.data.AUTOTUNE
train_dataset = train_dataset.prefetch(buffer_size=AUTOTUNE)
validation_dataset = validation_dataset.prefetch(buffer_size=AUTOTUNE)
test_dataset = test_dataset.prefetch(buffer_size=AUTOTUNE)
#augmented data (usefull for small data sets)
data_augmentation = tf.keras.Sequential([
tf.keras.layers.RandomFlip('horizontal'),
tf.keras.layers.RandomRotation(0.2),
])
for image, _ in train_dataset.take(1):
plt.figure(figsize=(10, 10))
first_image = image[0]
for i in range(9):
ax = plt.subplot(3, 3, i + 1)
augmented_image = data_augmentation(tf.expand_dims(first_image, 0))
plt.imshow(augmented_image[0] / 255)
plt.axis('off')
preprocess_input = tf.keras.applications.mobilenet_v2.preprocess_input
rescale = tf.keras.layers.Rescaling(1./127.5, offset=-1)
# Create the base model from the pre-trained model MobileNet V2
IMG_SHAPE = IMG_SIZE + (3,)
base_model = tf.keras.applications.MobileNetV2(input_shape=IMG_SHAPE,
include_top=False,
weights='imagenet')
#or load your own
#base_model= tf.saved_model.load("./pretrained_models/ssd_mobilenet_v2_320x320_coco17_tpu-8/saved_model")
image_batch, label_batch = next(iter(train_dataset))
feature_batch = base_model(image_batch)
print(feature_batch.shape)
base_model.trainable = False
base_model.summary()
#classification header
global_average_layer = tf.keras.layers.GlobalAveragePooling2D()
feature_batch_average = global_average_layer(feature_batch)
print(feature_batch_average.shape)
prediction_layer = tf.keras.layers.Dense(1)
prediction_batch = prediction_layer(feature_batch_average)
print(prediction_batch.shape)
#create new neural network based on MobileNet
inputs = tf.keras.Input(shape=(160, 160, 3))
x = data_augmentation(inputs)
x = preprocess_input(x)
x = base_model(x, training=False)
x = global_average_layer(x)
x = tf.keras.layers.Dropout(0.2)(x)
outputs = prediction_layer(x)
model = tf.keras.Model(inputs, outputs)
base_learning_rate = 0.0001
model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=base_learning_rate),
loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
metrics=['accuracy'])
initial_epochs = 10
loss0, accuracy0 = model.evaluate(validation_dataset)
print("initial loss: {:.2f}".format(loss0))
print("initial accuracy: {:.2f}".format(accuracy0))
history = model.fit(train_dataset,
epochs=initial_epochs,
validation_data=validation_dataset)
#plot learning curves
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']
plt.figure(figsize=(8, 8))
plt.subplot(2, 1, 1)
plt.plot(acc, label='Training Accuracy')
plt.plot(val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.ylabel('Accuracy')
plt.ylim([min(plt.ylim()),1])
plt.title('Training and Validation Accuracy')
plt.subplot(2, 1, 2)
plt.plot(loss, label='Training Loss')
plt.plot(val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.ylabel('Cross Entropy')
plt.ylim([0,1.0])
plt.title('Training and Validation Loss')
plt.xlabel('epoch')
plt.show()
#fine tuning
base_model.trainable = True
# Let's take a look to see how many layers are in the base model
print("Number of layers in the base model: ", len(base_model.layers))
# Fine-tune from this layer onwards
fine_tune_at = 100
# Freeze all the layers before the `fine_tune_at` layer
for layer in base_model.layers[:fine_tune_at]:
layer.trainable = False
model.compile(loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
optimizer = tf.keras.optimizers.RMSprop(learning_rate=base_learning_rate/10),
metrics=['accuracy'])
model.summary()
fine_tune_epochs = 10
total_epochs = initial_epochs + fine_tune_epochs
history_fine = model.fit(train_dataset,
epochs=total_epochs,
initial_epoch=history.epoch[-1],
validation_data=validation_dataset)
#plot fine learning curves
acc += history_fine.history['accuracy']
val_acc += history_fine.history['val_accuracy']
loss += history_fine.history['loss']
val_loss += history_fine.history['val_loss']
plt.figure(figsize=(8, 8))
plt.subplot(2, 1, 1)
plt.plot(acc, label='Training Accuracy')
plt.plot(val_acc, label='Validation Accuracy')
plt.ylim([0.8, 1])
plt.plot([initial_epochs-1,initial_epochs-1],
plt.ylim(), label='Start Fine Tuning')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')
plt.subplot(2, 1, 2)
plt.plot(loss, label='Training Loss')
plt.plot(val_loss, label='Validation Loss')
plt.ylim([0, 1.0])
plt.plot([initial_epochs-1,initial_epochs-1],
plt.ylim(), label='Start Fine Tuning')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.xlabel('epoch')
plt.show()
#evaluate
loss, accuracy = model.evaluate(test_dataset)
print('Test accuracy :', accuracy)
model.save('saved_models/my_model')
Utilisation du modèle entrainé
Vous pouvez utiliser le modèle entrainé pour classifier de nouvelles images contenant un seul type d’objet par image. Pour cela, il vous suffit de charger le modèle précédemment sauvegardé (saved_models/my_model)
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# ObjectRecognitionTFVideo.py
# Description:
# Use ModelNetV2-SSD model to detect objects on video
#
# www.aranacorp.com
# import packages
import sys
from imutils.video import VideoStream
from imutils.video import FPS
import numpy as np
import argparse
import imutils
import time
import cv2
import tensorflow as tf
from PIL import Image
# load model from path
#model= tf.saved_model.load("./pretrained_models/ssd_mobilenet_v2_320x320_coco17_tpu-8/saved_model")
model= tf.saved_model.load("./saved_models/my_model")
#model.summary()
print("model loaded")
#load class names
#category_index = label_map_util.create_category_index_from_labelmap(PATH_TO_LABELS,use_display_name=True)
def read_label_map(label_map_path):
item_id = None
item_name = None
items = {}
with open(label_map_path, "r") as file:
for line in file:
line.replace(" ", "")
if line == "item{":
pass
elif line == "}":
pass
elif "id" in line:
item_id = int(line.split(":", 1)[1].strip())
elif "display_name" in line: #elif "name" in line:
item_name = line.split(":", 1)[1].replace("'", "").strip()
if item_id is not None and item_name is not None:
#items[item_name] = item_id
items[item_id] = item_name
item_id = None
item_name = None
return items
#class_names=read_label_map("./pretrained_models/ssd_mobilenet_v2_320x320_coco17_tpu-8/mscoco_label_map.pbtxt")
class_names = read_label_map("./saved_models/label_map.pbtxt")
class_names = list(class_names.values()) #convert to list
class_colors = np.random.uniform(0, 255, size=(len(class_names), 3))
print(class_names)
if __name__ == '__main__':
# Open image
#img= cv2.imread('./data/cats_and_dogs_filtered/train/cats/cat.1.jpg') #from image file
img= cv2.imread('./data/cats_and_dogs_filtered/train/dogs/dog.1.jpg') #from image file
img = cv2.resize(img,(160,160))
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
#input_tensor = np.expand_dims(img, 0)
input_tensor = tf.convert_to_tensor(np.expand_dims(img, 0), dtype=tf.float32)
# predict from model
resp = model(input_tensor)
print("resp: ",resp)
score= tf.nn.sigmoid(resp).numpy()[0][0]*100
cls = int(score>0.5)
print("classId: ",int(cls))
print("score: ",score)
print("score: ",tf.nn.sigmoid(tf.nn.sigmoid(resp)))
# write classname for bounding box
cls=int(cls) #convert tensor to index
label = "{}".format(class_names[cls])
img = cv2.resize(img,(640,640))
cv2.putText(img, label, (5, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, class_colors[cls], 2)
# Show frame
cv2.imshow("Frame", img)
cv2.waitKey(0)
Applications
reconnaissance de différentes races d’animaux
reconnaissance de différents type d’objets comme des cartes électroniques
Nous allons voir dans ce tutoriel comment mettre en place l’entrainement d’un modèle YOLO pour de la reconnaissance d’objets sur des données spécifiques. La difficulté se trouve dans l’élaboration de la banque d’images qui servira pour l’entrainement
Matériel
Un ordinateur avec une installation de Python3
Une caméra
Principe
Nous avons vu dans un précédent tutoriel comment reconnaitre des objets avec Yolo. Ce modèle a été entrainé pour détecter un certain nombre d’objets mais cette liste est limitée.
Une fois que vous avez créer une base de données d’images avec label et boites au format Yolo Placer la base de données dans le dossier YOLO\datasets. Ensuite, vous pouvez résumer les informations dans un fichier YAML dans lequel vous spécifiez:
le chemin de la base de données contenue dans datasets (coffe_mug)
N.B.: vous pouvez passer les chemin d’accès comme répertoire d’images, fichier texte (path/images.txt), ou list de chemin ([path1/images/, path2/images/])
Il est possible de récupérer un modèle pré-entrainé à partir du script python qui servira de base pour l’entrainement du nouveau modèle.
# load the pre-trained YOLOv8n model
model = YOLO("yolov8n.pt")
N.B.: regardé bien le modèle qui correspond à votre machine et à votre besoin car ils ont des performances différentes.
Script Python pour l’entrainement de Yolo
Une fois votre banque d’image prête, le script d’entrainement est assez simple. Il suffit de spécifier:
le nom du nouveau modèle (yolov8n_v8_50e)
le nombre d’itération (epochs)
la base de données à utiliser (data)
le nombre de fichier à utiliser sur une itération (batch)
train_yolo.py
from ultralytics import YOLO
# Load the model.
model = YOLO('yolov8n.pt')
# Training.
results = model.train(
data='coffee_mug_v8.yaml',
imgsz=1280,
epochs=50,
batch=8,
name='yolov8n_v8_50e'
)
L’algorithme d’entrainement enregistre un certains nombre de données pendant le process que vous pouvez visualiser à la suite pour analyser l’entrainement. Les résultats se trouve dans le dossier .\runs\detect\
Script Python pour l’évaluation du modèle
Une fois le modèle entrainé, vous pouvez comparer ses performances sur de nouvelles images.
Pour récupérer le modèle entrainé, vous pouvez le copier à la racine ou entrer le chemin d’accès
« ./runs/detect/yolov8n_v8_50e2/weights/best.pt »
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
import datetime
from ultralytics import YOLO
import cv2
from imutils.video import VideoStream
#from helper import create_video_writer
# define some constants
CONFIDENCE_THRESHOLD = 0.65
GREEN = (0, 255, 0)
image_list=['./datasets/coffee_mug/test/10.png','./datasets/coffee_mug/test/19.png']
# load the pre-trained YOLOv8n model
#model = YOLO("yolov8n.pt")
model = YOLO("./runs/detect/yolov8n_v8_50e2/weights/best.pt") # test trained model
for i,img in enumerate(image_list):
#detect on image
frame= cv2.imread(img)#from image file
detections = model(frame)[0]
# loop over the detections
#for data in detections.boxes.data.tolist():
for box in detections.boxes:
#extract the label name
label=model.names.get(box.cls.item())
# extract the confidence (i.e., probability) associated with the detection
data=box.data.tolist()[0]
confidence = data[4]
# filter out weak detections by ensuring the
# confidence is greater than the minimum confidence
if float(confidence) < CONFIDENCE_THRESHOLD:
continue
# if the confidence is greater than the minimum confidence,
# draw the bounding box on the frame
xmin, ymin, xmax, ymax = int(data[0]), int(data[1]), int(data[2]), int(data[3])
cv2.rectangle(frame, (xmin, ymin) , (xmax, ymax), GREEN, 2)
#draw confidence and label
y = ymin - 15 if ymin - 15 > 15 else ymin + 15
cv2.putText(frame, "{} {:.1f}%".format(label,float(confidence*100)), (xmin, y), cv2.FONT_HERSHEY_SIMPLEX, 0.5, GREEN, 2)
# show the frame to our screen
cv2.imshow("Img{}".format(i), frame)
while True:
if cv2.waitKey(1) == ord("q"):
break
Résultats
L’objectif est atteint car nous obtenons un nouveau modèle qui reconnait les mugs ({0: ‘mug’}), seulement.
Vous pouvez tester ce code avec votre webcam ou avec des photos, par exemple, pour voir les performances du modèle et de la reconnaissance d’objet
Pour permettre au modèle de reconnaitre plus de type d’objets, il faut rajouter des images de l’objet considéré dans la base de données.