LC-Sat

LC-SAT : Application de traitement de données

Retour à l’acceuil.

Cette application est reliée directement à l’application embarquée dans la canette. Développeur application de bord: Aristide URLI. Github Développeur application bureau: Thomas HODSON. Github Langage utilisé: Python. Version 3.9 Editeur utilisé: Sublime Text . Le télécharger ici. Logiciel pour les diagrammes: LucidChart. Le télécharger ici.

Modules:

Objectifs :

Les objectifs de cette application sont:

Réalisation:

La réalisation de cette application s’est effectuée en plusieurs étapes:

Après de nombreuses réunions, pour définir le projet de la canette, nous avons lister les besoins. Puis, avec Aristide, nous nous sommes accorder sur les modules et les formats d’enregistrement des données. Voici un résumé des besoins et solutions techniques:

Application de design utilisée: Adobe Xd. La téléchargée ici. Durant cette phase, l’idée est de dessiner les différentes fenêtres de l’application, les liens qui les relient. Cette phase permet de s’assurer de n’avoir pas oublier de fenêtres, et de faciliter le développement de l’application. Exemple d’une des fenêtres imaginée lors de la phase de design:

Design de la première fenêtre

Structuration du projet:

Voici la structuration du projet:

LC-Sat Data Treatment/
|
| -- bin/
|	| -- __init__.py
|
| -- files/
|	| -- data/
|	|	| -- cam.mp4.aes
|	|	| -- data.bin.aes
|	| 
|	| -- cam.mp4
|	| -- data.bin
|	| -- output.avi
|	| -- planisphere.html
|	| -- schematic.html
|	| -- terrain.html
|	| -- thermal.avi
|
| -- res/
|	| -- config/
|	|	| -- app_config.json
|	|	| -- data_config.json
|	|	| -- paths.json
|	|	| -- settings.json
|	|	| -- thermal_camera_config.json
|	|
|	| -- i18n/
|	|	| -- cz.json
|	|	| -- en-us.json
|	|	| -- fr.json
|	|
|	| -- img/
|	|	| -- logo.ico
|	|	| -- logo.png
|	|
|	| -- themes/
|	|	| -- bright.json
|	|	| -- dark.json
|
| -- src/
|	| -- controllers/
|	|	| -- languages.py
|	|	| -- load_consts.py
|	|	| -- paths.py
|	|	| -- settings.py
|	|	| -- themes.py
|	|
|	| -- models/
|	|	| -- buttons.py
|	|	| -- inputs.py
|	|	| -- labels.py
|	|	| -- menus.py
|	|
|	| -- script/
|	|	| -- crypt.py
|	|	| -- graphs.py
|	|	| -- maps.py
|	|	| -- read_data.py
|	|	| -- video.py
|	|
|	| -- views/
|	|	| -- load_data.py
|	|	| -- main_view.py
|	|
|	| -- root.py
|
| -- main.py
| -- READ_ME.md
| -- requirements.txt

L’application utilise une infrastructure MVC (pour Models, Views et Controllers). Cette infrastructure permet d’accélérer le développement frontend (lié au visuel) car les composants graphiques sont issus de modèles (moules d’un objet : boutons, textes, etc…). Exemple de modèles:

# Class de liaison entre les différents bouttons

class Buttons:


	def __init__(self):

		self.browse_folder_button = Browse_Folder_Button()
		self.valid_button = Valid_Button()
		self.normal_button = Normal_Button()


	def __str__(self):

		return "Class Bouttons"


# Bouttons de recherche de fichier

class Browse_Folder_Button:


	def __str__(self):

		return "Class Browse Bouttons"


	def create_widget(self, parent, text):

		button = tk.Button(parent, text=text, width=25, font=('Arial', 20, 'bold'))
		return button


# boutton valider

