fbpixel
Créer une application Bluetooth pour ESP32 avec React Native

Créer une application Bluetooth pour ESP32 avec React Native

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
  • Un appareil Bluetooth (ESP32)

Code de gestion du Bluetooth pour ESP32

Pour tester l’application React Native, nous allons utiliser le code de gestion du Bluetooth pour ESP32.

#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

const BluetoothClassicTerminal = () =>  {
  const [devices, setDevices] = useState<any[]>([]);
  const [paired, setPaired] = useState<any[]>([]);
  const [selectedDevice, setSelectedDevice] = useState<BluetoothDevice>();
  const [messageToSend, setMessageToSend] = useState("");
  const [receivedMessage, setReceivedMessage] = useState("");
  const [isConnected, setIsConnected] = useState(false);
  const [intervalId, setIntervalId] = useState<NodeJS.Timer>();

N.B.: il est possible de créer un composant qui dérive de ReactNative.Components

Gestion des permissions

Pour pouvoir découvrir et se connecter à des appareils Bluetooth, il faut 3 permissions au minimum:

  • BLUETOOTH_SCAN
  • BLUETOOTH_CONNECT
  • ACCESS_FINE_LOCATION

N.B.: ces permissions dépendent de la version et de l’OS utilisé

Voici les balises à ajouter dans le fichier AndroidManifest.xml

  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.BLUETOOTH" />
  <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
  <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
  <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

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.

const checkBluetoothEnabled = async () => {
    try {
      const enabled = await RNBluetoothClassic.isBluetoothEnabled();
      if (!enabled) {
        await RNBluetoothClassic.requestBluetoothEnabled();
      }
      
    } catch (error) {
      console.error('Bluetooth Classic is not available on this device.');
    }
  }
  
  const startDeviceDiscovery = async () => {
    console.log("searching for devices...");
    try {
      const paired = await RNBluetoothClassic.getBondedDevices();
      console.log("Bonded peripherals: " + paired.length);
      setPaired(paired);
    } catch (error) {
      console.error('Error bonded devices:', error);
    }
  }

  const connectToDevice = async (device: BluetoothDevice) => {
    try {
      console.log("Connecting to device");
      let connection = await device.isConnected();
      if (!connection) {
        console.log("Connecting to device");
        await device.connect({
          connectorType: "rfcomm",
          DELIMITER: "\n",
          DEVICE_CHARSET: Platform.OS === "ios" ? 1536 : "utf-8",
        });
      }
      setSelectedDevice(device);
      setIsConnected(true);
      console.log("is connected : ",isConnected);
      //device.onDataReceived((data) => this.readData());
      //const intervalId = setInterval(() => {readData();}, 100);
      //setIntervalId(intervalId);

      
    } catch (error) {
      console.error('Error connecting to device:', error);
    }
  }


const sendMessage = async () => {
  if(selectedDevice && isConnected){
    console.log("isConnected in message",isConnected);
    try {
      await selectedDevice.write(messageToSend);
      
    } catch (error) {
      console.error('Error sending message:', error);
    }
  }
}

const readData = async () => {  
  if (selectedDevice && isConnected) {
    try {
        let message = await selectedDevice.read();
        if(message){
          message = message.trim();
          if (message !== "" && message !== " "){
            if(receivedMessage.length>300){
              setReceivedMessage("");
            }
            setReceivedMessage(receivedMessage => receivedMessage + message +"\n" );
          }
        }

    } catch (error) {
      console.error('Error reading message:', error);
    }
  }
}

useEffect(() => {
  let intervalId: string | number | NodeJS.Timer | undefined;
  if (selectedDevice && isConnected) {
    intervalId = setInterval(() => readData(), 100);
  }
  return () => {
    clearInterval(intervalId);
  };
}, [isConnected,selectedDevice]);

const disconnect = () => {
  //need to reset esp32 at disconnect
  if(selectedDevice && isConnected){
    try {
      clearInterval(intervalId);
      setIntervalId(undefined);
      
      selectedDevice.clear().then( () => {
        console.log("BT buffer cleared");
      });

      selectedDevice.disconnect().then( () => {
        setSelectedDevice(undefined);
        setIsConnected(false);
        setReceivedMessage("");
        console.log("Disconnected from device");
      });

    } catch (error) {
      console.error('Error disconnecting:', error);
    }
  }
}

La fonction de rendu de l’écran

Pour l’affichage, nous choisissons de tous mettre sur un même écran. Il y aura :

  • Un titre
  • La liste des appareils qui n’apparait que si on n’est pas connecté (!isConnected &&)
  • Un encart type terminal de communication qui n’apparait que si on est connecté (selectedDevice && isConnected &&)
    • TextInput pour écrire le message à envoyer messageToSend
    • Un bouton d’envoi
    • Une zone de texte pour afficher receivedMessage
    • Un bouton de déconnexion
    return (
      <View>
      <Text
            style={{
              fontSize: 30,
              textAlign: 'center',
              borderBottomWidth: 1,
            }}>
            AC Bluetooth Terminal
          </Text>
        <ScrollView>

          {!isConnected && (
          <>
          {/*
          <Text>Available Devices:</Text>
          {devices.map((device) => (
            <Button
              key={device.id}
              title={device.name || 'Unnamed Device'}
              onPress={() => this.connectToDevice(device)}
            />
          ))}
          */}
          <Text>Paired Devices:</Text>
          {paired.map((pair: BluetoothDevice,i) => (
                      <View  key={i}
                      style={{
                        flexDirection: 'row',
                        justifyContent: 'space-between',
                        marginBottom: 2,
                      }}>
                      <View style={styles.deviceItem}>
                        <Text style={styles.deviceName}>{pair.name}</Text>
                        <Text style={styles.deviceInfo}>{pair.id}</Text>
                      </View>
                      <TouchableOpacity
                        onPress={() =>
                          isConnected
                            ?  disconnect()
                            :  connectToDevice(pair)
                        }
                        style={styles.deviceButton}>
                        <Text
                          style={[
                            styles.scanButtonText,
                            {fontWeight: 'bold', fontSize: 12},
                          ]}>
                          {isConnected ? 'Disconnect' : 'Connect'}
                        </Text>
                      </TouchableOpacity>
                    </View>
          ))}
          </>  
          )}
          {selectedDevice && isConnected && (
            <>
              <View
                      style={{
                        flexDirection: 'row',
                        justifyContent: 'space-between',
                        margin: 5,
                      }}>
                      <View style={styles.deviceItem}>
                        <Text style={styles.deviceName}>{selectedDevice.name}</Text>
                        <Text style={styles.deviceInfo}>{selectedDevice.id}</Text>
                      </View>
                      <TouchableOpacity
                        onPress={() =>
                          isConnected
                            ?  disconnect()
                            :  connectToDevice(selectedDevice)
                        }
                        style={styles.deviceButton}>
                        <Text
                          style={styles.scanButtonText}>
                          {isConnected ? 'Disconnect' : 'Connect'}
                        </Text>
                      </TouchableOpacity>
                    </View>

          <View
              style={{
                flexDirection: 'row',
                justifyContent: 'space-between',
                margin: 5,
              }}>        
              <TextInput
                style={{
                  backgroundColor: '#888888',
                  margin: 2,
                  borderRadius: 15,
                  flex:3,
                  }}
                placeholder="Enter a message"
                value={messageToSend}
                onChangeText={(text) => setMessageToSend(text )}
              />
              <TouchableOpacity
                        onPress={() => sendMessage()
                        }
                        style={[styles.sendButton]}>
                        <Text
                          style={[
                            styles.scanButtonText,
                          ]}>
                          SEND
                        </Text>
                      </TouchableOpacity>
        </View>
              <Text>Received Message:</Text>
              <TextInput
              editable = {false}
              multiline
              numberOfLines={20}
              maxLength={300}
                style={{
                  backgroundColor: '#333333',
                  margin: 10,
                  borderRadius: 2,
                  borderWidth: 1,
                  borderColor: '#EEEEEE',
                  textAlignVertical: 'top',
                  }} >
                    {receivedMessage}
              </TextInput>
              
            </>
          )}
        </ScrollView>
      </View>
    );

Résultat

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

npx react-native start

Code complet de l’application React Native

/**
 * https://kenjdavidson.com/react-native-bluetooth-classic/
 */

import React, {useState, useEffect} from 'react';
import {   
  StyleSheet,
  Dimensions,
  View, ScrollView, Text,
   Button, TextInput,PermissionsAndroid,Platform, TouchableOpacity } from 'react-native';
import RNBluetoothClassic, {BluetoothDevice,} from 'react-native-bluetooth-classic';

const BluetoothClassicTerminal = () =>  {
  const [devices, setDevices] = useState<any[]>([]);
  const [paired, setPaired] = useState<any[]>([]);
  const [selectedDevice, setSelectedDevice] = useState<BluetoothDevice>();
  const [messageToSend, setMessageToSend] = useState("");
  const [receivedMessage, setReceivedMessage] = useState("");
  const [isConnected, setIsConnected] = useState(false);
  const [intervalId, setIntervalId] = useState<NodeJS.Timer>();
  
  
  /*const [state, setState] = useState({
    devices: [],
    paired: [],
    selectedDevice: null,
    messageToSend: "",
    receivedMessage: "",
    isConnected: false,
    intervalId: null,
  })*/




  const checkBluetoothEnabled = async () => {
    try {
      const enabled = await RNBluetoothClassic.isBluetoothEnabled();
      if (!enabled) {
        await RNBluetoothClassic.requestBluetoothEnabled();
      }
      
    } catch (error) {
      console.error('Bluetooth Classic is not available on this device.');
    }
  }
  
  const startDeviceDiscovery = async () => {
    console.log("searching for devices...");
    try {
      const paired = await RNBluetoothClassic.getBondedDevices();
      console.log("Bonded peripherals: " + paired.length);
      setPaired(paired);
    } catch (error) {
      console.error('Error bonded devices:', error);
    }
    
    /*try {
      const devices = await RNBluetoothClassic.startDiscovery();
      this.setState({ devices });
      console.log("Discovered peripherals: " + devices.length);
    } catch (error) {
      console.error('Error discovering devices:', error);
    }*/
  }

  const connectToDevice = async (device: BluetoothDevice) => {
    try {
      console.log("Connecting to device");
      let connection = await device.isConnected();
      if (!connection) {
        console.log("Connecting to device");
        await device.connect({
          connectorType: "rfcomm",
          DELIMITER: "\n",
          DEVICE_CHARSET: Platform.OS === "ios" ? 1536 : "utf-8",
        });
      }
      setSelectedDevice(device);
      setIsConnected(true);
      console.log("is connected : ",isConnected);
      //device.onDataReceived((data) => this.readData());
      //const intervalId = setInterval(() => {readData();}, 100);
      //setIntervalId(intervalId);

      
    } catch (error) {
      console.error('Error connecting to device:', error);
    }
  }

    /*async onReceivedData() {
  const { selectedDevice, receivedMessage } = this.state;
  //console.log("event : recived message", event);
  try{
    //const message = await selectedDevice.read();
    console.log("reieved msg from", selectedDevice.name);
    const messages = await selectedDevice.available();
  if (messages.length > 0) {
    console.log("msg waiting : ", messages.length);
  }
    //this.setState({ receivedMessage: message.data });
  } catch (error) {
    console.error('Error receiving data:', error);
  }
}*/

const sendMessage = async () => {
  if(selectedDevice && isConnected){
    console.log("isConnected in message",isConnected);
    try {
      await selectedDevice.write(messageToSend);
      
    } catch (error) {
      console.error('Error sending message:', error);
    }
  }
}

/*const readData = async () => {  
  console.log("reading data connected", isConnected);
  if(selectedDevice && isConnected){
    try {
      console.log("reading data from", selectedDevice.name);
      //const available = await selectedDevice.available();
      //if (available>1){
        let message = await selectedDevice.read();
        if(message){
          message = message.trim();
          if (message !== "" && message !== " "){
            console.log("reading data from", selectedDevice.name);
            //console.log(" available : ",  available);
            //console.log("available", selectedDevice.available());
            //console.log("read", selectedDevice.read());
            setReceivedMessage(receivedMessage + message +"\n" );
            console.log('message', message);
            console.log('message', receivedMessage);
            
          }
        }
    //  }

    } catch (error) {
      //console.log("isConnected",isConnected);
      //console.log("selectedDevice",selectedDevice);
      console.error('Error reading message:', error);
    }
  }
}*/

const readData = async () => {  
  if (selectedDevice && isConnected) {
    try {
      //const available = await selectedDevice.available();
      //if (available>1){
        let message = await selectedDevice.read();
        if(message){
          message = message.trim();
          if (message !== "" && message !== " "){
            if(receivedMessage.length>300){
              setReceivedMessage("");
            }
            setReceivedMessage(receivedMessage => receivedMessage + message +"\n" );
          }
        }
    //  }

    } catch (error) {
      //console.log("isConnected",isConnected);
      //console.log("selectedDevice",selectedDevice);
      console.error('Error reading message:', error);
    }
  }
}

useEffect(() => {
  let intervalId: string | number | NodeJS.Timer | undefined;
  if (selectedDevice && isConnected) {
    intervalId = setInterval(() => readData(), 100);
  }
  return () => {
    clearInterval(intervalId);
  };
}, [isConnected,selectedDevice]);

const disconnect = () => {
  //need to reset esp32 at disconnect
  if(selectedDevice && isConnected){
    try {
      clearInterval(intervalId);
      setIntervalId(undefined);
      
      selectedDevice.clear().then( () => {
        console.log("BT buffer cleared");
      });

      selectedDevice.disconnect().then( () => {
        setSelectedDevice(undefined);
        setIsConnected(false);
        setReceivedMessage("");
        console.log("Disconnected from device");
      });

      /*RNBluetoothClassic.unpairDevice(uuid).then( () => {
        console.log("Unpaired from device");
      });
      
      RNBluetoothClassic.pairDevice(uuid).then( () => {
        console.log("paired from device");
      });*/



    } catch (error) {
      console.error('Error disconnecting:', error);
    }
  }
}
  useEffect(() => {

    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);
      }
    }

      checkBluetoothEnabled();

      requestBluetoothPermission().then( () => {
        startDeviceDiscovery();
      });
  }, [])


    return (
      <View>
      <Text
            style={{
              fontSize: 30,
              textAlign: 'center',
              borderBottomWidth: 1,
            }}>
            AC Bluetooth Terminal
          </Text>
        <ScrollView>

          {!isConnected && (
          <>
		  
		  <TouchableOpacity
            onPress={() => startDeviceDiscovery()}
            style={[styles.deviceButton]}>
            <Text
                style={[
                    styles.scanButtonText,
                    ]}>
                SCAN
            </Text>
         </TouchableOpacity>
          {/*
          <Text>Available Devices:</Text>
          {devices.map((device) => (
            <Button
              key={device.id}
              title={device.name || 'Unnamed Device'}
              onPress={() => this.connectToDevice(device)}
            />
          ))}
          */}
          <Text>Paired Devices:</Text>
          {paired.map((pair: BluetoothDevice,i) => (
                      <View  key={i}
                      style={{
                        flexDirection: 'row',
                        justifyContent: 'space-between',
                        marginBottom: 2,
                      }}>
                      <View style={styles.deviceItem}>
                        <Text style={styles.deviceName}>{pair.name}</Text>
                        <Text style={styles.deviceInfo}>{pair.id}</Text>
                      </View>
                      <TouchableOpacity
                        onPress={() =>
                          isConnected
                            ?  disconnect()
                            :  connectToDevice(pair)
                        }
                        style={styles.deviceButton}>
                        <Text
                          style={[
                            styles.scanButtonText,
                            {fontWeight: 'bold', fontSize: 12},
                          ]}>
                          {isConnected ? 'Disconnect' : 'Connect'}
                        </Text>
                      </TouchableOpacity>
                    </View>
          ))}
          </>  
          )}
          {selectedDevice && isConnected && (
            <>
              <View
                      style={{
                        flexDirection: 'row',
                        justifyContent: 'space-between',
                        margin: 5,
                      }}>
                      <View style={styles.deviceItem}>
                        <Text style={styles.deviceName}>{selectedDevice.name}</Text>
                        <Text style={styles.deviceInfo}>{selectedDevice.id}</Text>
                      </View>
                      <TouchableOpacity
                        onPress={() =>
                          isConnected
                            ?  disconnect()
                            :  connectToDevice(selectedDevice)
                        }
                        style={styles.deviceButton}>
                        <Text
                          style={styles.scanButtonText}>
                          {isConnected ? 'Disconnect' : 'Connect'}
                        </Text>
                      </TouchableOpacity>
                    </View>

          <View
              style={{
                flexDirection: 'row',
                justifyContent: 'space-between',
                margin: 5,
              }}>        
              <TextInput
                style={{
                  backgroundColor: '#888888',
                  margin: 2,
                  borderRadius: 15,
                  flex:3,
                  }}
                placeholder="Enter a message"
                value={messageToSend}
                onChangeText={(text) => setMessageToSend(text )}
              />
              <TouchableOpacity
                        onPress={() => sendMessage()
                        }
                        style={[styles.sendButton]}>
                        <Text
                          style={[
                            styles.scanButtonText,
                          ]}>
                          SEND
                        </Text>
                      </TouchableOpacity>
        </View>
              <Text>Received Message:</Text>
              <TextInput
              editable = {false}
              multiline
              numberOfLines={20}
              maxLength={300}
                style={{
                  backgroundColor: '#333333',
                  margin: 10,
                  borderRadius: 2,
                  borderWidth: 1,
                  borderColor: '#EEEEEE',
                  textAlignVertical: 'top',
                  }} >
                    {receivedMessage}
              </TextInput>
              
            </>
          )}
        </ScrollView>
      </View>
    );

};//end of component

