Kamera

Aus Kicker
Zur Navigation springenZur Suche springen

Ballerfassung mit Hilfe einer Kamera

Informationen zur verwendeten Hardware

Kamera

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

//kamera.c
#include <stdio.h>
#include <time.h>
#include "windows.h"
#include "mvfgdrv.h"
#include "bitmap.h"

//C:\PROGRA~1\MIKROT~1\Inspecta\Inspecta-4DC.cam
int 
APIENTRY WinMain( HINSTANCE hInstance,
HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	LONG ret;
	char buffer[256];
	void *ptr;
	FORMAT_INFO info;
	time_t t1, t2;
	int err = 0, i;

	/* 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);
	}
	
	/* grab picture */
#if 0
	time(&t1);
	for (i = 0; i < 100; i++) {
		ret = mvfg_grab(GRAB_WAIT, 0);
		if (ret != MVFG_GRAB_READY) 
			++err;
	}
	time(&t2);
	if (err > 0) {
		wsprintf(buffer, "Es sind %d Fehler aufgetreten", err);
		MessageBox(NULL, buffer, "Informationen ueber Bilder", MB_OK);
	} else  {
		wsprintf(buffer, "Es wurden 100 Bilder in %d aufgenommen", t2 - t1);
		MessageBox(NULL, buffer, "Informationen ueber Bilder", MB_OK);
	}
#endif
	ret = mvfg_grab(GRAB_WAIT, 0);
	if (ret != MVFG_GRAB_READY) 
		MessageBox(NULL,"Fehler beim aufnehmen", "Informationen ueber Bilder", MB_OK);
	/* ptr points to the address of the picture */

	ptr = malloc(info.lFrameSize);
	memcpy(ptr, mvfg_getbufptr(0), info.lFrameSize);

	/* 
	 * XXX save file as bitmap
	 * we have to build an bitmap header ...
	 */

	writeBitmapToFile(ptr, info.lFrameSize);


	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;
}
//bitmap.h zum anhängen des Bitmap-Headers:
#ifndef _BITMAP_H
#define _BITMAP_H

#include <stdio.h>


int writeBitmapToFile(void *, DWORD );


struct bitmapFileHeader {
	WORD bfType;
	DWORD bfSize;
	DWORD bfReserved;
	DWORD bfOffBits;
};

struct bitmapInformationBlock {
	DWORD biSize;
	LONG biWidth;
	LONG biHeight;
	WORD biPlanes;
	WORD biBitCount;
	DWORD biCompression;
	DWORD biSizeImage;
	LONG biXPelsPerMeter;
	LONG biYPelsPerMeter;
	DWORD biClrUsed;
	DWORD biClrImportant;
};

#endif
//bitmap.cpp
#include <windows.h>
#include <stdio.h>
#include <time.h>
#include "bitmap.h"

int
writeBitmapToFile(void *buffer, DWORD bSize)
{
	struct bitmapFileHeader header;
	struct bitmapInformationBlock infoBlock;
	FILE *fd;

	header.bfOffBits = 54; // Offset vom Beginn der Datei in Byte
	header.bfReserved = 0;
	header.bfType = 19778; //Magic Sequenz ascii BM
	header.bfSize = 300 * 1024;

	infoBlock.biBitCount = 8; // Farbtiefe
	infoBlock.biClrImportant = 0; //Anzahl der im Bild vorkommenden Farben
	infoBlock.biClrUsed = 0;
	infoBlock.biCompression = 0; //keine Compression
	infoBlock.biHeight = 480;
	infoBlock.biWidth = 640;
	infoBlock.biPlanes = 1;
	infoBlock.biSize = 40;
	infoBlock.biSizeImage = bSize;
	infoBlock.biXPelsPerMeter = 0;
	infoBlock.biYPelsPerMeter = 0;

	fd = fopen("file.bmp", "wb+");
	fwrite(&header, 1, sizeof(header), fd);
	fwrite(&infoBlock, 1, sizeof(infoBlock), fd);
	fwrite(buffer, 1, bSize, fd);
	fclose(fd);

	return 1;
}

Binärbild auswerten und Koordinaten bekommen

/***************************************************************************
 *   Copyright (C) 2008 by Manuel Zimmermann   *
 *   manuel.zimmermann@manyou.de   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/


#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#define OFFSET 0x0436 
//Header 'wegschneiden'
#define XSIZE 640
#define YSIZE 480
#define LIMITVAL 0x80
//Alles was heller als LIMITVAL ist wird als aktiv betrachtet!

#include "time.h"   //Geschwindigkeit feststellen
#include <fcntl.h>  // open
#include <unistd.h> //read, close
#include <iostream>
#include <fstream>
#include <cstdlib>


using namespace std;

int main(int argc, char *argv[])
{
  for(int picnr=20; picnr<=65; picnr++) //für jedes Bild durchführen:
  {
/*****************************************
    Bild einlesen
*****************************************/
    char filename[32];  //zu ladendes Bild
    char outname[32];   //Textfile, Übersicht aktive Pixel
    sprintf (filename, "bilder/test%.3d.bmp",picnr);
    sprintf (outname, "test%.3d.txt",picnr);
    int m_iFd = open(filename, O_RDONLY);
    ofstream myfile;
    myfile.open (outname, fstream::binary);
    char buffer[YSIZE][XSIZE];  //Bild in 2dim Array speichern
    for(int y=0; y<YSIZE; y++)
    {
      long rdvalue = lseek(m_iFd, y*XSIZE + OFFSET, SEEK_SET);  //Bearbeitungsposition in Datei setzen
      read(m_iFd, buffer[y], XSIZE);  //Je eine Pixelzeile auslesen
    }