class Valid_Button:


	def __str__(self):

		return "Class Valid Bouttons"


	def create_widget(self, parent, text):

		button = tk.Button(
			parent,
			text=text,
			background="#1e8449",
			foreground="#F0F8FF",
			font=('Arial', 20, 'bold'),
			width=25
			)
		return button


# boutton normal

class Normal_Button:


	def __str__(self):

		return "Class Normal Button"


	def create_widget(self, parent, text):
		
		button = tk.Button(parent, text=text, font=('Arial', 15), width=20)
		return button

L’utilisation de l’infrastructure MVC permet de séparer la logique de l’affichage au travers des fichiers type Views. Chaque fichier contient l’appel des composants graphiques d’une fenêtre. Par exemple, voici le fichier de la page de chargement de données :

class Load_Data_Window:


	def __init__(self, controllers, models):
		
		self.controllers = controllers
		self.models = models
	

	def __str__(self):

		return "Load Data Window class"


	# Gère l'affichage du chemin du dossier sélectionné
	def folder_path(self):

		self.filename = filedialog.askopenfilename(defaultextension='.gz', initialdir ="Téléchargements")
		self.path_label.configure(text=self.filename)


	# créer les composants
	def create_widget(self, parent):
		
		# titre
		self.title = self.models.labels.label_title.create_widget(parent,
			self.controllers.language_controller.get_text("loadData"),
			self.controllers.theme_controller.get_theme("backgroundColor"),
			self.controllers.theme_controller.get_theme("color")
			)
		self.title.pack()

		# Frame du formulaire
		 
		self.form_frame = tk.Frame(parent,
			bg=self.controllers.theme_controller.get_theme("backgroundColor"),
			borderwidth=5,
			highlightbackground=self.controllers.theme_controller.get_theme("color"),
			highlightcolor=self.controllers.theme_controller.get_theme("color"),
			highlightthickness=5,
			)
		self.form_frame.pack()

		# indicateur d'enter de clef de décryptage
		self.key_input_indicator = self.models.labels.label_classic.create_widget(self.form_frame,
		 	self.controllers.language_controller.get_text("keyInput"),
			self.controllers.theme_controller.get_theme("backgroundColor"),
			self.controllers.theme_controller.get_theme("color")
			)
		self.key_input_indicator.configure(padding=35)
		self.key_input_indicator.pack()

		# Champs de saisie de la clef de décryptage
		self.key_input = self.models.inputs.entry.create_widget(self.form_frame)
		self.key_input.pack()

		# indicateur d'enter de clef de décryptage
		self.folder_path_indicator = self.models.labels.label_classic.create_widget(self.form_frame,
			self.controllers.language_controller.get_text("folderInput"),
			self.controllers.theme_controller.get_theme("backgroundColor"),
			self.controllers.theme_controller.get_theme("color")
			)
		self.folder_path_indicator.configure(padding=35)
		self.folder_path_indicator.pack()

		# boutton de selection de fichier
		self.browse_folder = self.models.buttons.browse_folder_button.create_widget(
			self.form_frame,
			self.controllers.language_controller.get_text("browse")
			)
		self.browse_folder.configure(command=self.folder_path)
		self.browse_folder.pack()

		# Chemin du fichier choisi
		self.path_label = self.models.labels.subtitle_label.create_widget(self.form_frame,
			'',
			self.controllers.theme_controller.get_theme("backgroundColor"),
			self.controllers.theme_controller.get_theme("color")
			)
		self.path_label.pack()

Les controllers permettent de communiquer les données stockées dans les fichiers de configurations, transmettre les textes dans la langue sélectionnée, de transmettre les caractéristiques du style choisit. Exemple du controller des paramètres:

class Settings:

	def __init__(self, file_path):

		self.file_path = file_path

		with open(self.file_path, 'r') as f:
			self.data = json.load(f)
			f.close()


	def __str__(self):

		return "Settings Controller"


	def get_setting(self, setting_name):
		
		return self.data[setting_name]

	
	def update_setting(self, setting_name, new_value):
		
		self.data[setting_name] = new_value

		with open(self.file_path, 'w') as f:
			json.dump(self.data, f, indent=4)
			f.close()

