IA et lecture du détecteur de qualité de l’air

But

Le but est de démontrer le fonctionnement de base d’un réseau de neurones artificiels. Cette démonstration passe par la lecture de l’affichage du détecteur de qualité de l’air JSM-131 SE à partir d’un ordinateur et d’une caméra. Les données capturées sont enregistrées dans un fichier texte. Le formatage du fichier permettra d’importer ces données dans Excel pour analyse détaillée.



L’appareil ne fait que présenter les données sur un écran LCD. Une caméra capture ces données. Un programme, en Python, basé sur les réseaux de neurones artificiels interprète l’affichage des segments clairs, les transformant en chaîne de caractères compatibles à l’importation dans Excel.



La caméra se place à courte distance de détecteur, l’image ne doit pas être déformée. Les caméras web communes produisent une image en barillet (bombée), ce qui n’est pas adéquat. La caméra de marque ELP, modèle ELP-USBFHD01M-MFV, placée à 15 cm devant le détecteur, génère une image très peu déformée, de bonne qualité, facilement traitable. L’ouverture du diaphragme de la caméra s’opère manuellement, ce qui offre un ajustement de la luminosité parfaite et stable.


Équipements

Détecteur de qualité de l’air JSM-131 SE

VIVOSUN Moniteur de qualité de l’air – Testeur de CO2 – Détecteur de pollution intérieur – HCHO/TVOC/PM2.5/PM10 – Testeur avec données en temps réel et enregistrement de valeur moyenne pour la maison et le bureau : Amazon.ca: Commerce, Industrie et Science

Caméra

Mini webcam USB à objectif varifocal 2,8-12 mm haute définition USB avec caméra, mise au point manuelle, compatible avec la plupart des OS, mise au point réglable, webcam USB 2.0 haute vitesse : Amazon.ca: Électronique

Référence

Pour s’initier au traitement par réseau de neurones, je vous suggère de visionner le diaporama que j’ai produit sur ce sujet. Voir la partie « Simulation d’un réseau de neurones» :

https://espacerm.com/Rnia/rnia_cible.php?numero_image=001

Fonctionnement du réseau de neurones artificiels

Nous allons simuler, par programmation, un réseau de neurones précablé, fixe, configuré pour exécuter une tâche bien particulière. Ce réseau sera le plus simple possible, ayant une seule couche.






Programmation

Le traitement des images se fait à partir de la bibliothèque de fonctionnalités d’OpenCV.

Examinons, par étape, la programmation

# Défini le port de capture vidéo   
cap = cv2.VideoCapture(0)
# Lecture en boucle de l'image vidéo
while True:
    _, frame = cap.read()



L’image contenue dans «frame» est encodée en valeurs RGB (Rouge-Green-Blue) ou RVB (Rouge-Vert-Bleu)
Pour le traitement ultérieur, il faut que l’image soit encodée en valeurs HSV (Hue-Saturation-value) ou TSV (Teinte-Saturation-Valeur)

# Conversion de l'image RGB en HSV
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)



Création d’un masque qui servira à différentier les segments allumés du reste de l’image. Comme l’écran est en majorité de couleur bleue et que les segments allumés sont blancs, nous allons convertir ce qui est bleu (selon une certaine marge) en blanc, ce qui est blanc en noir.

    lower_blue = np.array([38, 112, 87])
    upper_blue = np.array([179, 255, 255])
    
    mask = cv2.inRange(hsv, lower_blue, upper_blue)



Affichage à l’écran de l’image originale et de l’image masque

    cv2.imshow("Frame", frame)
    cv2.imshow("Mask", mask)



Recherche de la position des segments

Maintenant, il s’agit de situer la position des segments dans l’image. Nous découpons la fenêtre en partie. Chaque partie correspond à une valeur de la mesure; HCHO, TVOC, PM2.5, PM10, Co2. Nous cherchons à quel pixel correspond le coin supérieur gauche du premier chiffre de la mesure. Par la méthode essais-erreurs, nous trouvons pour HCHO, le pixel y=69, x= 185 que nous pouvons aussi définir comme ligne 69, colonne 185.

    """""""""""""""""""""""""""""""""
    crop_img = source[y: y+h, x: x+w]
    """""""""""""""""""""""""""""""""
    HCHO = mask[69: 125:, 185: 480]
    cv2.imshow("HCHO", HCHO)

Par cet affichage, nous pouvons constater aussi que la caméra est bien alignée par rapport au détecteur. L’image ne penche pas, le haut des segments sont tous alignés sur l’horizontal.



Nous trouvons pour TVOC, le pixel y=127, x= 185.

TVOC = mask[127: 183:, 185: 480]
cv2.imshow("TVOC", TVOC)



Pour PM2.5, le pixel y=186, x= 185.

PM2_5 = mask[186: 242:, 185: 480]
cv2.imshow("PM2_5", PM2_5)

Pour PM10, le pixel y=246, x= 185.

    PM10 = mask[246: 302:, 185: 480]
    cv2.imshow("PM10", PM10)

Pour Co2, le pixel y=305, x= 185.

    CO2 = mask[305: 361:, 185: 480]
    cv2.imshow("CO2", CO2)



Recherche de la position des points décimaux

Il reste à trouver la position des points décimaux.

Pour le HCHO, le pixel 104, 220. Comme la valeur du HCHO sur cet appareil se situe entre 0 et 1.999, il n’y a qu’une position pour le point décimal.

    # HCHO maximum = 1.999
    HCHO_dot1 = mask[104: 109:, 220: 226]
    cv2.imshow("HCHO_dot1", HCHO_dot1)



Pour le TVOC, sa valeur se situe entre 0 et 12, il y a deux positions possibles pour le point décimal.