const windowHeight = Dimensions.get('window').height;
const styles = StyleSheet.create({
  mainBody: {
    flex: 1,
    justifyContent: 'center',
    height: windowHeight,
  },

  scanButtonText: {
    color: 'white',
    fontWeight: 'bold',
    fontSize: 12,
	textAlign: 'center',
  },
  noDevicesText: {
    textAlign: 'center',
    marginTop: 10,
    fontStyle: 'italic',
  },
  deviceItem: {
    marginBottom: 2,
  },
  deviceName: {
    fontSize: 14,
    fontWeight: 'bold',
  },
  deviceInfo: {
    fontSize: 8,
  },
  deviceButton: {
    backgroundColor: '#2196F3',
    padding: 10,
    borderRadius: 10,
    margin: 2,
    paddingHorizontal: 20,
  },
  sendButton: {
  backgroundColor: '#2196F3',
  padding: 15,
  borderRadius: 15,
  margin: 2,
  paddingHorizontal: 20,
  },
});

export default BluetoothClassicTerminal;

Sources

Streams vidéo synchronisés avec OpenCV et Multithreading

Streams vidéo synchronisés avec OpenCV et Multithreading

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.

Pré-requis: Streaming vidéo entre deux machines

Matériel

  • Ordinateur avec Python et OpenCV
  • 2 sources vidéo (fichier, stream, webcam, etc.)
  • Une connexion internet ou ethernet

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.