Paramétrage de l’application:

Langues:

L’application dispose de plusieurs langages (actuellement anglais, français et tchèque). On peut ajouter manuellement des langages. Il suffit de copier un fichier de langue existant, le renommer avec le nom de la langue, changer le contenu dans la langue en question et placer le fichier dans le dossier i18n. Fichier sur la langue française:

{
	"title": "LC-SAT Traitement de donnees",
	"loadData": "Charger Des Donnees : ",
	"keyInput": "Entrer la clef de decryptage :",
	"folderInput": "Choisir un fichier :",
	"browse": "Rechercher",
	"decrypt": "Decrypter",
	"treatmentTools": "Outils de Traitement de Données",
	"graphs": "Graphs",
	"video": "Video",
	"gps": "GPS",
	"map": "Cartes",
	"planisphere": "Planisphere",
	"schematic": "Schematique",
	"satellite": "Satellite",
	"classicVideo": "Video Classique",
	"thermialVideo": "Video Thermique",
	"showNormalVideo": "Voir la video",
	"renderTrackingVideo": "Detourage",
	"renderMotionFiltering": "Filtrage des Mouvements",
	"normalGraph": "Graphique classique",
	"multipleGraph": "Graphiques multiples",
	"fullGraph": "Graphique complet",
	"time": "Temps (s)",
	"overTime": "en fonction du temps",
	"temperature": "Temperature",
	"pression": "Pression",
	"altitude": "Altitude",
	"acceleration": "Acceleration",
	"speed": "Vitesse",
	"quality": "Qualite du signal",
	"humidity": "Humidite",
	"show": "Dessiner"
}

Thèmes:

Comme pour les langues, l’application dispose de plusieurs thèmes. Pour en ajouter un, il suffit de procéder de la manière que pour les langues. L’application dispose de deux thèmes. Vous pouvez ajouter les thèmes dans le dossier themes. Le fichier sur le thème sombre:

{
	"backgroundColor": "#212121",
	"color": "#F0F8FF"
}

Configuration de la fenêtre:

Le fichier app_config.json contient les paramètres de la fenêtre :

{
	"WIN_SIZE": "1366x768"
}

Configuration des données:

Vous pouvez changez les paramètres d’enregistrement des données dans le fichier data_config.json:

{
	"temperature": {
		"fps": 10,
		"name": "Temperature",
		"unit": "Celsius"
	},
	"pression": {
		"fps": 10,
		"name": "Pression",
		"unit": "hPa"
	},
	"altitude": {
		"fps": 10,
		"name": "Altitude",
		"unit": "m"
	},
	"accelerometre": {
		"fps": 10,
		"name": "Acceleration",
		"unit": "m/s²"
	},
	"satellite": {
		"fps": 10,
		"name": "Satellite",
		"unit": ""
	},
	"quality": {
		"fps": 10,
		"name": "Quality",
		"unit": ""
	},
	"speed": {
		"fps": 10,
		"name": "Speed",
		"unit": "m.s"
	},
	"humidity": {
		"fps": 10,
		"name": "humidity",
		"unit": "%"
	}
}

Le fichier de configuration de la caméra thermique est un fichier à part. Il s’agit du fichier thermal_camera_config.json. Vous pouvez modifier les configurations de la caméra thermique dans ce fichier:

{
	"FPS": 10,
	"RESOLUTION": [8, 8],
	"FINAL_RESOLUTION": [440, 440],
	"MIN_TEMP": -10,
	"MAX_TEMP": 50,
	"MIN_COLOR": [0, 0, 255],
	"MOY_COLOR": [0, 255, 0],
	"MAX_COLOR": [255, 0, 0]
}

Paramètres :

Pour sélectionner les paramètres de l’application, il suffit de modifier manuellement les valeurs dans le fichier settings.json:

