Java/Mustererkennung
Im Jahre 2007 wurde im Mainzer Hauptbahnhof ein aufwändiges Experiment durchgeführt: Alle Personen, welche das Gebäude verließen, wurden von Kameras erfasst; automatisiert wurde die jeweilige Physiognomie mit den Daten von 50 Testpersonen verglichen. Der Versuch war nicht erfolgreich, da man eine höhere Erkennungsrate erwartet hatte; die verwendeten Suchalgorithmen waren allerdings nicht das Problem, sondern schlicht und ergreifend die Tatsache, dass die Lichtverhältnisse im Bahnhof abends nicht ausreichend sind: Menschen, nach denen gefahndet wird, lassen sich nun mal nicht vorschreiben, am Tage zu reisen.[1]
Die Mustererkennung ist nicht nur ein Teilgebiet der Informatik, sondern sie hat schon sehr früh die Technik und unseren Alltag bestimmt:
- Schon als studentischer Briefträger in den siebziger Jahren stellte ich Briefe zu, auf denen Codes zur automatischen Vorsortierung aufgedruckt waren.
- Unsere Schulsekretärinnen verwenden schon seit 1995 Spracherkennung zur Eingabe ihrer Texte.
- Texterkennung beim Einlesen von Dokumenten
- Zur Aufklärung von Gewalttaten werden am Tatort gefundene DNA-Spuren mit den DNA-Codes Verdächtiger abgeglichen.
Die Liste mit Beispielen zum "Pattern Matching" (Musterabgleich) ließe sich noch beliebig ergänzen, wenn wir z.B. an das Scannen unserer Autonummern auf der Autobahn (offiziell nur, um säumige LKW-Fahrer zu entdecken), an Viren-Scanner (die ja auch die Codes von Schadprogrammen entdecken sollen) oder an das Suchen im Text oder im Internet via Suchmaschine denken. Auch beim Decodieren von Text arbeitet man in der Kryptologie gelegentlich mit Pattern Matching.
Aufgabenstellung
Für die Praxis lässt sich die Mustererkennung auf der Ebene des digitalen Suchens beschreiben:
Eine Bitstruktur gegebener Länge soll in einer längeren Dualzahl gefunden werden.
Bereitstellung von Feldern als wesentliches Werkzeug zur Mustererkennung
Wir benötigen geeignete "Container", in die wir das Bit-Muster und den Suchbereich eingeben können. Diese flexibel adressierbaren Container müssen unter einem Namen für jedes Bit einen Integer-Speicherplatz vorsehen und den direkten Zugriff auf das einzelne Bit ermöglichen.
Ein solcher strukturierter Datentyp heißt Feld oder Array.
Wir betrachten ein Integer-Feld der Länge 3:
Gewöhnungsbedürftig ist, dass der erste Feldplatz mit dem Index 0 angesprochen wird und der dritte entsprechend mit dem Index 2.
Ein Array muss vereinbart und erzeugt werden
Wie jede Variable muss man ein ARRAY a vereinbaren; dies geschieht mit
int[] a;
Gleichzeitig muss man bei einem Objekt auch den konkreten Speicherbedarf organisieren, und dies geschieht für ein Feld der Länge 3 mit
int[] a = new int[3];
Wie oben schon angedeutet, erfolgt der Zugriff auf einen Feldplatz über den Feldindex.
Java-Quelltext für die Verwaltung eines Integer-Feldes der Länge 3
class Feld {
public static void main(String[] args) {
int[] a = new int[3];
a[0] = 1;
a[1] = 0;
a[2] = 1;
for (int i = 0; i < 3; i++)
System.out.printf("%nIm %d.ten Feldplatz befindet sich die Zahl %d%n",i, a[i]);
}
}
Der Benutzer liest ein Feld mit beliebigen Integerzahlen ein
import java.util.Scanner;
class Feld {
public static void main(String[] args) {
Scanner s = new Scanner (System.in);
int n;
// Eingabeschleife für Feldlänge; läuft bis Eingabewert im gültigen Bereich
do {
System.out.println("Wie viel Feldelemente sollen eingelesen werden? (hoechstens 10)");
n = s.nextInt();
}
while ((n < 1) || (n > 10));
int[] a = new int[n];
// Eingabe
for (int i = 1; i <= n; i++) {
System.out.printf("%n%d.tes Feldelement als ganze Zahl ",i);
a[i - 1] = s.nextInt();
}
// Ausgabe
for (int i = 1; i <= a.length; i++)
System.out.printf("%nDas %d.te Feldelement ist %d ",i, a[i - 1]);
}
}
Bemerkungen:
- Der Benutzer legt erst zur Laufzeit auch die Dimensionierung des Feldes fest
- Eine Eingabeabsicherung immerhin für den Größenbereich wurde realisiert
- Die Feldgröße ist über
a.length
abrufbar.
Im Hinblick auf das Ziel dieses Kapitels werden wir jetzt größere Felder mit Zufalls(dual-)zahlen füllen.
Per Zufallsgenerator Felder mit Dualzahlen füllen
import java.util.Scanner;
class Feld {
public static void main(String[] args) {
Scanner s = new Scanner (System.in);
int n;
// Eingabeschleife für Feldlänge
do {
System.out.println("Wie viel Feldelemente sollen eingelesen werden? (hoechstens 10)");
n = s.nextInt();
}
while ((n < 1) || (n > 10));
int[] a = new int[n];
// Eingabe
for (int i = 1; i <= a.length; i++)
a[i - 1] = (int) Math.floor(Math.random() * 2);
// Ausgabe
for (int i = 1; i <= a.length; i++)
System.out.printf("%nDas %2d.te Feldelement ist %d ",i, a[i - 1]);
}
}
Den Zufallsgenerator haben wir schon in letzten Kapitel benutzt. Wenn man Math.random()
verdoppelt und mit Math.floor
die Nachkommastellen abschneidet, erhält man 0 und 1 als Zufallszahlen. (Der Typ int
wird erzwungen, da es sonst Typ-Konflikte mit dem Typ long
gäbe).
Suchbereich und Suchmuster realisieren
import java.util.Scanner;
class Feld {
public static void main (String[] args) {
Scanner s = new Scanner (System.in);
int n,m;
do {
System.out.println("Wie viel Feldelemente soll der Suchbereich enthalten (hoechstens 20)");
n = s.nextInt();
System.out.println("Wie viel Feldelemente soll das Suchmuster enthalten (hoechstens 5)");
m = s.nextInt();
}
while ((n < 1) || (n > 20) || (m < 1) || (m > 5));
System.out.printf("Grosses Feld der Laenge %d, kleines Feld der Laenge %d%n",n,m);
int[] a,b;
a = new int[n];
b = new int[m];
//Felder füllen
for (int i = 0; i < n; i++)
a[i] = (int) Math.floor(Math.random()*(2));
for (int i = 0; i < m; i++)
b[i] = (int) Math.floor(Math.random()*(2));
// Ausgabe;
for (int i = 0; i < n; i++)
System.out.printf("%3d",a[i]);
System.out.printf("%n%n");
for (int i = 0;i < m; i++)
System.out.printf("%3d",b[i]);
System.out.println();
}
}
Überlegungen zum Algorithmus "Vergleich von Suchbereich und Suchmuster"
Wenn wir paarweise die jeweils ersten 3 Feldelemente vergleichen, haben wir beim ersten Durchgang keinen Erfolg. Also werden wir das Suchmuster eine Stelle nach rechts schieben.
In diesem Fall müssen wir ausgeben, dass wir das Suchmuster ab dem Index 1 im Suchfeld gefunden haben. Entsprechend sind 6 weitere Durchgänge möglich, aber das Suchmuster wird in diesem Fall nicht mehr gefunden
Struktogramm und Datentyp Boolean
Das Struktogramm nutzt Wahrheitswerte; diese stehen über dem Datentyp boolean
zur Verfügung. Für eine Variable dieses Typs gibt es nur die Werte true
oder false
.
// Anfang wie oben;
import java.util.Scanner;
class Feld {
public static void main (String[] args) {
Scanner s = new Scanner (System.in);
int n,m;
do {
System.out.println("Wie viel Feldelemente soll der Suchbereich enthalten (hoechstens 20)");
n = s.nextInt();
System.out.println("Wie viel Feldelemente soll das Suchmuster enthalten (hoechstens 5)");
m = s.nextInt();
}
while ((n < 1) || (n > 20) || (m < 1) || (m > 5));
System.out.printf("Grosses Feld der Laenge %d, kleines Feld der Laenge %d%n",n,m);
int[] a,b;
a = new int[n];
b = new int[m];
//Felder füllen
for (int i = 0; i < n; i++)
a[i] = (int) Math.floor(Math.random()*(2));
for (int i = 0; i < m; i++)
b[i] = (int) Math.floor(Math.random()*(2));
// Ausgabe;
for (int i = 0; i < n; i++)
System.out.printf("%3d",a[i]);
System.out.printf("%n%n");
for (int i = 0;i < m; i++)
System.out.printf("%3d",b[i]);
System.out.println();
// Suchbereich durchmustern
for (int i = 0; i <= n - m; i++) {
// Zunächst gehen wir davon aus, dass das Suchmuster gefunden wird
boolean gefunden = true;
for (int k = 0; k < m; k++)
if (b[k] != a[i+k])
gefunden = false;
if (gefunden)
System.out.printf("Das Suchmuster wurde ab dem Index %d im Suchbereich gefunden%n",i);
}
}
}
Datentyp String und weitere Anwendungen des Datentyp boolean
String text = "Muster";
Zeichenketten kann man über +
verknüpfen; diese Konkatenation ist polymorph, d.h. Zahlen werden ohne Transformation in den String eingebaut; also ist möglich:
text = text + 1;
Wir nutzen dies, um unser Zahlenmuster in eine Stringvariable zu packen, welche wir bei der Meldung einsetzen können.
Außerdem kann man das Ergebnis einer relationalen Operation direkt (ohne if-Abfrage) einer booleschen Variable zuweisen. Schließlich kann man mit einer zweiten booleschen Variable überwachen, ob das Suchmuster ggf. bei keinem Suchlauf gefunden wurde.
// Anfang wie oben;
import java.util.Scanner;
class Feld_5 {
public static void main (String[] args) {
Scanner s = new Scanner (System.in);
int n,m;
do {
System.out.println("Wie viel Feldelemente soll der Suchbereich enthalten (hoechstens 20)");
n = s.nextInt();
System.out.println("Wie viel Feldelemente soll das Suchmuster enthalten (hoechstens 5)");
m = s.nextInt();
}
while ((n < 1) || (n > 20) || (m < 1) || (m > 5));
System.out.printf("Grosses Feld der Laenge %d, kleines Feld der Laenge %d%n",n,m);
int[] a,b;
a = new int[n];
b = new int[m];
//Felder füllen
for (int i = 0; i < n; i++)
a[i] = (int) Math.floor(Math.random()*(2));
for (int i = 0; i < m; i++)
b[i] = (int) Math.floor(Math.random()*(2));
// Ausgabe;
for (int i = 0; i < n; i++)
System.out.printf("%3d",a[i]);
System.out.printf("%n%n");
String suchmuster = "";
for (int i = 0;i < m; i++) {
System.out.printf("%3d",b[i]);
suchmuster = suchmuster + b[i];
}
System.out.println();
// Suchbereich durchmustern
// zunächst noch nichts gefunden
boolean nix_drin = true;
for (int i = 0; i <= n - m; i++) {
// Zunächst gehen wir davon aus, dass das Suchmuster in diesem Durchgang gefunden wird
boolean gefunden = true;
for (int j = 0; j < m; j++)
gefunden = ((b[j] == a[i+j]) && (gefunden));
if (gefunden) {
System.out.printf("Das Suchmuster "+suchmuster+" wurde ab dem Index %d im Suchbereich gefunden%n",i);
nix_drin = false;
}
}
if (nix_drin)
System.out.println("Das Suchmuster "+suchmuster+" wurde in keinem Durchgang gefunden");
}
}
oder:
Effizientes Programmieren
Eigentlich ist das Suchen über die ganze Länge m
des Suchmusters nicht effektiv, wenn schon am Anfang eines Suchdurchgangs klar ist, dass es keine Übereinstimmung geben wird (vgl. unser Einstiegsbeispiel beim 4. Durchgang). Wir müssen uns also wieder mal von der Starrheit der Zählschleife lösen. Eine while-Schleife und die geschickte Kombination von logischen Ausdrücken und booleschen Variablen lösen das Problem:
// Anfang wie oben;
import java.util.Scanner;
class Feld_6 {
public static void main (String[] args) {
Scanner s = new Scanner (System.in);
int n,m;
do {
System.out.println("Wie viel Feldelemente soll der Suchbereich enthalten (hoechstens 20)");
n = s.nextInt();
System.out.println("Wie viel Feldelemente soll das Suchmuster enthalten (hoechstens 5)");
m = s.nextInt();
}
while ((n < 1) || (n > 20) || (m < 1) || (m > 5));
System.out.printf("Grosses Feld der Laenge %d, kleines Feld der Laenge %d%n",n,m);
int[] a,b;
a = new int[n];
b = new int[m];
//Felder füllen
for (int i = 0; i < n; i++)
a[i] = (int) Math.floor(Math.random()*(2));
for (int i = 0; i < m; i++)
b[i] = (int) Math.floor(Math.random()*(2));
// Ausgabe;
for (int i = 0; i < n; i++)
System.out.printf("%3d",a[i]);
System.out.printf("%n%n");
String suchmuster = "";
for (int i = 0;i < m; i++) {
System.out.printf("%3d",b[i]);
suchmuster = suchmuster + b[i];
}
System.out.println();
// Suchbereich durchmustern
// zunächst noch nichts gefunden
boolean nix_drin = true;
for (int i = 0; i <= n - m; i++) {
// Zunächst gehen wir davon aus, dass das Suchmuster in diesem Durchgang gefunden wird
boolean gefunden = true;
int j = 0;
//Jetzt wird der Suchvorgang direkt abgebrochen, wenn ein Bit nicht übereinstimmt
while ((gefunden) && (j < m)) {
gefunden = ((b[j] == a[i+j]) && (gefunden));
j++;
}
if (gefunden) {
System.out.printf("Das Suchmuster "+suchmuster+" wurde ab dem Index %d im Suchbereich gefunden%n",i);
nix_drin = false;
}
}
if (nix_drin)
System.out.println("Das Suchmuster "+suchmuster+" wurde in keinem Durchgang gefunden");
}
}
Siehe auch
Quellen
- ↑ Gesichtserkennung fällt im Praxis-Test durch (welt.de) 10.07.2007