Flux vidéo 0

ffmpeg -video_size 640x480 -i /dev/video0 -f mpegts udp://{ip_address}:8553?pkt_size=1316

Flux vidéo 1

ffmpeg -video_size 640x480 -i /dev/video0 -f mpegts udp://{ip_address}:8554?pkt_size=1316

Pour tester le flux vidéo, vous pouvez utiliser la commande ffplay

ffplay upd://127.0.0.1:8553 #video streaming 0
ffplay upd://127.0.0.1:8554 #video streaming 1

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.

-vf "drawtext=fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.tff: text='%{{localtime\:%T}}': fontsize=24: fontcolor=white@0.8: x=7: y=7"

N.B.: il est possible de mettre la commande ffmpeg dans un script Python

Lancement des commandes via SSH

Pour des raisons de simplicité, nous lançons les commandes ffmpeg à partir du script Python via SSH. Pour cela, nous utilisons la librairie paramiko

import socket
import paramiko

#computer ip address
hostname = socket.gethostname()
ip_address = socket.gethostbyname(hostname)
print(f"IP Address: {ip_address}")
#ip_address= "192.168.1.70"

ssh0 = paramiko.SSHClient()
ssh0.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh1 = paramiko.SSHClient()
ssh1.set_missing_host_key_policy(paramiko.AutoAddPolicy())