{
    "language": "fr",
    "theme": "dark",
    "data_type": [
    	"data.bin",
    	"cam.mp4"
    ]
}

Flexibilité de l’application :

L’application s’adapte quel que soit son emplacement de stockage. Le module os permet de trouver l’adresse du dossier racine. Le fichier paths.json stocke les destinations depuis le fichier racine:

{
	"DATA_PATH": "/res/config/",
	"SETTINGS_PATH": "/res/config/settings.json",
	"CONST_PATH": "/res/config/app_const.json",
	"THERMAL_CONFIG_PATH": "/res/config/thermal_camera_config.json",
	"DATA_CONFIG_PATH": "/res/config/data_config.json",
	"LANG_PATH": "/res/i18n/",
	"IMG_PATH": "/res/img/",
	"THEME_PATH": "/res/themes/",
	"FILES_DATA": "/files/"
}

Décryptage et lecture des données :

Décryptage :

Voici l’organigramme du programme de décryptage:

Programme décrytptage

Nous avons opter pour un stockage compressé afin de réduire le poids des données en transite. Voici le script de décryptage :

class Decrypt:


	def __str__(self):

		return "Decrypt class"


	# Décrypt le fichier
	def decrypt(self, folder_path, destination, data_type, password):

		print("chemin fichier :{0}\nchemin destination: {1}\ntype_donnée: {2}\nmdp: {3}".format(folder_path, destination, data_type, password))

		filename, file_extension = os.path.splitext(folder_path)
		# Décompresse le fichier
		if file_extension == '.gz':
			
			shutil.unpack_archive(folder_path, destination)

		# Transfert les fichiers si le dossier est déjà décompressé
		else:

			for filename in os.listdir(folder_path):
				shutil.copyfile(filename, destination)

		# Décrypte chaque fichier:
		# 	- key est le nom initial
		# 	- value est le nom final
		for key, value in data_type.items():

			pyAesCrypt.decryptFile(key, value, password, 16 * 1024)

Lecture des données :

Une fois les données décryptées et décompressées, le dossier /files contient 2 fichiers: cam.mp4 et data.bin. Le fichier cam.mp4 contient la vidéo standard enregistrée par la canette. Le fichier data.bin contient l’ensemble des données enregistrées par la canette. Voici un script que vous pouvez utiliser ce script pour voir les données enregistrées:

import pickle

data = pickle.load(open("data.bin", "rb"))
print(data.keys())

résultat: |Nom| Données| |–|–| | press | Pression | | temp | Température | | alt | Altitude | | ax | Accélération axe X | | ay | Accélération axe Y | | az | Accélération axe Z | | lat | Latitude | | lon | Longitude | | sat | Nombre de satellites en communication avec le GPS | | qual | Qualité de la réception du GPS | | speed | Vitesse estimée de la canette par le satellite | | hum | Humidité | | therm | Caméra thermique |

Les données sont chargées dans des array numpy "float64":

class Data_Reader:

	def __init__(self, folder_path):

		self.folder_path = folder_path


	def __str__(self):

		return "Data Reader"


	def read_data(self):

		# Reconvertit les données et les enregistres dans des arrays numpy en "float64"
		data = pickle.load(open(self.folder_path + "data.bin", "rb"))

		self.pression = np.array(data["press"], dtype="float64")
		self.temperature = np.array(data["temp"], dtype="float64")
		self.altitude = np.array(data["alt"], dtype="float64")
		self.x_acceleration = np.array(data["ax"], dtype="float64")
		self.y_acceleration = np.array(data["ay"], dtype="float64")
		self.z_acceleration = np.array(data["az"], dtype="float64")
		self.latitude = np.array(data["lat"], dtype="float64")
		self.longitude = np.array(data["lon"], dtype="float64")
		self.satellite = np.array(data["sat"], dtype="float64")
		self.quality = np.array(data["qual"], dtype="float64")
		self.speed = np.array(data["speed"], dtype="float64")
		# self.humidity = np.array(data["hum"])
		self.thermique = np.array(data["therm"], dtype="float64")


	def return_all_data(self):

		return [self.pression, self.temperature, self.altitude, self.x_acceleration,self.y_acceleration, self.z_acceleration, self.satellite, self.quality, self.speed]