Première position du point décimal TVOC, pixel y=162, x=220 et pour la deuxième position, pixel y=162, x=272

    # TVOC maximum = 12
    TVOC_dot1 = mask[162: 168:, 220: 226]
    cv2.imshow("TVOC_dot1", TVOC_dot1)
    
    TVOC_dot2 = mask[162: 168:, 272: 278]
    cv2.imshow("TVOC_dot2", TVOC_dot2)

Il n’y a pas de point décimal pour les autres mesures; PM2.5, PM10, Co2.

Vue globale sur l’écran de l’ordinateur



Le mot «digit» sera employé dans la suite plutôt que «chiffre» pour référer à l’ensemble des 7 segments qui composent un chiffre. Cela a pour but de différentier entre chiffre la valeur 0,1,2,3,4,5,6,7,8,9 et chiffre en tant qu’unité, dizaine, centaine…

# Assignement mémoire pour 5 nombres de 4 «digits»
number=np.array([[1,2,3,4],[1,2,3,4],[1,2,3,4],[1,2,3,4],[1,2,3,4]])

    # Position en y (ligne) du premier pixel de chaque nombre
    posNumber=np.array([69,127,186,246,305])
    # Position en x (col) du premier pixel de chaque «digit»
    posDigit=np.array([185,239,289,340])

La position en x du premier pixel de chaque «digit» est déterminée encore par essais-erreurs.

La partie «réseau de neurones»

Deux boucles sont imbriquées pour retrouver chaque «digit» de chaque nombre qui correspond à une mesure.

    for m in range(0, 5): # 5 Nombres (position 0,1,2,3,4)
        line=posNumber[m]
        for j in range(0, 4): # 4 «digits» (position 0,1,2,3)
            col=posDigit[j]

Chaque «digit» est composé de 7 segments. Les segments sont allumés selon le chiffre à afficher.

Chaque segment est analysé selon l’état de 4 pixels qui en font parti. Les segments sont nommés a,b,c,d,e,f,g.



Le pixel nommé a1 vaut 1 s’il est noir ( == 0) sur le mask sinon il vaut -1

            # un pixel blanc sur le mask a comme valeur 255
            # un pixel noir sur le mask a comme valeur 0
            a1 = 1 if mask[line+1,col+9]== 0 else -1

Quatre pixels de chaque segment sont évalués pour éviter une fausse lecture si un pixel est brouillé par une poussière, un problème de caméra, de transmission, etc. C’est l’avantage du réseau de neurones, la lecture des chiffres est possible même si une partie de l’information est erronée.

    # Assignement mémoire pour 5 nombres de 4 «digits»
    number=np.array([[1,2,3,4],[1,2,3,4],[1,2,3,4],[1,2,3,4],[1,2,3,4]])
    
    # Position en y (ligne) du premier pixel de chaque nombre
    posNumber=np.array([69,127,186,246,305])
    # Position en x (col) du premier pixel de chaque «digit»
    posDigit=np.array([185,239,289,340])

    for m in range(0, 5): # 5 Nombres (position 0,1,2,3,4)
        line=posNumber[m]
        for j in range(0, 4): # 4 «digits» (position 0,1,2,3)
            col=posDigit[j]

            # un pixel blanc sur le mask a comme valeur 255
            # un pixel noir sur le mask a comme valeur 0
            a1 = 1 if mask[line+1,col+9]    == 0 else -1
            a2 = 1 if mask[line+1,col+10]   == 0 else -1
            a3 = 1 if mask[line+1,col+11]   == 0 else -1
            a4 = 1 if mask[line+1,col+12]   == 0 else -1

La position du pixel a1, du segment a, du premier nombre (HCH0), premier «digit» se calcul ainsi:

line = posNumber[0] égal 69 ; col=posDigit[0] égal 185

mask[line+1,col+9]

donc dans le mask[69+1,185+9], soit mask[ligne 70, colonne194]

            a1 = 1 if mask[line+1,col+9]== 0 else -1



Chaque segment est évalué

            a1 = 1 if mask[line+1,col+9]    == 0 else -1
            a2 = 1 if mask[line+1,col+10]   == 0 else -1
            a3 = 1 if mask[line+1,col+11]   == 0 else -1
            a4 = 1 if mask[line+1,col+12]   == 0 else -1

    
            b1 = 1 if mask[line+7,col+20]   == 0 else -1
            b2 = 1 if mask[line+8,col+20]   == 0 else -1
            b3 = 1 if mask[line+9,col+20]   == 0 else -1
            b4 = 1 if mask[line+10,col+20]  == 0 else -1

    
            c1 = 1 if mask[line+27,col+20]  == 0 else -1
            c2 = 1 if mask[line+28,col+20]  == 0 else -1
            c3 = 1 if mask[line+29,col+20]  == 0 else -1
            c4 = 1 if mask[line+30,col+20]  == 0 else -1   
           
    
            d1 = 1 if mask[line+37,col+9]   == 0 else -1
            d2 = 1 if mask[line+37,col+10]  == 0 else -1
            d3 = 1 if mask[line+37,col+11]  == 0 else -1
            d4 = 1 if mask[line+37,col+12]  == 0 else -1

    
            e1 = 1 if mask[line+27,col+0]   == 0 else -1
            e2 = 1 if mask[line+28,col+0]   == 0 else -1
            e3 = 1 if mask[line+29,col+0]   == 0 else -1
            e4 = 1 if mask[line+30,col+0]   == 0 else -1
           
    
            f1 = 1 if mask[line+7,col+0]    == 0 else -1
            f2 = 1 if mask[line+8,col+0]    == 0 else -1
            f3 = 1 if mask[line+9,col+0]    == 0 else -1
            f4 = 1 if mask[line+10,col+0]   == 0 else -1
           
    
            g1 = 1 if mask[line+18,col+9]   == 0 else -1
            g2 = 1 if mask[line+18,col+10]  == 0 else -1
            g3 = 1 if mask[line+18,col+11]  == 0 else -1
            g4 = 1 if mask[line+18,col+12]  == 0 else -1