ssh0.connect("192.168.1.32", username="root", password="root")
ssh1.connect("192.168.1.33", username="root", password="root")

#stream_cmd0 = "python3 video-stream.py {}:{}?pkt_size=1316".format(ip_address,8553)
#stream_cmd1 = "python3 video-stream.py {}:{}?pkt_size=1316".format(ip_address,8554)
stream_cmd0="""ffmpeg -video_size 640x480 -i /dev/video0 -vf "drawtext=fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.tff: text='%{{localtime\:%T}}': fontsize=24: fontcolor=red@0.8: x=7: y=7" -f mpegts udp://{}:{}?pkt_size=1316""".format(ip_address,8553)
stream_cmd1="""ffmpeg -video_size 640x480 -i /dev/video0 -vf "drawtext=fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.tff: text='%{{localtime\:%T}}': fontsize=24: fontcolor=white@0.8: x=7: y=7" -f mpegts udp://{}:{}?pkt_size=1316""".format(ip_address,8554)

ssh1_stdin, ssh1_stdout, ssh1_stderr = ssh1.exec_command(stream_cmd1)
ssh0_stdin, ssh0_stdout, ssh0_stderr = ssh0.exec_command(stream_cmd0)

N.B.: ne pas oublié de fermer la connexion ssh en fin de programme ssh0.close(), ssh1.close()