La caméra thermique requière cependant plus d’opérations pour rendre les données compréhensibles et exploitables. La caméra thermique enregistre toutes les 0.1 secondes un tableau de 8x8 pixels, où chaque pixels se voient attribuer une valeur.Le script suivant traduit ces valeurs en couleurs et la liste de tableau en une vidéo. La vidéo étant de 8x8 pixels, le script l’agrandit. Le script:

class Thermal_Video:


	def __str__(self):

		return "Thermal Video Maker Class"


	def __init__(self, config_path, store_path):

		# Charge les consantes depuis le fichier de configuration
		with open(config_path, "r") as file:

			config = json.load(file)

			file.close()


		self.MIN_TEMP = [config["MIN_TEMP"], config["MIN_COLOR"]]
		self.MAX_TEMP = [config["MAX_TEMP"], config["MAX_COLOR"]]
		self.MOY_TEMP = [(self.MIN_TEMP[0] + self.MAX_TEMP[0]) / 2, config["MOY_COLOR"]]

		self.WIDTH = config["RESOLUTION"][0]
		self.HEIGHT = config["RESOLUTION"][1]

		self.FPS = config["FPS"]

		self.store_path = store_path

		self.FINAL_RESOLUTION = [config["FINAL_RESOLUTION"][0], config["FINAL_RESOLUTION"][1]]

	def convert_to_color(self, value):

		# Détermination du ratio à appliquer pour changer la couleur
		# 0 <= couleur <= 255
		# Renvoi la couleur RGB du pixel

		if value < MOY_TEMP[0]:

			ratio = (self.MOY_TEMP[0] + self.MIN_TEMP[0] + value) / 300
			return [
				round((self.MOY_TEMP[1][0] + self.MIN_TEMP[1][0]) * ratio),
				round((self.MOY_TEMP[1][1] + self.MIN_TEMP[1][1]) * ratio),
				round((self.MOY_TEMP[1][2] + self.MIN_TEMP[1][2]) * ratio)
			]

		elif value > MOY_TEMP[0]:

			ratio = (self.MOY_TEMP[0] + self.MIN_TEMP[0] + value) / 300
			return [	
				round((self.MOY_TEMP[1][0] + self.MAX_TEMP[1][0]) * ratio),
				round((self.MOY_TEMP[1][0] + self.MAX_TEMP[1][0]) * ratio),
				round((self.MOY_TEMP[1][0] + self.MAX_TEMP[1][0]) * ratio)
			]

		else:

			return self.MOY_TEMP[1]


	def resize_thermal_video(self):

		cap = cv.VideoCapture(self.store_path + 'thermal.avi')
		fourcc = cv.VideoWriter_fourcc(*'XVID')
		out = cv.VideoWriter('output.avi', fourcc, 5, (self.FINAL_RESOLUTION[0], self.FINAL_RESOLUTION[1]))

		while True:

		    ret, frame = cap.read()

		    if ret == True:

		        b = cv.resize(frame, (440, 440), fx=0, fy=0, interpolation = cv.INTER_CUBIC)
		        out.write(b)

		    else:

		        break

		cap.release()
		out.release()
		cv.destroyAllWindows()


	def create_thermal_video(self, data):

		# Transforme la valeur de temperature des pixels en une valeur RGB
		# Ecrit cette valeur dans  

		fourcc = VideoWriter_fourcc(*'MP42')	
		video = VideoWriter(self.store_path +'thermal.avi', fourcc, float(self.FPS), (self.WIDTH, self.HEIGHT))

		for frame in data["therm"]:

			colored_image = []

			for row in frame:

				colored_row = []

				for pixel in row:

					colored_image.append(self.convert_to_color(pixel))

				colored_image.append(colored_row)

			image = np.uint8(colored_image)

			video.write(image)

		video.realease()

		self.resize_thermal_video()