La somme de la valeur trouvée pour chaque pixel pour chaque segment est calculée. Cette somme sera donc comprise entre 4 et -4.

            sa=a1+a2+a3+a4 # Segment a
            sb=b1+b2+b3+b4 # Segment b
            sc=c1+c2+c3+c4 # Segment c
            sd=d1+d2+d3+d4 # Segment d
            se=e1+e2+e3+e4 # Segment e
            sf=f1+f2+f3+f4 # Segment f
            sg=g1+g2+g3+g4 # Segment g



La valeur de sortie de chaque neurone est calculée. Exemple pour le neurone «1», il faut additionner la valeur des segments «b» et «c».

            # Assignement mémoire pour chaque chiffre à représenter
            r=np.array([0,1,2,3,4,5,6,7,8,9])
    
            r[0]=sa+sb+sc+sd+se+sf         # valeur de sortie du neurone «0»
            r[1]=sb+sc                     # valeur de sortie du neurone «1»
            r[2]=sa+sb+sg+se+sd            # valeur de sortie du neurone «2»
            r[3]=sa+sb+sg+sc+sd            # valeur de sortie du neurone «3»
            r[4]=sb+sc+sf+sg               # valeur de sortie du neurone «4»
            r[5]=sa+sf+sg+sc+sd            # valeur de sortie du neurone «5»
            r[6]=sa+sf+sg+se+sd+sc         # valeur de sortie du neurone «6»
            r[7]=sa+sb+sc                  # valeur de sortie du neurone «7»
            r[8]=sa+sb+sc+sd+se+sf+sg      # valeur de sortie du neurone «8»
            r[9]=sa+sb+sc+sd+sf+sg         # valeur de sortie du neurone «9»



Prochaine étape, il faut trouver la valeur maximum

 mx=max(r[0],r[1],r[2],r[3],r[4],r[5],r[6],r[7],r[8],r[9])



Il reste à trouver qui possède la valeur maximum

            for i in range(0, 10):
                if r[i] == mx: break
        
            number[m,j]=i

La valeur de i correspond au numéro du neurone qui a la valeur maximum de sortie. Le chiffre du «digit» traité correspond au numéro de neurone. Par exemple number[m,j]=i, si m=1, j=3 et i=7, number[1,3]=7, m=1 correspond à TVOC, j à la position du dernier «digit», celui complètement à droite, sa valeur affichée est 7.



Détection de la position du point décimal pour TVOC
        if m == 1:
 
            tvocD1a = 1 if mask[166,222]  == 0 else -1
            tvocD1b = 1 if mask[166,223]  == 0 else -1
            tvocD1c = 1 if mask[167,222]  == 0 else -1
            tvocD1d = 1 if mask[167,223]  == 0 else -1
            
            tvocD1 = tvocD1a+tvocD1b+tvocD1c+tvocD1d
            
            
            tvocD2a = 1 if mask[166,274]  == 0 else -1
            tvocD2b = 1 if mask[166,274]  == 0 else -1
            tvocD2c = 1 if mask[167,275]  == 0 else -1
            tvocD2d = 1 if mask[167,275]  == 0 else -1
            

            tvocD2 = tvocD2a+tvocD2b+tvocD2c+tvocD2d



Impression du nombre trouvé dans la console Python. Attente de 1 seconde (1000 millisecondes) avant de traiter une autre capture de la caméra. Sortir de la boucle si la touche «esc», key==27, est appuyée. Terminaison de la capture et fermeture des fenêtres.

        print(number[m])
        
    key = cv2.waitKey(1000)
    if key == 27:
        break
 
cap.release()
cv2.destroyAllWindows()
 



Programme complet

# -*- coding: utf-8 -*-
"""
 Créer le 2022-02-03

@auteur: Richard Morel

Air Quality Monitor

"""

import time

import cv2
import numpy as np


    
 # Défini le port de capture vidéo   
cap = cv2.VideoCapture(0)

