Optischer Sensor, Software

Aus FHEMWiki
Zur Navigation springen Zur Suche springen



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

Forumsthread

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. Wer das nicht alles von Hand installieren will, kann es sich (jedenfalls mit Debian 8) auch einfacher machen:

apt-get install libopencv-dev python-opencv

Windows

Folgende Programme runterladen und in die vorgeschlagenen (!) Ordner installieren


Jetzt bitte alles von C:\opencv\build\python\x86\2.7\ (vermutlich nur cv2.pyd) in den Ordner C:\Python27\Lib\site-packages\ kopieren. Das ist wichtig, damit Python OpenCV findet.


Zum Test kann man unter Windows eimfach maal Python starten und

import cv2

Eingeben... gibt es keinen Fehler, hat es geklappt.

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 3-8 natürlich rauswerfen; andernfalls werden sie schlicht ignoriert, tun also 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 :)