Analyses des données :

Cartes:

Pour analyser les données du GPS, j’utilise les modules folium et webbrowser. Le module folium permet de créer des cartes et de placer des marqueurs. Le module webbrowser permet d’ouvrir la carte sur le navigateur dès sa création. Voici le script de création des cartes :

class Map:


	def __init__(self):

		self.planisphere_map = Planisphere_Map()
		self.schematic_map = Schematic_Map()
		self.terrain_map = Terrain_Map()


	def __str__(self):

		return "Class Map"



class Planisphere_Map:


	def __str__(self):

		return "Marker Map class"


	def render_map(self, latitude, longitude, store_path):

		self.map = folium.Map(location=[latitude[0], longitude[0]], zoom_start=13)

		for i in range(len(latitude)):

			try:

				folium.Marker(
				    location=[latitude[i], longitude[i]],
				    icon=folium.Icon(color="red", icon="info-sign"),
				).add_to(self.map)

				print("Marker {0} :\tlat = {1}\tlon = {2}".format(i, latitude[i], longitude[i]))

			except IndexError:

				break


		# affiche la carte dans le navigateur
		self.map.save(store_path + "planisphere.html")
		self.url = store_path + "planisphere.html"
		webbrowser.open(self.url, new=2)



class Schematic_Map:


	def __str__(self):

		return "Class Schematic Map"


	def render_map(self, latitude, longitude, store_path):

		self.map = folium.Map(location=[latitude[0], longitude[0]], zoom_start=13, tiles="Stamen Toner")

		for i in range(len(latitude)):

			try:

				folium.Marker(
				    location=[latitude[i], longitude[i]],
				    icon=folium.Icon(color="red", icon="info-sign"),
				).add_to(self.map)

				print("Marker {0} :\tlat = {1}\tlon = {2}".format(i, latitude[i], longitude[i]))

			except IndexError:

				break


		# affiche la carte dans le navigateur
		self.map.save(store_path + "schematic.html")
		self.url = store_path + "schematic.html"
		webbrowser.open(self.url, new=2)



class Terrain_Map:


	def __str__(self):

		return "Class Terrain Map"


	def render_map(self, latitude, longitude, store_path):

		self.map = folium.Map(location=[latitude[0], longitude[0]], zoom_start=13, tiles="Stamen Terrain")

		for i in range(len(latitude)):

			try:

				folium.Marker(
				    location=[latitude[i], longitude[i]],
				    icon=folium.Icon(color="red", icon="info-sign"),
				).add_to(self.map)

				print("Marker {0} :\tlat = {1}\tlon = {2}".format(i, latitude[i], longitude[i]))

			except IndexError:

				break


		# affiche la carte dans le navigateur
		self.map.save(store_path + "terrain.html")
		self.url = store_path + "terrain.html"
		webbrowser.open(self.url, new=2)

Vidéos:

Le module opencv est un module de traitement d’image. Il permet d’afficher une vidéo, de créer une vidéo et d’analyser une vidéo. La vidéo thermique est téléchargée en format brute. Voici le diagramme de création de la vidéo thermique:

Diagramme de création de la vidéo thermique

Voici le script utilisé pour traiter les vidéos:

class Video:

	def __init__(self):

		self.classic_video = Render_Video()	
		self.motion_filtering = Video_Motion_Filtering()
		self.motion_detection = Motion_Detection()


# Affiche la vidéo sans changements
class Render_Video:

	def render(self, video_path):

		cap = cv.VideoCapture(video_path)

		# Read until video is completed
		while(cap.isOpened()):
		    
		    # Capture frame-by-frame
		    ret, frame = cap.read()
		    
		    if ret == True:

		        # Display the resulting frame
		        cv.imshow('Frame', frame)

		    # Press Q on keyboard to  exit
		    if cv.waitKey(25) & 0xFF == ord('q'):
		        break

		# When everything done, release the video capture object
		cap.release()

		# Closes all the frames
		cv.destroyAllWindows()


