kramann.info
© Guido Kramann

Login: Passwort:










kramann.info
© Guido Kramann

Login: Passwort:




Optimierung der Linienverfolgung mittells evolutionärem Algorithmus

(EN google-translate)

(PL google-translate)

Die zuletzt diskutierte Möglichkeit Wegmarken zu verwenden, soll zunächst zugunsten des nachfolgenden vielversprechenderen Ansatzes für ein selbst lernendes System zurückgestellt werden:

Auf der Grundlage der zuletzt verbesserten Schaltgeschwindigkeit der GPIO, wurde ein weiteres verbessertes Processing-Skript entwickelt, das nun Linienverfolgung ermöglicht.

Deshalb wird hier die komplette Software in modifizierter Version erneut gezeigt:

Graduell verbesserte Version zur Erzeugung von PWM-Signalen mit Kamera-basiertem Beispiel für Linienverfolgung


Besondere Tricks:


  1. Kamera auf "Schwanenhals" setzen, um den Bildausschnitt zu vergrößern.
  2. in PWMlinks und PWMrechts wird eine feine Auflösung für das PWM-Signal möglich, indem die Dauer der Schaltvorgänge "ignoriert" und die Pausen dazwischen in Millisekunden eingestellt werden. Der PWM-Bereich wird dadurch begrenzt aber feiner.
  3. in dem Hauptprogramm des Sketches wird die zuletzt gültige Abweichung in aalt gespeichert und benutzt, um auch dann in die richtige Richtung zu drehen, wenn die rote Linie verloren wurde.
jnilinie.zip -- Alle nachfolgend aufgeführten Quelltexte.
cd jetson_linienverfolgung/04_jnineu
javac jetson/JetsonMotor.java 
javah -jni -o ./jetson/jetsonmotor.h jetson.JetsonMotor
cd jetson
gcc -o jetsonmotor.so -shared -Wl,-soname,jetsonmotor.so -I/usr/lib/jvm/java-8-openjdk-arm64/include/ -I/usr/lib/jvm/java-8-openjdk-arm64/include/linux/ jetsonmotor.c -lc
sudo cp ./jetsonmotor.so /opt
sudo chmod +x /opt/jetsonmotor.so

neu anlegen:
fuer_jar_datei/jetson/JetsonMotor.class
cd ..
cd fuer_jar_datei
jar cvf jetson.jar *

..in vorhandener library die neue .jar Datei ersetzen.

Jetzt Testprogramm zur Motoransteuerung schreiben:
cd ~
./processing-4.0b6-linux-arm64/processing-4.0b6/processing 

Die Abarbeitung eines Motorbefehls dauert JETZT knapp etwas unter 10ms

Code 0-1: Ablauf zu Herstellung, Ersetzung und Test der JNI-Library

package jetson;

public class JetsonMotor
{
    static 
    {
        System.load("/opt/jetsonmotor.so");
    }
    public static native void init(); //NEU
    public static native void enablelinks(); //NEU
    public static native void disablelinks(); //NEU
    public static native void enablerechts(); //NEU
    public static native void disablerechts(); //NEU
    public static native void linksstop();
    public static native void linksvor();
    public static native void linksrueck();
    public static native void rechtsstop();
    public static native void rechtsvor();
    public static native void rechtsrueck();
}

Code 0-2: jetson/JetsonMotor.java -- Neue Java-Klasse zur Ansteuerung der GPIO

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class jetson_JetsonMotor */

#ifndef _Included_jetson_JetsonMotor
#define _Included_jetson_JetsonMotor
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     jetson_JetsonMotor
 * Method:    init
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_jetson_JetsonMotor_init
  (JNIEnv *, jclass);