Réception des streams non-synchronisés

Pour réceptionner les flux vidéo, nous utilisons OpenCV avec Python.

Nous ouvrons d’abord les deux streams vidéo, que nous lisons dans une boucle tant qu’ils sont ouvert

cap0 = cv2.VideoCapture("udp://127.0.0.1:8553")
cap1 = cv2.VideoCapture("udp://127.0.0.1:8554")

Pour des raisons pratique, nous concaténons l’image dans une même fenêtre en nous assurant qu’elles ont des dimensions compatibles

frame0 =cv2.resize(frame0, (640,480))
frame1 =cv2.resize(frame1, (640,480))
Hori = np.concatenate((frame0, frame1), axis=1)

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

class VideoStream:
	def __init__(self, src=0):
		self.cap = cv2.VideoCapture(src)
		self.ret, self.frame = self.cap.read()
		self.started = False
		self.read_lock = Lock()
		
	def start(self):
		if self.started:
			return None
		self.started = True
		self.thread = Thread(target=self.update, args=())
		self.thread.start()
		return self
		
	def update(self):
		while self.started:
			ret,frame = self.cap.read()
			self.read_lock.acquire()
			self.ret, self.frame = ret,frame
			self.read_lock.release()
	
	def isOpened(self):
		return self.cap.isOpened()
				
	def read(self):
		self.read_lock.acquire()
		ret = self.ret
		frame = self.frame.copy()
		self.read_lock.release()
		return ret, frame
		
	def release(self):
		self.started = False
		self.thread.join()
		
	def __exit__(self, exc_type, exc_value, traceback):
		self.cap.release()

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

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# pip install opencv-python
# pip install paramiko

