Webcam Projekt 10/2015 Der Router koppelt mit dem 2. Raspi

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/