Über die Benutzerfreundlichkeit verschiedener Menüauswahlmethoden ist schon viel gestritten worden. Sind Tastatur oder Menüs mit Mausbedienung das bessere Medium? Statt nun einer der beiden Methoden den Vorzug zu geben, habe ich mir ein völlig neues Konzept ausgedacht: eine Erkennungsroutine für Freihandzeichnungen.
Die Idee dazu kam mir während der Arbeit an einem Musikprogramm, bei dem man die Notensymbole erst in einer Menüleiste auswählt und dann in den Notenlinien plaziert. Und das ist doch eine ziemlich umständliche Sache. Wie einfach wäre es doch, wenn man, wie „früher“ (in der guten, alten Vor-Computer-Zeit) die Noten einfach zeichnen könnte. Natürlich sollte der Computer dann die schmuddelig gezeichneten Symbole erkennen und in schöne, saubere Zeichen umwandeln. Diesen Einfall setzte ich sogleich in ein Programm um, das das Aussehen der Notensymbole kennt und mit der Maus gezeichnete Figuren diesen Idealtypen zuordnet.
Die Symbole müssen natürlich etwas vereinfacht werden, sonst ist die Erkennung für den Computer zu schwierig. Immerhin kann das vorliegende Programm Vierundsechzigstel bis ganze Noten, die entsprechenden Pausen, Vorzeichen, Violin- und Baßschlüssel sowie Taktstriche erkennen, wobei man die Funktionsvielfalt ohne Probleme noch erweitern kann. Um zu wissen, um welche Zeichen es sich handelt, kennt das Programm ideale Linienzüge, die jeweils ein Symbol bedeuten. Es genügt vollkommen, 8 verschiedene Richtungen, nämlich rechts, links, oben, unten und die 4 Diagonalen, zu unterscheiden (siehe Bild 1). Eine Achtelnote hätte dann zum Beispiel die Linienfolge rechts, oben, diagonal nach rechts unten. Nun stellen sich also für das Erkennungsprogramm folgende Probleme:
Die Mausbewegungen zu speichern. ist nicht weiter schwierig. In der Freihandzeichenroutine werden dabei immer die Unterschiede der jetzigen Position der Maus zur vorigen festgehalten, das erleichtert das spätere Zerlegen in Linienzüge.
Diese vielen Mausdaten müssen nun in wenige, einfache Linien umgewandelt werden. Dabei sollen natürlich kleine Abweichungen schon möglichst in dieser Phase bereinigt werden, um der Erkennungsroutine die Arbeit zu erleichtern. Es genügt also nicht, stur die Mausbewegungen zu Linien zusammenzufassen. Vielmehr müssen auch gewisse Abweichungen vom Ideal geduldet werden. Also wird eine Änderung in der Bewegungsrichtung erst dann übernommen, wenn ihre Länge eine Mindestlänge eps überschreitet. Das geht so: Die Mausbewegungen werden in einer Schleife abgearbeitet. Solange sie sich mit der derzeitig gefundenen Richtung decken, bleibt alles beim alten. Weichen sie aber von der bisherigen Hypothese ab, so werden mehrere Mausbewegungen zusammengefaßt und dann kontrolliert, ob sich jetzt eine neue Richtung ergibt. Die Mindestlänge eps, ab der Änderungen der Bewegungsrichtung berücksichtigt werden, wird automatisch ermittelt, und zwar aus den Abmessungen der Freihandzeichnung. Weiterhin sind auch bei Linien, die als waagrecht oder senkrecht gedeutet werden sollen, gewisse Abweichungen erlaubt. Diese Abweichung ist als eps1# gespeichert. Je größer eps1# ist. umso „eckiger" werden die Linienzüge. Als brauchbarer Wert hat sich etwa 0.45 ergeben. Von jeder Linie werden nun Richtung und Länge gespeichert.
Nun muß diese Linienfolge mit den gespeicherten Idealtypen verglichen werden. Dazu sind die erlaubten Muster in einer Baumstruktur gespeichert, so braucht das Programm nicht sämtliche Idealmuster mit den Linien zu vergleichen. Dieser Baum wird übrigens vom Programm selber zusammengestellt. Für jeden Knoten in diesem Baum ist gespeichert, welche Linien ihm als nächstes folgen dürfen. Das Programm, muß also bei einer bestimmter Knotenposition immer den erlaubten Nachfolger suchen. Ist die Linienliste fertig abgearbeitet, steht im Baum am erreichten Knoten, ob und wenn ja, welches Symbol sich so ergibt. Um die oben geforderte Fehlertoleranz erreichen zu können, werden um +-1 abweichende Richtungen geduldet, wenn die Länge der abweichenden Linie kleiner als 2*eps ist und die ideale Richtung mindestens einmal auftaucht. Kann die abweichende Linie auch als Nachfolger des jetzigen Knotens erklärt werden, so erhält natürlich die sichere Hypothese den Vorzug. Mit diesen Methoden erkennt das Programm auch noch ziemlich richtige Zeichnungen.
Welche Notensymbole die Erkennungsroutine kennt, und wie die Linienzüge dafür aussehen, steht in der Tabelle in Bild 2. Wie schon erwähnt, baut sich das Programm daraus den Syntaxbaum selber zusammen. Damit gestaltet sich das Ändern der Idealmuster sehr einfach: In den DATA-Zeilen nach dem Labellinien sind die idealen Linienzüge folgendermaßen gespeichert: zuerst die Nummer des Symbols (z.B. 1 für halbe Note), danach die Richtungen (z.B. 4,0,2) und dann 255 als Endezeichen. Am Ende aller Richtungsdaten steht noch ***. Wer also lieber andere Linienzüge als Ideal hätte oder das Programm für seine Bedürfnisse anpassen will, kann hier die neuen Daten einfügen, eventuell ist eine Anpassung der Dimensionierungen der Arrays baum|() und temp|() erforderlich. Für kompliziertere Erkennungsvorgänge könnte man auch noch die ideale Länge der Linien speichern und auswerten.
Wofür kann man dieses Programm nun einsetzen? Kurz gesagt, für alle Vorgänge, wo man zur Auswahl von Funktionen die Maus von der augenblicklichen Position zu einem Menü und dann wieder zurück schieben muß. Neben dem schon erwähnten Musikprogramm dürfte das vor allem für umfangreiche Grafik- oder CAD-Programme zutreffen. Diese Programme müssen ja einerseits ihre vielen Funktionen zur Auswahl auf dem Bildschirm darstellen, brauchen aber andererseits den Platz für die Zeichnungen. Außerdem sind die riesigen „Auswahlwüsten" trotz aller schönen Icons ziemlich unübersichtlich. Hier könnte dieses neuartige Programm für Abhilfe sorgen.
' (c) 1991 MAXON Computer
PRINT "Zeicheneingabe mit der Maus"
DEFWRD "a-z"
init
DO
l=0
ARRAYFILL linien(),0
ARRAYFILL maus|(),0
eingabe
linien
erkennung
LOOP
PROCEDURE eingabe
p=0
REPEAT
IF UPPER$(INKEY$)="C"
CLS
ENDIF
MOUSE sx,sy,k
UNTIL k=1
PLOT sx,sy
xmax=sx
ymax=sy
xmin=sx
ymin=sy
ax=sx
ay=sy
REPEAT
MOUSE x,y,k
IF k=3
EDIT
ENDIF
DRAW TO x, y
xmax=MAX(xmax,x)
ymax=MAX(ymax,y)
xmin=MIN(xmin,x)
ymin=MIN(ymin,y)
IF ax-x<>0 OR ay-y<>0
maus|(p,0)=BYTE(x-ax)
maus|(p,1)=BYTE(y-ay)
ax=x
ay=y
INC p
ENDIF
IF p>max
ALERT 3,"Soviele Mausbewegungen|werden Sie ja wohl|nicht brauchen!", 1, "Stimmt",back
RUN
ENDIF
UNTIL k=0
RETURN
PROCEDURE linien
LOCAL p1,hypothese,h1
eps=MAX((MAX(xmax-xmin,ymax-ymin)/10)^2,16)
eps1#=0.45
hypothese=-1
x1=0
y1=0
FOR p1=0 TO p
dx=dx+WORD(maus|(p1,0)+SHL(BTST(maus|(p1,0),7),8))
dy=dy+WORD(maus|(p1,1)+SHL(BTST(maus|(p1,1),7),8))
IF dx=0
h1=2-4*(dy>0)
ELSE IF ABS(dy/dx)<eps1#
h1=-4*(dx<0)
ELSE IF ABS(dx/dy)<eps1#
h1=2-4*(dy>0)
ELSE
h1=1-4*(dy>0)-2*(SGN(dx)=SGN(dy))
ENDIF
IF h1<>hypothese
IF (dx*dx+dy*dy)>eps
' Neue Hypothese!
linien(l,0)=hypothese
linien(l,1)=SQR(x1*x1+y1*y1)
hypothese=h1
x1=dx
y1=dy
dx=0
dy=0
INC l
ENDIF
ELSE
' Immer noch alte Hypothese
ADD x1,dx
ADD y1,dy
dx=0
dy=0
ENDIF
NEXT p1
linien(l,0)=hypothese
linien(l,1)=SQR(x1*x1+y1*y1)
RETURN
PROCEDURE erkennung
ok!=TRUE
p=0 !p ist ein Pointer im Feld linien()
k1=letzter
DO WHILE ok!
INC p !nächste Linie untersuchen
k=k1
a=1
WHILE a<=4 AND baum|(k,a)<>255
h=baum|(baum|(k,a),0)
b=0 !Der Offset zu p bei der Suche
plausibel!=FALSE
' taucht eine Linie=h auf, so wird plausibel! auf TRUE gesetzt
DO
IF linien(p+b,0)=h
plausibel!=TRUE
ELSE IF linien(p+b,1)<=2*SQR(eps) AND (linien(p+b,0)=AND(h+1,7) OR linien(p+b,0)=AND(h-1,7))
IF plausibel!
' Es war schon eine Linie dieser Art da?
b1=1
flag!=FALSE
DO WHILE b1<=4 AND baum|(baum|(k,a), b1)<>255
IF linien(p+b,0)=baum|(baum|(baum|(k,a),b1) ,0)
flag!=TRUE
EXIT IF TRUE
ENDIF
INC b1
LOOP
EXIT IF flag!
ENDIF
ELSE
EXIT IF TRUE
ENDIF
INC b
LOOP UNTIL p+b>l
IF plausibel!
k1=baum|(k,a)
p=p+b-1
EXIT IF TRUE
ENDIF
INC a
WEND
IF k1=k AND p<=l
' Kein Nachfolger und noch Linien
ok!=FALSE
ENDIF
LOOP UNTIL l<p
IF ok! AND baum|(k,5)<>255
' Dann ist baum|(k,5) die erkannte Note
PRINT namen$(baum|(k,5))
ELSE
PRINT "Nicht erkannt!"
ENDIF
PAUSE 10
RETURN
PROCEDURE init
' Hier wird vor allem der Richtungsbaum
' zusammengebastelt.
LET letzter=40
DIM baum|(letzter,5),temp|(150)
ARRAYFILL baum|(),255
RESTORE linien
a=0
DO
READ t$
EXIT IF t$="***"
temp|(a)=VAL(t$)
INC a
LOOP
t_p=1
p=letzter
freier_platz=0
nr=temp|(0)
REPEAT
b=1
WHILE b<=4
IF baum|(p,b)=255
' Das heißt, es wurde kein Nachfolger
' mit der richtigen Richtung gefunden
baum|(p,b)=freier_platz
p=freier_platz
baum|(p,0)=temp|(t_p)
INC freier_platz
EXIT IF TRUE
ELSE IF baum|(baum|(p,b),0)=temp|(t_p)
' Das heißt, der Nachfolger des
' jetzigen Platzes stimmt in der
'' Richtung. Dann springe dahin.
p=baum|(p,b)
EXIT IF TRUE
ENDIF
INC b
WEND
INC t_p
IF temp|(t_p)=255
' Eine Endemarke
' Dann trage die nr ein
baum|(p,5)=nr
INC t_p
nr=temp|(t_p)
INC t_p
p=letzter
ENDIF
UNTIL t_p>=a
linien:
DATA 0,4,0,255,1,4,0,2,255,2,0,2,255,3,0,2,7,255,4,0,2,7,4,7,255
DATA 5,0,2,7,4,7,4,7,255,6,0,2,7,4,7,4,7,4,7,255,7,0,255,8,4,255
DATA 9,7,5,7,255,10,0,5,255,11,0,5,0,5,255,12,0,5,0,5,0,5,255
DATA 13,0,5,0,5,0,5,0,5,255,14,6,1,6,255,15,6,3,0,255,16,6,0,255
DATA 17,0,7,6,5,4,3,2,1,6,255,18,0,7,6,5,255,19,6,255,***
ERASE temp|()
max=1000
DIM maus|(max,1),linien(max/10,1),namen$(19)
RESTORE n
FOR a=0 TO 19
READ namen$(a)
NEXT a
n:
DATA ganze Note,halbe Note,Viertelnote,Achtelnote,Sechzehntelnote
DATA 32telnote,64telnote,ganze Pause,halbe Pause,Viertelpause,Achtelpause
DATA 16telpause,32telpause,64telpause,Auflösungszeichen,Kreuz,Be
DATA Violinschlüssel,BaPschlüssel,Taktstrich
RETURN