import numpy as np
import cv2
import paramiko
import time
import datetime
from threading import Thread, Lock


#computer ip address

import socket
hostname = socket.gethostname()
ip_address = socket.gethostbyname(hostname)
print(f"IP Address: {ip_address}")
#ip_address= "192.168.1.70"

ssh0 = paramiko.SSHClient()
ssh0.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh0.connect("192.168.1.32", username="root", password="root")
ssh1 = paramiko.SSHClient()
ssh1.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh1.connect("192.168.1.33", username="root", password="root")

#python
#stream_cmd0 = "python3 video-stream.py {}:{}?pkt_size=1316".format(ip_address,8553)
#stream_cmd1 = "python3 video-stream.py {}:{}?pkt_size=1316".format(ip_address,8554)

#ffmpeg
stream_cmd0="""ffmpeg -video_size 640x480 -i /dev/video0 -vf "drawtext=fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.tff: text='%{{localtime\:%T}}': fontsize=24: fontcolor=red@0.8: x=7: y=7" -f mpegts udp://{}:{}?pkt_size=1316""".format(ip_address,8553)
stream_cmd1="""ffmpeg -video_size 640x480 -i /dev/video0 -vf "drawtext=fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.tff: text='%{{localtime\:%T}}': fontsize=24: fontcolor=white@0.8: x=7: y=7" -f mpegts udp://{}:{}?pkt_size=1316""".format(ip_address,8554)