/*
 * Class:     jetson_JetsonMotor
 * Method:    enablelinks
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_jetson_JetsonMotor_enablelinks
  (JNIEnv *, jclass);

/*
 * Class:     jetson_JetsonMotor
 * Method:    disablelinks
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_jetson_JetsonMotor_disablelinks
  (JNIEnv *, jclass);

/*
 * Class:     jetson_JetsonMotor
 * Method:    enablerechts
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_jetson_JetsonMotor_enablerechts
  (JNIEnv *, jclass);

/*
 * Class:     jetson_JetsonMotor
 * Method:    disablerechts
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_jetson_JetsonMotor_disablerechts
  (JNIEnv *, jclass);

/*
 * Class:     jetson_JetsonMotor
 * Method:    linksstop
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_jetson_JetsonMotor_linksstop
  (JNIEnv *, jclass);

/*
 * Class:     jetson_JetsonMotor
 * Method:    linksvor
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_jetson_JetsonMotor_linksvor
  (JNIEnv *, jclass);

/*
 * Class:     jetson_JetsonMotor
 * Method:    linksrueck
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_jetson_JetsonMotor_linksrueck
  (JNIEnv *, jclass);

/*
 * Class:     jetson_JetsonMotor
 * Method:    rechtsstop
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_jetson_JetsonMotor_rechtsstop
  (JNIEnv *, jclass);

/*
 * Class:     jetson_JetsonMotor
 * Method:    rechtsvor
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_jetson_JetsonMotor_rechtsvor
  (JNIEnv *, jclass);

/*
 * Class:     jetson_JetsonMotor
 * Method:    rechtsrueck
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_jetson_JetsonMotor_rechtsrueck
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

Code 0-3: jetsonmotor.h -- Automatisch erstelltes Header-File

#include <jni.h>
#include <stdio.h>
#include <stdlib.h>

//NEU

JNIEXPORT void JNICALL Java_jetson_JetsonMotor_init(JNIEnv *env, jclass clazz)
{
    system("echo 77 > /sys/class/gpio/export");
    system("echo out > /sys/class/gpio/gpio77/direction");
    system("echo 78 > /sys/class/gpio/export");
    system("echo out > /sys/class/gpio/gpio78/direction");
    system("echo 13 > /sys/class/gpio/export");
    system("echo out > /sys/class/gpio/gpio13/direction");
    system("echo 19 > /sys/class/gpio/export");
    system("echo out > /sys/class/gpio/gpio19/direction");
    system("echo 20 > /sys/class/gpio/export");
    system("echo out > /sys/class/gpio/gpio20/direction");
    system("echo 51 > /sys/class/gpio/export");
    system("echo out > /sys/class/gpio/gpio51/direction");
}

JNIEXPORT void JNICALL Java_jetson_JetsonMotor_enablelinks(JNIEnv *env, jclass clazz)
{
    system("echo 1 > /sys/class/gpio/gpio77/value");
}

JNIEXPORT void JNICALL Java_jetson_JetsonMotor_disablelinks(JNIEnv *env, jclass clazz)
{
    system("echo 0 > /sys/class/gpio/gpio77/value");
}

JNIEXPORT void JNICALL Java_jetson_JetsonMotor_enablerechts(JNIEnv *env, jclass clazz)
{
    system("echo 1 > /sys/class/gpio/gpio19/value");
}

JNIEXPORT void JNICALL Java_jetson_JetsonMotor_disablerechts(JNIEnv *env, jclass clazz)
{
    system("echo 0 > /sys/class/gpio/gpio19/value");
}



//ALT

JNIEXPORT void JNICALL Java_jetson_JetsonMotor_linksstop(JNIEnv *env, jclass clazz)
{
    system("echo 0 > /sys/class/gpio/gpio77/value");
    system("echo 0 > /sys/class/gpio/gpio78/value");
    system("echo 0 > /sys/class/gpio/gpio13/value");
}

JNIEXPORT void JNICALL Java_jetson_JetsonMotor_linksvor(JNIEnv *env, jclass clazz)
{
    system("echo 1 > /sys/class/gpio/gpio77/value");
    system("echo 1 > /sys/class/gpio/gpio78/value");
    system("echo 0 > /sys/class/gpio/gpio13/value");
}

JNIEXPORT void JNICALL Java_jetson_JetsonMotor_linksrueck(JNIEnv *env, jclass clazz)
{
    system("echo 1 > /sys/class/gpio/gpio77/value");
    system("echo 0 > /sys/class/gpio/gpio78/value");
    system("echo 1 > /sys/class/gpio/gpio13/value");
}

JNIEXPORT void JNICALL Java_jetson_JetsonMotor_rechtsstop(JNIEnv *env, jclass clazz)
{
    system("echo 0 > /sys/class/gpio/gpio19/value");
    system("echo 0 > /sys/class/gpio/gpio20/value");
    system("echo 0 > /sys/class/gpio/gpio51/value");
}

JNIEXPORT void JNICALL Java_jetson_JetsonMotor_rechtsvor(JNIEnv *env, jclass clazz)
{
    system("echo 1 > /sys/class/gpio/gpio19/value");
    system("echo 0 > /sys/class/gpio/gpio20/value");
    system("echo 1 > /sys/class/gpio/gpio51/value");
}

JNIEXPORT void JNICALL Java_jetson_JetsonMotor_rechtsrueck(JNIEnv *env, jclass clazz)
{
    system("echo 1 > /sys/class/gpio/gpio19/value");
    system("echo 1 > /sys/class/gpio/gpio20/value");
    system("echo 0 > /sys/class/gpio/gpio51/value");
}

Code 0-4: jetsonmotor.c -- unter Verwendung des Header-Files implementierte C-Funktionen. init() muß zuerst einmal aufgerufen werden!

PWM-Erzeugung in den Test-Sketchen in Processing und Linienverfolgung
import processing.video.*;

public class Kamera
{

    Capture cam;
    public int VGABREITE = 640;
    int VGAHOEHE = 480;

    Kamera(Capture cam) 
    {
        this.cam = cam;
        cam.start();
    }
    public void frischeBildAuf()
    {
        if (cam!=null && cam.available() == true) 
        {
            cam.read();
            cam.updatePixels();
        }
    }
  
    public int berechneSchwerpunkt()
    {
       if(cam==null)
       {
          return width/2;
       }   
          int[] pix = cam.pixels;
          double zaehler = 0.0;
          double nenner  = 0.0;
          for(int y=0;y<VGAHOEHE;y++)
          {
            for(int x =0; x<VGABREITE;x++)
            {
              int index   = y*VGABREITE + x;
              int farbe   = pix[index];
              int rot     = (int)red(farbe);
              int gruen   = (int)green(farbe);
              int blau    = (int)blue(farbe);
              int echtesrot = rot - gruen - blau;
              if(echtesrot<0)
                 echtesrot = 0;
              nenner  += (double)echtesrot;
              zaehler += (double)x*(double)echtesrot; 
                 
            }     
          }
          
          double xsp = zaehler / nenner;
          
          return (int)xsp;
    }

    public void zeichneKamerabild(int xoff, int yoff)
    {
       if(cam!=null)
         image(cam, xoff, yoff);
    }
    
    public void zeichneSchwerpunkt(int xoff, int yoff, int xsp)
    {
       if(cam==null)
          return;
         stroke(255);
         strokeWeight(4);
         line(xsp+xoff,yoff,xsp+xoff,yoff+VGAHOEHE);
    }
    
}//ende Klasse Kamera

Code 0-5: Klasse Kamera

public class PWM
{
     int plinks;
     int prechts;
     int plinksabs;
     int prechtsabs;
     int STUFEN;
     
     PWMlinks pwmlinks;
     PWMrechts pwmrechts;
     
     public PWM(int STUFEN)
     {
         this.STUFEN = STUFEN;
         plinks=0;
         prechts=0;
         //NEU: (nicht vergessen!)
         JetsonMotor.init();
         JetsonMotor.linksvor();
         JetsonMotor.rechtsvor();
         JetsonMotor.disablelinks();
         JetsonMotor.disablerechts();
         
         pwmlinks = new PWMlinks(this);
         pwmrechts = new PWMrechts(this);
     }
     
     
     public void pwmLinks(int x)
     {
          if(x>=0 && plinks<0)
              JetsonMotor.linksvor();
          else if(x<0 && plinks>=0)
              JetsonMotor.linksrueck();
              
          plinks = x;
          
          if(x>=0)
             plinksabs = x;
          else   
             plinksabs = -x;
     }
     
     public void pwmRechts(int x)
     {
          if(x>=0 && prechts<0)
              JetsonMotor.rechtsvor();
          else if(x<0 && prechts>=0)
              JetsonMotor.rechtsrueck();
              
          prechts = x;
          
          if(x>=0)
             prechtsabs = x;
          else   
             prechtsabs = -x;
     }
     
}

Code 0-6: Klasse PWM

public class PWMlinks implements Runnable
{
     PWM pwm;
     
     public PWMlinks(PWM pwm)
     {
         this.pwm = pwm;
         (new Thread(this)).start();
     }
     
     //12 Millisekunden Zyklus.
     //5 Geschwindigkeitsstufen => PWM-Periode ist 60 Millisekunden lang
     public void run()
     {
       while(true)
       {
         if(pwm.plinksabs>0)
             JetsonMotor.enablelinks();
         try
         {
             Thread.sleep(pwm.plinksabs);
         }
         catch(Exception e)
         {
         }
         
         JetsonMotor.disablelinks();  
         try
         {
             Thread.sleep(pwm.STUFEN - pwm.plinksabs);
         }
         catch(Exception e)
         {
         }
       }         
     }
}

Code 0-7: Klasse PWMlinks (von PWM benutzt.)

public class PWMrechts implements Runnable
{
     PWM pwm;
     
     public PWMrechts(PWM pwm)
     {
         this.pwm = pwm;
         (new Thread(this)).start();
     }
     
     //12 Millisekunden Zyklus.
     //5 Geschwindigkeitsstufen => PWM-Periode ist 60 Millisekunden lang
     public void run()
     {
       while(true)
       {
         if(pwm.prechtsabs>0)       
             JetsonMotor.enablerechts();
         try
         {
             Thread.sleep(pwm.prechtsabs);
         }
         catch(Exception e)
         {
         }
         
         JetsonMotor.disablerechts();  
         try
         {
             Thread.sleep(pwm.STUFEN - pwm.prechtsabs);
         }
         catch(Exception e)
         {
         }
       }         
     }
}

Code 0-8: Klasse PWMrechts (von PWM benutzt)

import processing.video.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import jetson.*;

Capture cam;
Kamera kamera;
PWM pwm;
int STUFEN = 50;
float anteilvorz=0.0f;
public void setup()
{
    
    cam = new Capture(this, 640, 480);
    kamera = new Kamera(cam);
    
    pwm = new PWM(STUFEN);
        
    size(640,480);
    frameRate(30);
}

int x=0;
float aalt=0.0f;
public void draw()
{
    background(0);
    //kamera.drawTestpixel();
    kamera.frischeBildAuf();
    int xsp = kamera.berechneSchwerpunkt();
    kamera.zeichneKamerabild(0,0);
    kamera.zeichneSchwerpunkt(0,0,xsp);
        
    stroke(0,0,255);
    line(width/2,0,width/2,height);

    //Aktivität der Motoren und Regelabweichung anzeigen:
    textSize(24.0f);
    fill(255);
    text("Regeldifferenz = "+anteilvorz,30,30);
    fill(255,0,0);
    text("PWM links  = "+pwm.plinks,30,90);
    fill(0,255,0);
    text("PWM rechts = "+pwm.prechts,width-160,90);
    
    
    //Motoren über Lage des Schwerpunkts ansteuern:
    float RAND = 10.0;
    if(xsp>width/RAND && xsp<width-RAND)
    {
          float anteil = (xsp-width/RAND)/(width-RAND); // 0..1
          anteilvorz = (anteil-0.5)*2.0; // -1..+1
          if(anteilvorz==0.0)
          {
              pwm.pwmLinks(STUFEN);
              pwm.pwmRechts(STUFEN);
          }
          else if(anteilvorz>0.0)
          {
              float u = (float)STUFEN/2 - 0.4f*(float)STUFEN*anteilvorz;
              pwm.pwmLinks(STUFEN/2);
              pwm.pwmRechts((int)round(u));
              aalt = anteilvorz;
          }
          else //if(anteilvorz<0.0)
          {
              float u = (float)STUFEN/2 - 0.4f*(float)STUFEN*(-anteilvorz);
              pwm.pwmLinks((int)round(u));              
              pwm.pwmRechts(STUFEN/2);
              aalt = anteilvorz;
          }
    }
    else if(aalt>0.0f)
    {
              pwm.pwmLinks(STUFEN/2);
              pwm.pwmRechts(STUFEN/4);
    }
    else if(aalt<0.0f)
    {
              pwm.pwmLinks(STUFEN/4);
              pwm.pwmRechts(STUFEN/2);
    }
    else
    {
         pwm.pwmLinks(0);
         pwm.pwmRechts(0);
    }
    x++;    
}

Code 0-9: Hauptprogramm von TestneuJetsonMotor_Kamera_NEU

Testergebnisse

Probefahrt als Video.
Jetson-Nano bei Linienverfolgung mittels USB-Kamera.

Bild 0-1: Jetson-Nano bei Linienverfolgung mittels USB-Kamera.

ÜBUNG: "Mehr Rot bitte" -- Verbesserung des Verhaltens bei der Linienverfolgung mittels eines evolutionären Algorithmus'
  • In der aktuellen Implenetierung verliert das Fahrzeug für längere Zeit die rote Linie aus dem Fokus.
  • Mit dem Trick, sich die letzte Richtung zwischenzumerken, bleibt es aber immer auf der Bahn.
  • Nun gibt es eine Reihe an Parametern, die das Gesamtverhalten des Fahrzeugs beeinflussen, wie den Verstärkungsfaktor der Reglungsabweichung, Höhe und Ausrichtung der Kamera und viele weitere.
  • Die Parameter, die in der Software enthalten sind, ließen sich ohne weiteres ändern, mit dem Ziel länger in der Kamera "rot zu sehen".
  • Durch eine kleine grüne Markierung kann der Abschluss ganzer Runden erkannt werden.
  • Die Kamera-Klasse müßte so erweitert werden, dass es die anders farbige Markierung erkennt.
  • Die Summe der Schwerpunktswerte in einer Runde könnte minimiert, oder die Menge der insgesamt gesehenen roten Pixel maximiert werden.
  • Eine Generation von 10 Parametersätzen, die sich alle nicht zu weit von den aktuellen Parametern entfernen sollten, könnte eine Generation in einem evolutionären Prozeß bilden.
  • Für alle 10 Gene müßte jeweils eine Runde mit den entsprechdenden Parametern gefahren werden.
  • Die drei Rundenbesten könnten in die nächste Generation übernommen und die restlichen 7 Plätze mit leicht mutierten Rekombinationen aus den 3 Betsen gebildet werden.
  • Der jeweils beste Parametersatz, die Zeit für eine Runde und die zugehörige Bewertung, sollten nach jeder Generation in einer Datei gespeichert werden, um die Ergebnisse nicht zu verlieren (siehe loadStrings() und saveStrings() bei Processing).
  • Schätzen Sie ab, wie lange das Durchlaufen einer Generation benötigen wird.
  • Bilden Sie Zweiergruppen, um diese Aufgabe anzugehen.
  • Jede Gruppe sollte sinnvollerweise eine eigene rote Bahn herstellen.
  • Laden Sie die Powerbank während der Entwicklungsphase.
  • Testen Sie Ihren besten Parametersatz später auch auf den Bahnen der anderen Gruppen.

Hier ist noch einmal das Konzept für einen evolutionären Algorithmus:

73_COACH3/09_Algorithmen/03_Evoopt

In Hardware-Form mit einzelnen Versuchen, soll hier also ungefähr das passieren, was hier mit den Rennwagen dargestellt ist:

https://www.youtube.com/watch?v=wL7tSgUpy8w
Hilfestellung 1: Laden / Speichern
public void setup()
{
    double x = 3.0;
    double y = 4.0;
    saveStrings("param.txt",new String[] {x+"",y+""});
    
    String[] param = loadStrings("param.txt");
    
    double xx = Double.parseDouble(param[0]);
    double yy = Double.parseDouble(param[1]);
    
    println("xx="+xx);
    println("yy="+yy);
}

Code 0-10: Processing-Sketch zum Speichern und Laden von Parametern.