# Lecture en boucle de l'image vidéo
while True:
    time.sleep(1)
    _, frame = cap.read()
    #print(frame.shape)

    # Conversion de l'image RGB en HSV
    hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
    
    lower_blue = np.array([38, 112, 87])
    upper_blue = np.array([179, 255, 255])
    
    mask = cv2.inRange(hsv, lower_blue, upper_blue)
    
    cv2.imshow("Frame", frame)
    cv2.imshow("Mask", mask)
    
    
    """""""""""""""""""""""""""""""""
    crop_img = source[y: y+h, x: x+w]
    """""""""""""""""""""""""""""""""
    colFin=480
        
    HCHO = mask[69: 125:, 185: colFin]
    cv2.imshow("HCHO", HCHO)

    TVOC = mask[127: 183:, 185: colFin]
    cv2.imshow("TVOC", TVOC)
    
    PM2_5 = mask[186: 242:, 185: colFin]
    cv2.imshow("PM2_5", PM2_5)
    
    PM10 = mask[246: 302:, 185: colFin]
    cv2.imshow("PM10", PM10)
    
    CO2 = mask[304: 360:, 185: colFin]
    cv2.imshow("CO2", CO2)
    
         
    # HCHO maximum = 1.999
    HCHO_dot1 = mask[104: 109:, 220: 226]
    cv2.imshow("HCHO_dot1", HCHO_dot1)
    
    # TVOC maximum = 12
    TVOC_dot1 = mask[162: 168:, 220: 226]
    cv2.imshow("TVOC_dot1", TVOC_dot1)
    
    TVOC_dot2 = mask[162: 168:, 272: 278]
    cv2.imshow("TVOC_dot2", TVOC_dot2)
    
    
   
    # Assignement mémoire pour 5 nombres de 4 «digits»
    number=np.array([[1,2,3,4],[1,2,3,4],[1,2,3,4],[1,2,3,4],[1,2,3,4]])
    
    # Position en y (ligne) du premier pixel de chaque nombre
    posNumber=np.array([69,127,186,246,305])
    # Position en x (col) du premier pixel de chaque «digit»
    posDigit=np.array([185,239,289,340])


    print('-')
    for m in range(0, 5): # 5 Nombres (position 0,1,2,3,4)
        line=posNumber[m]
        for j in range(0, 4): # 4 «digits» (position 0,1,2,3)
            col=posDigit[j]
            #print(col)
            #print(line)
            # un pixel blanc sur le mask a comme valeur 255
            # un pixel noir sur le mask a comme valeur 0
            a1 = 1 if mask[line+1,col+9]    == 0 else -1
            a2 = 1 if mask[line+1,col+10]   == 0 else -1
            a3 = 1 if mask[line+1,col+11]   == 0 else -1
            a4 = 1 if mask[line+1,col+12]   == 0 else -1

    
            b1 = 1 if mask[line+7,col+20]   == 0 else -1
            b2 = 1 if mask[line+8,col+20]   == 0 else -1
            b3 = 1 if mask[line+9,col+20]   == 0 else -1
            b4 = 1 if mask[line+10,col+20]  == 0 else -1

    
            c1 = 1 if mask[line+27,col+20]  == 0 else -1
            c2 = 1 if mask[line+28,col+20]  == 0 else -1
            c3 = 1 if mask[line+29,col+20]  == 0 else -1
            c4 = 1 if mask[line+30,col+20]  == 0 else -1   
           
    
            d1 = 1 if mask[line+37,col+9]   == 0 else -1
            d2 = 1 if mask[line+37,col+10]  == 0 else -1
            d3 = 1 if mask[line+37,col+11]  == 0 else -1
            d4 = 1 if mask[line+37,col+12]  == 0 else -1

    
            e1 = 1 if mask[line+27,col+0]   == 0 else -1
            e2 = 1 if mask[line+28,col+0]   == 0 else -1
            e3 = 1 if mask[line+29,col+0]   == 0 else -1
            e4 = 1 if mask[line+30,col+0]   == 0 else -1
           
    
            f1 = 1 if mask[line+7,col+0]    == 0 else -1
            f2 = 1 if mask[line+8,col+0]    == 0 else -1
            f3 = 1 if mask[line+9,col+0]    == 0 else -1
            f4 = 1 if mask[line+10,col+0]   == 0 else -1
           
    
            g1 = 1 if mask[line+18,col+9]   == 0 else -1
            g2 = 1 if mask[line+18,col+10]  == 0 else -1
            g3 = 1 if mask[line+18,col+11]  == 0 else -1
            g4 = 1 if mask[line+18,col+12]  == 0 else -1


    
    
            sa=a1+a2+a3+a4 # Segment a
            sb=b1+b2+b3+b4 # Segment b
            sc=c1+c2+c3+c4 # Segment c
            sd=d1+d2+d3+d4 # Segment d
            se=e1+e2+e3+e4 # Segment e
            sf=f1+f2+f3+f4 # Segment f
            sg=g1+g2+g3+g4 # Segment g
            
    
            # Assignement mémoire pour chaque «digit» à représenter
            r=np.array([0,1,2,3,4,5,6,7,8,9])
    
            r[0]=sa+sb+sc+sd+se+sf         # valeur de sortie du neurone «0»
            r[1]=sb+sc                     # valeur de sortie du neurone «1»
            r[2]=sa+sb+sg+se+sd            # valeur de sortie du neurone «2»
            r[3]=sa+sb+sg+sc+sd            # valeur de sortie du neurone «3»
            r[4]=sb+sc+sf+sg               # valeur de sortie du neurone «4»
            r[5]=sa+sf+sg+sc+sd            # valeur de sortie du neurone «5»
            r[6]=sa+sf+sg+se+sd+sc         # valeur de sortie du neurone «6»
            r[7]=sa+sb+sc                  # valeur de sortie du neurone «7»
            r[8]=sa+sb+sc+sd+se+sf+sg      # valeur de sortie du neurone «8»
            r[9]=sa+sb+sc+sd+sf+sg         # valeur de sortie du neurone «9»
    
            mx=max(r[0],r[1],r[2],r[3],r[4],r[5],r[6],r[7],r[8],r[9])
            #print (mx)
    
            for i in range(0, 10):
                if r[i] == mx: break
        
            number[m,j]=i
            
            #
            # HCHO_dot1 = mask[104: 109:, 220: 225]
            #

        if m == 1:
 
            tvocD1a = 1 if mask[166,222]  == 0 else -1
            tvocD1b = 1 if mask[166,223]  == 0 else -1
            tvocD1c = 1 if mask[167,222]  == 0 else -1
            tvocD1d = 1 if mask[167,223]  == 0 else -1
            
            tvocD1 = tvocD1a+tvocD1b+tvocD1c+tvocD1d
            
            
            tvocD2a = 1 if mask[166,274]  == 0 else -1
            tvocD2b = 1 if mask[166,274]  == 0 else -1
            tvocD2c = 1 if mask[167,275]  == 0 else -1
            tvocD2d = 1 if mask[167,275]  == 0 else -1
            

            tvocD2 = tvocD2a+tvocD2b+tvocD2c+tvocD2d
            
            
            print(tvocD1)
            print(tvocD2)

            
        print(number[m])
     

    key = cv2.waitKey(100)
    #(curseur dans une des imshow)
    if key == 27:
        break
  

cap.release()
cv2.destroyAllWindows()

Essais

Il faut vérifier que chaque «digit» est bien décodé pour les valeurs de 0 à 9. Le truc rapide est de faire « inhaler » un peu de Purel au détecteur ou souffler sur la flamme d’une chandelle près du détecteur. Les «digits» HCH0, TVOC passe par tous les chiffres éventuellement. C’est quand même un bon test pour le Co2 et les autres.


