(M)Auswahl

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

Bild 1: Diese acht Richtungen genügen vollkommen.

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.

Bild 2: Die Idealtypen des Beispielprogramms
' (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

Jonathan Eroms
Links

Copyright-Bestimmungen: siehe Über diese Seite