Optischer Sensor, Software: Unterschied zwischen den Versionen
Rince (Diskussion | Beiträge) |
Rince (Diskussion | Beiträge) |
||
Zeile 97: | Zeile 97: | ||
===Kalibrierung des Sensors=== | ===Kalibrierung des Sensors=== | ||
Das Programm kennt zwei verschiedene Betriebsmodi: | |||
====RGB / BGR==== | |||
RGB (dafür in der json den Wert "hsv" auf false setzen) | |||
RGB eignet sich gut, um Grafiken zu untersuchen. Siehe das DWD Beispiel. Bitte als B G R die Werte eingeben (nicht RGB). | |||
Wenn es jeweils nur eine Farbe ist, die gesucht werden soll, so ist der upper/lower Wert natürlich identisch. | |||
Die Werte kann man schlicht mit Gimp auslesen (Pipette, auf die gewünschte Farbe klicken, im Werkzeugfenster auf die Farbe klicken, dann stehen die Werte da...) | |||
====HSV==== | |||
Das ist etwas tricky. | |||
Obacht: didaktische Reduktion! | |||
Prinzipiell kann man sich HSV zunächst mal als einen Farbkreis vorstellen. Damit bekommt jede Farbe einen Winkel. Der Witz an der Sache ist, dass die Helligkeit hier keine Rolle spielt. Die versteckt sich nämlich nicht auf unserem Farbkreis! Diese wird durch die Tiefe repräsentiert. Dazu legen wir jetzt unseren Farbkreis auf eine Papprolle (und knippsen oben ein imaginäres Licht an). | |||
Wenn wir uns jetzt die Farbe "Rot" vorstellen, dann gibt es davon ziemlich viele verschiedene. Von sehr hell, bis sehr dunkel. Der Winkel für helles und der für dunkles Rot ist im HSV Farbkreis der gleiche. Das macht diesen Farbraum für die Auswertung von Fotos sehr praktisch. Man kann schön mit zwei Werten quasi alle Rot-Töne abdecken. | |||
Der Haken: | |||
OpenCV sieht leider HSV anders, als GIMP. Daher ist es nicht ganz trivial, die passenden Werte für die Farben rauszufinden. | |||
Gebe im Forum dazu gerne Hilfestellung. | |||
==Programmcode== | ==Programmcode== |
Version vom 4. August 2015, 11:56 Uhr
Todo: Das wird die Doku für den optischen Sensor. Der Programmcode ist ziemlich weit gediehen. Ich dachte mir, die Doku mal zum Start eines Projekts fertig zu haben, ist auch nicht schlecht |
Was ich noch nicht weiß
- soll das Programm regelmäßig per Cron ausgeführt werden, oder ist es schlauer einen WebSocket zu öffnen, so dass fhem dem Programm sagen kann, wenn es ein Bild bearbeiten soll
Treshold für Farben im Programm anwenden,oder nackten Zähler direkt an fhem senden- besser, eine Weiterverarbeitung der Werte in fhem
- fhem ist diesbezüglich viel mächtiger
Übersicht
Das kleine Programm liest ein Farbbild ein. Anschließend werden (in definierbaren Bereichen) Farbwerte ermittelt. Das Resultat wird dann an fhem gesendet.
Wozu ist das gut?
Mein Anwendungsfall:
Eine USB Kamera am Raspberry macht regelmäßig Bilder meiner Küche. Darin sind insbesondere die Geschirrspülmaschine und der Herd zu sehen. Wenn meine Geschirrspülmaschine Dinge wie Salz oder Klarspüler benötigt, leuchtet eine entsprechende LED rot auf. Das kann das Programm erkennen und fhem mitteilen. Ähnlich beim Herd: ist der an, leuchtet es gelb. Beim Verlassen des Hauses kann mir fhem sagen, dass der Herd noch an ist...
Wichtig: Die Kamera darf nicht bewegt werden, sonst funktionieren die Masken nicht mehr! Also z.B. festschrauben...
Anderer Anwendungsfall:
Ein Bild (z.B. Bodenfeuchte) vom DWD runterladen (das müsst ihr selbst!), dann analysieren lassen, welche Bodenfeuchte bei euch ist. Selbiges wird an fhem gesendet.
Einfach für jede mögliche Farbe eine eigene Maske erstellen (die ist natürlich identisch), in die conf die Farbwerte und die setreadings schreiben, fertig...
Beschreibung
Ein bisschen mehr ins Detail gehend:
Das Programm erwartet ein farbiges Bitmap Bild. Anschließend werden Masken über das Bild gelegt. Was übrig bleibt (idealerweise also der Bildausschnitt wo die entsprechende LED zu sehen ist) wird in den HSV Farbraum konvertiert. Eine Beschreibung warum und wie er funktioniert, folgt noch. Dann werden Die gewünschten Farben gefiltert. Intern übrig bleiben dann einige weiße Pixel auf schwarzem Grund. Die werden schlicht gezählt. Der Wert wird an fhem gesendet.
Konfiguration der Software
Das Programm an sich benötigt eine Config-Datei.
{ "fhem_host_port": "http://192.168.5.31:8083", "fhem_request": "/fhem?cmd=", "different_ROI": "5", "path_to_picture": "./Picture", "picture": "picture_kitchen", "type": "jpg", "mask": "dishwasher", ...
Was ist hier einzustellen?
- "fhem_host_port": Adresse des fhem Servers, ebenso wie der Port. (Muss man also anpassen)
- "fhem_request": (weiß nicht, wie es heißt), sollte aber so passen
- "different_ROI": ist spannend: hier sagt man, wie viele LEDs (ROI = region of interesst) man überwachen will. (für jede ROI muss genau 1 Maske existieren)
- "path_to_picture": logisch, wo liegt das Bild? In diesem Fall in einem Unterordner Namens Picture
- "picture": der Name des Bildes (aufgemerkt: das Programm geht also immer vom gleichen Dateinamen aus!) (Quellbild)
- "type": bitte mit angeben; jpg, png...
- "mask": der erste Teil des Maskennamens
- die Dateiendung nicht angeben, bitte die gleiche wie beim Quellbild verwenden
- so kann man bei einem Quellbild verschiedene Masken angeben (in verschiedenen Config Dateien)
- bei Karten vom DWD z.B. kann man so aus einem Quellbild Werte für verschiedene Regionen ermitteln
- munic.conf, berlin.conf
- bei mehr Küchengeräten z.B. dishwasher.conf und waterboiler.conf
Was sind diese Masken?
Die sind cool. Einfache schwarz/weiß Bilder.
Zum erstellen: Ein Musterbild von der Kamera besorgen. In Gimp öffnen, den Bereich um eine der gewünschten LEDs ein Viereck oder ein Kreis, ist egal) markieren. Löschen, Füllfarbe weiß. Markierung umdrehen, löschen, Füllfarbe schwarz. Fertig.
Stellt euch vor, die Maske wäre aus Tonpapier. Da wo weiß ist, ist ein Loch drin. Die Maske wird über das Bild gelegt. Nun seht ihr nur noch die gewünschte LED...
Wichtig:
Die Maske braucht nun einen festen Dateinamen, damit sie gefunden werden kann! Hängt an den in der Config angegebenen Dateinamen ein _mask_1 an, für eure erste Maske. Hättet ihr ROI = 1, ist das alles. Bei ROI = 2 wollen wir 2 Masken abarbeiten.
Also, bei ROI = 3
- quellbild.jpg (das, was eure Kamera immer generiert, oder euch der DWD zur Verfügung stellt)
- <AngegebenerName>_mask_1.jpg (die Dateiendung bitte wie beim Quellbild)
- <AngegebenerName>_mask_2.jpg
- <AngegebenerName>_mask_3.jpg
Benötigte Frameworks
folgt noch
- Linux
- Diese Anleitung ist wirklich gut. Habe es auf meinem RasPi auch so gemacht.
- Windows
- bei Bedarf gerne
Kalibrierung des Sensors
Das Programm kennt zwei verschiedene Betriebsmodi:
RGB / BGR
RGB (dafür in der json den Wert "hsv" auf false setzen) RGB eignet sich gut, um Grafiken zu untersuchen. Siehe das DWD Beispiel. Bitte als B G R die Werte eingeben (nicht RGB).
Wenn es jeweils nur eine Farbe ist, die gesucht werden soll, so ist der upper/lower Wert natürlich identisch.
Die Werte kann man schlicht mit Gimp auslesen (Pipette, auf die gewünschte Farbe klicken, im Werkzeugfenster auf die Farbe klicken, dann stehen die Werte da...)
HSV
Das ist etwas tricky.
Obacht: didaktische Reduktion!
Prinzipiell kann man sich HSV zunächst mal als einen Farbkreis vorstellen. Damit bekommt jede Farbe einen Winkel. Der Witz an der Sache ist, dass die Helligkeit hier keine Rolle spielt. Die versteckt sich nämlich nicht auf unserem Farbkreis! Diese wird durch die Tiefe repräsentiert. Dazu legen wir jetzt unseren Farbkreis auf eine Papprolle (und knippsen oben ein imaginäres Licht an).
Wenn wir uns jetzt die Farbe "Rot" vorstellen, dann gibt es davon ziemlich viele verschiedene. Von sehr hell, bis sehr dunkel. Der Winkel für helles und der für dunkles Rot ist im HSV Farbkreis der gleiche. Das macht diesen Farbraum für die Auswertung von Fotos sehr praktisch. Man kann schön mit zwei Werten quasi alle Rot-Töne abdecken.
Der Haken:
OpenCV sieht leider HSV anders, als GIMP. Daher ist es nicht ganz trivial, die passenden Werte für die Farben rauszufinden.
Gebe im Forum dazu gerne Hilfestellung.
Programmcode
Der Programmcode ist im ersten Forumsthread mit einigen Beispielen zum Download. Download
Sensor
Anmerkung:
Ich bin kein Programmierer. Wenn das jemand durchliest der Ahnung hat, sich totlacht => ich übernehme keine Verantwortung
#!/usr/bin/env python # Optical Sensor # using Python 2.7 and OpenCV 2.4.11 # do not use german umlauts, not even in description # ver 0.2 first public release import cv2 import argparse import json import urllib import numpy as np #cv2 importiert OpenCV #argparse wird benoetigt fuer die Parameteruebergabe #json zum lesen des Config-Files #urllib um uns mit fhem zu unterhalten # construct the argument parse and parse the arguments ap = argparse.ArgumentParser() ap.add_argument("-c", "--conf", required=True, help="path to the JSON configuration file; eg try: >sensor.py --conf dishwasher.json") args = vars(ap.parse_args()) #ConfigFile laden conf = json.load(open(args["conf"])) client = None #Gespeichertes Bild mit Pfad in Variable path = (conf["path_to_picture"]) picture = (conf["picture"]) type = (conf["type"]) first_picture = path + "/" + picture + "." + type #Namen der Maske auslesen first_mask = (conf["mask"]) #Laden des Bildes image = cv2.imread(first_picture) # Bild anzeigen #cv2.imshow("Originalbild", image) # Schleife erzeugen ROI = int(conf["different_ROI"]) print "Soll-Durchlaeufe " + str(ROI) i = 0 while i < ROI: i = i + 1 # Maskenpfad und Dateinamen generieren mask = path + "/" + first_mask + "_mask_" + str(i) + "." + type # Maske laden mask = cv2.imread(mask,0) # Maske anwenden masked = cv2.bitwise_and(image, image, mask = mask) #cv2.imshow("Maske", masked) # BGR in HSV konvertieren wenn gewollt if conf["hsv"]: masked = cv2.cvtColor(masked, cv2.COLOR_BGR2HSV) #cv2.imshow("HSV", masked) # Grenzwerte aus Config auslesen lower_col_value = tuple(conf["lower_col" + "_" + str(i)]) upper_col_value = tuple(conf["upper_col" + "_" + str(i)]) #print lower_col #print upper_col # Range definieren lower_col = np.array([(lower_col_value)], dtype=np.uint8) upper_col = np.array([(upper_col_value)], dtype=np.uint8) # Threshold the image to get only selected colors mask = cv2.inRange(masked, lower_col, upper_col) #cv2.imshow("Maske", mask) # Pixel zaehlen white = cv2.countNonZero(mask) print('The number of positive pixels is: ' + str(white)) # Pixelanzahl an fhem senden # fhem Kommando erzeugen url = (conf["fhem_host_port"]) request = (conf["fhem_request"]) command = (conf["fhem_command" + "_" + str(i)]) value = "%20" + str(white) fhem = url + request + command + value #print fhem urllib.urlopen(fhem) #cv2.waitKey(0) print "Fertig" #cv2.waitKey(0) cv2.destroyAllWindows()
Config-File
So kann ein Config File aussehen
5seenland.json (könnte ein guter Dateinname sein...)
{ "config_version": "0.2", "fhem_host_port": "http://192.168.5.31:8083", "fhem_request": "/fhem?cmd=", "different_ROI": "8", "path_to_picture": "./Picture", "picture": "bodenfeuchte", "type": "png", "mask": "5seenland", "hsv": false, "lower_col_1": [45,100,200], "upper_col_1": [45,100,200], "lower_col_2": [60,200,250], "upper_col_2": [60,200,250], "lower_col_3": [75,250,250], "upper_col_3": [75,250,250], "lower_col_4": [75,250,200], "upper_col_4": [75,250,200], "lower_col_5": [75,220,150], "upper_col_5": [75,220,150], "lower_col_6": [75,150,75], "upper_col_6": [75,150,75], "lower_col_7": [250,200,50], "upper_col_7": [250,200,50], "lower_col_8": [250,0,0], "upper_col_8": [250,0,0], "fhem_command_1": "setreading%20Bodenfeuchte%200-10", "fhem_command_2": "setreading%20Bodenfeuchte%2010-30", "fhem_command_3": "setreading%20Bodenfeuchte%2030-50", "fhem_command_4": "setreading%20Bodenfeuchte%2050-80", "fhem_command_5": "setreading%20Bodenfeuchte%2080-95", "fhem_command_6": "setreading%20Bodenfeuchte%2095-100", "fhem_command_7": "setreading%20Bodenfeuchte%20100-105", "fhem_command_8": "setreading%20Bodenfeuchte%20105-" }
Eigentlich ganz einfach. Für jede ROI benötigt man lower_col_?, upper_col_? und fhem_command_?
Die lower/upper cols definieren für jede ROI den zu suchenden Farbbereich... das Ergebnis wird dann an den fhem_command_? angehängt.
Hat man nur 2 ROI, kann man die Werte 4-8 natürlich rauswerfen; andernfalls werden sie schlicht ignoriert, tun also auch nicht weh. Sind nur eben sinnbefreit.
Beispiel mit Bildern
Ist denke ich im Forumsthread schon ganz schön beschrieben.
Wer eigene Beispiele hat, immer her damit :)