Programme incluant l’écriture des données sur disque

Comme l’image peut tout de même être déformée par la caméra (les «digits» n’ont pas exactement la même dimension et le même espacement), il est préférable de trouver la position exacte de chaque «digit» par essais-erreurs et de modifier le programme en conséquence.

La caméra a été bougée, donc les repères initiaux sont aussi modifiés. Les changements importants sont :

posDigit=np.array([[186,236,285,335],[186,234,284,334],[185,235,285,335],[185,235,283,332],[184,233,283,334]])
col=posDigit[m,j]

Vue globale du début de la boucle

    # Assignement mémoire pour 5 nombres de 4 «digits»
    number=np.array([[1,2,3,4],[1,2,3,4],[1,2,3,4],[1,2,3,4],[1,2,3,4]])
    
    # Position en y (ligne) du premier pixel de chaque nombre
    posNumber=np.array([70,125,181,238,294])
    # Position en x (col) du premier pixel de chaque «digit»
    posDigit=np.array([[186,236,285,335],
                       [186,234,284,334],
                       [185,235,285,335],
                       [185,235,283,332],
                       [184,233,283,334]])


    print('-')
    for m in range(0, 5): # 5 Nombres (position 0,1,2,3,4)
        line=posNumber[m]
        for j in range(0, 4): # 4 «digits» (position 0,1,2,3)
            col=posDigit[m,j]



Les données lues seront écrites dans un fichier «.txt». Le « ; » sert de délimiteur entre les données dans le fichier texte.

DATA_PATH = "C:/Documents/Programmation/Python/Travail/analyse d image/"
dataFileName = DATA_PATH + "dataDir_00/dataFile_00.txt" 
dataFile = open(dataFileName, 'a')  #mode «append», ajout

#Entête des colonnes
dataFile.write('Date;'+'Heure;'+'HCHO;'+'HCHO;'+'TVOC;'+'TVOC;'+'PM2.5;'
               +'PM2.5;'+'PM10;'+'PM10;'+'Co2;'+'Co2;'+'\r')

Nouveaux «masks» d’essais



Programme complet incluant l’écriture des données sur disque

# -*- coding: utf-8 -*-
"""
 Créer le 2022-02-03

@auteur: Richard Morel
     
    Lecture de l'affichage du senseur de qualité de l'air

    Air Quality Monitor Rechargeable
    JSM-131 SE
 
    Camera
    ELP-USBFHD01M-MFV
    
    # HCHO maximum = 1.999
    # TVOC maximum = 12
 
"""

import time
import datetime

import cv2
import numpy as np

DATA_PATH = "C:/Documents/Programmation/Python/Travail/analyse d image/"
dataFileName = DATA_PATH + "dataDir_00/dataFile_00.txt" 
dataFile = open(dataFileName, 'a')  #mode «append», ajout

#Entête des colonnes
dataFile.write('Date;'+'Heure;'+'HCHO;'+'HCHO;'+'TVOC;'+'TVOC;'+'PM2.5;'
               +'PM2.5;'+'PM10;'+'PM10;'+'Co2;'+'Co2;'+'\r')
   
cap = cv2.VideoCapture(0)


"""""""""""""""""""""""""""""""""""""""""""""""""""
        
    Lecture de l'affichage du JSM-131 SE

""""""""""""""""""""""""""""""""""""""""""""""""""" 