#manual
#ffmpeg -video_size 640x480 -i /dev/video0 -f mpegts udp://192.168.1.70:8553?pkt_size=1316
#ffmpeg -video_size 640x480 -i /dev/video0 -f mpegts udp://192.168.1.70:8554?pkt_size=1316

ssh1_stdin, ssh1_stdout, ssh1_stderr = ssh1.exec_command(stream_cmd1)
ssh0_stdin, ssh0_stdout, ssh0_stderr = ssh0.exec_command(stream_cmd0)
#print(ssh0_stdout.read().decode())
#print(ssh1_stdout.read().decode())


class VideoStream:
	def __init__(self, src=0):
		self.cap = cv2.VideoCapture(src)
		self.ret, self.frame = self.cap.read()
		self.started = False
		self.read_lock = Lock()
		
	def start(self):
		if self.started:
			return None
		self.started = True
		self.thread = Thread(target=self.update, args=())
		self.thread.start()
		return self
		
	def update(self):
		while self.started:
			ret,frame = self.cap.read()
			self.read_lock.acquire()
			self.ret, self.frame = ret,frame
			self.read_lock.release()
	
	def isOpened(self):
		return self.cap.isOpened()
				
	def read(self):
		self.read_lock.acquire()
		ret = self.ret
		frame = self.frame.copy()
		self.read_lock.release()
		return ret, frame
		
	def release(self):
		self.started = False
		self.thread.join()
		
	def __exit__(self, exc_type, exc_value, traceback):
		self.cap.release()





print("waiting for response...")


#cap0 = cv2.VideoCapture("udp://127.0.0.1:8553")
#cap1 = cv2.VideoCapture("udp://127.0.0.1:8554")
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()
#kill ffmpeg
ssh1_stdin, ssh1_stdout, ssh1_stderr = ssh1.exec_command("killall ffmpeg")
ssh0_stdin, ssh0_stdout, ssh0_stderr = ssh0.exec_command("killall ffmpeg")
#close ssh
ssh0.close()
ssh1.close()

Applications

  • Réseau de caméra de surveillance CCTV
  • Reconnaissance d’objet sur des streams vidéos synchronisés

Sources

Le protocole SSH pour se connecter à distance

Le protocole SSH pour se connecter à distance

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

plink -ssh <username>@<host> -pw <password> <command>

Exemple

plink -ssh root@192.168.1.32 -pw root uname -a

Utiliser Python pour envoyer des commandes SSH

Nous utilisons la bibliothèque subprocess pour exécuter des commande du terminal

import subprocess

