Kamera: Unterschied zwischen den Versionen

Aus Kicker
Zur Navigation springenZur Suche springen
Keine Bearbeitungszusammenfassung
Keine Bearbeitungszusammenfassung
 
(22 dazwischenliegende Versionen von einem anderen Benutzer werden nicht angezeigt)
Zeile 1: Zeile 1:
= Ballerfassung mit Hilfe einer Kamera =
= Ballerfassung mit Hilfe einer Kamera =
== Informationen zur verwendeten Hardware ==
== Informationen zur verwendeten Hardware ==
=== Kamera ===
=== Kamera (sichtbares Licht)===
Bei der Kamera handelt es sich um eine schwarzweiß-Hochgeschwindigkeitskamera der Firma [http://www.mikrotron.de/index.php?de_home Mikrotron], die [http://www.mikrotron.de/index.php?de_cams_mikrotron_mc1302 MC1302]. Diese Kamera ist mit bis zu '''118 Bilder/s''' bei maximaler Auflösung ('''1280x1024Pixel''') für die Ballerkennung völlig ausreichend, zumal bei geringerer Auflösung die Bildwiederholrate noch gesteigert werden könnte.
Bei der Kamera handelt es sich um eine schwarzweiß-Hochgeschwindigkeitskamera der Firma [http://www.mikrotron.de/index.php?de_home Mikrotron], die [http://www.mikrotron.de/index.php?de_cams_mikrotron_mc1302 MC1302]. Diese Kamera ist mit bis zu '''118 Bilder/s''' bei maximaler Auflösung ('''1280x1024Pixel''') für die Ballerkennung völlig ausreichend, zumal bei geringerer Auflösung die Bildwiederholrate noch gesteigert werden könnte.
Als Ansprechpartner bei Mikrotron stellte sich Hr. Ertl, ein ehemaliger Diplomant der FH München, zur Verfügung.
Als Ansprechpartner bei Mikrotron stellte sich Hr. Ertl, ein ehemaliger Diplomant der FH München, zur Verfügung.
=== Beleuchtung ===
Bei der Beleuchtung ist darauf zu achten, dass möglichst LEDs verwendet werden, oder Lampen mit extrem hoher Eigenfrequenz, damit die Frequenz der Beleuchtung nicht mit der Bildrate der Kamera interferiert. Anbieten würden sich daher LED-Spots, z.B. folgende:

'''[http://www1.conrad.de/scripts/wgate/zcop_b2c/?~template=pcat_area&p_load_area=0212493&p_selected_area=0212493&zhmmh_area_kz=13&navi=mitte klick]'''

Fraglich ist, welche Farbe sich anbieten würde. Da bisherige Testdurchläufe mit einem Roten Ball erfolgten wären wohl die roten LEDs am effektivsten! '''<Überprüfen!>'''

=== Objektiv ===
=== Objektiv ===
Das zunächst verwendete Objektiv '''CANON LENS FD 20mm 1:2,8''' hat den Nachteil, dass der nötige Abstand zum Objekt, dem Spielfeld, bei einer Auflösung von 640x480 (Grund für diese Auflösung weiter unten!) viel zu groß sein müsste. Bei einem Spielfeld, das '''125cm&nbsp;x&nbsp;68,5cm''' bemisst wäre ein Abstand von Kamera zu Spielfeld von etwa '''3,38m''' nötig. Diese Annahme ergibt sich aus zwei Messungen und der Tatsache, dass sich der Blickwinkel nicht verändert und somit bei doppeltem Abstand auch eine doppelte Länge bzw. Breite erfasst werden kann (Abweichungen durch Scharfstellen und dem damit verbundenen verändern der Linsen vernachlässigt!).
Das zunächst verwendete Objektiv '''CANON LENS FD 20mm 1:2,8''' hat den Nachteil, dass der nötige Abstand zum Objekt, dem Spielfeld, bei einer Auflösung von 640x480 (Grund für diese Auflösung weiter unten!) viel zu groß sein müsste. Bei einem Spielfeld, das '''125cm&nbsp;x&nbsp;68,5cm''' bemisst wäre ein Abstand von Kamera zu Spielfeld von etwa '''3,38m''' nötig. Diese Annahme ergibt sich aus zwei Messungen und der Tatsache, dass sich der Blickwinkel nicht verändert und somit bei doppeltem Abstand auch eine doppelte Länge bzw. Breite erfasst werden kann (Abweichungen durch Scharfstellen und dem damit verbundenen verändern der Linsen vernachlässigt!).
Zeile 16: Zeile 23:
Ein besseres Resultat wird mit einem Objektiv mit geringerer Brennweite erzielt. Dafür stehen zum einen ein Objektiv mit 12.5mm ('''PENTAX TV LENS 12.5mm 1:1.4''') oder mit 8mm ('''MEGAPIXEL CCTV LENS 0814MP f8mm F1.4 A6314''') zur Verfügung.
Ein besseres Resultat wird mit einem Objektiv mit geringerer Brennweite erzielt. Dafür stehen zum einen ein Objektiv mit 12.5mm ('''PENTAX TV LENS 12.5mm 1:1.4''') oder mit 8mm ('''MEGAPIXEL CCTV LENS 0814MP f8mm F1.4 A6314''') zur Verfügung.
==== PENTAX TV LENS 12.5mm 1:1.4 ====
==== PENTAX TV LENS 12.5mm 1:1.4 ====
Ein durchaus besseres Resultat liefert das Objektiv mit 12.5mm Brennweite. Bei einem Abstand von Objektiv zu Objekt von 113.5cm konnte Feld von 70cm Länge und 51cm Breite aufgenommen werden. Der Quotient 113.5cm/70cm=1,62 ergibt, dass ein Abstand von '''1,62*1,25cm=2,03m''' nötig wäre, um das gesamte Kickerfeld aufzunehmen, also bedeutend weniger als die 3,38m mit dem CANON-Objektiv. Zusätzlich kann noch mit einem Spiegel gearbeitet werden, um die Kamera nicht 2m vom Spielfeld entfernt positionieren zu müssen.

==== MEGAPIXEL CCTV LENS 0814MP f8mm F1.4 A6314 ====
==== MEGAPIXEL CCTV LENS 0814MP f8mm F1.4 A6314 ====
Dieses Objektiv würde mit der 8mm Brennweite den nötigen Abstand zwischen Objektiv und Spielfeld weiter reduzieren. Leider stellte es sich als inkompatibel zur Kamera heraus, es war nicht möglich das Bild scharf zu stellen.


=== Rechner ===

=== Rechner, ggf. Grapperkarte etc - TODO ===
RAM: 1GB
RAM: 1GB
CPU: AMD Athlon 64 3200
CPU: AMD Athlon 64 3200
Framegrapper-Karte ohne eigenen Speicher


== Infos zu verwendeter, vorläufiger Software ==
== Infos zu verwendeter, vorläufiger Software ==
=== Kamera konfigurieren ===
=== Kamera konfigurieren ===
Um die Kamera zu konfigurieren wird die Software MC1302_25 Mikrotron GMbH '''(korrekter Programmname?)''' verwendet. Gute Werte wurden erzielt, indem das User Profile 2 geladen wurde, dir 'shutter-mode' auf Async timer gesetzt wurde und die 'exposure time [sec]' den Lichtverhältnissen angepasst wurde. '''<TODO: genauere Konfiguration angeben!>'''. Diese Software wurde durch durch einen Download von [http://www.mikrotron.de/index.php?de_home Mikrotron] auf den aktuellen Stand gebracht (firmware: V1.43-F2.45) '''<Versionsnummer überprüfen!>'''
Als erstes muss das Programm VCAM gestartet werden (vor camera control tool). Um die Kamera zu konfigurieren wird die Software '''Mikrotron camera control tool - MC1302_25 Version 1.2.9''' verwendet. Gute Werte wurden erzielt, indem das User Profile 2 (für 640x480px - für 1280x1024 ggf. UserProvile3 laden) geladen wurde, (ggf. 'shutter-mode' auf Async timer gesetzt wurde) und die 'exposure time [sec]' den Lichtverhältnissen angepasst wurde (so klein wie möglich, so groß wie nötig - vgl. Berechnungen). Diese Software wurde durch durch einen Download von [http://www.mikrotron.de/index.php?de_home Mikrotron] auf den aktuellen Stand gebracht '''(firmware: V1.43-F2.45).'''
=== Resultate der Kamera betrachten und Bildfolgen abspeichern ===
=== Resultate der Kamera betrachten und Bildfolgen abspeichern ===
Um sofort überprüfen zu können, ob die verwendeten Einstellungen zu gebrauchen sind bietet sich die Verwendung des Programms '''VCAM''' von [http://www.mikrotron.de/index.php?de_home Mikrotron] an. Dieses zeigt zum einen die aktuellen Bilder der Kamera wieder, zum anderen gibt es auch die Möglichkeit, Bildfolgen direkt auf die Festplatte abzuspeichern, um sie anschliesend auszuwerten. Dabei fiel allerdings auf, dass selbst bei einer geringen Auflösung von 640x480Pixeln die Geschwindigkeit der Festplatte das Bottleneck des Speichervorgangs darstellt. Folgende Messreien zeigen deutlich, dass bei einer hohen Bildwiederholrate nicht alle Bilder abgespeichert werden können (Speicherung einer Bildreihe von einer Uhr mit Zehntel Sekunden):
Um sofort überprüfen zu können, ob die verwendeten Einstellungen zu gebrauchen sind bietet sich die Verwendung des Programms '''VCAM''' von [http://www.mikrotron.de/index.php?de_home Mikrotron] an. Dieses gibt zum einen die aktuellen Bilder der Kamera wieder, zum anderen gibt es auch die Möglichkeit, Bildfolgen direkt auf die Festplatte abzuspeichern, um sie anschliesend auszuwerten. Um das Bild der Kamera wiederzugebn muss zum einen ein zu den Einstellungen des 'camera control tool's passendes Profil geladen werden (für 640x480 beispielsweise '''MC1310_11_02_03 640x480 206fps''' über Camera -> Profile load/save) und anschliesend Camera -> Capture. Dabei fiel allerdings auf, dass selbst bei einer geringen Auflösung von 640x480Pixeln nicht alle Bilder mit eingestellter fps aufgenommen werden konnten. Zunächst wurde davon ausgegangen, dass die Geschwindigkeit der Festplatte das Bottleneck des Speichervorgangs darstellte. Folgende Messreien zeigen deutlich, dass bei einer hohen Bildwiederholrate nicht alle Bilder abgespeichert werden können (Speicherung einer Bildreihe von einer Uhr mit Zehntel Sekunden):


'''4x .7'''<br>
'''4x .7'''<br>
Zeile 68: Zeile 76:
* RAM -> RAM: 875msec
* RAM -> RAM: 875msec
* RAM -> HDD: 18567msec
* RAM -> HDD: 18567msec
==== Ramdisk erstellen ([http://www.winfaq.de/faq_html/Content/tip1000/onlinefaq.php?h=tip1260.htm Win2000]):====
==== Ramdisk erstellen ([http://www.chip.de/downloads/RAMDisk-1.9_13000196.html Win2000]):====
Wie in der Messung unter Linux festzustellen sollte das Problem, dass die Bilder nicht schnell genug auf die HDD geschrieben werden können, mit einer Ramdisk gelöst bzw. umgangen werden können. Von den 1GB Arbeitsspeicher wurde dafür eine Ramdisk von 700MB erstellt. Bei einer neuen Aufnahme einer Bildfolge ergab sich dabei folgendes Resultat:
///TODO: Bisher nicht getestet da Win2000 nur in Labor verfügbar!

# ///TODO: Softwareimplementation
'''5x .7'''<br>
'''5x .8'''<br>
'''5x .9'''<br>
'''4x .0'''<br>
'''5x .1'''<br>
'''5x .2'''<br>
'''5x .3'''<br>
'''5x .4'''<br>
'''5x .5'''<br>
'''5x .6'''<br>
Das ergibt eine Rate von '''49fps''' bei einer eingestellten Rate von '''205fps''', was gegenüber der 2. Messung ohne Ramdisk keinerlei Verbesserung darstellt. Das Problem scheint also an anderer Stelle zu liegen. Nächster Lösungsansatz wird sein, die Bilder direkt über C-Code von der Kamera zu holen, in reserviertem Speicher abzulegen und danach in Dateien zu speichern.

=== Koordinaten des Balles aus Bild ermitteln ===
Was später automatisiert in Sourcecode gefasst passieren soll wird zunächst Step by Step in einem eigenem Programm durchgeführt: Das Auswerten jedes Bildes, um die Koordinaten des Balls zu ermitteln. Dafür wird ein Macro für die Software '''<MCR - genauer Softwarename!>''' geschrieben, mit dessen Hilfe folgende Schritte für jedes einzelne Bild durchlaufen werden:
* Bild [bmp] einlesen
* Bereiche mit bestimmter Farbe / bestimmtem Grauwert als aktiv markieren
* Aktive Bereiche, die unterhalb eines Grenzwertes sind löschen (z.B. <200px)
* Schwerpunkt der verbleibenden aktiven Punkte ermitteln (bei Ball -> Mittelpunkt)
* Koordinaten (Mittelpunkt) zu Liste hinzufügen
Das Macro, das dafür notwendig ist sieht folgendermasen aus:
<source lang="text">
imgdelete "*"


# Bilder 12 bis 41 laden
# for i0=1; i0 <= 40; i0 = i0+1
# imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_000"i0+10".bmp",i0
# endfor
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00011.bmp",1
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00012.bmp",2
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00013.bmp",3
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00014.bmp",4
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00015.bmp",5
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00016.bmp",6
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00017.bmp",7
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00018.bmp",8
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00019.bmp",9
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00020.bmp",10
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00021.bmp",11
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00022.bmp",12
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00023.bmp",13
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00024.bmp",14
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00025.bmp",15
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00026.bmp",16
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00027.bmp",17
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00028.bmp",18
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00029.bmp",19
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00030.bmp",20
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00031.bmp",21
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00032.bmp",22
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00033.bmp",23
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00034.bmp",24
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00035.bmp",25
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00036.bmp",26
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00037.bmp",27
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00038.bmp",28
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00039.bmp",29
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00040.bmp",30
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00041.bmp",31
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00042.bmp",32
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00043.bmp",33
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00044.bmp",34
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00045.bmp",35
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00046.bmp",36
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00047.bmp",37
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00048.bmp",38
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00049.bmp",39
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00050.bmp",40

# imgdisplay 1

for i1=1; i1 <= 40; i1 = i1+1
dislev i1,i1+100,90,213,1
endfor
# dislev 1,1+100,90,213,1
# dislev 2,2+100,90,213,1
# dislev 3,3+100,90,213,1
# dislev 4,4+100,90,213,1
# dislev 5,5+100,90,213,1
# dislev 6,6+100,90,213,1
# dislev 7,7+100,90,213,1
# dislev 8,8+100,90,213,1
# dislev 9,9+100,90,213,1
# dislev 10,10+100,90,213,1
# dislev 11,11+100,90,213,1
# dislev 12,12+100,90,213,1

for i2=1; i2 <= 40; i2 = i2+1
binscrap i2+100,i2+200,0,3000,0
endfor
# binscrap 1+100,1+200,0,3000,0
# binscrap 2+100,2+200,0,3000,0
# binscrap 3+100,3+200,0,3000,0
# binscrap 4+100,4+200,0,3000,0
# binscrap 5+100,5+200,0,3000,0
# binscrap 6+100,6+200,0,3000,0
# binscrap 7+100,7+200,0,3000,0
# binscrap 8+100,8+200,0,3000,0
# binscrap 9+100,9+200,0,3000,0
# binscrap 10+100,10+200,0,3000,0
# binscrap 11+100,11+200,0,3000,0
# binscrap 12+100,12+200,0,3000,0

MSload "kicker1"
# MSsetfeat "REGIONFEAT"

MSmeasmask 1+200,1,"KICKER1",0,1,10
for i3=2; i3 <= 40; i3 = i3+1
MSmeasmask i3+200,1,"KICKER1",1,1,10
endfor
# MSmeasmask 2+200,1,"KICKER1",1,1,10
# MSmeasmask 3+200,1,"KICKER1",1,1,10
# MSmeasmask 4+200,1,"KICKER1",1,1,10
# MSmeasmask 5+200,1,"KICKER1",1,1,10
# MSmeasmask 6+200,1,"KICKER1",1,1,10
# MSmeasmask 7+200,1,"KICKER1",1,1,10
# MSmeasmask 8+200,1,"KICKER1",1,1,10
# MSmeasmask 9+200,1,"KICKER1",1,1,10
# MSmeasmask 10+200,1,"KICKER1",1,1,10
# MSmeasmask 11+200,1,"KICKER1",1,1,10
# MSmeasmask 12+200,1,"KICKER1",1,1,10

datalist "KICKER1",0,0
# for i112=2; i112 <= 12; i112 = i112+1
# MSmeasmask i112+100,1,"KICKER1",1,1,10
# endfor
datalist "KICKER1",0,0
MSsave "kicker1"
</source>
Deutlich zu erkennen ist, dass für jeden der Schritte bei größerer Anzahl von Bildern leicht mit Schleifen gearbeitet werden, nur bei dem ersten Schritt, dem einlesen der einzelnen Bilder treten hier durch die unterschiedlichen Dateinamen Probleme auf. Um trotzdem nicht alle Befehle per Hand editieren zu müssen wurde diese Aufgabe von einem kleinen C-Programm übernommen:
<source lang="cpp">#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define MAX_BILDER 10

int
main(int argc, char *argv[])
{
int i;
int start, ende;

if (argc <= 3)
return -1;

start = atoi(argv[2]);
ende = atoi(argv[3]);

for (i = start; i <= ende; i++) {
printf("%s%.4d.bmp\n", argv[1], i);
}

return 0;
}</source>
Das Programm wird mit 3 Parametern aufgerufen. Der erste Parameter ist der Dateipfad bis zu der Stelle der Nummerierung. Der 2. und 3. Parameter ist Anfang und Ende der Schleife. Soll beispielsweise Bild 35 bis 250 eingelesen werden, wird 35 als 2. Parameter und 250 als 3. Parameter übergeben. Die Ausgabe kann man nun direkt in das Makro kopieren.

Ein Testdurchlauf mit diesem Makro ergab folgende Wertetabelle (2. und 3. Spalte):

<source lang="text">
No X/pixel Y/pixel Dist Richtung [px ]
1 599,5 251,83 /[px] x: y:
2 588,51 251,84 10,99 -10,99 -0,01
3 577 251,87 11,51 -11,51 -0,03
4 563,88 251,76 13,12 -13,12 0,11
5 550,94 251,44 12,94 -12,94 0,32
6 537,38 251,35 13,56 -13,56 0,09
7 524,5 251,07 12,88 -12,88 0,28
8 510,85 250,94 13,65 -13,65 0,13
9 497,96 250,79 12,89 -12,89 0,15
10 477,69 250,77 20,27 -20,27 0,02
11 458,23 250,81 19,46 -19,46 -0,04
12 444,73 250,94 13,50 -13,50 -0,13
13 431,89 250,88 12,84 -12,84 0,06
14 418,07 251,07 13,82 -13,82 -0,19
15 398,71 251,18 19,36 -19,36 -0,11
16 384,71 251,41 14,00 -14,00 -0,23
17 372,33 251,49 12,38 -12,38 -0,08
18 357,79 251,78 14,54 -14,54 -0,29
19 338,33 251,94 19,46 -19,46 -0,16
20 323,7 252,27 14,63 -14,63 -0,33
21 310,82 252,55 12,88 -12,88 -0,28
22 296,42 252,9 14,40 -14,40 -0,35
23 276,66 253,26 19,76 -19,76 -0,36
24 262,2 253,57 14,46 -14,46 -0,31
25 249,2 253,8 13,00 -13,00 -0,23
26 234,76 254,15 14,44 -14,44 -0,35
27 221,4 254,54 13,37 -13,36 -0,39
28 207,61 254,94 13,80 -13,79 -0,40
29 193,82 255,39 13,80 -13,79 -0,45
30 180,51 255,92 13,32 -13,31 -0,53
31 166,19 256,46 14,33 -14,32 -0,54
32 153,25 257,02 12,95 -12,94 -0,56
33 138,69 257,72 14,58 -14,56 -0,70
34 125,58 258,31 13,12 -13,11 -0,59
35 111,12 259,01 14,48 -14,46 -0,70
36 97,41 259,77 13,73 -13,71 -0,76
37 83,45 260,4 13,97 -13,96 -0,63
38 69,24 261,14 14,23 -14,21 -0,74
39 55,58 261,77 13,67 -13,66 -0,63
40 42,97 262,5 12,63 -12,61 -0,73
</source>

Aus diesen Koordinaten lässt sich ohne weiteres die zurückgelegte Distanz (Spalte 4) sowie die Änderung in x-Richtung (Spalte 5) wie auch in y-Richtung (Spalte 6) feststellen.

== Eigener Sourcecode ==
Damit das vom Computer gesteuerte Team sofort auf Positionsänderungen des Balls reagieren kann muss die Auswertung der Koordinaten automatisiert werden. Dazu sind im grunde die Selben Schritte wie bei der Software nötig. Diese Schritte werden nach und nach in C-Code implementiert.
* Kameraprofil laden
* Bild und Bildinformationen von Kamera holen
* Binärbild mithilfe der Informationen mit Header versehen und als *.bmp abspeichern (Zwischenschritt um Erfolg zu prüfen
* Bildfolge abspeichern, fps überprüfen!!!

//TODO:
* Mit offenen Bibliotheken Koordinaten des Balls auf den Bildern ermitteln
* Optimierungen (z.B. nur Bildbereiche in bestimmten Umkreis der Koordinaten des letzten Bildes untersuchen o.ä.

Dabei entsteht folgender Code:
=== Kameraprofil laden und Binärbilder + Bildinformationen holen ===
<source lang="c">
#include <stdio.h>
#include <time.h>
#include "windows.h"
#include "mvfgdrv.h"


#include "bitmap.h"


#define IMAGE_CNT 60
#define IMAGE_PATH "bilder"
#define IMAGE_PREFIX "test"


//C:\PROGRA~1\MIKROT~1\Inspecta\Inspecta-4DC.cam
int
APIENTRY WinMain( HINSTANCE hInstance,
HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
LONG ret;
char buffer[4096];
char buffer1[256];
FILE *data;
void *ptr[IMAGE_CNT];
void **base;
unsigned int nTime[IMAGE_CNT];
FORMAT_INFO info;
SYSTEMTIME t1, t2;
int err = 0, i;
int x, y;
//GtkWidget *label;

/* open camera in Testmode, number 0 */
ret = mvfg_open("C:\\PROGRA~1\\MIKROT~1\\Inspecta\\Inspecta-4DC.cam;MC1310_11_02_03 640x480 206fps", 0);
if (ret != MVFG_OK) {
mvfg_errmessage(ret);
return -1;
}

/* get some camera informations */
ret = mvfg_getparam(MVFGPAR_FORMAT_INFO,&info,0);
if (ret != MVFG_OK) {
mvfg_errmessage(ret);
return -1;
} else {
wsprintf(buffer, "Pixel breite: %d\nPixel hoehe: %d", info.lImageWidth,
info.lImageHeight);
// MessageBox(NULL, buffer, "Informationen ueber aktuelle Einstellungen", MB_OK);
}

for (i = 0; i < IMAGE_CNT; i++) {
ptr[i] = malloc(info.lFrameSize);
if (ptr[i] == NULL) {
MessageBox(NULL, "Kein Speicher mehr vorhanden", "Speicher", MB_OK);
return 0;
}
}
/* grab picture */
MessageBox(NULL, buffer, "Informationen ueber aktuelle Einstellungen", MB_OK);
memset(buffer, 0x0, sizeof(buffer));
GetSystemTime(&t1);
for (i = 0; i < IMAGE_CNT; i++) {
ret = mvfg_grab(GRAB_NOWAIT, 0);
if (ret != MVFG_GRAB_READY) {
++err;
} else {
GetSystemTime(&t2);
nTime[i] = (t2.wMilliseconds + t2.wSecond * 1000) - (t1.wMilliseconds + t1.wSecond * 1000);
memcpy(ptr[i], mvfg_getbufptr(0), info.lFrameSize);
sprintf(buffer1, "%s/%s%.3i.bmp", IMAGE_PATH, IMAGE_PREFIX, i);
base = &ptr[i];
//createCuttedBinaryObject(ptr[i], 0x55, info.lFrameSize, &x, &y);
mpauswertung(ptr[i], &x,&y);
writeBitmapToFile(ptr[i], info.lFrameSize, buffer1);
wsprintf(buffer, "%s x: %i y: %i\n", buffer, x, y);
free(ptr[i]);
free(*base);
*base = NULL;
}
}
data = fopen("time.data", "w");
if (data == NULL) {
MessageBox(NULL, "Kann time.data nicht oeffnen", "Fehler", MB_OK);
return 0;
}
MessageBox(NULL, buffer, "Bildauswertung", MB_OK);
#if 0
MessageBox(NULL, buffer, "Bildauswertung", MB_OK);

for (i = 0; i < IMAGE_CNT; i++) {
sprintf(buffer, "%s/%s%.3i.bmp", IMAGE_PATH, IMAGE_PREFIX, i);
fprintf(data, "%lu\t%s\n", nTime[i], buffer);
writeBitmapToFile(ptr[i], info.lFrameSize, buffer);
free(ptr[i]);
ptr[i] = NULL;
}
fclose(data);
if (err > 0) {
wsprintf(buffer, "Es sind %d Fehler aufgetreten", err);
MessageBox(NULL, buffer, "Informationen ueber Bilder", MB_OK);
} else {
wsprintf(buffer, "Es wurden %d Bilder in %dms aufgenommen", IMAGE_CNT,
(t2.wMilliseconds + t2.wSecond * 1000) - (t1.wMilliseconds + t1.wSecond * 1000));
MessageBox(NULL, buffer, "Informationen ueber Bilder", MB_OK);
}
#endif
ret = mvfg_close(0);
if (ret != MVFG_OK) {
mvfg_errmessage(ret);
return -1;
} else {
MessageBox(NULL, "Bild wurde abgespeichert, Kamera Handle geschlossen", "Fertig", MB_OK);
}
return 0;
}

</source>

<source lang="c">
#ifndef _BITMAP_H
#define _BITMAP_H
/* search the environment of PIXEL */
#define PIXEL 70
/* find at least > CUT_PIXEL otherwise the pixel is not
* part of our object ("noise factor")
*/
#define CUT_PIXEL 2
typedef struct {
unsigned int pixel;
unsigned int line;
unsigned int oneCnt;
} binaryInfoBlock_t;
/* is needed because the compiler align on a 4byte boundary */
#pragma pack(1)
typedef struct {
short type; // should be BM
unsigned long size; // should be 0 for uncompressed bmps
short res0 ; // 0
short res1; // 0
unsigned long offset; //offset where the data begins
} bmpFileHeader_t;
typedef struct {
unsigned long size; // sizeof(bmpInfoHeader_t)
long width; // width of the image in pixel
long height; // height of the image in pixel
short planes; // set to 1
short cDepth; // typical values are 1, 4, 8, 16, 24, 32
unsigned long compression;
unsigned long rawSize; //size of the raw bitmap data
long hRes; //horizontal resolution
long vRes; // vertical resolution
unsigned long nrColors; // number of colors 0, for 2^n
unsigned long impColors; //important colors... set this to 0
} bmpInfoHeader_t;
typedef struct {
unsigned char red;
unsigned char green;
unsigned char blue;
unsigned char reserved;
} bmpRGBQuad_t;
#pragma pack()
typedef struct {
unsigned int x;
unsigned int y;
unsigned int gruppeNr;
unsigned int ueberschrieben; /* wird gesetzt, falls die GruppenNummer bereits
* ueberschrieben wurde. Dann nicht mehr ueberschreiben!
*/
}infoBlock_t;
int writeBitmapToFile(void *data, unsigned long size, const char *filename);
int mpauswertung(char *buffer, int *xmittel, int *ymittel);
void pictofile(char *buffer, int* xmittel, int* ymittel, infoBlock_t koordinaten[], unsigned int aktcount, unsigned int maxGroup);

#endif
</source>

<source lang="c">
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>

#include "windows.h"
#include "bitmap.h"

#define WIDTH 640
#define HEIGHT 480


/*
* write our raw data into a bitmap file
* adding the correct header and put it all
* together in a single file
*/
int
writeBitmapToFile(void *data, unsigned long size, const char *filename)
{
bmpFileHeader_t header;
bmpInfoHeader_t info;
bmpRGBQuad_t rgb = { 0, 0, 0, 0};
FILE *out;
size_t len;
int i;


header.type = 0x4d42; // magic sequence 'BM'
header.size = 0;
header.res0 = 0;
header.res1 = 0;
/* 256 different colors, each is defined by an bmpRBQuad type */
header.offset = 256 * sizeof(bmpRGBQuad_t) +
sizeof(bmpInfoHeader_t) + sizeof(bmpFileHeader_t);
info.size = sizeof(bmpInfoHeader_t);
info.width = WIDTH;
info.height = HEIGHT;
info.planes = 1;
info.cDepth = 8; /* 8 Bit */
info.compression = 0;
info.rawSize = size;
info.hRes = 0;
info.vRes = 0;
info.nrColors = 0x100;
info.impColors = 0;
out = fopen(filename, "wb");
if (out == NULL) {
printf("error cannot open %s for writing\n", filename);
return -1;
}
len = fwrite(&header, 1, sizeof(header), out);
if (len != sizeof(header)) {
printf("error while writing header\n");
return -1;
}
len = fwrite(&info, 1, sizeof(info), out);
if (len != sizeof(info)) {
printf("error while writing info header\n");
return -1;
}
/* stupid try and error programming leads to this */
for (i = 0; i < 256; i++) {
rgb.red = i;
rgb.green = i;
rgb.blue = i;
len = fwrite(&rgb, 1, sizeof(rgb), out);
if (len != sizeof(rgb)) {
printf("error while writing rgb header\n");
return -1;
}
}

len = fwrite(data, 1, size, out);
if (len != size ) {
printf("error while writing bitmap data\n");
return -1;
}
return 0;
}


/*
* was used for analyzing bitmap files
*/
int
dumpHeaderField(bmpFileHeader_t *header, bmpInfoHeader_t *info,
bmpRGBQuad_t **rgb)
{
printf("######### header #########\n");
printf("type: 0x%x (%c%c)\n", header->type, header->type & 0xff,
(header->type >> 8) & 0xff);
printf("size: 0x%lx (%lu)\n", header->size, header->size);
printf("res0: 0x%x\n", header->res0);
printf("res1: 0x%x\n", header->res1);
printf("data offset: 0x%lx (%lu)\n", header->offset,
header->offset);
printf("######### info #########\n");
printf("size: 0x%lx (%lu)\n", info->size, info->size);
printf("width: 0x%lx (%lu)\n", info->width, info->width);
printf("height: 0x%lx (%lu)\n", info->height, info->height);
printf("planes: 0x%x\n", info->planes);
printf("cDepth: 0x%x\n", info->cDepth);
printf("compression: 0x%lx\n", info->compression);
printf("rawSize: 0x%lx\n", info->rawSize);
printf("hRes: 0x%lx\n", info->hRes);
printf("vRes: 0x%lx\n", info->vRes);
printf("nrColors: 0x%lx\n", info->nrColors);
printf("important colors: 0x%lx\n", info->impColors);
return 0;
}
</source>
=== Binärbild auswerten und Koordinaten bekommen ===
<source lang="cpp">
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "bitmap.h"



#define XSIZE 640
#define YSIZE 480
#define LIMITVAL 0x30
#define OFFSET 0x0
#define MIN_SIZE 3000 /* minimale groesse des zu erkennenden Objekts */
#define MAX_SIZE 4500 /* maximale groesse des zu erkennenden Objekts */



int picnr = 0;
infoBlock_t koordinaten[XSIZE * YSIZE];

/*
* Funktion zur Ermittlung des groessten Objektes und
* dessen Mittelpunkt. Es wurde dabei folgendermassen
* vorgegangen:
* - Alle Pixel muessen einen bestimmten Schwellwert
* (Graustufenwert) ueberschreiten, um als aktiv
* gezaehlt zu werden.
* - Alle aktiven Pixel werden im ersten Schritt in die
* Struktur infoBlock_t abgelegt und Gruppennummern vergeben.
* - Die Vergabe der Gruppennummer laeuft inkrementel und die
* Nummer erhoeht sich fuer jeden Pixel, der nicht an einem vorigen
* Pixel haengt (horizontale Ausrichtung).
* Es ergibt sich in etwa folgendes Bild:
* xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
* xxxxxxxx11xxxxxxxxxxxxx2222222xxxxxxxxx
* xxxxxxxxxxxxxxxxxxxxx3333333333xxxxxxxx
* xxxxxx444xxxxxxxxxxxxxx555555xxxxxxxxxx
* - Anschliessend werden alle aktiven Pixel erneut untersucht, ob
* evtl. vertikal Pixel miteinander verbunden sind. Ist dies der
* Fall, wird die komplette Zeile durchgegangen und die Gruppennummer
* auf die der darueberliegenden Pixel gesetzt. So ergibt sich eine Kette
* in der nach und nach alle zusammenhaengenden Pixeln die gleiche Nummer.
* xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
* xxxxxxxx11xxxxxxxxxxxxx2222222xxxxxxxxx
* xxxxxxxxxxxxxxxxxxxxx2222222222xxxxxxxx
* xxxxxx444xxxxxxxxxxxxxx222222xxxxxxxxxx
* - Danach werden alle aktiven Pixel einer bestimmten Gruppe gezaehlt um somit
* die groesste Gruppe ermitteln zu koennen (es kann so auch spaeter z.B. nach der
* zweit groessten Gruppe gesucht werden...)
* - Zur Berechnung des Mittelpunkts werden alle X und Y Koordinaten der Gruppe
* addiert und im letzten Schritt durch die Anzahl der aktiven Pixel dividiert
*/
int
mpauswertung(char *buffer, int *xmittel, int *ymittel)
{
unsigned int y, x, aktCount = 0, groupNumber = 0, i, j, zeilenAnfang, maxGroup, k = 0;
unsigned int xsum = 0, ysum = 0, l;
unsigned int *groups;
memset(koordinaten, 0x0, XSIZE * YSIZE * sizeof(infoBlock_t));
for (y = 0; y < YSIZE; y++) {
for (x = 0; x < XSIZE; x++) {
if ((buffer[y * XSIZE +x] & 0xff) > LIMITVAL) {
koordinaten[aktCount].x = x;
koordinaten[aktCount].y = y;
if (aktCount > 0 && (koordinaten[aktCount-1].x == x -1) &&
(koordinaten[aktCount-1].y == y)) {
koordinaten[aktCount].gruppeNr = groupNumber;
} else {
koordinaten[aktCount].gruppeNr = ++groupNumber;
}
aktCount++;
}
}
}
maxGroup = groupNumber;
groups = calloc(sizeof(int), maxGroup);

/* Gruppen vertikal miteinander verbinden */
for (i = 0; i <= aktCount; i++) {
y = koordinaten[i].y;
j = i;
/* gehe zum naechsten aktiven pixel in der naechsten zeile */
while (koordinaten[j].y == y)
j++;
/* speichern des ersten aktiven Pixels in der naechsten Zeile */
zeilenAnfang = j;
while (koordinaten[j].y == y+1) {
/* ueberpruefe ob Vertikal mehrere aktive Pixel vorhanden sind */
if (koordinaten[i].x == koordinaten[j].x) {
groupNumber = koordinaten[j].gruppeNr;
if (koordinaten[j].ueberschrieben == 0) {
/* setze alle Pixel die aktiv sind und zusammenhaengen auf die
* gleiche Gruppennummer
*/
for (l = zeilenAnfang; koordinaten[l].y == y + 1 ; l++) {
if (koordinaten[l].gruppeNr == groupNumber) {
koordinaten[l].gruppeNr = koordinaten[i].gruppeNr;
koordinaten[j].ueberschrieben = 1;
}
}
} else {
for (l = 0; koordinaten[l].y <= koordinaten[j].y; l++) {
if (koordinaten[l].gruppeNr == koordinaten[i].gruppeNr)
koordinaten[l].gruppeNr = koordinaten[j].gruppeNr;
}
}
}
++j;
}
}

for (i = 0; i < aktCount; i++) {
groups[koordinaten[i].gruppeNr]++;
}
j = 1;
#if 0 /* finden der groessten gruppe! */
for (i = 0; i < maxGroup; i++) {
if (j < groups[i]) {
j = groups[i]; /* Anzahl der Elemente der Gruppe */
k = i; /* Gruppennummer */
}
}
#else
for (i = 0; i < maxGroup; i++) {
if (groups[i] >= MIN_SIZE && groups[i] <= MAX_SIZE) {
j = groups[i];
k = i;
break;
}
}
#endif

for (i = 0; i < aktCount; i++) {
if (koordinaten[i].gruppeNr == k) {
xsum += koordinaten[i].x;
ysum += koordinaten[i].y;
}
}
*xmittel = xsum / j;
/* Zeilen sind "verkehrtrum" im Bitmap */
*ymittel = YSIZE - ysum / j;
free(groups);

pictofile(buffer, xmittel, ymittel, koordinaten, aktCount, k);
return 0;
}



void
pictofile(char *buffer, int* xmittel, int* ymittel, infoBlock_t koordinaten[], unsigned int aktCount, unsigned int maxGroup)
{
char outname[32]; //Textfile, Übersicht aktive Pixel
FILE *fout;
unsigned int x, y, i, flag = 0;

picnr++;
sprintf (outname, "bilderout/test%.3d.xpm", picnr);

fout = fopen(outname, "wb");
fprintf(fout, "/* XPM */\n");
fprintf(fout, "static char * Ball_xpm[] = {\n");
fprintf(fout, "\"640 480 4 1\",\n");
fprintf(fout, "\"0 c #36c21f\",\n");
fprintf(fout, "\"1 c #ffffff\",\n");
fprintf(fout, "\"2 c #000000\",\n");
fprintf(fout, "\"3 c #ff0000\",\n");

for (y = YSIZE - 1; y >= 0; y--) {
fprintf(fout, "\"");
for (x = 0; x < XSIZE; x++) {
if ((buffer[x+y*XSIZE] & 0xff) > LIMITVAL) {
for (i = 0; i < aktCount; i++) {
if (koordinaten[i].x == x && koordinaten[i].y == y) {
if (koordinaten[i].gruppeNr == maxGroup)
fprintf(fout, "1");
else
fprintf(fout, "2");
flag = 1;
}
}
if (!flag)
fprintf(fout, "0");
flag = 0;
} else {
fprintf(fout, "0");
}
}
fprintf(fout, "\",\n");
if (y == 0)
break;
}
fprintf(fout, "};\n");
fclose(fout);
}
</source>
Daraus ergibt sich folgendes Resultat:
<source lang="text">
X(Code) Y(Code) X (SW) Y (SW)
600 198 600,91 197,01
585 202 585,4 200,89
569 205 569,61 204,52
553 209 553,49 208,17
538 213 538,37 211,73
523 216 523,71 215,15
509 219 509,14 218,52
495 223 495,12 221,8
481 226 481,37 224,92
468 229 468,13 228,11
454 232 454,99 231,13
442 235 442,13 233,99
429 238 429,4 236,89
416 241 416,57 239,8
403 244 403,83 242,67
390 247 391 245,69
378 250 378,38 248,6
365 252 365,56 251,53
352 255 352,78 254,41
339 258 340,08 257,35
327 261 327,29 260,2
314 264 314,49 263,1
301 267 301,74 266,02
288 270 288,92 268,92
275 273 276,21 271,85
263 276 263,53 274,76
250 279 250,75 277,72
237 282 238,04 280,62
225 285 225,26 283,6
212 287 212,68 286,53
199 290 200,01 289,39
187 293 187,41 292,26
174 296 174,78 295,17
162 299 162,21 298,03
149 302 149,57 301,01
136 305 137,14 303,95
124 308 124,67 306,81
112 311 112,08 309,68
99 313 99,65 312,53
87 316 87,24 315,42
74 319 74,77 318,28
62 322 62,45 321,24
50 325 50,15 324,08
37 328 37,8 326,93
25 331 25,46 329,75
14 334 14,55 332,6
</source>
Der eigene Sourcecode liefert also gegenüber der fertigen Software sehr gute Ergebnisse. Einziges Problem könnte die Dauer von fast 70% der verfügbaren Zeit sein, optimieren!!! (durch Optimierung auf ca 20% gesenkt!)

Resultat bei je '''100''' Auswertungen pro Bild:
<source lang="text">
Picnr. X Y time[sec] (für je 100 Auswertungen!)
20 600 198 0.24
21 585 202 0.18
22 569 206 0.21
23 553 209 0.19
24 538 213 0.18
25 523 216 0.18
26 508 219 0.16
27 494 223 0.17
28 481 226 0.17
29 467 229 0.17
30 454 232 0.16
31 441 235 0.16
32 429 238 0.16
33 416 241 0.15
34 403 244 0.15
35 390 247 0.15
36 378 250 0.15
37 365 252 0.15
38 352 255 0.16
39 339 258 0.15
40 327 261 0.15
41 314 264 0.16
42 301 267 0.15
43 288 270 0.15
44 275 273 0.16
45 263 276 0.14
46 250 279 0.16
47 237 282 0.16
48 224 285 0.15
49 212 287 0.15
50 199 290 0.15
51 187 293 0.15
52 174 296 0.15
53 161 299 0.15
54 149 302 0.14
55 136 305 0.15
56 124 308 0.15
57 111 311 0.15
58 99 314 0.14
59 86 316 0.15
60 74 319 0.14
61 62 322 0.15
62 49 325 0.14
63 37 328 0.15
64 25 331 0.14
65 14 334 0.14</source>
Jedes Bild wurde 100x ausgewertet, die Zeit blieb dabei um die 0.2sec. Theoretisch haben wir zur Verarbeitung jedes Bildes 10msec (bei 100fps), da sollte ein Auswerten von jeweils 2msec i.O. sein.

[[Bild:test020-bmp.jpg]]
[[Bild:test020-xpm.jpg]]
[[Bild:test021-bmp.jpg]]
[[Bild:test021-xpm.jpg]]


Eine weitere Möglichkeit der Ballerkennung:
(hier wird die Umgebung der Pixel berücksichtigt
und erst bei einem bestimmten Schwellwert an aktiven Pixeln
in der Umgebung, wird ein Pixel als aktiv markiert):
<source lang="c">#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <time.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>

#include "bitmap.h"

#define WIDTH 640
#define HEIGHT 480


/*
* write our raw data into a bitmap file
* adding the correct header and put it all
* together in a single file
*/
int
writeBitmapToFile(void *data, unsigned long size, const char *filename)
{
bmpFileHeader_t header;
bmpInfoHeader_t info;
bmpRGBQuad_t rgb = { 0, 0, 0, 0};
FILE *out;
size_t len;
int i;

header.type = 0x4d42; // magic sequence 'BM'
header.size = 0;
header.res0 = 0;
header.res1 = 0;
/* 256 different colors, each is defined by an bmpRBQuad type */
header.offset = 256 * sizeof(bmpRGBQuad_t) +
sizeof(bmpInfoHeader_t) + sizeof(bmpFileHeader_t);
info.size = sizeof(bmpInfoHeader_t);
info.width = WIDTH;
info.height = HEIGHT;
info.planes = 1;
info.cDepth = 8; /* 8 Bit */
info.compression = 0;
info.rawSize = size;
info.hRes = 0;
info.vRes = 0;
info.nrColors = 0x100;
info.impColors = 0;
out = fopen(filename, "wb");
if (out == NULL) {
printf("error cannot open %s for writing\n", filename);
return -1;
}
len = fwrite(&header, 1, sizeof(header), out);
if (len != sizeof(header)) {
printf("error while writing header\n");
return -1;
}
len = fwrite(&info, 1, sizeof(info), out);
if (len != sizeof(info)) {
printf("error while writing info header\n");
return -1;
}
/* stupid try and error programming leads to this */
for (i = 0; i < 256; i++) {
rgb.red = i;
rgb.green = i;
rgb.blue = i;
len = fwrite(&rgb, 1, sizeof(rgb), out);
if (len != sizeof(rgb)) {
printf("error while writing rgb header\n");
return -1;
}
}

len = fwrite(data, 1, size, out);
if (len != size ) {
printf("error while writing bitmap data\n");
return -1;
}
return 0;
}


/*
* was used for analyzing bitmap files
*/
int
dumpHeaderField(bmpFileHeader_t *header, bmpInfoHeader_t *info)
{
printf("######### header #########\n");
printf("type: 0x%x (%c%c)\n", header->type, header->type & 0xff,
(header->type >> 8) & 0xff);
printf("size: 0x%lx (%lu)\n", header->size, header->size);
printf("res0: 0x%x\n", header->res0);
printf("res1: 0x%x\n", header->res1);
printf("data offset: 0x%lx (%lu)\n", header->offset,
header->offset);
printf("######### info #########\n");
printf("size: 0x%lx (%lu)\n", info->size, info->size);
printf("width: 0x%lx (%lu)\n", info->width, info->width);
printf("height: 0x%lx (%lu)\n", info->height, info->height);
printf("planes: 0x%x\n", info->planes);
printf("cDepth: 0x%x\n", info->cDepth);
printf("compression: 0x%lx\n", info->compression);
printf("rawSize: 0x%lx\n", info->rawSize);
printf("hRes: 0x%lx\n", info->hRes);
printf("vRes: 0x%lx\n", info->vRes);
printf("nrColors: 0x%lx\n", info->nrColors);
printf("important colors: 0x%lx\n", info->impColors);
return 0;
}


/*
* Bildet ein einfaches Kreuz und zaehlt die dabei entdeckten aktiven Pixel.
* Dies soll verhindern, dass einzelne Stoerpixel ebenfalls gezaehlt werden
* und ggf. den Start des aktiven Pixels veraendern.
* Die Werte fuer PIXEL sowie fuer CUT_PIXEL koennen in der bitmap.h
* eingestellt werden.
*/
unsigned int
checkPixelEnvironment(unsigned char *data, unsigned long long pixel, int mark)
{
unsigned int i, cnt;
cnt = i = 0;
for (; i < PIXEL; i++) {
if (pixel >= i *WIDTH)
cnt += ((data[pixel - i * WIDTH] & 0xff) > mark ? 1 : 0);
if (pixel + i < WIDTH * HEIGHT)
cnt += ((data[pixel + i] & 0xff) > mark ? 1 : 0);
if (pixel + i * WIDTH < WIDTH * HEIGHT)
cnt += ((data[pixel + i * WIDTH] & 0xff) > mark ? 1 : 0);
if (pixel >= i)
cnt += ((data[pixel - i] & 0xff)> mark ? 1 : 0);
}

return cnt;
}



int
sortBinaryBlock(const void *a, const void *b)
{
binaryInfoBlock_t *a1 = (binaryInfoBlock_t *)a;
binaryInfoBlock_t *b1 = (binaryInfoBlock_t *)b;
if (a1->pixel < b1->pixel)
return -1;
else if (a1->pixel > b1->pixel)
return 1;
else
return 0;

}



/*
* Erstellt ein Binaerbild und markiert Pixel deren Wert > mark ist als aktiv.
* Damit keine Stoerpixel im Bild sind, wird mit checkPixelEnvironment die
* Umgebung durchsucht und erst bei einem genuegend hohen Wert an aktiven Pixeln
* in der Umgebung, wird der einzelne Pixel als aktiv markiert und gezaehlt.
* Es wird jeweils der Anfangspixel gespeichert (der erste aktive in der Zeile).
* Spaeter wird nach dem linkesten Pixel gesucht (in dieser Zeile muss sich der
* Mittelpunkt befinden). Um bessere Resultate zu bekommen, wurden alle Zeilen
* mit gleichem ersten aktiven Pixel mit in die Berechnung einbezogen. Zur
* Geschwindigkeitsoptimierung wurde das Feld vorher mit qsort sortiert und somit
* muss am Ende nur noch ein Bruchteil des Arrays durchlaufen werden.
*/
int
createCuttedBinaryObject(unsigned char *data, int mark, unsigned long long size)
{

int line = 0, res, minPixel, maxOneCnt;
unsigned long long i;
unsigned long long startTime;
binaryInfoBlock_t info[HEIGHT];
info[line].oneCnt = 0;
info[line].pixel = -1;
info[line].line = line;
line = HEIGHT - 1;
startTime = clock();
for (i = 0 ; i < size; i++) {
if (i && !(i % WIDTH)) {
line-- ;
info[line].oneCnt = 0;
info[line].pixel = -1;
/* lines are reversed in the bitmap */
info[line].line = line;
}
if ((data[i] & 0xff) > mark) {
data[i] = 0x1;
res = checkPixelEnvironment(data, i, mark);
if (res < CUT_PIXEL) {
data[i] = 0x0;
continue;
}
info[line].oneCnt++;
if (info[line].pixel == -1)
info[line].pixel = i % WIDTH;
} else {
data[i] = 0x0;
}
}
qsort(info, HEIGHT, sizeof(binaryInfoBlock_t), sortBinaryBlock);


minPixel = info[0].pixel;
maxOneCnt = line = 0;
while (info[line].pixel == minPixel && line < HEIGHT) {
res += info[line].line;
maxOneCnt += info[line].oneCnt;
line++;
}
printf("y: %.2f\tx: %.2f\tZeit: %.3f\n", (double )res / (double)line,
(double)minPixel + ((double)maxOneCnt / (double)line ) / 2,
((float)(clock()-startTime)/CLOCKS_PER_SEC));

return 0;
}

int
main(int argc, char *argv[])
{
FILE *in;
struct stat sb;
int i = 0;
unsigned char *ptr = NULL;
char fileName[1024];

for (i = 20; i < 66; i++) {
snprintf(fileName, sizeof(fileName), "../bilder2/test0%i.bmp", i);
if (stat(fileName, &sb) == -1) {
printf("error stat() failed\n");
return 1;
}
ptr = calloc(1,sb.st_size);
if (ptr == NULL) {
printf("error can not allocate space\n");
return 1;
}
in = fopen(fileName, "rb");
if (in == NULL) {
printf("error cannot open file: %s\n", argv[1]);
return 1;
}
if (fread(ptr, 1, sb.st_size, in) != sb.st_size) {
printf("error read failed for file: %s\n", argv[1]);
return 1;
}


createCuttedBinaryObject(ptr + 1078, 0x80, WIDTH * HEIGHT);


free(ptr);
}
return 0;
}
</source>
<source lang="c">/*
* GTK Ausgabe der Koordinaten. getKoordinates(<bbild nummer>, &x, &y)
* holt die Koordinaten und gibt diese dann auf dem Bildschirm kontiniuerlich
* aus.
*
* Kompilierung mit:
* gcc -o ball graphick.c bitmap.c -Wall `pkg-config --libs gtk+-2.0` `pkg-config --cflags gtk+-2.0`
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <gtk/gtk.h>
#include <glib.h>

GtkWidget *window;
GtkWidget *fixed;
GtkWidget *label;

gboolean
getData(gpointer data)
{
int x, y;
static int i = 20;
char buf[128];
// Hole Koordinaten
if ( i >= 65)
i = 20;
getKoordinates(i++, &x,&y);
snprintf(buf, sizeof(buf), "o x: %i y: %i", x, y);
gtk_label_set_text(GTK_LABEL(label), buf);
gtk_fixed_move(GTK_FIXED(fixed), label, x, y);
gtk_timeout_add(100, getData, NULL);
return 0;
}


int
main (int argc, char *argv[])
{

gtk_init (&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
fixed = gtk_fixed_new();
label = gtk_label_new("o");
gtk_container_add(GTK_CONTAINER(window), fixed);
gtk_fixed_put(GTK_FIXED(fixed), label, 1, 30);
gtk_window_set_default_size (GTK_WINDOW(window), 640, 480);
gtk_window_set_title(GTK_WINDOW (window), "Ballbestimmung");
gtk_widget_show(fixed);
gtk_widget_show(label);
gtk_widget_show(window);
gtk_timeout_add(1, getData, NULL);

gtk_main();
return 0;
}
</source>
== Berechnungen ==
=== Nötige Auflösung ===
Bei der zu verwendenden Auflösung gilt: so groß wie nötig, so klein wie nötig. Je größer jedes auszuwertende Bild ist, desto mehr Rechenaufwand und Zeit wird für die Auswertung benötigt. Allerdings wird eine gewisse minimale Auflösung benötigt, damit der Ball gegenüber anderen Störeinflüssen wie Schatten, Flecken o.ä. eindeutig erkannt werden kann. Außerdem wirkt sich die Auflösung des Bildes auf die Genauigkeit der ermittelten Koordinaten aus.

Um diesen Anforderungen gerecht zu werden wurde eine Auflösung von 640x480 gewählt. Die Spielfeldlänge von 1,25m wird dabei in 480px aufgeteilt bzw. von diesen erfasst, die Breite von 68,5cm hingegen von 350px, es werden also nicht alle der 480 möglichen Pixeln für die Breite benötigt, da das Spielfeld nicht in einem Verhältnis von 4:3 also 1,33:1 sondern von 1,82:1 steht.

x-Achse: 640px/125cm = 5,12px/cm = 1px/2mm
y-Achse: 5,12px/cm * 68,5cm = 350,7px

* Durchmesser des Balls: 3,5cm = 3,5cm * 5,12px/cm = 18px (genauer: 17,92px)
* Fläche des Balls: A = r² * PI = (17,92px/2)² * PI = 252px
Das heißt, dass bei der Ballerkennung etwa 250px den Ball darstellen, was zum einen ausreichend ist um ihn von Störungen zu unterscheiden, was zum anderen auch eine weit höhere Genauigkeit bringt als die Auflösung selbst (1px/2mm), da die Koordinaten aus dem Mittelpunkt dieser 250px ermittelt werden, also die Koordinaten auf mehrere Pixelkomastellen genau darstellen (was bei Pixeln zwar nicht viel Sinn macht da es nu ganze Pixel geht, aber beim Betrachten der Koordinaten zur verbesserten Genauigkeit beiträgt).
=== Nötige Exposure Time ===
Eine weiterer Parameter den man bestimmen muss ist der mögliche Maximalwert der exposure time. Darunter kann man sich die Belichtungszeit vorstellen. Die nötige exposure time ist abhängig von der Beleuchtung: Je geringer die Beleuchtung desto länger die Belichtungszeit, um ein gutes Bild mit ausreichender Helligkeit zu erhalten. Allerdings ist ein Bild mit hoher exposure time bei schnellen Bewegungen sehr verschwommen. Aber auch die Beleuchtung sollte gering gehalten werden, da Hitze, Stromkosten und das Blenden der Spieler Nebenwirkungen sind.

Um die Koordinaten des Balles ohne größere Verfälschung ermitteln zu können wird als Grenze eine exposure time verwendet, in der sich die Position des Balls während der Belichtungszeit maximal um '''3px/Bild''' ändert. Geht man dabei von einer Maximalgeschwindigkeit des Balls von 4m/sec aus, so ergibt sich folgende maximale exposure time:
* 4m/sec = 4mm/msec = 2px/msec (bei 1px/2mm)
* Grenze 3px: 1,5msec = 1/667 sec
Dieser Wert ist gegenüber den momentan verwendeten Werten von etwa 1/280 sec sehr gering und setzt daher eine sehr gute Beleuchtung vorraus. Ggf. wäre es möglich, diesen Wert auch etwas zu vergrößern, was zwar zu einem verschwommenen Ball auf dem Bild führen würde, aber evtl. trotzdem ausreichen würde, um gute Koordinaten zu erhalten.
Mit einer exposure time von 1/280 sec hätte man in einem Bild eine Positionsänderung des Balls bei einer Geschwindigkeit von 2px/msec von:
* 1sec/280 * 2px/msec = 7,14px '''(Frage an Hr. Jaschul: möglich?)'''


== Nächste Schritte ==
...will be continued
Nun steht es an, einen direkten Vergleich von Ballerkennung via Kamera und Lichtgitter zu erreichen. Dazu wird ein Ball über eine Strecke, die von der Kamera als auch vom Lichtgitter überwacht wird, geschossen, und die erhaltenen Daten auf Genauigkeit und Zuverlässigkeit ausgewertet.
== Abschlusspräsentation ==
[[Media:Kamera_praesentation.pdf]]

Aktuelle Version vom 29. August 2008, 12:09 Uhr

Ballerfassung mit Hilfe einer Kamera

Informationen zur verwendeten Hardware

Kamera (sichtbares Licht)

Bei der Kamera handelt es sich um eine schwarzweiß-Hochgeschwindigkeitskamera der Firma Mikrotron, die MC1302. Diese Kamera ist mit bis zu 118 Bilder/s bei maximaler Auflösung (1280x1024Pixel) für die Ballerkennung völlig ausreichend, zumal bei geringerer Auflösung die Bildwiederholrate noch gesteigert werden könnte. Als Ansprechpartner bei Mikrotron stellte sich Hr. Ertl, ein ehemaliger Diplomant der FH München, zur Verfügung.

Beleuchtung

Bei der Beleuchtung ist darauf zu achten, dass möglichst LEDs verwendet werden, oder Lampen mit extrem hoher Eigenfrequenz, damit die Frequenz der Beleuchtung nicht mit der Bildrate der Kamera interferiert. Anbieten würden sich daher LED-Spots, z.B. folgende:

klick

Fraglich ist, welche Farbe sich anbieten würde. Da bisherige Testdurchläufe mit einem Roten Ball erfolgten wären wohl die roten LEDs am effektivsten! <Überprüfen!>

Objektiv

Das zunächst verwendete Objektiv CANON LENS FD 20mm 1:2,8 hat den Nachteil, dass der nötige Abstand zum Objekt, dem Spielfeld, bei einer Auflösung von 640x480 (Grund für diese Auflösung weiter unten!) viel zu groß sein müsste. Bei einem Spielfeld, das 125cm x 68,5cm bemisst wäre ein Abstand von Kamera zu Spielfeld von etwa 3,38m nötig. Diese Annahme ergibt sich aus zwei Messungen und der Tatsache, dass sich der Blickwinkel nicht verändert und somit bei doppeltem Abstand auch eine doppelte Länge bzw. Breite erfasst werden kann (Abweichungen durch Scharfstellen und dem damit verbundenen verändern der Linsen vernachlässigt!).

CANON LENS FD 20mm 1:2,8

  • 1. Messung:

Abstand: ca. 66cm, erfasstes Feld: 25cm x 18,7cm

  • 2. Messung:

Abstand: ca. 110cm, erfasstes Feld: 40,3cm x 31,2cm

  • Nötiger Abstand, um vollständiges Feld zu erfassen:

Der Quotient Abstand/Spielfeldlänge beträgt etwa 2,7. Daher ergibt sich eine notwendige Höhe von etwa 2,7*125cm= 3,38m, was sehr schwer umzusetzen wäre. Es ist also nötig, ein weitwinkligeres Objektiv zu verwenden, mit dem ein geringerer Abstand ausreicht.

Ein besseres Resultat wird mit einem Objektiv mit geringerer Brennweite erzielt. Dafür stehen zum einen ein Objektiv mit 12.5mm (PENTAX TV LENS 12.5mm 1:1.4) oder mit 8mm (MEGAPIXEL CCTV LENS 0814MP f8mm F1.4 A6314) zur Verfügung.

PENTAX TV LENS 12.5mm 1:1.4

Ein durchaus besseres Resultat liefert das Objektiv mit 12.5mm Brennweite. Bei einem Abstand von Objektiv zu Objekt von 113.5cm konnte Feld von 70cm Länge und 51cm Breite aufgenommen werden. Der Quotient 113.5cm/70cm=1,62 ergibt, dass ein Abstand von 1,62*1,25cm=2,03m nötig wäre, um das gesamte Kickerfeld aufzunehmen, also bedeutend weniger als die 3,38m mit dem CANON-Objektiv. Zusätzlich kann noch mit einem Spiegel gearbeitet werden, um die Kamera nicht 2m vom Spielfeld entfernt positionieren zu müssen.

MEGAPIXEL CCTV LENS 0814MP f8mm F1.4 A6314

Dieses Objektiv würde mit der 8mm Brennweite den nötigen Abstand zwischen Objektiv und Spielfeld weiter reduzieren. Leider stellte es sich als inkompatibel zur Kamera heraus, es war nicht möglich das Bild scharf zu stellen.

Rechner

RAM: 1GB CPU: AMD Athlon 64 3200 Framegrapper-Karte ohne eigenen Speicher

Infos zu verwendeter, vorläufiger Software

Kamera konfigurieren

Als erstes muss das Programm VCAM gestartet werden (vor camera control tool). Um die Kamera zu konfigurieren wird die Software Mikrotron camera control tool - MC1302_25 Version 1.2.9 verwendet. Gute Werte wurden erzielt, indem das User Profile 2 (für 640x480px - für 1280x1024 ggf. UserProvile3 laden) geladen wurde, (ggf. 'shutter-mode' auf Async timer gesetzt wurde) und die 'exposure time [sec]' den Lichtverhältnissen angepasst wurde (so klein wie möglich, so groß wie nötig - vgl. Berechnungen). Diese Software wurde durch durch einen Download von Mikrotron auf den aktuellen Stand gebracht (firmware: V1.43-F2.45).

Resultate der Kamera betrachten und Bildfolgen abspeichern

Um sofort überprüfen zu können, ob die verwendeten Einstellungen zu gebrauchen sind bietet sich die Verwendung des Programms VCAM von Mikrotron an. Dieses gibt zum einen die aktuellen Bilder der Kamera wieder, zum anderen gibt es auch die Möglichkeit, Bildfolgen direkt auf die Festplatte abzuspeichern, um sie anschliesend auszuwerten. Um das Bild der Kamera wiederzugebn muss zum einen ein zu den Einstellungen des 'camera control tool's passendes Profil geladen werden (für 640x480 beispielsweise MC1310_11_02_03 640x480 206fps über Camera -> Profile load/save) und anschliesend Camera -> Capture. Dabei fiel allerdings auf, dass selbst bei einer geringen Auflösung von 640x480Pixeln nicht alle Bilder mit eingestellter fps aufgenommen werden konnten. Zunächst wurde davon ausgegangen, dass die Geschwindigkeit der Festplatte das Bottleneck des Speichervorgangs darstellte. Folgende Messreien zeigen deutlich, dass bei einer hohen Bildwiederholrate nicht alle Bilder abgespeichert werden können (Speicherung einer Bildreihe von einer Uhr mit Zehntel Sekunden):

4x .7
4x .8
3x .9
4x .0
3x .1
4x .2
3x .3
3x .4
3x .5
4x .6
4x .7
Betrachtet man dabei die fett markierte Sekunde, so erhält man statt den eingestellten 137fps lediglich 35fps.

6x .4
6x .5
6x .6
5x .7
6x .8
4x .9
5x .0
5x .1
5x .2
4x .3
3x .4
5x .5
Auch hier bleibt man mit den 49fps weit unter den eingestellten 260fps. Allerdings fällt auf, dass sich die effektive Bildwiederholrate gegenüber der vorherigen Messung erhöht hat. Grund müsste laut Hr. Ertl die eingeschränkte Schreibgeschwindigkeit auf Festplatte sein. Da das Programm VCAM ein Demonstationsprogramm darstellt ist es nicht darauf ausgelegt, so viele Bilder innerhalb kürzester Zeit abzuspeichern. Da der Grapper keinen eigenen Speicher besitzt und die Bilder auch nicht im RAM zwischengelagert werden verwirft VCAM alle Bilder, die vor der Beendigung des vorherigen Schreibvorgangs geliefert werden. Dies wäre auch eine erklärung, warum in der 2. Messung mehr fps erreicht werden, da sich die Wartezeit zwischen beendetem Schreibvorgang und der Lieferung des nächsten Bildes kleiner wird.

Sollte das der alleinige Grund für die Reduzierte Abspeicherrate sein, so lässt sich dieses Problem vorläufig mit der Verwendung einer RAM-Disk, also einer virtuellen Festplattenpartition im RAM, lösen.

Ramdisk erstellen (Linux):

sudo mount -t ramfs ramfs /media/RAM-Disk

Geschwindigkeit RAM-Disk an Referenzrechner (AmiloPa2530): Testfile: 700MB

date1=`date '+%s%N'` && cp testdatei1 testdatei2 && expr '(' `date '+%s%N'` - $date1 ')' / 1000000
  • HDD -> HDD: 42273msec
  • HDD -> RAM: 12886msec
  • RAM -> RAM: 875msec
  • RAM -> HDD: 18567msec

Ramdisk erstellen (Win2000):

Wie in der Messung unter Linux festzustellen sollte das Problem, dass die Bilder nicht schnell genug auf die HDD geschrieben werden können, mit einer Ramdisk gelöst bzw. umgangen werden können. Von den 1GB Arbeitsspeicher wurde dafür eine Ramdisk von 700MB erstellt. Bei einer neuen Aufnahme einer Bildfolge ergab sich dabei folgendes Resultat:

5x .7
5x .8
5x .9
4x .0
5x .1
5x .2
5x .3
5x .4
5x .5
5x .6
Das ergibt eine Rate von 49fps bei einer eingestellten Rate von 205fps, was gegenüber der 2. Messung ohne Ramdisk keinerlei Verbesserung darstellt. Das Problem scheint also an anderer Stelle zu liegen. Nächster Lösungsansatz wird sein, die Bilder direkt über C-Code von der Kamera zu holen, in reserviertem Speicher abzulegen und danach in Dateien zu speichern.

Koordinaten des Balles aus Bild ermitteln

Was später automatisiert in Sourcecode gefasst passieren soll wird zunächst Step by Step in einem eigenem Programm durchgeführt: Das Auswerten jedes Bildes, um die Koordinaten des Balls zu ermitteln. Dafür wird ein Macro für die Software <MCR - genauer Softwarename!> geschrieben, mit dessen Hilfe folgende Schritte für jedes einzelne Bild durchlaufen werden:

  • Bild [bmp] einlesen
  • Bereiche mit bestimmter Farbe / bestimmtem Grauwert als aktiv markieren
  • Aktive Bereiche, die unterhalb eines Grenzwertes sind löschen (z.B. <200px)
  • Schwerpunkt der verbleibenden aktiven Punkte ermitteln (bei Ball -> Mittelpunkt)
  • Koordinaten (Mittelpunkt) zu Liste hinzufügen

Das Macro, das dafür notwendig ist sieht folgendermasen aus:

imgdelete "*"


# Bilder 12 bis 41 laden  
# for i0=1; i0 <= 40; i0 = i0+1 
# imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_000"i0+10".bmp",i0 
# endfor 
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00011.bmp",1 
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00012.bmp",2 
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00013.bmp",3 
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00014.bmp",4 
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00015.bmp",5 
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00016.bmp",6 
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00017.bmp",7 
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00018.bmp",8 
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00019.bmp",9 
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00020.bmp",10 
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00021.bmp",11 
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00022.bmp",12 
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00023.bmp",13 
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00024.bmp",14
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00025.bmp",15
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00026.bmp",16
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00027.bmp",17
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00028.bmp",18
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00029.bmp",19
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00030.bmp",20
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00031.bmp",21
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00032.bmp",22 
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00033.bmp",23 
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00034.bmp",24 
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00035.bmp",25
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00036.bmp",26
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00037.bmp",27
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00038.bmp",28
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00039.bmp",29
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00040.bmp",30
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00041.bmp",31
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00042.bmp",32
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00043.bmp",33
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00044.bmp",34 
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00045.bmp",35 
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00046.bmp",36 
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00047.bmp",37
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00048.bmp",38 
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00049.bmp",39 
imgload "c:\dokumente und einstellungen\administrator\desktop\kicker\bilder\080416_00050.bmp",40 

# imgdisplay 1  

for i1=1; i1 <= 40; i1 = i1+1 
dislev i1,i1+100,90,213,1 
endfor 
# dislev 1,1+100,90,213,1   
# dislev 2,2+100,90,213,1   
# dislev 3,3+100,90,213,1   
# dislev 4,4+100,90,213,1   
# dislev 5,5+100,90,213,1   
# dislev 6,6+100,90,213,1   
# dislev 7,7+100,90,213,1   
# dislev 8,8+100,90,213,1   
# dislev 9,9+100,90,213,1   
# dislev 10,10+100,90,213,1   
# dislev 11,11+100,90,213,1   
# dislev 12,12+100,90,213,1   

for i2=1; i2 <= 40; i2 = i2+1 
binscrap i2+100,i2+200,0,3000,0
endfor 
# binscrap 1+100,1+200,0,3000,0 
# binscrap 2+100,2+200,0,3000,0 
# binscrap 3+100,3+200,0,3000,0 
# binscrap 4+100,4+200,0,3000,0 
# binscrap 5+100,5+200,0,3000,0 
# binscrap 6+100,6+200,0,3000,0 
# binscrap 7+100,7+200,0,3000,0 
# binscrap 8+100,8+200,0,3000,0 
# binscrap 9+100,9+200,0,3000,0 
# binscrap 10+100,10+200,0,3000,0 
# binscrap 11+100,11+200,0,3000,0 
# binscrap 12+100,12+200,0,3000,0 

MSload "kicker1"
# MSsetfeat "REGIONFEAT" 

MSmeasmask 1+200,1,"KICKER1",0,1,10
for i3=2; i3 <= 40; i3 = i3+1
MSmeasmask i3+200,1,"KICKER1",1,1,10 
endfor
# MSmeasmask 2+200,1,"KICKER1",1,1,10 
# MSmeasmask 3+200,1,"KICKER1",1,1,10 
# MSmeasmask 4+200,1,"KICKER1",1,1,10 
# MSmeasmask 5+200,1,"KICKER1",1,1,10 
# MSmeasmask 6+200,1,"KICKER1",1,1,10 
# MSmeasmask 7+200,1,"KICKER1",1,1,10 
# MSmeasmask 8+200,1,"KICKER1",1,1,10 
# MSmeasmask 9+200,1,"KICKER1",1,1,10 
# MSmeasmask 10+200,1,"KICKER1",1,1,10 
# MSmeasmask 11+200,1,"KICKER1",1,1,10 
# MSmeasmask 12+200,1,"KICKER1",1,1,10 

datalist "KICKER1",0,0
# for i112=2; i112 <= 12; i112 = i112+1 
# MSmeasmask i112+100,1,"KICKER1",1,1,10 
# endfor 
datalist "KICKER1",0,0
MSsave "kicker1"

Deutlich zu erkennen ist, dass für jeden der Schritte bei größerer Anzahl von Bildern leicht mit Schleifen gearbeitet werden, nur bei dem ersten Schritt, dem einlesen der einzelnen Bilder treten hier durch die unterschiedlichen Dateinamen Probleme auf. Um trotzdem nicht alle Befehle per Hand editieren zu müssen wurde diese Aufgabe von einem kleinen C-Programm übernommen:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define MAX_BILDER 10

int
main(int argc, char *argv[])
{
	int i;
	int start, ende;

	if (argc <= 3)
		return -1;

	start = atoi(argv[2]);
	ende = atoi(argv[3]);

	for (i = start; i <= ende; i++) {
		printf("%s%.4d.bmp\n", argv[1], i);
	}

	return 0;
}

Das Programm wird mit 3 Parametern aufgerufen. Der erste Parameter ist der Dateipfad bis zu der Stelle der Nummerierung. Der 2. und 3. Parameter ist Anfang und Ende der Schleife. Soll beispielsweise Bild 35 bis 250 eingelesen werden, wird 35 als 2. Parameter und 250 als 3. Parameter übergeben. Die Ausgabe kann man nun direkt in das Makro kopieren.

Ein Testdurchlauf mit diesem Makro ergab folgende Wertetabelle (2. und 3. Spalte):

No	X/pixel	Y/pixel	Dist	Richtung [px ]	
1	599,5	251,83	/[px]	x:	y:
2	588,51	251,84	10,99	-10,99	-0,01
3	577	251,87	11,51	-11,51	-0,03
4	563,88	251,76	13,12	-13,12	0,11
5	550,94	251,44	12,94	-12,94	0,32
6	537,38	251,35	13,56	-13,56	0,09
7	524,5	251,07	12,88	-12,88	0,28
8	510,85	250,94	13,65	-13,65	0,13
9	497,96	250,79	12,89	-12,89	0,15
10	477,69	250,77	20,27	-20,27	0,02
11	458,23	250,81	19,46	-19,46	-0,04
12	444,73	250,94	13,50	-13,50	-0,13
13	431,89	250,88	12,84	-12,84	0,06
14	418,07	251,07	13,82	-13,82	-0,19
15	398,71	251,18	19,36	-19,36	-0,11
16	384,71	251,41	14,00	-14,00	-0,23
17	372,33	251,49	12,38	-12,38	-0,08
18	357,79	251,78	14,54	-14,54	-0,29
19	338,33	251,94	19,46	-19,46	-0,16
20	323,7	252,27	14,63	-14,63	-0,33
21	310,82	252,55	12,88	-12,88	-0,28
22	296,42	252,9	14,40	-14,40	-0,35
23	276,66	253,26	19,76	-19,76	-0,36
24	262,2	253,57	14,46	-14,46	-0,31
25	249,2	253,8	13,00	-13,00	-0,23
26	234,76	254,15	14,44	-14,44	-0,35
27	221,4	254,54	13,37	-13,36	-0,39
28	207,61	254,94	13,80	-13,79	-0,40
29	193,82	255,39	13,80	-13,79	-0,45
30	180,51	255,92	13,32	-13,31	-0,53
31	166,19	256,46	14,33	-14,32	-0,54
32	153,25	257,02	12,95	-12,94	-0,56
33	138,69	257,72	14,58	-14,56	-0,70
34	125,58	258,31	13,12	-13,11	-0,59
35	111,12	259,01	14,48	-14,46	-0,70
36	97,41	259,77	13,73	-13,71	-0,76
37	83,45	260,4	13,97	-13,96	-0,63
38	69,24	261,14	14,23	-14,21	-0,74
39	55,58	261,77	13,67	-13,66	-0,63
40	42,97	262,5	12,63	-12,61	-0,73

Aus diesen Koordinaten lässt sich ohne weiteres die zurückgelegte Distanz (Spalte 4) sowie die Änderung in x-Richtung (Spalte 5) wie auch in y-Richtung (Spalte 6) feststellen.

Eigener Sourcecode

Damit das vom Computer gesteuerte Team sofort auf Positionsänderungen des Balls reagieren kann muss die Auswertung der Koordinaten automatisiert werden. Dazu sind im grunde die Selben Schritte wie bei der Software nötig. Diese Schritte werden nach und nach in C-Code implementiert.

  • Kameraprofil laden
  • Bild und Bildinformationen von Kamera holen
  • Binärbild mithilfe der Informationen mit Header versehen und als *.bmp abspeichern (Zwischenschritt um Erfolg zu prüfen
  • Bildfolge abspeichern, fps überprüfen!!!

//TODO:

  • Mit offenen Bibliotheken Koordinaten des Balls auf den Bildern ermitteln
  • Optimierungen (z.B. nur Bildbereiche in bestimmten Umkreis der Koordinaten des letzten Bildes untersuchen o.ä.

Dabei entsteht folgender Code:

Kameraprofil laden und Binärbilder + Bildinformationen holen

#include <stdio.h>
#include <time.h>
#include "windows.h"
#include "mvfgdrv.h"


#include "bitmap.h"


#define IMAGE_CNT 60
#define IMAGE_PATH "bilder"
#define IMAGE_PREFIX "test"


//C:\PROGRA~1\MIKROT~1\Inspecta\Inspecta-4DC.cam
int 
APIENTRY WinMain( HINSTANCE hInstance,
HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	LONG ret;
	char buffer[4096];
	char buffer1[256];
	FILE *data;
	void *ptr[IMAGE_CNT];
	void **base;
	unsigned int nTime[IMAGE_CNT];
	FORMAT_INFO info;
	SYSTEMTIME t1, t2;
	int err = 0, i;
	int x, y;
	//GtkWidget *label;

	/* open camera in Testmode, number 0 */
	ret = mvfg_open("C:\\PROGRA~1\\MIKROT~1\\Inspecta\\Inspecta-4DC.cam;MC1310_11_02_03 640x480 206fps", 0);
	if (ret != MVFG_OK) {
		mvfg_errmessage(ret);
		return -1;
	}

	/* get some camera informations */
	ret = mvfg_getparam(MVFGPAR_FORMAT_INFO,&info,0);
	if (ret != MVFG_OK) {
		mvfg_errmessage(ret);
		return -1;
	} else {
		wsprintf(buffer, "Pixel breite: %d\nPixel hoehe: %d", info.lImageWidth, 
				 info.lImageHeight);
	//	MessageBox(NULL, buffer, "Informationen ueber aktuelle Einstellungen", MB_OK);
	}

	for (i = 0; i < IMAGE_CNT; i++) {
		ptr[i] = malloc(info.lFrameSize);
		if (ptr[i] == NULL) {
			MessageBox(NULL, "Kein Speicher mehr vorhanden", "Speicher", MB_OK);
			return 0;
		}
	}
	/* grab picture */
	MessageBox(NULL, buffer, "Informationen ueber aktuelle Einstellungen", MB_OK);
	memset(buffer, 0x0, sizeof(buffer));
	GetSystemTime(&t1);
	for (i = 0; i < IMAGE_CNT; i++) {
		ret = mvfg_grab(GRAB_NOWAIT, 0);
		if (ret != MVFG_GRAB_READY) {
			++err;
		} else {
			GetSystemTime(&t2);
			nTime[i] = (t2.wMilliseconds + t2.wSecond * 1000) - (t1.wMilliseconds + t1.wSecond * 1000);
			memcpy(ptr[i], mvfg_getbufptr(0), info.lFrameSize);
			sprintf(buffer1, "%s/%s%.3i.bmp", IMAGE_PATH, IMAGE_PREFIX, i);
			base = &ptr[i];
			//createCuttedBinaryObject(ptr[i], 0x55, info.lFrameSize, &x, &y);
			mpauswertung(ptr[i], &x,&y);
			writeBitmapToFile(ptr[i], info.lFrameSize, buffer1);
			wsprintf(buffer, "%s x: %i y: %i\n", buffer, x, y);
			free(ptr[i]);
			free(*base);
			*base = NULL;
		}
	}
	data = fopen("time.data", "w");
	if (data == NULL) {
		MessageBox(NULL, "Kann time.data nicht oeffnen", "Fehler", MB_OK);
		return 0;
	}
	MessageBox(NULL, buffer, "Bildauswertung", MB_OK);
#if 0
	MessageBox(NULL, buffer, "Bildauswertung", MB_OK);

	for (i = 0; i < IMAGE_CNT; i++) {
		sprintf(buffer, "%s/%s%.3i.bmp", IMAGE_PATH, IMAGE_PREFIX, i);
		fprintf(data, "%lu\t%s\n", nTime[i], buffer);
		writeBitmapToFile(ptr[i], info.lFrameSize, buffer);
		free(ptr[i]);
		ptr[i] = NULL;
	}
	fclose(data);
	if (err > 0) {
		wsprintf(buffer, "Es sind %d Fehler aufgetreten", err);
		MessageBox(NULL, buffer, "Informationen ueber Bilder", MB_OK);
	} else  {
		wsprintf(buffer, "Es wurden %d Bilder in %dms aufgenommen", IMAGE_CNT,
		    (t2.wMilliseconds + t2.wSecond * 1000) - (t1.wMilliseconds + t1.wSecond * 1000));
		MessageBox(NULL, buffer, "Informationen ueber Bilder", MB_OK);
	}
#endif 
	ret = mvfg_close(0);
	if (ret != MVFG_OK) {
		mvfg_errmessage(ret);
		return -1;
	} else {
		MessageBox(NULL, "Bild wurde abgespeichert, Kamera Handle geschlossen", "Fertig", MB_OK);
	}
	return 0;
}
#ifndef _BITMAP_H
#define _BITMAP_H
 
 
 
 
/* search the environment of PIXEL */
#define PIXEL 70
 
/* find at least > CUT_PIXEL otherwise the pixel is not
 * part of our object ("noise factor")
 */
#define CUT_PIXEL 2
 
typedef struct {
	unsigned int pixel;
	unsigned int line;
	unsigned int oneCnt;
} binaryInfoBlock_t;
 
 
/* is needed because the compiler align on a 4byte boundary */
#pragma pack(1)
 
typedef struct {
	short type; // should be BM
	unsigned long size; // should be 0 for uncompressed bmps
	short res0 ; // 0 
	short res1; // 0
	unsigned long offset; //offset where the data begins
} bmpFileHeader_t;
 
 
typedef struct {
	unsigned long size; // sizeof(bmpInfoHeader_t)
	long width; // width of the image in pixel
	long height; // height of the image in pixel
	short planes; // set to 1
	short cDepth; // typical values are 1, 4, 8, 16, 24, 32
	unsigned long compression;
	unsigned long rawSize; //size of the raw bitmap data
	long hRes; //horizontal resolution
	long vRes; // vertical resolution
	unsigned long nrColors; // number of colors 0, for 2^n
	unsigned long impColors; //important colors... set this to 0
} bmpInfoHeader_t;
 
typedef struct {
	unsigned char red;
	unsigned char green;
	unsigned char blue;
	unsigned char reserved;
} bmpRGBQuad_t;
#pragma pack()
typedef struct {
	unsigned int x;
	unsigned int y;
	unsigned int gruppeNr;
	unsigned int ueberschrieben;	/* wird gesetzt, falls die GruppenNummer bereits
									 * ueberschrieben wurde. Dann nicht mehr ueberschreiben!
									 */
}infoBlock_t;
 
 
int writeBitmapToFile(void *data, unsigned long size, const char *filename);
int mpauswertung(char *buffer, int *xmittel, int *ymittel);
void pictofile(char *buffer, int* xmittel, int* ymittel, infoBlock_t koordinaten[], unsigned int aktcount, unsigned int maxGroup);

 
#endif
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>

#include "windows.h"
#include "bitmap.h"

#define WIDTH 640
#define HEIGHT 480


/*
 * write our raw data into a bitmap file
 * adding the correct header and put it all
 * together in a single file
 */
int
writeBitmapToFile(void *data, unsigned long size, const char *filename)
{
	bmpFileHeader_t header;
	bmpInfoHeader_t info;
	bmpRGBQuad_t rgb = { 0, 0, 0, 0};
	FILE *out;
	size_t len;
	int i;


	header.type = 0x4d42; // magic sequence 'BM'
	header.size = 0;
	header.res0 = 0;
	header.res1 = 0;
	/* 256 different colors, each is defined by an bmpRBQuad type */
	header.offset = 256 * sizeof(bmpRGBQuad_t) + 
					sizeof(bmpInfoHeader_t) + sizeof(bmpFileHeader_t);
	info.size = sizeof(bmpInfoHeader_t);
	info.width = WIDTH;
	info.height = HEIGHT;
	info.planes = 1;
	info.cDepth = 8; /* 8 Bit */
	info.compression = 0;
	info.rawSize = size;
	info.hRes = 0;
	info.vRes = 0;
	info.nrColors = 0x100;
	info.impColors = 0;
	
	out = fopen(filename, "wb");
	if (out == NULL) {
		printf("error cannot open %s for writing\n", filename);
		return -1;
	}
	len = fwrite(&header, 1, sizeof(header), out);
	if (len != sizeof(header)) {
		printf("error while writing header\n");
		return -1;
	}
	len = fwrite(&info, 1, sizeof(info), out);
	if (len != sizeof(info)) {
		printf("error while writing info header\n");
		return -1;
	}
	/* stupid try and error programming leads to this */
	for (i = 0; i < 256; i++) {
		rgb.red = i;
		rgb.green = i;
		rgb.blue = i;
		len = fwrite(&rgb, 1, sizeof(rgb), out);
		if (len != sizeof(rgb)) {
			printf("error while writing rgb header\n");
			return -1;
		}
	}

	len = fwrite(data, 1, size, out);
	if (len != size ) {
		printf("error while writing bitmap data\n");
		return -1;
	}
	return 0;
}


/*
 * was used for analyzing bitmap files
 */
int
dumpHeaderField(bmpFileHeader_t *header, bmpInfoHeader_t *info,
    bmpRGBQuad_t **rgb)
{
	printf("######### header #########\n");
	printf("type: 0x%x (%c%c)\n", header->type, header->type & 0xff, 
	    (header->type >> 8) & 0xff);
	printf("size: 0x%lx (%lu)\n", header->size, header->size);
	printf("res0: 0x%x\n", header->res0);
	printf("res1: 0x%x\n", header->res1);
	printf("data offset: 0x%lx (%lu)\n", header->offset, 
	    header->offset);
	printf("######### info #########\n");
	printf("size: 0x%lx (%lu)\n", info->size, info->size);
	printf("width: 0x%lx (%lu)\n", info->width, info->width);
	printf("height: 0x%lx (%lu)\n", info->height, info->height);
	printf("planes: 0x%x\n", info->planes);
	printf("cDepth: 0x%x\n", info->cDepth);
	printf("compression: 0x%lx\n", info->compression);
	printf("rawSize: 0x%lx\n", info->rawSize);
	printf("hRes: 0x%lx\n", info->hRes);
	printf("vRes: 0x%lx\n", info->vRes);
	printf("nrColors: 0x%lx\n", info->nrColors);
	printf("important colors: 0x%lx\n", info->impColors);	
	
	return 0;
}

Binärbild auswerten und Koordinaten bekommen

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "bitmap.h"
 



#define XSIZE 640
#define YSIZE 480
#define LIMITVAL 0x30
#define OFFSET 0x0 
#define MIN_SIZE 3000 /* minimale groesse des zu erkennenden Objekts */
#define MAX_SIZE 4500 /* maximale groesse des zu erkennenden Objekts */



int picnr = 0;
infoBlock_t koordinaten[XSIZE * YSIZE];

/*
 * Funktion zur Ermittlung des groessten Objektes und
 * dessen Mittelpunkt. Es wurde dabei folgendermassen
 * vorgegangen:
 *	- Alle Pixel muessen einen bestimmten Schwellwert
 *	  (Graustufenwert) ueberschreiten, um als aktiv
 *	  gezaehlt zu werden.
 *	- Alle aktiven Pixel werden im ersten Schritt in die
 *	  Struktur infoBlock_t abgelegt und Gruppennummern vergeben.
 *	- Die Vergabe der Gruppennummer laeuft inkrementel und die 
 *	  Nummer erhoeht sich fuer jeden Pixel, der nicht an einem vorigen
 *	  Pixel haengt (horizontale Ausrichtung).
 *	  Es ergibt sich in etwa folgendes Bild:
 *		xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
 *		xxxxxxxx11xxxxxxxxxxxxx2222222xxxxxxxxx
 *		xxxxxxxxxxxxxxxxxxxxx3333333333xxxxxxxx
 *		xxxxxx444xxxxxxxxxxxxxx555555xxxxxxxxxx
 *	- Anschliessend werden alle aktiven Pixel erneut untersucht, ob
 *	  evtl. vertikal Pixel miteinander verbunden sind. Ist dies der 
 *	  Fall, wird die komplette Zeile durchgegangen und die Gruppennummer
 *	  auf die der darueberliegenden Pixel gesetzt. So ergibt sich eine Kette
 *	  in der nach und nach alle zusammenhaengenden Pixeln die gleiche Nummer.
 *		xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
 *		xxxxxxxx11xxxxxxxxxxxxx2222222xxxxxxxxx
 *		xxxxxxxxxxxxxxxxxxxxx2222222222xxxxxxxx
 *		xxxxxx444xxxxxxxxxxxxxx222222xxxxxxxxxx
 *	- Danach werden alle aktiven Pixel einer bestimmten Gruppe gezaehlt um somit
 *	  die groesste Gruppe ermitteln zu koennen (es kann so auch spaeter z.B. nach der
 *	  zweit groessten Gruppe gesucht werden...)
 *	- Zur Berechnung des Mittelpunkts werden alle X und Y Koordinaten der Gruppe
 *	  addiert und im letzten Schritt durch die Anzahl der aktiven Pixel dividiert
 */
int
mpauswertung(char *buffer, int *xmittel, int *ymittel)
{
	
	unsigned int y, x, aktCount = 0, groupNumber = 0, i, j, zeilenAnfang, maxGroup, k = 0;
	unsigned int xsum = 0, ysum = 0, l;
	unsigned int *groups;
 
	memset(koordinaten, 0x0, XSIZE * YSIZE * sizeof(infoBlock_t));
	for (y = 0; y < YSIZE; y++) {
		for (x = 0; x < XSIZE; x++) {
			if ((buffer[y * XSIZE +x] & 0xff) > LIMITVAL) {
				koordinaten[aktCount].x = x;
				koordinaten[aktCount].y = y;
				if (aktCount > 0 && (koordinaten[aktCount-1].x == x -1) &&
				    (koordinaten[aktCount-1].y == y)) {
					koordinaten[aktCount].gruppeNr = groupNumber;
				} else {
					koordinaten[aktCount].gruppeNr = ++groupNumber;
				}
				aktCount++;
			}
		}
	}
	maxGroup = groupNumber;
	groups = calloc(sizeof(int), maxGroup);

	/* Gruppen vertikal miteinander verbinden */
	for (i = 0; i <= aktCount; i++) {
		y = koordinaten[i].y;
		j = i;
		/* gehe zum naechsten aktiven pixel in der naechsten zeile */
		while (koordinaten[j].y == y)
			j++;
		/* speichern des ersten aktiven Pixels in der naechsten Zeile */
		zeilenAnfang = j;
		while (koordinaten[j].y == y+1) {
			/* ueberpruefe ob Vertikal mehrere aktive Pixel vorhanden sind */
			if (koordinaten[i].x == koordinaten[j].x) {
				groupNumber = koordinaten[j].gruppeNr;
				if (koordinaten[j].ueberschrieben == 0) {
					/* setze alle Pixel die aktiv sind und zusammenhaengen auf die
					 * gleiche Gruppennummer
					 */
					for (l = zeilenAnfang; koordinaten[l].y == y + 1 ; l++) {
						if (koordinaten[l].gruppeNr == groupNumber) {
							koordinaten[l].gruppeNr = koordinaten[i].gruppeNr;
							koordinaten[j].ueberschrieben = 1;
						}
					}
				} else {
					for (l = 0; koordinaten[l].y <= koordinaten[j].y; l++) {
						if (koordinaten[l].gruppeNr == koordinaten[i].gruppeNr)
							koordinaten[l].gruppeNr = koordinaten[j].gruppeNr;
					}
				}
			} 
			++j;
		}
	}
 

	for (i = 0; i < aktCount; i++) {
		groups[koordinaten[i].gruppeNr]++;
	}
	j = 1;
 
#if 0 /* finden der groessten gruppe! */
	for (i = 0; i < maxGroup; i++) {
		if (j < groups[i]) {
			j = groups[i]; /* Anzahl der Elemente der Gruppe */
			k = i; /* Gruppennummer */
		}
	}
#else
	for (i = 0; i < maxGroup; i++) {
		if (groups[i] >= MIN_SIZE && groups[i] <= MAX_SIZE) {
			j = groups[i];
			k = i;
			break;
		}	
	}
#endif

	for (i = 0; i < aktCount; i++) {
		if (koordinaten[i].gruppeNr == k) {
			xsum += koordinaten[i].x;
			ysum += koordinaten[i].y;
		}
	}
	*xmittel = xsum / j;
	/* Zeilen sind "verkehrtrum" im Bitmap */
	*ymittel = YSIZE - ysum / j;
	free(groups);
 

	pictofile(buffer, xmittel, ymittel, koordinaten, aktCount, k);
	return 0;
}



void 
pictofile(char *buffer, int* xmittel, int* ymittel, infoBlock_t koordinaten[], unsigned int aktCount, unsigned int maxGroup)
{
	char outname[32];   //Textfile, Übersicht aktive Pixel
	FILE *fout;
	unsigned int x, y, i, flag = 0;

	picnr++;
	sprintf (outname, "bilderout/test%.3d.xpm", picnr);

	fout = fopen(outname, "wb");
	fprintf(fout, "/* XPM */\n");
	fprintf(fout, "static char * Ball_xpm[] = {\n");
	fprintf(fout, "\"640 480 4 1\",\n");
	fprintf(fout, "\"0      c #36c21f\",\n");
	fprintf(fout, "\"1      c #ffffff\",\n");
	fprintf(fout, "\"2      c #000000\",\n");
	fprintf(fout, "\"3      c #ff0000\",\n");

	for (y = YSIZE - 1; y >= 0; y--) {
		fprintf(fout, "\"");
		for (x = 0; x < XSIZE; x++) {
			if ((buffer[x+y*XSIZE] & 0xff) > LIMITVAL) {
				for (i = 0; i < aktCount; i++) {
					if (koordinaten[i].x == x && koordinaten[i].y == y) {
						if (koordinaten[i].gruppeNr == maxGroup)
							fprintf(fout, "1");
						else 
							fprintf(fout, "2");
						flag = 1;
					} 
				}
				if (!flag)
					fprintf(fout, "0");
				flag = 0;
			} else {
				fprintf(fout, "0");
			}
		}
		fprintf(fout, "\",\n");
		if (y == 0)
			break;
	}
	fprintf(fout, "};\n");
	fclose(fout);
}

Daraus ergibt sich folgendes Resultat:

X(Code)	Y(Code)	X (SW)	Y (SW)
600	198	600,91	197,01
585	202	585,4	200,89
569	205	569,61	204,52
553	209	553,49	208,17
538	213	538,37	211,73
523	216	523,71	215,15
509	219	509,14	218,52
495	223	495,12	221,8
481	226	481,37	224,92
468	229	468,13	228,11
454	232	454,99	231,13
442	235	442,13	233,99
429	238	429,4	236,89
416	241	416,57	239,8
403	244	403,83	242,67
390	247	391	245,69
378	250	378,38	248,6
365	252	365,56	251,53
352	255	352,78	254,41
339	258	340,08	257,35
327	261	327,29	260,2
314	264	314,49	263,1
301	267	301,74	266,02
288	270	288,92	268,92
275	273	276,21	271,85
263	276	263,53	274,76
250	279	250,75	277,72
237	282	238,04	280,62
225	285	225,26	283,6
212	287	212,68	286,53
199	290	200,01	289,39
187	293	187,41	292,26
174	296	174,78	295,17
162	299	162,21	298,03
149	302	149,57	301,01
136	305	137,14	303,95
124	308	124,67	306,81
112	311	112,08	309,68
99	313	99,65	312,53
87	316	87,24	315,42
74	319	74,77	318,28
62	322	62,45	321,24
50	325	50,15	324,08
37	328	37,8	326,93
25	331	25,46	329,75
14	334	14,55	332,6

Der eigene Sourcecode liefert also gegenüber der fertigen Software sehr gute Ergebnisse. Einziges Problem könnte die Dauer von fast 70% der verfügbaren Zeit sein, optimieren!!! (durch Optimierung auf ca 20% gesenkt!)

Resultat bei je 100 Auswertungen pro Bild:

Picnr.	X	Y	time[sec] (für je 100 Auswertungen!)
20      600     198     0.24
21      585     202     0.18
22      569     206     0.21
23      553     209     0.19
24      538     213     0.18
25      523     216     0.18
26      508     219     0.16
27      494     223     0.17
28      481     226     0.17
29      467     229     0.17
30      454     232     0.16
31      441     235     0.16
32      429     238     0.16
33      416     241     0.15
34      403     244     0.15
35      390     247     0.15
36      378     250     0.15
37      365     252     0.15
38      352     255     0.16
39      339     258     0.15
40      327     261     0.15
41      314     264     0.16
42      301     267     0.15
43      288     270     0.15
44      275     273     0.16
45      263     276     0.14
46      250     279     0.16
47      237     282     0.16
48      224     285     0.15
49      212     287     0.15
50      199     290     0.15
51      187     293     0.15
52      174     296     0.15
53      161     299     0.15
54      149     302     0.14
55      136     305     0.15
56      124     308     0.15
57      111     311     0.15
58      99      314     0.14
59      86      316     0.15
60      74      319     0.14
61      62      322     0.15
62      49      325     0.14
63      37      328     0.15
64      25      331     0.14
65      14      334     0.14

Jedes Bild wurde 100x ausgewertet, die Zeit blieb dabei um die 0.2sec. Theoretisch haben wir zur Verarbeitung jedes Bildes 10msec (bei 100fps), da sollte ein Auswerten von jeweils 2msec i.O. sein.

Test020-bmp.jpg Test020-xpm.jpg Test021-bmp.jpg Test021-xpm.jpg


Eine weitere Möglichkeit der Ballerkennung: (hier wird die Umgebung der Pixel berücksichtigt und erst bei einem bestimmten Schwellwert an aktiven Pixeln in der Umgebung, wird ein Pixel als aktiv markiert):

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <time.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>

#include "bitmap.h"

#define WIDTH 640
#define HEIGHT 480


/*
 * write our raw data into a bitmap file
 * adding the correct header and put it all
 * together in a single file
 */
int
writeBitmapToFile(void *data, unsigned long size, const char *filename)
{
	bmpFileHeader_t header;
	bmpInfoHeader_t info;
	bmpRGBQuad_t rgb = { 0, 0, 0, 0};
	FILE *out;
	size_t len;
	int i;

	header.type = 0x4d42; // magic sequence 'BM'
	header.size = 0;
	header.res0 = 0;
	header.res1 = 0;
	/* 256 different colors, each is defined by an bmpRBQuad type */
	header.offset = 256 * sizeof(bmpRGBQuad_t) + 
					sizeof(bmpInfoHeader_t) + sizeof(bmpFileHeader_t);
	info.size = sizeof(bmpInfoHeader_t);
	info.width = WIDTH;
	info.height = HEIGHT;
	info.planes = 1;
	info.cDepth = 8; /* 8 Bit */
	info.compression = 0;
	info.rawSize = size;
	info.hRes = 0;
	info.vRes = 0;
	info.nrColors = 0x100;
	info.impColors = 0;
	
	out = fopen(filename, "wb");
	if (out == NULL) {
		printf("error cannot open %s for writing\n", filename);
		return -1;
	}
	len = fwrite(&header, 1, sizeof(header), out);
	if (len != sizeof(header)) {
		printf("error while writing header\n");
		return -1;
	}
	len = fwrite(&info, 1, sizeof(info), out);
	if (len != sizeof(info)) {
		printf("error while writing info header\n");
		return -1;
	}
	/* stupid try and error programming leads to this */
	for (i = 0; i < 256; i++) {
		rgb.red = i;
		rgb.green = i;
		rgb.blue = i;
		len = fwrite(&rgb, 1, sizeof(rgb), out);
		if (len != sizeof(rgb)) {
			printf("error while writing rgb header\n");
			return -1;
		}
	}

	len = fwrite(data, 1, size, out);
	if (len != size ) {
		printf("error while writing bitmap data\n");
		return -1;
	}
	return 0;
}


/*
 * was used for analyzing bitmap files
 */
int
dumpHeaderField(bmpFileHeader_t *header, bmpInfoHeader_t *info)
{
	printf("######### header #########\n");
	printf("type: 0x%x (%c%c)\n", header->type, header->type & 0xff, 
	    (header->type >> 8) & 0xff);
	printf("size: 0x%lx (%lu)\n", header->size, header->size);
	printf("res0: 0x%x\n", header->res0);
	printf("res1: 0x%x\n", header->res1);
	printf("data offset: 0x%lx (%lu)\n", header->offset, 
	    header->offset);
	printf("######### info #########\n");
	printf("size: 0x%lx (%lu)\n", info->size, info->size);
	printf("width: 0x%lx (%lu)\n", info->width, info->width);
	printf("height: 0x%lx (%lu)\n", info->height, info->height);
	printf("planes: 0x%x\n", info->planes);
	printf("cDepth: 0x%x\n", info->cDepth);
	printf("compression: 0x%lx\n", info->compression);
	printf("rawSize: 0x%lx\n", info->rawSize);
	printf("hRes: 0x%lx\n", info->hRes);
	printf("vRes: 0x%lx\n", info->vRes);
	printf("nrColors: 0x%lx\n", info->nrColors);
	printf("important colors: 0x%lx\n", info->impColors);	
	
	return 0;
}


/*
 * Bildet ein einfaches Kreuz und zaehlt die dabei entdeckten aktiven Pixel.
 * Dies soll verhindern, dass einzelne Stoerpixel ebenfalls gezaehlt werden
 * und ggf. den Start des aktiven Pixels veraendern.
 * Die Werte fuer PIXEL sowie fuer CUT_PIXEL koennen in der bitmap.h
 * eingestellt werden.
 */
unsigned int
checkPixelEnvironment(unsigned char *data, unsigned long long pixel, int mark)
{
	unsigned int i, cnt;
	
	cnt = i = 0;
	for (; i < PIXEL; i++) {
		if (pixel  >= i *WIDTH) 
			cnt += ((data[pixel - i * WIDTH] & 0xff) > mark ? 1 : 0);
		if (pixel + i < WIDTH * HEIGHT)
			cnt += ((data[pixel + i] & 0xff) > mark ? 1 : 0);
		if (pixel + i * WIDTH < WIDTH * HEIGHT)
			cnt += ((data[pixel + i * WIDTH] & 0xff) > mark ? 1 : 0);
		if (pixel >= i)
			cnt += ((data[pixel - i] & 0xff)> mark ? 1 : 0);
	}

	return cnt;
}



int
sortBinaryBlock(const void *a, const void *b)
{
	binaryInfoBlock_t *a1 = (binaryInfoBlock_t *)a;
	binaryInfoBlock_t *b1  = (binaryInfoBlock_t *)b;
	
	if (a1->pixel < b1->pixel)
		return -1;
	else if (a1->pixel > b1->pixel)
		return 1;
	else
		return 0;

}



/*
 * Erstellt ein Binaerbild und markiert Pixel deren Wert > mark ist als aktiv.
 * Damit keine Stoerpixel im Bild sind, wird mit checkPixelEnvironment die 
 * Umgebung durchsucht und erst bei einem genuegend hohen Wert an aktiven Pixeln
 * in der Umgebung, wird der einzelne Pixel als aktiv markiert und gezaehlt.
 * Es wird jeweils der Anfangspixel gespeichert (der erste aktive in der Zeile).
 * Spaeter wird nach dem linkesten Pixel gesucht (in dieser Zeile muss sich der
 * Mittelpunkt befinden). Um bessere Resultate zu bekommen, wurden alle Zeilen
 * mit gleichem ersten aktiven Pixel mit in die Berechnung einbezogen. Zur
 * Geschwindigkeitsoptimierung wurde das Feld vorher mit qsort sortiert und somit
 * muss am Ende nur noch ein Bruchteil des Arrays durchlaufen werden.
 */
int
createCuttedBinaryObject(unsigned char *data, int mark, unsigned long long size)
{

	int line = 0, res, minPixel, maxOneCnt;
	unsigned long long i;
	unsigned long long startTime;
	binaryInfoBlock_t info[HEIGHT];
	
	info[line].oneCnt = 0;
	info[line].pixel = -1;
	info[line].line = line;
	line = HEIGHT - 1;
	
	startTime = clock();	
	for (i = 0 ; i < size; i++) {
		if (i && !(i % WIDTH)) {
			line-- ;
			info[line].oneCnt = 0;
			info[line].pixel = -1;
			/* lines are reversed in the bitmap */
			info[line].line = line;
		}
		if ((data[i] & 0xff) > mark) {
			data[i] = 0x1;
			res = checkPixelEnvironment(data, i, mark);
			if (res < CUT_PIXEL) {
				data[i] = 0x0;
				continue;
			}
			info[line].oneCnt++;
			if (info[line].pixel == -1)
				info[line].pixel = i % WIDTH;
		} else {
			data[i] = 0x0;
		}	
	}
	qsort(info, HEIGHT, sizeof(binaryInfoBlock_t), sortBinaryBlock);


	minPixel = info[0].pixel;
	maxOneCnt = line = 0;
		
	while (info[line].pixel == minPixel && line < HEIGHT) {
		res += info[line].line;
		maxOneCnt += info[line].oneCnt;
		line++;
	}
	printf("y: %.2f\tx: %.2f\tZeit: %.3f\n", (double )res / (double)line, 
	    (double)minPixel + ((double)maxOneCnt / (double)line ) / 2,
	    ((float)(clock()-startTime)/CLOCKS_PER_SEC));

	return 0;
}

int
main(int argc, char *argv[])
{
	FILE *in; 
	struct stat sb;
	int i = 0;
	unsigned char *ptr = NULL;
	char fileName[1024];
	

for (i = 20; i < 66; i++) {
	snprintf(fileName, sizeof(fileName), "../bilder2/test0%i.bmp", i);
	if (stat(fileName, &sb) == -1) {
		printf("error stat() failed\n");
		return 1;
	}
	ptr = calloc(1,sb.st_size);
	if (ptr == NULL) {
		printf("error can not allocate space\n");
		return 1;
	}
	in = fopen(fileName, "rb");
	if (in == NULL) {
		printf("error cannot open file: %s\n", argv[1]);
		return 1;
	}
	if (fread(ptr, 1, sb.st_size, in) != sb.st_size) {
		printf("error read failed for file: %s\n", argv[1]);
		return 1;
	}


	createCuttedBinaryObject(ptr + 1078, 0x80, WIDTH * HEIGHT);


	free(ptr);
}	
	return 0;
}
/*
 * GTK Ausgabe der Koordinaten. getKoordinates(<bbild nummer>, &x, &y)
 * holt die Koordinaten und gibt diese dann auf dem Bildschirm kontiniuerlich
 * aus. 
 *
 * Kompilierung mit: 
 *    gcc -o ball graphick.c bitmap.c -Wall `pkg-config --libs gtk+-2.0` `pkg-config --cflags gtk+-2.0`
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <gtk/gtk.h>
#include <glib.h>

GtkWidget *window;
GtkWidget *fixed;
GtkWidget *label;

gboolean
getData(gpointer data)
{
	int x, y;
	static int i = 20;
	char buf[128];
	// Hole Koordinaten
	if ( i >= 65)
		i = 20;
	getKoordinates(i++, &x,&y);
	snprintf(buf, sizeof(buf), "o x: %i y: %i", x, y);
	gtk_label_set_text(GTK_LABEL(label), buf); 
	gtk_fixed_move(GTK_FIXED(fixed), label, x, y);
	gtk_timeout_add(100, getData, NULL);
	return 0;
}


int 
main (int argc, char *argv[])
{

	gtk_init (&argc, &argv);
	window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	fixed = gtk_fixed_new();
	label = gtk_label_new("o");
	gtk_container_add(GTK_CONTAINER(window), fixed);
	gtk_fixed_put(GTK_FIXED(fixed), label, 1, 30);
	gtk_window_set_default_size (GTK_WINDOW(window), 640, 480);
	gtk_window_set_title(GTK_WINDOW (window), "Ballbestimmung");
	gtk_widget_show(fixed);
	gtk_widget_show(label);
 	gtk_widget_show(window);
	gtk_timeout_add(1, getData, NULL);

	gtk_main();
	return 0;
}

Berechnungen

Nötige Auflösung

Bei der zu verwendenden Auflösung gilt: so groß wie nötig, so klein wie nötig. Je größer jedes auszuwertende Bild ist, desto mehr Rechenaufwand und Zeit wird für die Auswertung benötigt. Allerdings wird eine gewisse minimale Auflösung benötigt, damit der Ball gegenüber anderen Störeinflüssen wie Schatten, Flecken o.ä. eindeutig erkannt werden kann. Außerdem wirkt sich die Auflösung des Bildes auf die Genauigkeit der ermittelten Koordinaten aus.

Um diesen Anforderungen gerecht zu werden wurde eine Auflösung von 640x480 gewählt. Die Spielfeldlänge von 1,25m wird dabei in 480px aufgeteilt bzw. von diesen erfasst, die Breite von 68,5cm hingegen von 350px, es werden also nicht alle der 480 möglichen Pixeln für die Breite benötigt, da das Spielfeld nicht in einem Verhältnis von 4:3 also 1,33:1 sondern von 1,82:1 steht.

x-Achse: 640px/125cm = 5,12px/cm = 1px/2mm y-Achse: 5,12px/cm * 68,5cm = 350,7px

  • Durchmesser des Balls: 3,5cm = 3,5cm * 5,12px/cm = 18px (genauer: 17,92px)
  • Fläche des Balls: A = r² * PI = (17,92px/2)² * PI = 252px

Das heißt, dass bei der Ballerkennung etwa 250px den Ball darstellen, was zum einen ausreichend ist um ihn von Störungen zu unterscheiden, was zum anderen auch eine weit höhere Genauigkeit bringt als die Auflösung selbst (1px/2mm), da die Koordinaten aus dem Mittelpunkt dieser 250px ermittelt werden, also die Koordinaten auf mehrere Pixelkomastellen genau darstellen (was bei Pixeln zwar nicht viel Sinn macht da es nu ganze Pixel geht, aber beim Betrachten der Koordinaten zur verbesserten Genauigkeit beiträgt).

Nötige Exposure Time

Eine weiterer Parameter den man bestimmen muss ist der mögliche Maximalwert der exposure time. Darunter kann man sich die Belichtungszeit vorstellen. Die nötige exposure time ist abhängig von der Beleuchtung: Je geringer die Beleuchtung desto länger die Belichtungszeit, um ein gutes Bild mit ausreichender Helligkeit zu erhalten. Allerdings ist ein Bild mit hoher exposure time bei schnellen Bewegungen sehr verschwommen. Aber auch die Beleuchtung sollte gering gehalten werden, da Hitze, Stromkosten und das Blenden der Spieler Nebenwirkungen sind.

Um die Koordinaten des Balles ohne größere Verfälschung ermitteln zu können wird als Grenze eine exposure time verwendet, in der sich die Position des Balls während der Belichtungszeit maximal um 3px/Bild ändert. Geht man dabei von einer Maximalgeschwindigkeit des Balls von 4m/sec aus, so ergibt sich folgende maximale exposure time:

  • 4m/sec = 4mm/msec = 2px/msec (bei 1px/2mm)
  • Grenze 3px: 1,5msec = 1/667 sec

Dieser Wert ist gegenüber den momentan verwendeten Werten von etwa 1/280 sec sehr gering und setzt daher eine sehr gute Beleuchtung vorraus. Ggf. wäre es möglich, diesen Wert auch etwas zu vergrößern, was zwar zu einem verschwommenen Ball auf dem Bild führen würde, aber evtl. trotzdem ausreichen würde, um gute Koordinaten zu erhalten. Mit einer exposure time von 1/280 sec hätte man in einem Bild eine Positionsänderung des Balls bei einer Geschwindigkeit von 2px/msec von:

  • 1sec/280 * 2px/msec = 7,14px (Frage an Hr. Jaschul: möglich?)

Nächste Schritte

Nun steht es an, einen direkten Vergleich von Ballerkennung via Kamera und Lichtgitter zu erreichen. Dazu wird ein Ball über eine Strecke, die von der Kamera als auch vom Lichtgitter überwacht wird, geschossen, und die erhaltenen Daten auf Genauigkeit und Zuverlässigkeit ausgewertet.

Abschlusspräsentation

Media:Kamera_praesentation.pdf