En esta entrada se va a tratar la fusión de sensores. En este caso se ha optado por usar la lógica fuzzy, también llamada lógica difusa, ya que da bastantes buenos resultados, ya que se adapta más al mundo real, al no usar valores fijos (5 o 10), sino un rango de valores (5 a 10) y es capaz de adaptar expresiones humanas como "hace mucho calor" o "no hace frío", donde no existe una certeza de qué cantidad de temperatura tiene que hacer para que se pueda pensar una opción u la otra.
La clave de la lógica fuzzy es que trata de comprender los cuantificadores de cualidad para nuestras inferencias (mucho, poco, muy, etc.). También esta lógica hace uso de operaciones sobre conjuntos como la unión, la intersección, diferencia, negación, etc. para llevar a cabo el tratamiento de la información y llegar a un resultado.
En la lógica fuzzy existen dos conjuntos difusos básicos llamados antecedentes (conjunto difuso de entrada) y consecuentes (conjunto difuso de salida). Por otro lado, para cada elemento a tratar existe una función de pertenencia, que indica en qué medida el elemento forma parte de ese conjunto difuso.
En la imagen anterior se puede ver la temperatura, que contiene 3 etiquetas, correspondientes a los trapecios que se aprecian. Cada una de estas etiquetas tienen un rango de valores, donde en ciertos puntos la certeza es máxima (1, donde habría un grado de pertenencia máximo a la etiqueta de frío), es decir, se está seguro de que hace frío (cold) y otros donde empieza a subir la temperatura y según donde esté se puede considerar frío en cierto grado o comenzar a acercarse más a una temperatura más cálida.
Además de lo anteriormente mencionado, la lógica fuzzy hace uso de una serie de reglas heurísticas, del tipo
SI (antecedentes) ENTONCES (consecuente). Un ejemplo sería:
- Si hace mucho frío entonces subir mucho la calefacción.
- Si hace poco frío entonces subir un poco la calefacción.
- Si hace mucho calor entonces apagar la calefacción.
Dependiendo del valor de entrada, éste tendrá un grado de pertenencia con cada una de las reglas y se tomará una decisión (consecuente) de acuerdo a ese grado. La decisión puede ser la regla con mayor grado, la suma aritmética de todos los grados con el mismo consecuente y eligiendo el de mayor valor y otra serie de técnicas.
Una vez explicado por encima el principio básico de la lógica fuzzy y para llevarla a cabo con Arduino se ha optado por usar la "librería Fuzzy para Arduino y sistemas embebidos", comentada en la entrada de
Fusión de sensores. Esta librería es bastante tediosa de usar, pero, una vez conocida la estructura a usar, es bastante fácil de manejar.
A continuación se procede a explicar el uso de esta librería:
Lo primero que hay que hacer es descargar la librería y añadirla en Arduino haciendo uso de la inclusión de librerías de Arduino (simplemente se añade un zip con los ficheros descargados del GitHub del proyecto y en Arduino ir a Programa -> Incluir librería -> Añadir librería .zip). Una vez se añade la librería se incluye esta al programa eligiendo la nueva opción que aparecerá llamada eFLL dentro de la opción de Incluir librería anteriormente mencionada.
A continuación se generaría un objeto de la clase Fuzzy y se generarían las etiquetas correspondientes a cada uno de los conjuntos a definir. En nuestro caso como conjunto de entrada se tendría la luz con dos etiquetas correspondientes a que no existe luz o existe luz, el conjunto de entrada para la distancia con tres etiquetas que serían no existe coche, coche alto y coche bajo. Para el conjunto de salida simplemente tendría dos etiquetas correspondientes a si hay un coche o no.
En el siguiente extracto del programa se ve lo explicado anteriormente:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| #include <Fuzzy.h>
#include <FuzzyComposition.h>
#include <FuzzyInput.h>
#include <FuzzyIO.h>
#include <FuzzyOutput.h>
#include <FuzzyRule.h>
#include <FuzzyRuleAntecedent.h>
#include <FuzzyRuleConsequent.h>
#include <FuzzySet.h>
// Paso 1 - Instanciando un objeto de la librería fuzzy
Fuzzy* fuzzy = new Fuzzy();
//Paso 2 - Crear etiquetas
//Etiquetas luz
FuzzySet* noExisteLuz = new FuzzySet(0, 0, 275, 425);
FuzzySet* existeLuz = new FuzzySet(275, 425, 800, 800);
//Etiquetas distancia
FuzzySet* cocheAlto = new FuzzySet(0, 0, 127.5, 142.5);
FuzzySet* cocheBajo = new FuzzySet(127.5, 142.5, 155, 165);
FuzzySet* noExisteDistancia = new FuzzySet(165, 170, 210, 210);
//Etiquetas salida
FuzzySet* noExisteCoche = new FuzzySet(0, 0, 0, 0);
FuzzySet* existeCoche = new FuzzySet(1, 1, 1, 1);
|
Una vez se crean las etiquetas hay que crear los conjuntos de entrada y salida usando esas etiquetas. También se generaría el conjunto de reglas que se usa, como por ejemplo, Si no hay luz y coche alto Entonces existe coche.
Todo ello se ha añadido a una función para hacer uso de esta en el setup. A continuación se ve lo descrito:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
| void initFuzzy(){
// Paso 3 - Creando conjunto de entrada de luz
FuzzyInput* luz = new FuzzyInput(1);// Como parámetro necesita un ID
luz->addFuzzySet(noExisteLuz); // Se añade la etiqueta
luz->addFuzzySet(existeLuz);
fuzzy->addFuzzyInput(luz);
//Igual para la distancia
FuzzyInput* distancia = new FuzzyInput(2);// Como parámetro necesita un ID
distancia->addFuzzySet(noExisteDistancia); // Se añade la etiqueta
distancia->addFuzzySet(cocheBajo);
distancia->addFuzzySet(cocheAlto);
fuzzy->addFuzzyInput(distancia); // Se añade el conjunto al objeto fuzzy
// Paso 4 - Creando conjunto de salida
FuzzyOutput* coche = new FuzzyOutput(1);// ID de parámetro
coche->addFuzzySet(noExisteCoche); // Añadir etiqueta al conjunto de salida
coche->addFuzzySet(existeCoche);
fuzzy->addFuzzyOutput(coche); // Añadir el conjunto de salida al objeto fuzzy
//Paso 5 - Conjunto de reglas
//Consecuentes
FuzzyRuleConsequent* thenExisteCoche = new FuzzyRuleConsequent(); //Creación de consecuente
thenExisteCoche->addOutput(existeCoche);// Juntando consecuente con salida
FuzzyRuleConsequent* thenNoExisteCoche = new FuzzyRuleConsequent();
thenNoExisteCoche->addOutput(noExisteCoche);
// Regla 1 - Si existe luz y no existe distancia, existe un coche
FuzzyRuleAntecedent* siNoExisteLuzYnoExisteDistancia = new FuzzyRuleAntecedent(); //Creando antecedente
siNoExisteLuzYnoExisteDistancia->joinWithAND(noExisteLuz, noExisteDistancia); //Añadiendo etiquetas al antecedente
FuzzyRule* fuzzyRule01 = new FuzzyRule(1, siNoExisteLuzYnoExisteDistancia, thenExisteCoche); // Creando la regla
fuzzy->addFuzzyRule(fuzzyRule01); // Añadiendo regla al conjunto de reglas
// Regla 2 - Si existe luz y coche alto, existe un coche
FuzzyRuleAntecedent* siNoExisteLuzYcocheAlto = new FuzzyRuleAntecedent();
siNoExisteLuzYcocheAlto->joinWithAND(noExisteLuz, cocheAlto);
FuzzyRule* fuzzyRule02 = new FuzzyRule(2, siNoExisteLuzYcocheAlto, thenExisteCoche);
fuzzy->addFuzzyRule(fuzzyRule02);
// Regla 3 - Si existe luz y coche bajo, existe un coche
FuzzyRuleAntecedent* siNoExisteLuzYcocheBajo = new FuzzyRuleAntecedent();
siNoExisteLuzYcocheBajo->joinWithAND(noExisteLuz, cocheBajo);
FuzzyRule* fuzzyRule03 = new FuzzyRule(3, siNoExisteLuzYcocheBajo, thenExisteCoche);
fuzzy->addFuzzyRule(fuzzyRule03);
// Regla 4 - Si no existe luz y no existe distancia, no existe un coche
FuzzyRuleAntecedent* siExisteLuzYnoExisteDistancia = new FuzzyRuleAntecedent();
siExisteLuzYnoExisteDistancia->joinWithAND(existeLuz, noExisteDistancia);
FuzzyRule* fuzzyRule04 = new FuzzyRule(4, siExisteLuzYnoExisteDistancia, thenNoExisteCoche);
fuzzy->addFuzzyRule(fuzzyRule04);
// Regla 5 - Si no existe luz y coche Alto, existe un coche
FuzzyRuleAntecedent* siExisteLuzYcocheAlto = new FuzzyRuleAntecedent();
siExisteLuzYcocheAlto->joinWithAND(existeLuz, cocheAlto);
FuzzyRule* fuzzyRule05 = new FuzzyRule(5, siExisteLuzYcocheAlto, thenExisteCoche);
fuzzy->addFuzzyRule(fuzzyRule05);
// Regla 6 - Si no existe luz y coche bajo, no existe un coche
FuzzyRuleAntecedent* siExisteLuzYcocheBajo = new FuzzyRuleAntecedent();
siExisteLuzYcocheBajo->joinWithAND(existeLuz, cocheBajo);
FuzzyRule* fuzzyRule06 = new FuzzyRule(6, siExisteLuzYcocheBajo, thenNoExisteCoche);
fuzzy->addFuzzyRule(fuzzyRule06);
}
|
Una vez se tiene creada la lógica fuzzy, para hacer uso de esta se usan los métodos que esta librería incluye. Básicamente se trata de darle un valor a cada uno de los conjuntos de entrada, usar la función de fuzzify, ver el grado de emparejamiento si se desea usando el método getPertinence de cada etiqueta y recoger la salida del conjunto de consecuente con deffuzify.
Hay que tener en cuenta que cada conjunto de entrada y salida tiene asociado un identificador para poder llamarlos después.
Todo lo descrito anteriormente se ha plasmado en un método, que recibe como parámetros de entrada la luz y la distancia y devuelve si no hay coche (0) o si lo hay (1) que son los valores correspondientes a las etiquetas del conjunto de salida. Este método es el que se usará en el loop
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| int getFuzzyValue(float luz, float distancia){
Serial.println("Datos que llegan: ");
Serial.print("Luz: ");
Serial.print(luz);
Serial.print(" distancia ");
Serial.println(distancia);
//Paso 7 - Iniciar entradas y fuzzificar
fuzzy->setInput(1, luz);
fuzzy->setInput(2, distancia);
fuzzy->fuzzify();
//Ver pertenencia a cada etiqueta
Serial.print("Luz: ");
Serial.print("no existe luz: ");
Serial.print(noExisteLuz->getPertinence());
Serial.print(", existe luz ");
Serial.println(existeLuz->getPertinence());
Serial.print("Distancia: ");
Serial.print("No existe distancia: ");
Serial.print(noExisteDistancia->getPertinence());
Serial.print(", Coche alto: ");
Serial.print(cocheAlto->getPertinence());
Serial.print(", cocheBajo");
Serial.println(cocheBajo->getPertinence());
//Paso 8 - Defuzzificar y coger valor salida
int coche = fuzzy->defuzzify(1);
Serial.print("Existe coche? ");
Serial.println(coche);
return coche;
}
|
Con estos dos métodos de creación y uso del Fuzzy se finalizaría la explicación de esta fase. Habría que mencionar que cada una de las etiquetas hay que darle un valor para generar un trapezoide como se ve con la temperatura. Estos valores se han extraído de la experimentación y se han puesto directamente para ahorrar tiempo,
pero lo ideal sería que esos valores se automatizaran usando los datos de la calibración esta forma de generar las etiquetas usando los valores almacenados en la EEPROM se pueden ver en el repositorio, en el enlace siguiente:
Plaza arduino con etiquetas fuzzy generadas automáticamente