/*****************************************
 Bild auswerten
*****************************************/
    int xmittel, ymittel;
    long anfangszeit = clock();
    for(int timei=0; timei<100; timei++)  
    {
      int linegrnr=0;
      short koordinaten[3][XSIZE*YSIZE];  //evtl. Größe geringer wählen!
      int aktkoor=0;
      //jede Zeile ausgeben, beginnt mit letzter Zeile!
      for(int y=0; y<YSIZE; y++)  //Jede Zeile
      {
	for(int x=0; x<XSIZE; x++)  //Jeden Pixel in der Zeile
	{
	  if((short)(buffer[y][x] & 0xff) > LIMITVAL)
	  {
	  //4debug in txt-File schreiben
  // 	myfile.width (2);
  // 	myfile << 1;
  // 	myfile << " | ";
	    koordinaten[0][aktkoor]=x;
	    koordinaten[1][aktkoor]=y;
	    if(koordinaten[0][aktkoor-1]==x-1)
	    {
	      (koordinaten[2][aktkoor]=linegrnr);  //hängt an anderen aktivem Zeilenpixel
	    }else{
	      (koordinaten[2][aktkoor]=++linegrnr);
	    }
	    aktkoor++;
	  }
	  //4debug in txt-File schreiben
  // 	else
  // 	{
  // 	  myfile.width (2);
  // 	  myfile << 0;
  // 	  myfile << " | ";
  // 	}
	}
	myfile << endl;
      }
  
  //Gruppen vertikal verbinden:
      for(int i=0; i<aktkoor; i++)
      {
	int j=i;
	while(((koordinaten[1][++j])<=(koordinaten[1][i]+1))&&(j<=aktkoor))
// 	for(int j=i+1; j<aktkoor; j++)
	{
	  if((koordinaten[2][i]!=koordinaten[2][j])&&(koordinaten[0][i]==koordinaten[0][j])&&(koordinaten[1][i]+1==koordinaten[1][j]))
	    //Wenn die X-Koordinaten identisch und sich die Y-Koordinaten um 1 unterscheiden und sie unterschiedlichen Gruppen angehören:
	  {
	    int temp=koordinaten[2][j];  //Gruppennummer die überschrieben wird
	    int k=i;
	    while((koordinaten[1][i]==koordinaten[1][++k])||(koordinaten[1][i]+1==koordinaten[1][k]))  //Optimierung
//	    for(int k=i; k<aktkoor; k++)
	    {
	      if(koordinaten[2][k]==temp)
	      koordinaten[2][k]=koordinaten[2][i];
	    }
//   	    k++;
	  }
	}
      }
  
      //Tabelle ausgeben:
  //     int i=0;
  //     while(koordinaten[0][i]!=0 | koordinaten[1][i]!=0)
  //     {
  //       cout << koordinaten[0][i] << "\t";
  //       cout << koordinaten[1][i] << "\t";
  //       cout << koordinaten[2][i++] << endl;
  //     }
  
      //Gruppenübersicht ausgeben:
      int maxgrnr=-1, maxanz=-1;
      for(int i=0; i<=linegrnr; i++)
      {
	int j=0, anz=0;
	while(koordinaten[0][j]!=0 | koordinaten[1][j]!=0)
	{
	  if(koordinaten[2][j++]==i) anz++;
	}
  //      if(anz>0) cout << "Gruppe " << i << " hat " << anz << " aktive Pixel!" << endl;
	if(anz>maxanz)
	{
	  maxanz=anz;
	  maxgrnr=i;
	}
      }
  //    cout << endl << "Größte Gruppe: " << maxgrnr << " mit " << i << " Elementen." << endl;
	
  
      //Mittelpunkt aller aktiven Pixel einer bestimmten Gruppe:
      int i=0;
      int xsum=0, ysum=0;
      while(koordinaten[0][i]!=0 | koordinaten[1][i]!=0)
      {
	if(koordinaten[2][i]==maxgrnr)
	{
	  xsum+=koordinaten[0][i];
	  ysum+=koordinaten[1][i];
	}
	i++;
      }
      xmittel=(xsum/maxanz);
      ymittel=480-(ysum/maxanz);
    }
    cout << picnr << "\t" << xmittel << "\t" << ymittel << "\t";
    cout << ((float)(clock()-anfangszeit)/CLOCKS_PER_SEC) << /*"\t" << CLOCKS_PER_SEC <<*/ endl; 
      

    //Zeile mit maximalen aktiven Pixeln ermitteln:
//     int maxline=0;
//     for(int y=0; y<480; y++)
//     {
//       if(zeilen[y] > zeilen[maxline])
//       {
// 	maxline = y;
//       }
//     }
//     cout << "Maximale aktive Pixel in Zeile " << maxline << ", und zwar " << zeilen[maxline] << endl;

    myfile.close();
    close(m_iFd);
    
/*    
    int test=1;
    cout.width (3);
    cout.fill ('0');
    cout << test << endl;
*/
  }
  return EXIT_SUCCESS;
}

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!!!

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

Jedes Bild wurde 100x ausgewertet, die Zeit blieb dabei um die 30msec. Theoretisch haben wir zu verarbeitung jedes Bildes 1msec, da sollte ein Auswerten von 0,3msec i.O. sein.

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 Lichtgitta überwacht wird, geschossen, und die erhaltenen Daten auf Genauigkeit und Zuverlässigkeit ausgewertet.