while True:
    _, frame = cap.read()
    # print(frame.shape)

    hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
    
    
    lower_blue = np.array([38, 112, 87])
    upper_blue = np.array([179, 255, 255])
    
    mask = cv2.inRange(hsv, lower_blue, upper_blue)
    
    cv2.imshow("Frame", frame)
    cv2.imshow("Mask", mask)
    
    
    """""""""""""""""""""""""""""""""
    crop_img = source[y: y+h, x: x+w]
    """""""""""""""""""""""""""""""""
    colFin=480
    
    # HCHO
    """""""""""""""""""""""""""""""""""""""
    ligne 69: l+h= 69+42, colonne 186: 480
    """""""""""""""""""""""""""""""""""""""   
    HCHO = mask[69: 111:, 186: 480]
    cv2.imshow("HCHO", HCHO)
    
    """""""""""""""""""""""""""""""""""""""
    ligne 69: l+h= 69+42, colonne 186: c+26
    """""""""""""""""""""""""""""""""""""""
    
    g=21
    d=38
    HCHO1 = mask[69: 111:, 186: 212]
    cv2.imshow("HCHO1", HCHO1)
    
    HCHO2 = mask[69: 111:, 236: 260]
    cv2.imshow("HCHO2", HCHO2)
    
    HCHO3 = mask[69: 111:, 285: 310]
    cv2.imshow("HCHO3", HCHO3)
    
    HCHO4 = mask[69: 111:, 335: 358]
    cv2.imshow("HCHO4", HCHO4)
    
    HCHO4g = mask[69+g: 111:, 186: 358]
    cv2.imshow("HCHO4g", HCHO4g)
    
    HCHO4d = mask[69+d: 111:, 335: 358]
    cv2.imshow("HCHO4d", HCHO4d)
    
        
    
    # TVOC
    TVOC = mask[125: 167:, 186: colFin]
    cv2.imshow("TVOC", TVOC)
    
    TVOC1 = mask[125: 167:, 186: 210]
    cv2.imshow("TVOC1", TVOC1)
    
    TVOC2 = mask[125: 167:, 234: 260]
    cv2.imshow("TVOC2", TVOC2)
    
    TVOC3 = mask[125: 167:, 284: 308]
    cv2.imshow("TVOC3", TVOC3)
    
    TVOC4 = mask[125: 167:, 334: 358]
    cv2.imshow("TVOC4", TVOC4)
    
    TVOC4g = mask[125+g: 167:, 186: 358]
    cv2.imshow("TVOC4g", TVOC4g)    
    
    TVOC4d = mask[125+d: 167:, 334: 358]
    cv2.imshow("TVOC4d", TVOC4d)
    
        
    
    
    PM2_5 = mask[181: 223:, 185: colFin]
    cv2.imshow("PM2_5", PM2_5)
    
    PM2_5_1 = mask[181: 223:, 185: 210]
    cv2.imshow("PM2_5_1", PM2_5_1)
    
    PM2_5_2 = mask[181: 223:, 235: 258]
    cv2.imshow("PM2_5_2", PM2_5_2)
    
    PM2_5_3 = mask[181: 223:, 285: 308]
    cv2.imshow("PM2_5_3", PM2_5_3)
    
    PM2_5_4 = mask[181: 223:, 335: 358]
    cv2.imshow("PM2_5_4", PM2_5_4)
    
    PM2_5_4g = mask[181+g: 223:, 185: 358]
    cv2.imshow("PM2_5_4g", PM2_5_4g)

    PM2_5_4d = mask[181+d: 223:, 335: 358]
    cv2.imshow("PM2_5_4d", PM2_5_4d)
    
        
    
    PM10 = mask[238: 281:, 185: colFin]
    cv2.imshow("PM10", PM10)
    
    PM10_1 = mask[238: 281:, 185: 208]
    cv2.imshow("PM10_1", PM10_1)
    
    PM10_2 = mask[238: 281:, 235: 258]
    cv2.imshow("PM10_2", PM10_2)
    
    PM10_3 = mask[238: 281:, 283: 308]
    cv2.imshow("PM10_3", PM10_3)
    
    PM10_4 = mask[238: 281:, 332: 358]
    cv2.imshow("PM10_4", PM10_4)  
    
    PM10_4g = mask[238+g: 281:, 185: 358]
    cv2.imshow("PM10_4g", PM10_4g) 

    PM10_4d = mask[238+d: 281:, 332: 358]
    cv2.imshow("PM10_4d", PM10_4d) 
    



    CO2 = mask[294: 336:, 184: colFin]
    cv2.imshow("CO2", CO2)
    
    CO2_1 = mask[294: 336:, 184: 208]
    cv2.imshow("CO2_1", CO2_1)
    
    CO2_2 = mask[294: 336:, 233: 258]
    cv2.imshow("CO2_2", CO2_2)

    CO2_3 = mask[294: 336:, 283: 308]
    cv2.imshow("CO2_3", CO2_3)

    CO2_4 = mask[294: 336:, 333: 357]
    cv2.imshow("CO2_4", CO2_4)   
    
    CO2_4g = mask[294+g: 336:, 184: 357]
    cv2.imshow("CO2_4g", CO2_4g)      
    
    CO2_4d = mask[294+d: 336:, 333: 357]
    cv2.imshow("CO2_4d", CO2_4d)  
    
         
    
    # HCHO maximum = 1.999
    HCHO_dot1 = mask[103: 109:, 220: 226]
    cv2.imshow("HCHO_dot1", HCHO_dot1)
    
    # TVOC maximum = 12
    TVOC_dot1 = mask[159: 168:, 220: 226]
    cv2.imshow("TVOC_dot1", TVOC_dot1)
    
    TVOC_dot2 = mask[159: 168:, 271: 277]
    cv2.imshow("TVOC_dot2", TVOC_dot2)
    

 
    # Assignement mémoire pour 5 nombres de 4 «digits»
    number=np.array([[1,2,3,4],[1,2,3,4],[1,2,3,4],[1,2,3,4],[1,2,3,4]])
    
    # Position en y (ligne) du premier pixel de chaque nombre
    posNumber=np.array([70,125,181,238,294])
    # Position en x (col) du premier pixel de chaque «digit»
    posDigit=np.array([[186,236,285,335],
                       [186,234,284,334],
                       [185,235,285,335],
                       [185,235,283,332],
                       [184,233,283,334]])


    print('-')
    for m in range(0, 5): # 5 Nombres (position 0,1,2,3,4)
        line=posNumber[m]
        for j in range(0, 4): # 4 «digits» (position 0,1,2,3)
            col=posDigit[m,j]
            #print(line)
            #print(col)

            # un pixel blanc sur le mask a comme valeur 255
            # un pixel noir sur le mask a comme valeur 0
            a1 = 1 if mask[line+1,col+11]   == 0 else -1
            a2 = 1 if mask[line+1,col+12]   == 0 else -1
            a3 = 1 if mask[line+1,col+13]   == 0 else -1
            a4 = 1 if mask[line+1,col+14]   == 0 else -1

    
            b1 = 1 if mask[line+8,col+22]   == 0 else -1
            b2 = 1 if mask[line+9,col+22]   == 0 else -1
            b3 = 1 if mask[line+10,col+22]  == 0 else -1
            b4 = 1 if mask[line+11,col+22]  == 0 else -1

    
            c1 = 1 if mask[line+29,col+22]  == 0 else -1
            c2 = 1 if mask[line+30,col+22]  == 0 else -1
            c3 = 1 if mask[line+31,col+22]  == 0 else -1
            c4 = 1 if mask[line+32,col+22]  == 0 else -1   
           
    
            d1 = 1 if mask[line+38,col+11]  == 0 else -1
            d2 = 1 if mask[line+38,col+12]  == 0 else -1
            d3 = 1 if mask[line+38,col+13]  == 0 else -1
            d4 = 1 if mask[line+38,col+14]  == 0 else -1

    
            e1 = 1 if mask[line+29,col+0]   == 0 else -1
            e2 = 1 if mask[line+30,col+0]   == 0 else -1
            e3 = 1 if mask[line+31,col+0]   == 0 else -1
            e4 = 1 if mask[line+32,col+0]   == 0 else -1
           
    
            f1 = 1 if mask[line+8,col+0]    == 0 else -1
            f2 = 1 if mask[line+9,col+0]    == 0 else -1
            f3 = 1 if mask[line+10,col+0]   == 0 else -1
            f4 = 1 if mask[line+11,col+0]   == 0 else -1
           
    
            g1 = 1 if mask[line+21,col+11]  == 0 else -1
            g2 = 1 if mask[line+21,col+12]  == 0 else -1
            g3 = 1 if mask[line+21,col+13]  == 0 else -1
            g4 = 1 if mask[line+21,col+14]  == 0 else -1


    
    
            sa=a1+a2+a3+a4 # Segment a
            sb=b1+b2+b3+b4 # Segment b
            sc=c1+c2+c3+c4 # Segment c
            sd=d1+d2+d3+d4 # Segment d
            se=e1+e2+e3+e4 # Segment e
            sf=f1+f2+f3+f4 # Segment f
            sg=g1+g2+g3+g4 # Segment g
            

    
            r=np.array([0,1,2,3,4,5,6,7,8,9])
    
            r[0]=sa+sb+sc+sd+se+sf         # digit 0
            r[1]=sb+sc                     # digit 1
            r[2]=sa+sb+sg+se+sd            # digit 2
            r[3]=sa+sb+sg+sc+sd            # digit 3
            r[4]=sb+sc+sf+sg               # digit 4
            r[5]=sa+sf+sg+sc+sd            # digit 5
            r[6]=sa+sf+sg+se+sd+sc         # digit 6
            r[7]=sa+sb+sc                  # digit 7
            r[8]=sa+sb+sc+sd+se+sf+sg      # digit 8
            r[9]=sa+sb+sc+sd+sf+sg         # digit 9
    
            mx=max(r[0],r[1],r[2],r[3],r[4],r[5],r[6],r[7],r[8],r[9])
            #print (mx)
    
            for i in range(0, 10):
                if r[i] == mx: break
        
            number[m,j]=i
            
        #print(number[m]) 
        
        # Détection de la position de la virgule pour TVOC
        if m == 1:   
            tvocD1a = 1 if mask[162,222]  == 0 else -1
            tvocD1b = 1 if mask[162,223]  == 0 else -1
            tvocD1c = 1 if mask[163,222]  == 0 else -1
            tvocD1d = 1 if mask[163,223]  == 0 else -1
            
            tvocD1 = tvocD1a+tvocD1b+tvocD1c+tvocD1d
            
            
            tvocD2a = 1 if mask[162,272]  == 0 else -1
            tvocD2b = 1 if mask[162,272]  == 0 else -1
            tvocD2c = 1 if mask[163,273]  == 0 else -1
            tvocD2d = 1 if mask[163,273]  == 0 else -1
            
            tvocD2 = tvocD2a+tvocD2b+tvocD2c+tvocD2d
            
            #print(tvocD1)
            #print(tvocD2)    
            
            
        """""""""""""""""""""""""""""""""""""""""""""""""""
        
            Enregistrement des données dans un fichier

        """""""""""""""""""""""""""""""""""""""""""""""""""           
       
        
        # Débuter les données par un horodatage
        if m == 0:
            timeStamp = datetime.datetime.now()
            date_time = timeStamp.strftime("%Y-%m-%d; %H:%M:%S")
            data = date_time + ';'
        
        # HCHO
        if m == 0:
            # Inclure l'ajout de la virgule pour HVCO
            stHCHO = str(number[m,0]) + '.' + str(number[m,1]) \
            + str(number[m,2]) + str(number[m,3])
            print(stHCHO)
            HCHO= float(stHCHO)
            #print(HCHO)
            
            if HCHO >= 0 and HCHO <=1.999:
                if HCHO >=     0 and HCHO <=  0.080:
                    quality_HCHO = '***** HCHO Excellent;'
                if HCHO >= 0.081 and HCHO <=  0.100:
                    quality_HCHO = '**** HCHO Good;'
                if HCHO >= 0.101 and HCHO <=  0.200:
                    quality_HCHO = '*** HCHO Mild;'            
                if HCHO >= 0.201 and HCHO <=  0.500:
                    quality_HCHO = '** HCHO Moderate;'             
                if HCHO >= 0.501 and HCHO <=  1.000:
                    quality_HCHO = '* HCHO Severe;' 
                if HCHO >= 1.001 and HCHO <=  1.999:
                    quality_HCHO = 'HCHO Serious;'  
            else:
                quality_HCHO = 'HCHO ?;'
                
            #print(quality_HCHO)
            data = data + stHCHO + ';' + quality_HCHO 
            
            
        
        #TVOC
        if m == 1:
            stTVOC = str(number[m,0])
            # ajout conditionnel de la virgule pour TVOC
            if tvocD1 == 4 and tvocD2 == -4:
                stTVOC = stTVOC + '.'  
            stTVOC = stTVOC + str(number[m,1])
            # ajout conditionnel de la virgule pour TVOC
            if tvocD2 == 4 and tvocD1 == -4:
                stTVOC = stTVOC + '.' 
            stTVOC = stTVOC + str(number[m,2])
            stTVOC = stTVOC + str(number[m,3])
            print(stTVOC)
            TVOC= float(stTVOC)
            #print(TVOC)
            
            if TVOC >= 0 and TVOC <=12:
                if TVOC >=     0 and TVOC <=  0.5:
                    quality_TVOC = '***** TVOC Excellent;'
                if TVOC >= 0.501 and TVOC <=  0.6:
                    quality_TVOC = '**** TVOC Good;'
                if TVOC >= 0.601 and TVOC <=  1.5:
                    quality_TVOC = '*** TVOC Mild;'            
                if TVOC >= 1.501 and TVOC <=  3.0:
                    quality_TVOC = '** TVOC Moderate;'             
                if TVOC >= 3.001 and TVOC <=  6.0:
                    quality_TVOC = '* TVOC Severe;' 
                if TVOC >= 6.001 and TVOC <= 12.0:
                    quality_TVOC = 'TVOC Serious;'  
            else:
                quality_TVOC = 'TVOC ?;'
                
            #print(quality_TVOC)
            data = data + stTVOC + ';' + quality_TVOC  
         
        # PM2.5
        if m == 2:
            stPM2_5 = str(number[m,0]) + str(number[m,1]) \
            + str(number[m,2]) + str(number[m,3])
            print(stPM2_5)
            PM2_5= float(stPM2_5)
            #print(PM2_5)            

            if PM2_5 >= 0 and PM2_5 <=1000:
                if PM2_5 >=   0 and PM2_5 <=  35:
                    quality_PM2_5 = '***** PM2.5 Excellent;'
                if PM2_5 >=  36 and PM2_5 <=  75:
                    quality_PM2_5 = '**** PM2.5 Good;'
                if PM2_5 >=  76 and PM2_5 <= 100:
                    quality_PM2_5 = '*** PM2.5 Mild;'            
                if PM2_5 >= 101 and PM2_5 <= 150:
                    quality_PM2_5 = '** PM2.5 Moderate;'             
                if PM2_5 >= 151 and PM2_5 <= 250:
                    quality_PM2_5 = '* PM2.5 Severe;' 
                if PM2_5 >= 251 and PM2_5 <=1000:
                    quality_PM2_5 = 'PM2.5 Serious;'  
            else:
                quality_PM2_5 = 'PM2_5 ?;'
                
            #print(quality_PM2_5)
            data = data + stPM2_5 + ';' + quality_PM2_5             
        
        # PM10
        if m == 3:
            stPM10 = str(number[m,0]) + str(number[m,1]) \
            + str(number[m,2]) + str(number[m,3])
            print(stPM10)
            PM10= float(stPM10)
            #print(PM10)
            
            if PM10 >= 0 and PM10 <=2000:
                if PM10 >=   0 and PM10 <=  50:
                    quality_PM10 = '***** PM10 Excellent;'
                if PM10 >=  51 and PM10 <= 100:
                    quality_PM10 = '**** PM10 Good;'
                if PM10 >= 101 and PM10 <= 130:
                    quality_PM10 = '*** PM10 Mild;'            
                if PM10 >= 131 and PM10 <= 200:
                    quality_PM10 = '** PM10 Moderate;'             
                if PM10 >= 201 and PM10 <= 300:
                    quality_PM10 = '* PM10 Severe;' 
                if PM10 >= 301 and PM10 <=2000:
                    quality_PM10 = 'PM10 Serious;'  
            else:
                quality_PM10 = 'PM10 ?;'
                
            #print(quality_PM10)
            data = data + stPM10 + ';' + quality_PM10            

        # Co2
        if m == 4:
            stCo2 = str(number[m,0]) + str(number[m,1]) \
            + str(number[m,2]) + str(number[m,3])
            print(stCo2)
            Co2= float(stCo2)
            #print(Co2)
            
            if Co2 >= 0 and Co2 <=5000:
                if Co2 >=    0 and Co2 <= 450:
                    quality_Co2 = '***** Co2 Excellent'
                if Co2 >=  451 and Co2 <=1000:
                    quality_Co2 = '**** Co2 Good'
                if Co2 >= 1001 and Co2 <=1500:
                    quality_Co2 = '*** Co2 Mild'            
                if Co2 >= 1501 and Co2 <=2000:
                    quality_Co2 = '** Co2 Moderate'             
                if Co2 >= 2001 and Co2 <=3000:
                    quality_Co2 = '* Co2 Severe' 
                if Co2 >= 3001 and Co2 <=5000:
                    quality_Co2 = 'Co2 Serious'  
            else:
                quality_Co2 = 'Co2 ?'
                
            #print(quality_Co2)
            data = data + stCo2 + ';' + quality_Co2
                        
    dataFile.write(data + '\r')                       
    data = ''
    
    print(quality_HCHO)
    print(quality_TVOC)
    print(quality_PM2_5)
    print(quality_PM10)
    print(quality_Co2)
    
    # en seconde
    time.sleep(0)
    
    # en milliseconde
    key = cv2.waitKey(100)
    #(Placer le curseur dans une des imshow)
    if key == 27:
        dataFile.close()
        break
 