subprocess.Popen(f"ssh {user}@{host} {cmd}", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()

Envoyer des commandes SSH nécessitant un mot de passe via Python

python3 -m pip install paramiko
ssh = paramiko.SSHClient()
#ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) #if missing
ssh.connect(server, username=username, password=password)
ssh_stdin, ssh_stdout, ssh_stderr = ssh.exec_command("uname -a")
print("connected via SSH")
print(ssh_stdout.read().decode())
ssh.close()

Applications

Sources

Les métiers et formations des Technologies de l’Information (IT)

Les métiers et formations des Technologies de l’Information (IT)

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 :

  1. 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.
  2. 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é.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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.
  8. 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.

Détection d’objets Raspberry Pi et TensorFlow Lite

Détection d’objets Raspberry Pi et TensorFlow Lite

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++

sudo apt-get install codeblocks

Installation d’OpenCV pour Cpp

Pour installer la version Cpp d’OpenCV nous passons par l’outil apt-get

sudo apt-get install libopencv-dev

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

Code pour la détection d’objet

Vous pouvez récupérer le code et projet sur le Github de QEngineering

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

Sources

Détection d’objet avec Yolo sur Raspberry Pi

Détection d’objet avec Yolo 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

Les librairies à installer sont les suivantes

pip install numpy imutils opencv-python
pip install ultralytics

Code pour la détection d’objet

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.

if img_counter % 5 == 0 :
    detections = model(frame)[0]
img_counter+=1

Sources

Communication BLE avec ESP32

Communication BLE avec ESP32

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.

Pour cela installer le paquet Bleak

python -m pip install bleak

Detect bluetooth devices

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

13:02:48.359 -> ESP32BLE server ready
13:02:49.049 -> Server address:3c:61:05:31:5f:12
13:02:53.967 -> Client connected
13:02:59.679 -> Client disconnected
13:03:16.293 -> Client connected
13:08:15.321 -> Points value: myPoints
13:08:28.964 -> Client disconnected
13:08:34.952 -> Client connected
13:08:48.883 -> IP value: myipaddress
13:08:53.353 -> Client disconnected
13:08:58.916 -> Client connected
13:09:07.574 -> EEPROM value: myeepromval

Application

Sources

Communication BLE avec ESP32

Communication Bluetooth avec ESP32

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

Nous allons tester la communication bluetooth à l’aide de l’application 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

Applications

Sources

Entrainer un modèle TensorFlow2 avec Keras

Entrainer un modèle TensorFlow2 avec Keras

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.

Récupérer une base d’image

Télechargez une des nombreuses bases d’image comme cats-and-dogs ou créez votre propre banque d’image

Dezippé le dossier dans sous Tensorflow>data

Entrainement du modèle

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

Autres modèles de classification à considérer

  • vgg16
  • vgg19
  • resnet50
  • resnet101
  • resnet152
  • densenet121
  • densenet169
  • densenet201
  • inceptionresnetv2
  • inceptionv3
  • mobilenet
  • mobilenetv2
  • nasnetlarge
  • nasnetmobile
  • xception

Sources

Entrainement d’un modèle Yolo

Entrainement d’un modèle Yolo

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.

{0: 'person', 1: 'bicycle', 2: 'car', 3: 'motorcycle', 4: 'airplane', 5: 'bus', 6: 'train', 7: 'truck', 8: 'boat', 9: 'traffic light', 10: 'fire hydrant', 11: 'stop sign', 12: 'parking meter', 13: 'bench', 14: 'bird', 15: 'cat', 16: 'dog', 17: 'horse', 18: 'sheep', 19: 'cow', 20: 'elephant', 21: 'bear', 22: 'zebra', 23: 'giraffe', 24: 'backpack', 25: 'umbrella', 26: 'handbag', 27: 'tie', 28: 'suitcase', 29: 'frisbee', 30: 'skis', 31: 'snowboard', 32: 'sports ball', 33: 'kite', 34: 'baseball bat', 35: 'baseball glove', 36: 'skateboard', 37: 'surfboard', 38: 'tennis racket', 39: 'bottle', 40: 'wine glass', 41: 'cup', 42: 'fork', 43: 'knife', 44: 'spoon', 45: 'bowl', 46: 'banana', 47: 'apple', 48: 'sandwich', 49: 'orange', 50: 'broccoli', 51: 'carrot', 52: 'hot dog', 53: 'pizza', 54: 'donut', 55: 'cake', 56: 'chair', 57: 'couch', 58: 'potted plant', 59: 'bed', 60: 'dining table', 61: 'toilet', 62: 'tv', 63: 'laptop', 64: 'mouse', 65: 'remote', 66: 'keyboard', 67: 'cell phone', 68: 'microwave', 69: 'oven', 70: 'toaster', 71: 'sink', 72: 'refrigerator', 73: 'book', 74: 'clock', 75: 'vase', 76: 'scissors', 77: 'teddy bear', 78: 'hair drier', 79: 'toothbrush'}

Il est possible d’entrainer le modèle pour reconnaitre des objets en plus ou d’autres objets à l’aide d’une banque d’image adaptée.

Configuration de Python

Si ce n’est pas le cas, vous pouvez télécharger et installer Python 3

Vous pouvez ensuite installer les librairies nécessaires imutils, OpenCV, ultralytics

python3 -m pip install imutils opencv-python ultralytics

Configuration des données

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)
path: coffee_mug/
train: 'train/'
val: 'test/'
 
# class names
names: 
  0: '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/])

# train and val data as 1) directory: path/images/, 2) file: path/images.txt, or 3) list: [path1/images/, path2/images/]
train: [./coco128/images/train2017/, coffee_mug/test/]  
val: [./coco128/images/train2017/, coffee_mug/train/] 


# class names
names: ['person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 'truck', 'boat', 'traffic light',
        'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow',
        'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee',
        'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard',
        'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple',
        'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch',
        'potted plant', 'bed', 'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone',
        'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors', 'teddy bear',
        'hair drier', 'toothbrush', 'mug']

Récupérer un modèle pré-entrainé

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.

Sources