Webcam Projekt 10/2015 Der Router koppelt mit dem 2. Raspi modul über die .81, daher ist ssh direkt verfügbar. Infos aus: http://pimylifeup.com/raspberry-pi-webcam-server/ sudo sudo sudo (doc sudo apt-get update (start 11:22) apt-get upgrade (start 11:24, done :49) apt-get install motion (start 11:49, done 11:52)) on: http://www.lavrsen.dk/foswiki/bin/view/Motion/MotionGuide) nano /etc/motion/motion.conf Find the following lines and change them to the following. daemon on webcam_localhost off Optional (Don’t include the text in brackets) webcam_maxrate 100 (This will allow for real-time streaming but requires more bandwidth) framerate 100 (This will allow for 100 frames to be captured per second allowing for smooth video) width 640 (This changes the width of the image displayed) height 480 (This changes the height of the image displayed) Now we need to setup up the daemon, first we need to edit the motion file. sudo nano /etc/default/motion Find the following line and change it to the following: start_motion_daemon=yes Now make sure the camera is connected and run the following line sudo service motion start If you need to stop the service simply run the following command sudo service motion stop Now to test it out! We can check out the Raspberry Pi Web Cam Stream at the ip address of our Pi so in your browser go to the following address: 192.168.1.103:8081 Tja, und das Ergebnis: funktioniert: Allerdings mit mieser Framerate. Gut, es werden andauernd bilder in /tmp/motion/ abgelegt. Die Last geht auf 30%, wenn bilder geschossen werden. Ohne Motion (alo ohne Bewegung) werden aber keine Bilder geschossen. Für eine Webcam müsst das ding irgendwie anders konfiguriert werden. Und es gibt viel zu konfigurieren. Also erstmal wieder abstellen: /etc/default/motion: start_motion_daemon=no und sudo service motion stop Tja, erfolg! Dies muss nur mal richtig konfiguriert werden, damit es nicht so laggt. Kamera liefert sowieso nur 15 fps, bei effektiv 358x268 pixel laut bild. Sollte für die Experimente aber erstmal reichen !!! Auch motion mit video4linux ist ein guter startpunkt. Gut, nun wieder einrichten, aber das vollspamen vorher deaktivieren: output_normal off ffmpeg_cap_new = off (default!) Gut, läuft wunderbar. pic: nun lohnt es sich noch, die jpg kompression für die webcam auf 100% quality zu setzen: webcam_quality 100 experimentell berücksichtigung der pixelzahl: 358x268 640x480: kamera lagt hart, mit 2 bildern/s 358x268 unsinn, funktioniert nicht. 352x288 ist motion default und geht auch - sieht aber jaggy aus. Ich bin im Source. Der Trunk kann tatsächlich kompiliert werden, wenn man ihn per svn holt. Er erwartet allerdings seine config in /usr/local/etc/ und die optionen heissen anders. restart mit killall motion; ./motion Der code für den Text steht in motion.c Tja, kann losgehen. Gut, ein Ansatz: 1) markerscan 2) select best 3) track it Hierfür muss das Bild gefaltet werden. Dafür muss ein Bereich festgelegt werden. Dieser ist zu markieren. Also, einen Rand festlegen, unten die Textzeilen aussparen. Backup anlegen. Auf usb stick c. Code finden, welcher den motion detection bereich markiert, hiervon die eigene markierung ableiten. Die config option hiess redbox. Wird verwendet in motion.c: if (cnt->locate_motion_style == LOCATE_REDBOX) { alg_draw_red_location(&img->location, &cnt->imgs, cnt->imgs.width, cnt->imgs.preview_image.image,LOCATE_REDBOX, LOCATE_NORMAL, cnt->process_thisframe); Man sieht, es ist nicht sehr gut gemacht. Wichtige infos stehen im kontext cnt, dieser wird aber dem zeichner nicht übergeben. Dadurch wird der aufruf länglich und es gibt für verschiedene farben verschiedene funktionen. Also wo ist cnt bzw. struct context definiert? in motion.h und es dient als context der threads. Dort sollte man also weitere infos über den mod ablegen. Initialisierung hoffentlich in motion.c:context_init Zunächst aber etwas fun: zeichnen einer box in rot von 20,20-100-100. Ja ne ist quatsch, die parameter liegen ja in der draw-funktion. Also muss die noch weiter analysiert werden ... Natürlich in alg.c: void alg_draw_red_location(struct coord *cent, struct images *imgs, int width, unsigned char *new, int style, int mode, int process_thisframe) im moment des aufrufs ist dann style= locate_redbox und mode=locate_normal. Allerdings ist der code für cross einfacher zu verstehen. Er lautet für die horizontale linie: void alg_draw_red_location( struct coord *cent, struct images *imgs, int width, unsigned char *new, int style, int mode, int process_thisframe) { unsigned char *out = imgs->out; unsigned char *new_u, *new_v; int x, y, v, cwidth, cblock; cwidth = width / 2; cblock = imgs->motionsize / 4; x = imgs->motionsize; v = x + cblock; (zentrum des kreuzes) out = imgs->out; new_u = new + x; new_v = new + v; int cwidth_maxy = cwidth * (cent->y / 2); for (x = cent->x - 10; x <= cent->x + 10; x += 2) { int cwidth_maxy_x = x / 2 + cwidth_maxy; new_u[cwidth_maxy_x] = 128; new_v[cwidth_maxy_x] = 255; } Und das ist schon echt verwirrend. Scheinbar werden 2 Byte geschrieben, und zwar 1111 1111 - 1000 0000. Wie soll da das farbformat aussehen, 9 bits für rot? Jedenfalls sind new_u und new_v um cblock verschoben, was imgs>motionsize/4 entspricht. Dies ist in struct images definiert. Dies ist in motion.h definiert. Gesetzt wird motionsize in alg.c, motion.c und 2 anderen, aber es wird wahrscheinlich nur der pixelraum belegt, also width x height. Evtl. wird dies auch durch vid_start gesetzt, liegt in video_common. Hierfür v4l2_set_input und v4l2_next. dies in video.c Interessant ist der aufruf _next, dieser holt nämlich das bild und wandelt es ggf. noch. Dabei werden die Formate beachtet. Der ringbuffer wird gehandelt. Standarmässig wird also das format 420p verwendet. Zum kopieren der bytes ist die grösse viddev->v41_bufsize verantwortlich. Beim starten wird diese angemerkt. dies landet in syslog. Also /var/log/syslog. Dort wird aber angezeigt: v4l2_do_set_pix_format: Using palette YUYV (320x240) bytesperlines 640 sizeimage 153600 colorspace 00000008 Dies befindet sich in video2.c Nun dieses Format ist natürlich schwul, da 4 bytes für 2 pixel codieren, aufgeteilt in Y-Cb-Y-Cr bzw. Y-U-Y-V, je nach Farmodell, was ich so schnell nun nicht lerne. In wirklichkeit werden hier 2 Byte für ein Pixel verwendet, woraus sich 8 Bit für Y und 8 bit für U und V ergeben. Man kann aber annehmen, dass es 2 Blöcke gibt, U und V, welche in den "red"-methoden beschrieben werden. Nun noch etwas experimentieren. Wir setzen mal den Wert der höheren Adresse einfach auf 0. Vorher noch cross configurieren. Cross sichtbar. Make gestartet. Nun ist der horizontale balken pink, der vertikale orange. Ausserdem sieht man immer den Hintergund. Das ist etwas seltsam. Wir setzen auch das andere byte auf 0. Nun ist der vertikale Streifen grün und die Helligkeit verändert sich immernoch mit dem Hintergrund. Nun setzten wir beide auf 255. 128,255: 255, 0 : 0,0 : 255,255: 255,128: 128,128: rötlich orange grün pink blau! ultrablass. Nun habe ich noch ein byte daneben (adresse+1) auf 128 gesetzt, dennoch ist der y-kanal nicht betroffen. Nun noch einmal adresse-1. Gleiches Resultat. Auf diese Weise kann Y nicht gesetzt werden. Gut, dann ist die Theorie, dass das Bildformat aus 4 Blöcken besteht, so dass jeder Block für einen Kanal Y,Y,U,V codiert. Wir konfigurieren wieder red box. ... Also, die Box ist 2 Pixel breit. Unten erscheinen Punkte, uben ist die linie unterschiedlich gefärbt. ... Ok, also aus der adressberechnung in alg_red_ kann man ersehen, dass zuerst der luminanzblock mit x*y=motionsize adressen kommt. Er beginnt bei "new". Dann kommen zwei Blöcke, die U und V kodieren. Sie heissen new_u und new_v. Der start von new_u berechnet sich zu new + motionsize, was klar ist. Dann berechnet sich new_v zu new + motionsize + cblock und cblock ist motionsize/4. Das bedeutet, ein cblock hat nicht halb so viele, sondern nur 1/4 der adressen der luminanz. Daher haben beide cblöcke zusammen die halben adressen wie die luminanz. Um nun hübsche Farbeffekte zu haben, muss man natürlich auf die Auflösung verzichten und immer 4 Pixel setzen. SO. Keine Ahnung warum, aber nicht mal exit geht bei alg_rgb_setpixel. genug für heute. Next Day. Es funktioniert. Der modifizierte text links unten verwirrte etwas. Der Aufruf von alg_rgb_setpixel war an der falschen Stelle. Nun klappts. Pause. cnt->imgs.image_virgin scheint das quellbild zu sein. alg_diff ist ein schnell-algorithmus, der dann alg_diff_standard aktiviert. sie werden mit image_virgin aufgerufen. Also alg_diff covert alg_diff_standard(slow) und alg_diff_fast(fast). Wenn fast etwas findet, wird standard gestartet. Das Ergebnis scheint diffs zu sein, anzahl der veränderten pixel. Alg_diff_fast ist natürlich viel einfacher und gibt nur 1/0 zurück. Es werden auch nur 2 images verwendet: ref = cnt->imgs->ref und new = cnt>imgs.image_virgin. Ausserdem wird nur jedes 10.000-ste Pixel analysiert. Aber wie kommt es zu cnt->cent ? Ah! Dies macht vermutlich alg_locate_center_size, aufgerufen in motion.c:1635 (Neuer Plan: Ein optischer Scanner. Surround wird als referenz verwendet, um das Zentrum zu scannen und ein Bitmap daraus zu erstellen. Bewegung "ergänzt" das Bitmap jeweils.) Anscheinend kann man die Verarbeitung mit cnt->process_thisframe=0 stoppen. Allerdings ist die main loop etwas unaufgeräumt und man sollte hier erst übersicht schaffen. ist zb unklar, ob dann der overlay noch gemacht wird. Ah, scheint aber zu gehen. Es sind dann auch "nur" noch 50% cpu, wenn ein browser attached ist. Ansonsten sogar nur 6%. Sieht doch gut aus. Nebeneffekt: das geschreibe von bildern ist nun auch deaktiviert. Gut, als experiment den Y-kanal quantisieren auf 2-bit, d.h. mit &0xc0 maskieren. Geht. Nun differenz zum ref bilden. Gut, ref scheint immer anders zu sein. also process thisframe wieder aktivieren? Versuchen kann mans ja. Übrigens ist nun auch keine farbe mehr im vergleich aktiv... Hm. Nun gibts wilde punkte im Bild. Also mal gucken, wer alles auf ref zugreift. Es gibt da eine konstante update_referencr_frame, die hilft vielleicht. Und eine funktion alg_update_reference_frame. Hm, in dieser wird auch verglichen und zwar cnt->imgs.image_virgin mit ref. Übrigens gibt es auch nocg 3x3 Faltungsoperationen wie erode, es ist auch mmx-code vorhanden. Gauss oder allgemeine faltungskernel gibt es aber nicht. Tja wie nun weiter. Eingene Strukturen aufbauen? Ich glaube schon, weil es ist einfach auch undurchsichtig, man sucht permanent. Gut, nun kann process_this wieder deaktiviert werden, da es funktioniert. Funktioniert ebenso. Bildchen: "Bewegung" wird jetzt sehr einfach mit d>50 direkt im Bild markiert. Es ist natürlich nur pixelweise oberschwellige zeitliche Helligkeitsänderung. Aber auf diese weise können alle Algorithmen visualisiert werden. Gut, genug für heute. Die Farb-Abbildung Yuv->Rgb->Yuv ist irgendwie falsch, da rot fehlt: Dazu habe ich im Moment aber nicht so Lust. Überlegungen: Das Finden/Tracken der maps ist gar nicht so einfach, weil das pixelgenaue finden mit der auflösung eben doch mehr zeit erfordert. Macht man es vollständig, sind es ungefähr (Img.pix x Map.pix) Vergleiche. Deshalb braucht man bei höheren Auflösungen einen Featurespace, der die vielen Pixel auf einen erheblich kleinerern Vektor komprimiert. Dadurch wird die Anzahl der möglichen Vergleiche erheblich vergrössert. Ausserdem braucht man einen Mechanismus, der von den konkreten Auflösungen fast unabhängig funktioniert. Eine grosse Rolle spielen dabei Frequenzen, bzw. die Anzahl der Pixel einer Map in einer Auflösung. Ein einzelner Pixel kann beim Wechsel in eine geviertelte Auflösung auch nur noch ein viertel so gut gefunden werden, da seine Umgebung 3/4 des Pixelwertes bestimmt. Besonders stabil sind daher niedrige Frequenzen. Aus diesem Grund sollten Maps in Bruchteilen des Bildes und Pixel in Bruchteilen der Maps angegeben werden, bzw. in Bezug gesetzt werden. Im Moment hat das Bild die Grösse 320x240, was klein klingt. Die Map hat die Grösse 64x64 und passt damit wie oft ganz ins Bild? -> 320x240=76.800, 64x64=4096, 76.800/4096=18.75. Und zwar 5x horizontal, da 5*64=320, und 3x vertikal, mit einem Rand von 48 Pixeln. Gekachelt wären dies dann 15 Stück. Das bedeutet, im Feature Space wären es nur 15 Vergleiche, um die Map zu lokalisieren. Allerdings auch nur mit der Genauigkeit von 1/15. Aber auch bei Maps mit tiefen Frequenzen reicht dies bei weitem nicht für eine sichere Detektion. Die Map dürfte höchstens einen shift von halber Mapbreite lotrecht zur Kontrastkante haben, wobei die Differenz dann schon maximal wird. Von einer annähernden Detektion darf man vielleicht bei einem Shift von viertel Mapbreite sprechen. Daraus folgt, dass man für den ersten Schritt der Detektion etwa einen Viertelschritt Mapgrösse benötigt. D.h. für je zwei benachbarte Mapkacheln benötigt man 3 weitere Zwischenpositionen. In der Fläche benötigt man für 4 benachbarte Mapkacheln etwa (2*3+3*3=15) 15 weitere Vergleiche, also 16 pro Kachel. Da im Moment 15 Kacheln verwendet werden, bedeutet dies 16*15 = 240 Vergleiche. Um auch nur die tiefste Frequenz (im Idealfall) zu finden, benötigt man also schon die 16-fache Überabstastung des Bildes, ziemlich verschwenderisch. Man erhält dabei Werte der Differenz von 0-max/2. Weitere Variationen der Verzerrung sind dabei noch gar nicht berücksichtigt. Als nächstes sollte man mal den zentralen Pixel in rgb und yuv messen und dann mit dem mapping vergleichen. Dann nachrechnen, was die formeln besagen. Evtl. andere Formel ausprobieren. Precision is beauty. Das Gelb entsteht beim Sampeln, wobei yuv2rgb verwendet wird. Das war allerdings auch schon bekannt. Ein erneutes Abtippen der formeln im deutschen wiki für yuv unter berücksichtigung von u,v := +-128 ergibt dann hübsche farben: Es kann also nun endlich mit der suche begonnen werden. Nächste Aktionen: bestmatch und fps. Die genaue Zeit der einzelnen Bilder muss festgestellt werden, da bei schwachem Licht weniger Bilder kommen. Die Zeitbasis wandert also. Bestmatch als einfacher Algorithmus soll zunächst eine Map auf sämlichen Positionen des Bildes testen und die besten koordinaten zurückgeben. Eine Karte als Darstellung der Matchgüte wäre aber auch gut. Zur Überprüfung soll später der Rahmen um diesen Match angezeigt werden und alle 5 sekunden ein sample genommen werden, welches dann auf diese weise getrackt wird. Also - es funktioniert: Allerdings ist es sehr helligkeitsempfindlich. Dies sollte aber nicht besonders verwundern. Im Moment wird geresampled, sofern die differenz über 20 liegt. Das Matchen liefert einen Differenzwert (diff), normiert auf das einzelne Pixel, welcher in der Grössenordnung des pixelweisen Bildrauschens (noise 1.8) liegt, im Moment etwa 4/255. Interessanterweise liegt der diff des zweitbesten Matches (d_2nd) weit darüber, beim 4-fachen, etwa 18/255. Dabei ist zu berücksichtigen, dass nach jedem erfolgreichen match bereits wieder geresampled wird. Der Unterschied zwischen diff und d_2nd kann als Erkennungsgüte (q=diff/d_2nd) angesehen werden. Der differenzwert an restlichen Verschiebungspositionen im Bild ist noch weiter darüber, im mittel bei 80 (q=20), maximal 140 (q=35). Ebenfalls interessant ist, dass bei bewegung diese Erkennungsgüte stark absinkt, hier: gegen 1..2 geht. Das ist sehr wahrscheinlich darauf zurückzuführen, dass das Objekt verschmiert erscheint, also objektiv anders aussieht und ausserdem feine Details verlorengehen. Die Differenz ist also zwangsweise höher. Ausserdem ist klar, dass Bewegung ein generelles, grosses Problem ist. -Weitere Planung ist nötig. Ursprünglich sollte ja ein Umgebungsscanner bzw. panoramagenerator gebaut werden.Dafür muss ein Bild gespeichert werden. Zu anfang soll dies als Transparent belegt sein und dann schrittweise durch patches belegt werden. Eine Datenstruktur ist aufzusetzen, speicher zu belegen und dann ein scan-algorthmus zu erstellen. Schwierig wird es mit den grenzen der Erkenung. Wie sicher muss diese sein? Es ist ja jetzt schon absehbar, dass Helligkeit stark variiert. Die Kamera wird sich anpassen und die Helligkeitswerte der patches werden auseinander driften. Dann passt nichts mehr zu anderen. Also wie kann man hier systematisch vorgehen? *** Dynamic Marker *** Der Panorama-scan wird sehr wahrscheinlich unter grossen Flächen leiden, die keine Struktur besitzen. Der Einfall ist nun, im Bild die Qualität von Markern direkt zu messen. Eine Verschiebung von 1 Pixel liefert dabei bereits ein Mass für die zu erwartende Erkennungsgüte q. Man setzt dann das Bild aus Patches zusammen, die von mehreren Markern gestützt werden. Dies ist letztlich natürlich ein Mass für den Bildkontrast im Mapbereich. FPS fehlt noch. Und es ist seltsam, dass noise unter diff liegt. Da stimmt evtl. etwas nicht. Es könnte sinnvoll sein, die rgb-werte einer ganzen map numerisch auszugeben. Liste mit weniger guten matches anlegen, diese mit rahmen im Bild markieren. Diese maps ebenfalls unten anzeigen? Die ganz grosse frage ist, ob marker nach einer Drehung der Kamera überhaupt noch wiedererkannt werden können. Bzw. wie man das wenigstens vorher schon feststellen bzw. prognostisch qualifizieren kann. Subpixel correction und augenmuskel-projekt. Speed-up durch bereichsbegrenzung und evtl. ausschluss durch summierte hintergrundfarbe (der map). Grund: eine geshiftete Hintergrundfarbe muss zwangsweise zur schlechteren Erkennung führen. Dies muss man nicht auf pixel-niveau ausrechnen. Daher können entsprechende Bereiche (linear separierbar) bereits vorher ausgeschlossen werden, sofern hierfür ein schwellwert bekannt ist. Nützt natürlich nichts, wenn man später einen anderen Weg zur erhöhten Helligkeits bzw. Umgebungsfarbtoleranz beschreitet. Wieviel Grad hat eigentlich ein Pixel? Es wurde ja 60° horizontal gemessen. Aufgeteilt auf 320 pixel macht das 0.18°. Quantisierung des Featurespace. Aus ziemlich offensichtlichen Gründen (ähm..) kann die Erkennung verbessert werden, wenn für gleichfarbige Pixel einer Map zunächst eine Summe gebildet wird, bevor die Differenz dieser Pixel berechnet wird. Die aus k Pixeln gebildete Differenz ist dann bis zu k-mal größer als beim einfachen Pixel. Normiert man dies dann wieder auf 255, ist allerdings wenig gewonnen. Die Idee ist, die Farbwerte der map zu quantisieren und dann wiederum die Pixelorte aufgrund der quantisierung zu Gruppieren. Leider liegt hier der Denkfehler zugrunde, dass der grössere Differenzwert irgendwie nützlich wäre, denn nach der abschliessenden Quantisierung ist der so berechnete Differenzwert ja wieder mit dem ursprünglichen identisch. Es schleicht sich die Vermutung ein, dass die pixelweise Differenz vermutlich ein schwer bis gar nicht zu schlagendes Kriterium der Erkennung ist. Es sei denn, man könnte die räumlich verteilten Pixel doch noch irgendwie zur konzertierten Verbesserung heranziehen. Wenn in der Map festgelegt, dass zwei Pixel die identische Farbe haben sollen, so kann immernoch zuerst die pixelweise differenz gebidet werden. Ausserdem kann aber auch die differenz der bildpixel gemessen werden. Ist diese Null, so liegt eine Übereinstimmung in höherer Güte vor, als wenn sie von Null verschieden ist. Die Differenz der Bildpixel kann also nicht nur zur Mutmassung der Markertauglichkeit, sondern auch zum Matching herangezogen werden. Nämlich zur Unterdrückung der ungeeigneten Matches (d_2nd). Fragt sich nur, wie dies in der Praxis effektiv ist. Hintergründe der Erkennung und der Reifentest. Bisher ist die Erkennung unter anderem wahrscheinlich deswegen so gut, weil der Hintergrund der map immer weiss war. Weil er dann im nächsten bild auch weiss ist, trägt er kaum zu differenz bei. Was passiert, wenn man dieses weiss abschneidet? Aufgrund global illumination ist natürlich dann auch das objekt in anderem kontext anders gefärbt. Also sollte die Umgebung schon irgendwie berücksichtigt werden. Der Reifentest besteht aus einem Bildschnipsel, bestehend aus eine Ralleyreifen mit starkem Profil, welches 100-fach im Hintergrund überlagert ist. Im Vordergrund ist im wesentlichen ein einziger Reifen zu sehen. Wie ist hier wohl die Erkennung? Erfolgreiches Versagertum In einem Bild (weiss), welches keine Strukturen enthält, kann auch nichts erkannt werden. Analog dazu muss man objektiv erkennen, dass das Versagen der Erkennung völlig korrekt ist, sofern die map nicht hinreichend sicher lokalisiert werden kann. Allerdings ist es sehr wichtig festzustellen, ob dies auch tatsächlich so ist. D.h. das Versagen der Erkennung muss objektiv quantisiert werden. Zu einer guten Dokmentation gehört deshalb eine Bildfolge mit Erkennungsversuchen und den numerischen Werten dazu. Also letztlich den rgb-map werten und den rgb-screen werten. Man kann dann später immernoch die maps vergrössern etc. Viel wichtiger ist dabei noch das Ablehnen übertriebener Erwartungen. Grosse Flecken Es ist ausserdem noch zu beachten, dass bei bewegung das bild so schlecht wird, dass es praktisch verschwindet. Dann gibt es aber immernoch Bildkontraste, allerdings sehr grosse. Ein Schrank, ein Baum, werden dann zu Flecken, die in einem tieferen frequenzbereich als kontraste erkannt werden können. Die Mapgrösse müsste also an die geschwindigkeit angepasst werden. Diese ist wiedrum natürlich relativ zur framerate der kamera, weshalb insgesamt nur hochgeschwindigkeitsalgorithmen in frage kommen. Zur Entwicklung dieser kann man immernoch 2 bilder unmittelbar nacheinander aufnehmen und sie später (langsam) verrechnen, um unoptimierte methoden mit hoher bildfrequenz auswerten zu können. Dies ermöglicht ausserdem den aufbau eines slow detection path. Aktueller Plan ist also panorama-cam. Doch was kommt danach ? 3d-navigation und location recognition. Tracker-optimierung: suche des besten markers und automatisches tracking von diesem. vorher:fps nebenbei: arbeitsplatz-setup. fps: referenz: http://www.chemie.fuberlin.de/chemnet/use/info/libc/libc_17.html Die neue Webcam 720p sieht nicht glänzend aus: Die neue differenzberechnug jedoch sieht glänzend aus: sie operiert noch bei 6 Hz. Ziemlich cool würde ich sagen. Jetzt fehlt noch eine räumliche summation, um den "wertvollsten" ausschnitt festzustellen. Diese muss nun wirklich optimiert werden, sonst dauert es ja ewig. Allerdings ist dies nun so allgemein, dass man gleich parametrisch schreiben kann. Und das ergebnis wird wohl nicht sehr ortsgenau werden. Aufgrund der linearität macht man je eine hauptschleife pro dimension. Zuerst werden in x-richtung je 16 werte addiert. Dann der vordere addiert, der letzte subtrahiert. Dies ist die teilsumme für x, die gespeichert wird. Dies wird für das gesamte bild gespeichert. Dann dasselbe für y, wobei die x-teilsumme addiert wird und schliesslich durch 256=16*16 geteilt wird. Jedenfalls theoretisch. Praktisch wird vor der Speicherung jeder teilsumme durch 16 geteilt. Für area_sum erscheint das seltsame problem, dass rot=255 grün ist, auch noch einige Werte (220)darunter, aber 128 ist rot, ebenso wie 200. Davon abgesehen funktioniert die summation sehr hübsch. Begonnen mit der xachse sieht es so aus: Nach der 2. Dimension siehts dann so aus: Man erkennt natürlich den etwas eckigen Faltungskern. Nun die Area_sum angewandt auf den diffscreen: Es sind nur noch sehr extreme kontrastdichten erkennbar. Daher experimentell x10 eingesetzt: Das ist nun das 10-fache einer 17x17 area sum auf einem 2x2 differenzkern. Diese Karte soll es aber nur erleichtern, etwa 16x16 grosse Marker zu finden, welche einen grossen Pixelkontrast auf dem Bild haben. Da der Differenzkern "nach rechts-unten" gebildet wurde, ist ein Pixel auf dieser Map für ein 16x16 Areal verantwortlich, welches dieses Pixel links oben in seinen zentralen 4 Pixeln hat. Vor der endgültigen Auswahl wird diese Map zur Veranschaulichung noch farblich markiert. Weiss für die besten 50%, blau für darunter. Nun ist es endgültig psychedelisch: Irgendwie nicht so. Tja, nun siehts so aus: Das sieht doch gleich viel geiler aus. Nun soll hieraus automatisch ein Marker selektiert werden, welcher dann getrackt wird. Unklar ist vor allem, wie die Abriss-Schwelle aussehen soll, also wann das Tracking versagt. Unter anderem dafür muss das lokale Bildrauschen und die lokale Bildschärfe gemessen werden, evtl. auch die lokale Bildfarbe. Gut wäre auch die Anzeige des Verlaufs der Trackinggüte im Bild. Zunächst sollte man wohl auf Bewegung verzichten, und in festen Bildern vom Stativ denken. Dennoch ist es wichtig, ein mass für die Erkennungsgüte zu haben. Tracking: Kopie des aktuellen Bildes in das rgb-Format, "screen". Berechnung aller pixelweisen Differenzen, sowohl lateral (2x2) als auch temporal: (prev_screen, screen) -> (diffscreen, noise). Für Noise wird (vor allem) festgehalten, wie gross das pixelweise temporale Bildrauschen im Mittel ist. Man kann sogar das vorherige Rauschen noch mit heranziehen, mit prev_noise>(noise+prev_noise)/2. Zur Bestimmung eines guten Markers wird das light maximum der 17-area sum des diffscreen bestimmt. Dies lokalisier einen Marker mit besonders vielen grossen pixelkontrasten im aktuellen Bild. Die Markergüte, gegeben durch den gemitteltnen Pixelkontrast, wird angegebn. Dieser Marker wird nun einmal statisch gesampelt. In allen folgenden Bildern wird dieser durch best_match lokalisiert und die Matchgüte angegeben. Es ist nicht zu erwarten, dass das Matching wesentlich besser ausfällt, als vorher bereits im Schraubenversuch. Denn der eigentlich Matchingalgorithmus hat sich ja nicht geändert, es gibt jetzt nur eine automatische Markersuche. Was man noch machen kann, ist eine automatische Farbanpassung. Man speichert die Markerfarbe separat und ändert vor dem konkreten Vergleich noch die lokale Farbe der Matcharea. Was man auch noch machen kann, ist das temporale rauschen aus dem marker "herausrechnen", durch temporale mittelwertbildung. Ausserdem kann man auf diese Weise für ruhige Bilder das Rauschen im Bild selbst auch noch mitteln. Wegen diff=(m+r1)-(s+r2) = (m-s)+(r1-r2) ~ (m-s) +2r und der pixelweisen Berechnung könnte die Bildschärfe erhalten bleiben, die zeitlich Auflösung wird nur schlechter. Mit anderen Worten man verschiebt das Rauschen vom Raum in die Zeit. Für das Panorama-programm nimmt man die besten 4 matches aus beiden bildern und berechnet den mittleren linearen Versatz daraus (ebenso wie die Güte). Ach so und eigentlich ist auch wieder Aufräumen angesagt, also: alg_rgb_setpixel: timing do_match: init sample_from_screen -> screen sample_from_screen -> map do_pixel_diff -> diffscreen area_sum -> diffscreen colorize map_to_screen Hm, ziemlich unordentlich. Man sieht auch nichts mehr wegen printf. Nun, nachdem ich die Ränder herausgenommen habe und neu zentriert habe, sieht es plötzlich sehr gut aus: Etwas seltsam, dass vorher die koordinatenbestimmung so völlig unerkennbar schlecht war. Die Werte sind nun: timing 0.2000 sec 5.00 Hz do pixel diff: max diff quality: 92.33 colorize: maxlight: 19.00 x256: 4864.00 lost. noise_level (avg/min/max): 001.34 0 132 hor contr (avg/min/max): 001.84 0 178 Gut. Nachdem die Messung des Noise noch einmal durchzelebriert wurde zeigt sich, dass einzelne Punkte sehr wohl hohen temporal-Kontrast haben, aber die überwiegende Mehrheit hat kaum Rauschen: Man sieht das daran, dass der avg-Wert bei Bewegung sehr stark zunimmt, also auf 20...50...80 geht. Der Maximalwert geht dann auf über 200. Möglicherweise liegt das an jpeg-Artefakten. Hierzu sollten auch irgendwann noch mal die umwandlungs-gleichungen gecheckt werden. Wie man hier sieht (es handelt sich eindeutig nur um einen roten Knopf auf weissem Hintergrund), ist es ziemlich sicher die Farbkonvertierung: Die beste Methode wäre aber wahrscheinlich, die Bildgewinnung von der yuyvMethode bzw. von der kompression zu befreien. Also rgb war da schon mal der richtige weg. Entwurf des Panomax Algorithmus: Panomax: 0 init A sample_image_sequence B image_stitch C save_image A1: A2: A3: A4: A5: autofind_marker_right detect_marker_left sample_patch -> A1 or A5 save_image 0: Initialisiere das Image mit (2x2) x 320x240 = 640x480 Pixeln. Dies enthält dann in jedem Fall das Ergebnis. Lasse die Zeit von 3 Sekunden zum eintunen der Helligkeit verstreichen. Lege die Anzahl num_patches_target fest, aus denen das Gesamtbild bestehen soll. A1: Stelle temporalen (td) und lateralen (ld) Kontrast fest (min/max/avg). Ermittle einen Marker im rechten Bildbereich. Dieser muss einen hohen Kontrast gegenüber dem Rauschen haben und im Bild relativ einmalig sein. Dann eignet er sich als Marker. Um dies festzustellen, erstelle eine Liste der potentiellen Marker, sortiert nach lokal summiertem Kontrast. Arbeite diese Liste durch und selektiere den Marker mit dem grössten Abstand zum second match, also der grössten Eindeutigkeit. Dies funktioniert folgendermassen: Zunächst wird eine Area-17 summierte Kontrastkarte erstellt. Das bedeutet, der pixelweise (lateral-)Kontrast wird mit einer gleichgewichteten 17x17 Maske aufsummiert. Als nächstes wird in diser Karte ein Maximum gesucht. Die Koordinaten werden gespeichert und in der Karte ein 16x16 Areal um diesen Punkt gelöscht. Darauf wird ein weiteres Maximum der Karte gesucht und wieder genauso verfahren, bis die Liste gefüllt oder die Karte leer ist. Im nächsten Arbeitsgang wird für jeden dieser Marker der best match, sein Qualitätsmass q1, der second match und ebenso dessen Qualitätsmass q2 bestimmt. Das Verhältniss von q1 zu q2 ist gegenüber dem Rauschen ein Mass für die momentane Eindeutigkeit (me) des Markers im Bild. Das Qualitätsmass ist (zunächst) einfach die pixelweise Differenz zwischen Marker und Bildpixel. Aus dieser Liste wird dann der Marker mit dem grössten me-Wert selektiert. Das Bildrauschen kann nicht weiter beachtet werden, weil es sowieso global ist. Es ist nur interessant in Bezug auf den Vergleich mehrerer MatchingSituationen, also z.B. bei Dunkelheit und Helligkeit. Speichere den Marker in einem (Bit-)Map. Speichere den Patch (das Bild) in einer Patchmap. Zu dieser gehört auch der Marker mit seiner Position im Bild, als R-Marker. Wichtig ist an diesem Vorgehen, dass die Funktion best_match die langsamste ist, da alle Marker-Pixel mit allem Bildpixeln bzw. auf allen Verschiebungen verglichen werden müssen. Mit grösserer Auflösung wird dieses Problem auch immer dramatischer. Die Länge der Liste geht auf diese Dramatik dagegen "nur" linear ein, lässt sich also noch leicht einstellen. A2: Verfolge diese Marker, bis er eine Position auf der linken Seite des Bildes eingenommen hat. Prüfe dabei auf Abriss. Prinzipiell muss dabei die funktion best_match verwendet werden, ersatzweise (später) ein Tracking. Eine mögliche Schwelle zum Abriss ist q1-me/2, weil es dann möglich wäre, dass der second match gefunden wäre. Man weiss aber, dass die Qualität bei Bewegung sehr stark schwankt. Eine Möglichkeit, dies zu kompensieren ist, das Tracking dynamisch zu machen. Aber auch dabei kann ein Abriss stattfinden. Besser ist es vielleicht, beim ursprünglichen Marker zu bleiben, ihn zu mitteln oder später zu ihm zurückzukehren. Hier ist der Hund begraben. Dennoch, wird durch best_match der Marker auf der linken Seite in ausreichender Qualität festgestellt, dann A3. Sonst: verweile in A2. A3: Speichere wieder den Patch (das aktuelle Bild) zusammen mit dem Marker und seiner Bildposition in der nächsten Patchmap. Diesmal muss der Marker als "L-Marker" gespeichert werden, da er sich auf der linken Seite des Patches befindet. A4: Sofern die gewünschte Anzahl Patches erreicht ist, gehe zu A1, sonst A5 A5: Berechne und speichere das Gesamtbild, zusammengesetzt aus den einzelnen Patches. Berechnen des Gesamtbildes: Zuerst müssten die Grenzen bestimmt werden. Dafür müssten aber zuerst die Relationen und Grenzen der einzelnen Patches bekannt sein. Es werden daher vorläufige Koordinaten verwendet, die sich auf den Ursprung des ersten Bildes beziehen. Daher sind auch negative Koordinaten (auf der Y-Achse) möglich. Bei jeweils zwei direkt benachbarten Patches entsprechen sich der R-Marker des linken Patches und der L-Marker des rechten Patches. Im Gesamtbild sind die jeweiligen Koordinaten der Marker identisch. Vorausgesetzt, die Koordinaten des linken Patches sind schon angepasst bzw. bestimmt, dann berechnen sich die rechten Patchkoordinaten aus den linken Patchkoordinaten plus der Differenz der Markerkoordinaten. Der erste Patch hat also die Koordinaten (0,0) und alle nachfolgenden Patches haben im Gesamtbild daraus abgeleitete Koordinaten. Die Koordinaten der R- und L-Marker bleiben immer relativ. Nun lassen sich die Grenzen des Gesamtbildes in einer weiteren Schleife über die Patches berechnen. Dann ein passendes Array anlegen, befüllen und dann eine Funktion zum speichern des Bildes aufrufen. Folgeprobleme Da immer Marker gesucht werden, wird im Moment die Funktion best_match verwendet, was langwierig ist. Dennoch ist die Übereinstimmung oft minderwertig. Nochmalige Überdenkung Vor dem Zusammenfügen des endgültigen Bildes werden die Patches schrittweise gespeichert: Der erste Patch mit einem R-Marker, dessen Position im Bild angegeben ist. Der Patch besteht aus einer rgba-map und zwei Markern. Ein Marker besteht aus einer rgba-map und einem Match. Ein Match besteht aus x/y Koordinaten, diff und d_2nd. Dies bezieht sich allerdings auf best_match, weil nur dort diese beiden Werte bestimmt werden. Der nächste Patch besteht aus einem L-Marker, dem aktuellen Bild und dem RMarker. Der L-Marker ist identisch mit dem R-Marker des vorherigen Patches. Seine Position ist aber relativ zum aktuellen Bild. Der Dekfehler war , dass A1: autofind marker zum speichern eines patches (in A3) erforderlich ist. Als Ausgleich ist das Speichern des PAtches nun in A0 untergebracht. A1 schreibt nur den R-Marker hinein. Nun (=mittendrin) erweist sich, es gibt Situationen, wo der beste marker (an A1) nur ein qr von 1.1 hat. Man weiss also vorher, dass die Suche danach (wegen Erfolg ab qr=2.0) nicht fruchten wird. Das musste irgendwann passieren. Was will man verlangen? qr=10? Nehmen wir qr=4; 13.10. die ersten Panoramabilder. Das ist dann doch geil: Erstes 3-Patch Bild: Nun mit Farbkorrektur (B und R vertauscht): Nun auch mit Stativ: Mit 6 Patches und Stativ: Das ist nun ziemlich abgefahren, weil er trotz der sehr grossen Verzerrungen dennoch die richtigen Bereiche erkannt hat. Wrap-Up Die erste Implementation des legendären Panomax-Algorithmus war ein voller Erfolg. Trotz unklarer Source-Lage konnten recht bald Bilder von der Webcam gezogen werden, automatisch Marker extrahiert werden und diese auch noch verzerrt relativ gut wiedererkannt werden. Das zusammengefügte Panoramabild ist erstaunlich gut, leidet aber unter den typischen Bildverzerrungen. Die Qualität ist erst auf einen Stativ richtig gut, weil dann das Bild nicht verwackelt ist. Licht muss in ausreichender Menge vorhanden sein, auch die Webcam selbst muss immerhin eine gewisse Qualität haben. Es ist möglich, dass Marker gewählt werden, die trotz allem praktisch nicht erkannt werden. Dann ist ein Neustart erforderlich. Die Wahl des "richtigen" Markers ist aber praktisch entscheidend. Im Moment ist dies lumax-a17-ldiff2x2, eingeschränkt auf den relevanten Bereich (rechts). Die Qualität dieses Markers wird durch best_match qualifiziert. Dies ist teuer und besonders für grössere Auflösungen nicht praktikabel. Ein Vergleich alternativer Marker zeigte im Einzelversuch, dass die Lumax-Werte scheinbar mit den best_match werten verteilt sind. Dies sollte eventuell näher untersucht werden. Ausserdem sollte ein weiterer Ansatz erarbeitet werden, um wiederfindbare Marker a priori qualifizieren zu können, auch ohne best_match. Genauso ist die lokale Bildfarbe zu beachten. Ebenso sind die Marker skalierbar zu machen. Parallel dazu sollte die Auflösung der Marker einstellbar sein. In der Summe sollen sehr grosse Marker möglich werden, welche aber nicht die selbe Pixelauflösung wie das Bild enthalten, sondern ebenfalls nur 16x16 oder noch weniger Pixel. Eine triviale Funktion zur Linenentzerrung könnte untersucht werden, dazu später ein Approximationsverfahren, eine auto-kalibrierung. Ein Tracking-Algorithmus, um den lästigen Best-Match rausnehmen zu können. Ausserdem alles Weitere machen, um die Frame-Rate hoch zu halten. Berechnungen 320x240 Pixel sind 76800 Stück oder 7*10^4. 16x16Pixel sind 256 Stück. Bei Bestmatch müssen fast 76800x256=19660800 oder 1.9x10^7 Vergleiche gemacht werden. Das ist für Echtzeit auf dem raspi viel zu viel. Genauer sind es nur (320-16)x16 x (240-16) x 16 = 1.7*10^7, also nur ein paar prozent weniger. Der 16x16 Marker passt 20x horizontal ins Bild und 15x vertikal. Um die Performance um den Faktor 256 zu erhöhen, müssten alle Einzeldimensionen um den Faktor 4 kleiner werden, also 4x4 Marker auf 80x60 Pixeln. Dann wären nur noch (80-4)x4 x (60-4)x4 = 68096 Vergleiche erforderlich, oder 6,8*10^4. In dieser Grössenordnung kann man dann Bestmatch in Echtzeit durchführen. b Ebenso könnte man direkt divide et impera verwenden und gleich in mehreren Stufen der Verfeinerung denken. Farbquantisierung des Bildes könnte hier interessant sein. Weiter gedacht, könnte man mal tatsächlich eine 360° Panoramaaufnahme machen und schauen, was bei den Matches oder Bestmatch in der Analyse herauskommt, wenn man tatsächlich nach global eindeutigen Markern sucht. Vielleicht sogar möglichst kleinen Markern. Ein weiterer Ansatz ist "Area of strange color". Hier wird nach einem Farbareal gesucht, welches nur 1x vorkommt und möglichst klein ist. Im Grunde ist aber auch die Farbe nur eine Dimension im Feature-Raum. Und im weiteren Grunde gibt es je Bild eine Grössengrenze der Marker, ab der jeder Marker eindeutig ist. Verzerrung ist dabei nicht beachtet. Möglicherweise ist es eine gute Idee, zunächst Bestmatch auszuschalten und das Markertracking zu optimieren. Möglicherweise dabei die Verzerrung approximieren und dann herauszurechnen. Globale Kamerabewegung muss dafür schnell erkannt werden. Hierfür kann man evtl. das realtime-bestmatch verwenden, bzw. eine Variante davon, spezialisiert auf grosse Bildbereiche und hohe Geschwindigkeiten. Schärfe geht dabei sowieso verloren, es kann also gar nicht auf der höchsten Auflösung operieren. Recherche zu Caching: Abgesehen von 4-8Hz Sampling rate ist das delay, bzw. die latency schlimm. Man kann sie bei vlc von 300 auf 0 ms runterdrehen. Bei video for linux (http://www.linuxtv.org/downloads/legacy/video4linux/API/V4L2_API/specsingle/v4l2.html#extended-controls) habe ich noch nichts gefunden. Es gibt aber eine Aleitung in diesem Link: http://zacharybears.com/low-latencyraspberry-pi-video-streaming/ Performance-Messung: copy_map_fast: timing 100000 times, total 8.75 sec, sample_from_screen: timing 1000 times, total 3.73 sec, temporal_diff: timing 1000 times, total 2.37 sec, lateral_diff: timing 1000 times, total 2.17 sec, area_sum: timing 1000 times, total 3.50 sec, 8.75E-05 3.73E-03 2.37E-03 2.17E-03 3.50E-03 sec/call sec/call sec/call sec/call sec/call Dies nach Entdeckung der Geschwindigkeit von memcpy und einer schönen Seite. Alle Aufrufe operieren auf einem 320x240 Bild in Rgba_pixel(=4Byte). Die Aussage 3.73E-03 sec/call bedeutet etwa 0.0037s = 3.7ms, was einer Frequenz von 270Hz entspricht. Wie ist dann aber die Langsamkeit (8Hz) zu erklären...? 4x3.7ms=0.0148s, entspricht noch immer 67Hz. Gut, es wird ja auch noch weiterer Code ausgeführt. Dieser verbraucht etwa 10ms. Dann wären es 0.0248s, entsprechend 40Hz. Aber eben nicht 8 Hz, was 0.125s entspricht. (Hierzu die Erklärung durch den 10x-Messfehler s.u.) Spasseshalber führen wir 10x die Folge der gemessenen Funktionen aus. Es ergibt sich 1.2sec, 0.83 Hz. Demnach ist die mehrfache Ausführung der Folge der Funktionen erheblich langsamer, als die mehrfache Ausführung der einzelnen Funktionen in Folge. Das ist seltsam. Da fällt mir nur der Cache ein, beeinflusst durch den Programmcode. Es gibt aber einen 16K Cache, der sollte für kleine Funktionen doch reichen..? (Auch hier gilt: der Messfehler verursacht diese Differenz) Fazit: es gibt gigantisches Optimierungspotential! (Nein, nur mittelgrosses.) Es muss doch aber RT fähig werden. Also wenigstens 30fps. Das heisst 0.03s pro Frame. Bereits sample_from_screen sprengt das mit 0.04s. Die andern 3 zusammen ergeben 0.08, was ja auch Sinn macht. Nachgeschlagen bei "Writing ARM assembly language" bzw. http://infocenter.arm.com/help/topic/com.arm.doc.dui0473c/DUI0473C_using_th e_arm_assembler.pdf Ergibt die einsicht, dass man evtl. auch (nach 5.17 block copy) statt einem Wort auch 8 Wörter pro 4 instructions kopieren kann. Dabei ist ein Wort 4 Byte. Eine Anfängerversion mit einem Wort sieht so aus: AREA Word, CODE, READONLY ; name this block of code num EQU 20 ; set number of words to be copied ENTRY ; mark the first instruction called start LDR r0, =src ; r0 = pointer to source block LDR r1, =dst ; r1 = pointer to destination block MOV r2, #num ; r2 = number of words to copy wordcopy LDR r3, [r0], #4 ; load a word from the source and STR r3, [r1], #4 ; store it to the destination SUBS r2, r2, #1 ; decrement the counter BNE wordcopy ; ... copy more stop MOV r0, #0x18 ; angel_SWIreason_ReportException LDR r1, =0x20026 ; ADP_Stopped_ApplicationExit SVC #0x123456 ; ARM semihosting (formerly SWI) AREA BlockData, DATA, READWRITE src DCD 1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8,1,2,3,4 dst DCD 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 END Hiervon sind nur Teile zu übersetzen: LDR r0, =src LDR r1, =dst MOV r2, #num wordcopy LDR r3, [r0], #4 STR r3, [r1], #4 SUBS r2, r2, #1 BNE wordcopy ; r0 = pointer to source block ; r1 = pointer to destination block ; r2 = number of words to copy ; load a word from the source and ; store it to the destination ; decrement the counter ; ... copy more Mit Hilfe einer(2) einfachen Anweisung: long src, dest, num; asm volatile( "ldr r0, %0" "ldr r1, %1" "mov r2, %2" "wordcopy" "ldr r3, [r0], #4" "str r3, [r1], #4" "stubs r2, r2, #1" "BNE wordcopy" : : "r" (src), "r" (dest), "r" (num)); Oder noch einfacher in einer .s datei: arm_wordcopy: // source in r0 // dest in r1 // num words to copy in r2 ldr r3, [r0], #4 str r3, [r1], #4 subs r2, r2, #1 bne arm_wordcopy bx lr Verbessert mit octocopy: ergibt noch nicht max. perf wie in memcpy. vermutlich neon-cpoy mit pld, entsprechend: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/ka13544.ht ml Nun, fast, also mit 2x verzögerung gegenüber memcpy. Guter Blog: http://thinkingeek.com/2013/03/28/arm-assembler-raspberry-pi-chapter-12/ Weitere Links: http://www.shervinemami.info/armAssembly.html www.coranac.com/tonc/text/asm.htm Letzter Tip wahrscheinlich im google groups post - "even faster memcpy" (über shervin): https://groups.google.com/forum/#!topic/beagleboard/Ese9QV8CZM -Aber auch dies bringt nur Bruchteile. Die Statistik sieht nun so aus: copy_map_arm_wordcopy: timing 10000 times, total 3.08 sec, copy_map_arm_octocopy: timing 10000 times, total 2.42 sec, copy_map_arm_neoncopy: timing 10000 times, total 1.42 sec, copy_map_slow: timing 10000 times, total 8.89 sec, copy_map_fast: timing 100000 times, total 8.71 sec, copy_map_alt: timing 10000 times, total 3.04 sec, sample_from_screen: timing 1000 times, total 3.72 sec, temporal_diff: timing 1000 times, total 2.35 sec, lateral_diff: timing 1000 times, total 2.16 sec, area_sum: timing 1000 times, total 3.50 sec, 3.08E-04 2.42E-04 1.42E-04 8.89E-04 8.71E-05 3.04E-04 3.72E-03 2.35E-03 2.16E-03 3.50E-03 sec/call sec/call sec/call sec/call sec/call sec/call sec/call sec/call sec/call sec/call Daher wird bei copy_map nun immer copy_map_fast verwendet, welches auf memcpy beruht. Der call bezieht sich immer auf 0,3 Mpix, also 1,2 Mbyte, da es 4 Byte pro Pixel sind. Ein Zyklus bei 700 Mhz sind 0.0014 x10-6 bzw. 1.4 x10-9 sec. Das bedeutet, jedes Pixel im (memcpy-)Call verbraucht etwa 8.7 x10-5 / 0.3 x 10+6 = 2.8 x10-9 sec. Dies sind sehr genau 2 Zyklen. Das wäre nun schön, aber es offenbarte sich dadurch ein 10x-Messfehler in der Messschleife (verwendung des 10x-erhöhten Messfaktors ausserhalb for). Die korrekten Zeiten sind nun bei 2 Stellen Genauigkeit: copy_map_arm_wordcopy: timing 100 times, total 0.31 sec, copy_map_arm_octocopy: timing 100 times, total 0.24 sec, copy_map_arm_neoncopy: timing 100 times, total 0.14 sec, copy_map_slow: timing 10 times, total 0.10 sec, copy_map_fast: timing 1000 times, total 0.88 sec, copy_map_alt: timing 100 times, total 0.30 sec, sample_from_screen: timing 10 times, total 0.37 sec, temporal_diff: timing 10 times, total 0.23 sec, lateral_diff: timing 10 times, total 0.22 sec, area_sum: timing 10 times, total 0.35 sec, 3.10E-03 2.40E-03 1.40E-03 1.00E-02 8.80E-04 3.00E-03 3.70E-02 2.30E-02 2.20E-02 3.50E-02 sec/call sec/call sec/call sec/call sec/call sec/call sec/call sec/call sec/call sec/call Hieraus folgt nun für memcpy etwa 20 Zyklen, was etwas viel ist. (falsch) Ausserdem folgt für copy_map: slow 0.01s -> fast 0.00088s: fast x 11 = slow Assembler kann also "nur" 11x schneller sein in diesem Fall. Die Coptimierung ("alt) bringt aber schon 0.01s -> 0.003s faktor x3. Es bleibt also nur noch Faktor x4 durch Assembler allein - jedenfalls in diesm Fall. Man kann also sagen Assebler-rgba C-bw. Ausserdem bleibt noch das Downscaling, um den Experimentalfall zu entschmerzen. Dann bliebe immer noch das sampling from Screen und die rgb-konvertierung. Ein wichtiger Versuch wäre noch, eine copy-shell zu bauen, die dann versuchsweise immer mehr innere Befehle enthält. Diese dann in cycles vermessen. Dies legen wir in die init-phase. Hm, ich verstehe nicht, warum das so lange dauert. Wieviele Zyklen pro wort benötigt wordcopy? Antwort: copy_map_arm_wordcopy: timing 100 times, total 0.28 sec, 2.80E-03 sec/call Bei 0.3Mpix sind dies 1.2 MB, aber nur 0.3Mworte, weil ein Pixel 32 Bit sind, also ein Wort. Es werden daher 2.8E-3 / 0.3E+6 = 9.3E-9 sec/wort benötigt. Ein Zyklus sind 1.4 x10-9 sec, es handelt sich also um 6,64 Zyklen. Wordcopy lautet: ldr r3, [r0], #4 str r3, [r1], #4 subs r2, r2, #1 bne Je nach alignment kann ldr ein result latency von 3 oder 4 haben (bei 7 Registern in LDM sind es maximal 6). Die gesamte Schleife hätte dann 4 Zyklen plus 4 = 8. Es sind aber nicht einmal 7, wahrscheinlich wegen branch prediction. Gut, Party, denn wir sind auf Cylecount-Level. Man kann nun noch den preloader hinzugeben. Bringt aber vermutlich nur für Neon etwas. Neon primer: http://armneon.blogspot.mx/2013/07/neon-tutorialpart-1-simple-function_13.html Beim Optimieren von sample from screen und map to screen verhaspel ich mich nur, da ist es leichter, einen scale factor einzubauen oder eine bw-option. Die erste Implementation war ja schon schwierig. orig: sample_from_screen: timing 10 times, total 0.35 sec, 3.50E-02 sec/call bw: sample_from_screen: timing 100 times, total 0.34 sec, 3.40E-03 sec/call der hit. Noch geilere Infos: Raspberry GPU programming (x10): http://petewarden.com/2014/08/07/how-to-optimize-raspberry-pi-code-usingits-gpu/
© Copyright 2024 ExpyDoc