# Filtrage des formes en mouvements sur la vidéo
class Video_Motion_Filtering:

	def render(self, video_path):

		# variables
		substractor_min = 20
		substractor_max = 50

		# charge la video et les composants nécéssaires pour le filtrage des mouvements 
		video = cv.VideoCapture(video_path)
		substractor = cv.createBackgroundSubtractorMOG2(20, 50)

		# Recherche si la vidéo est toujours en cours:
		# 	- si oui, applique le détourage tant que la touche 'x' n'est pas pressée
		# 	- si non, relance la vidéo

		while True:

			return_value, frame = video.read()

			if return_value:

				mask = substractor.apply(frame)
				cv.imshow('Mask', mask)

				# changement des variables
				key = cv.waitKey(30)&0xFF
				
				if key == ord('x'):
					break

				if key == ord('p'):

					substractor_max = min(100, substractor_max + 5)

				if key == ord("m"):

					substractor_min = max(5, substractor_min - 5)

			else:
				
				video = cv.VideoCapture(video_path)



		video.release()
		cv.destroyAllWindows()


# Detourage des formes en mouvements
class Motion_Detection:

	def render(self, video_path):

		# Chargement de la video
		capture = cv.VideoCapture(video_path)

		# Valeurs d'analyse
		kernel_blur = 1
		seuil = 15
		surface = 1000

		# Video sans changement des variables d'analyse
		ret, origin = capture.read()

		origin = cv.cvtColor(origin, cv.COLOR_BGR2GRAY)
		origin = cv.GaussianBlur(origin, (kernel_blur, kernel_blur), 0)

		kernel_dilate = np.ones((5, 5), np.uint8)

		while True:

			# Video avec changement des variables d'analyse
			ret, frame = capture.read()

			gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
			gray = cv.GaussianBlur(gray, (kernel_blur, kernel_blur), 0)
			cv.imshow("frame", gray)

			# Comparaison des deux flux videos
			mask = cv.absdiff(origin, gray)
			mask = cv.threshold(mask, seuil, 255, cv.THRESH_BINARY)[1]
			mask = cv.dilate(mask, kernel_dilate, iterations=3)

			# Ajout des contours des objets en mouvement
			contours, nada = cv.findContours(mask, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
			frame_contour = frame.copy()

			for c in contours:

				cv.drawContours(frame_contour, [c], 0, (0, 255, 0), 5)

				if cv.contourArea(c)< surface:

					continue

				x, y, w, h = cv.boundingRect(c)
				cv.rectangle(frame, (x, y), (x+w, y+h), (0, 0, 255), 2)

			origin = gray

			# Ajout des textes
			cv.putText(frame, "[o|1]seuil: {:d} [p|m]blur: {:d} [i|k]surface: {:d}".format(seuil, kernel_blur, surface), (10, 30), cv.FONT_HERSHEY_PLAIN, 1, (0, 255, 255), 2)
			# Affichage du resultat
			
			cv.imshow("frame", frame)

			# Changement des variables
			key = cv.waitKey(30)&0xFF

			if key == ord('x'):

				break
			
			try:

				if key == ord('p'):

					kernel_blur = min(43, kernel_blur + 2)

				if key == ord('m'):

					kernel_blur = max(1, kernel_blur - 2)
				
				if key == ord('s'):

					surface += 100

				if key == ord('r'):

					surface -= 100

				if key == ord('o'):

					seuil = min(255, seuil + 1)

				if key == ord('l'):

					seuil = max(1, seuil - 1)

			except Exception:

				pass

		capture.release()
		cv.destroyAllWindows()

Graphiques :

Pour rendre les données compréhensibles, j’utilise le module matplotlib afin de créer des graphiques traduisant l’évolution des données au cours du temps. La gestion des graphiques est basée sur un modèle ‘flex’ puisqu’il suffit d’appeler la fonction en plaçant les données que l’on veut traiter en paramètre. Voici le script:

class Graph:


	def __str__(self):

		return "Graph class"


	def __init__(self, data_config_path):

		self.data_config_path = data_config_path
		self.normal_graph = Normal_Graph(self.data_config_path)
		self.multiple_graph = Multiple_Graph(self.data_config_path)
		self.full_graph = Full_Graph(self.data_config_path)


# Trace l'evolution d'une seule mesure

class Normal_Graph:


	def __init__(self, data_config_path):

		with open(data_config_path, "r") as file:
			
			self.data_config = json.load(file)
			file.close()


	def __str__(self):

		return "Normal class Graph"


	def create_grah(self, data, data_name, language_controller):

		# valeur x et y du graph
		x = [0]
		y = []
		
		# frequence de l'enregistrement
		x_step = 1 / self.data_config[data_name]["fps"]

		for _ in range(len(data) - 1):

			x.append(x_step)
			x_step += x_step

		X = np.array(x)
		Y = np.array(data, dtype="float64")

		# configuration des graphiques
		
		plt.plot(X, Y, 'r-+')
		plt.xlabel(language_controller.get_text("time"))
		plt.ylabel(self.data_config[data_name]["name"] + ' (' + self.data_config[data_name]["unit"] + ")")

		plt.title(self.data_config[data_name]["name"] + ' ' + language_controller.get_text("overTime"))
		plt.show()



# Trace l'evolution d'un ensemble de mesures de meme type

class Multiple_Graph:


	def __init__(self, data_config_path):

		with open(data_config_path, "r") as file:
			
			self.data_config = json.load(file)
			file.close()


	def __str__(self):

		return "Multiple Graph Class"


	def create_graph(self, data, data_names, data_type):

		# Creation des 'sous' graphiques
		fig, axs = plt.subplots(len(data))
		fig.suptitle(self.data_config[data_type]["name"] + " en fonction du temps.")

		for i, unit in enumerate(data):

			x = []
			x_step = 1 / self.data_config[data_type]["fps"]


			for _ in range(len(unit)):

				x.append(x_step)
				x_step += x_step

			X = np.array(x)
			Y = np.array(unit, dtype="float64")

			axs[i].plot(X, Y, '-o', label=data_names[i])
			axs[i].legend(data_names[i])

		plt.show()


# Trace toutes les mesures

class Full_Graph:


	def __init__(self, data_config_path):

		with open(data_config_path, "r") as file:
			
			self.data_config = json.load(file)
			file.close()


	def __str__(self):

		return "Full Graph Class"


	def create_graph(self, datas, data_names):


		fig, ax = plt.subplots()

		# FPS
		x_step  = 1 / 10

		lines = []

		for i, data in enumerate(datas):

			x = []

			for _ in range(len(data)):

				x.append(x_step)
				x_step += x_step

			X = np.array(x)
			Y = np.array(data, dtype="float64")

			print(len(X))
			print(len(Y))
			i, = ax.plot(X, Y, label=data_names[i])
			lines.append(i)

		rax = plt.axes([0.05, 0.4, 0.1, 0.15])
		labels = [str(line.get_label()) for line in lines]
		visibility = [line.get_visible() for line in lines]
		check = CheckButtons(rax, labels, visibility)


		def change_visivility(label):

		    index = labels.index(label)
		    lines[index].set_visible(not lines[index].get_visible())
		    plt.draw()


		check.on_clicked(change_visivility)

		plt.show()

Ce que je compte ajouter :

L’application fonctionne correctement et ne requière pas de mise à jour particulière. Je souhaite donc orienter les améliorations sur le UX (l’expérience d’utilisation).

Le programme complet :

diagramme complet


Écrit avec StackEdit.