cap.release()
cv2.destroyAllWindows()



Le programme inclue les notes à allouer aux mesures de la qualité de l’air.

Importation des données dans Excel

Sélectionner « Ouvrir », puis parcourir pour aller dans le répertoire du fichier « .txt ».

Sélectionner « Tous les fichiers (*.*) », puis le fichier « .txt ».

Suivre les étapes 1,2,3





Sélection du point comme séparateur décimal, puis Terminer

Résultats avec l’option « Filtrer» active



Fonction d’insertion de graphiques


Conclusion

En général, les lectures sont bonnes, mais comme la largeur des segments est mince , le moindre déplacement ou une vibration peut causer une erreur de lecture. La caméra doit être bien centrée par rapport à l’écran de l’appareil de mesure et bien perpendiculaire verticalement et horizontalement pour réduire la déformation de l’image. Une erreur de lecture peut aussi se produire lors de la lecture d’un «digit» pendant de sa phase de changement du chiffre. Ces erreurs sont en général simples à détecter, nous les voyons facilement sur les graphiques, elles sortent du lot.

L’utilisation du «deep learning» augmenterait la précision de la lecture, le système serait moins sensible aux déformations et vibrations, mais sa mise en oeuvre est plus complexe, elle demande plus de travail. C’est une avenue à regarder.

En assemblant la caméra et le détecteur de qualité de l’air dans un montage rigide, les lectures seraient suffisamment fiables et exploitables. Le réseau de neurones artificiel fixe, prédéterminer, à une seule couche est simple à mettre en oeuvre pour cette application et rempli son objectif.