Infrarot-Shooter

Aus toolbox_interaktion
Wechseln zu: Navigation, Suche

Allgemeines

In diesem Artikel geht es um die Interaktion zwischen einem Computerspiel und dem Verarbeiten von Infrarotsignalen. Der Benutzer schießt mit einer Infrarotpistole auf eine Leinwand auf welcher das Spiel projiziert wird. In diesem Spiel fliegen in unserem Beispiel Hühner über den Bildschirm. Ziel des Spieles ist es so viele Hühner wie möglich zu treffen. Der mit der Pistole erzeugte Infrarot Punkt an der Leinwand wird von einer Kamera aufgenommen und ermittelt. Trifft der Spieler, bekommt er Punkte.

Diagramm.png


Infrarotdetektion

Anhand des Frameworks ofxOpenCv wird die ganze Bildverarbeitung gemacht. Ziel ist die Ermittlung der Position des Infrarot-Schusses gefolgt von der Übergabe dieser an das Spiel-Programm anhand des Frameworks ofxOsc.
Dabei wird die Bildverarbeitungsmethode Hintergrund-Subtraktion angewendet.

Initialisierung

void ofApp::setup(){
 vidGrabber.setDeviceID(1);
 vidGrabber.setVerbose(true);
 vidGrabber.setup(1280, 1024);
 colorImg.allocate(1280, 1024);
 grayImage.allocate(1280, 1024);
 // Background imge
 grayBg.allocate(1280, 1024);
 // Difference image
 grayDiff.allocate(1280, 1024);
 // Sez FPS
 ofSetFrameRate(17);
 bLearnBakground = true;
 threshold = 80;
 //Open outgoing port to : HOST:4000 to send the shoot--position in the game
 sender.setup(HOST, PORT);
}

Videoaufnahme und Bearbeitung

Hierzu wird vorab ein Bild des Hintergrunds aufgenommen, das in Folge kontinuierlich von dem Kamerabild subtrahiert wird. Sollte sich eine Differenz ergeben, dann handelt es sich um ein sogenanntes Blob, das dem Infrarot-Schuss entspricht.

if(bNewFrame){
  colorImg.setFromPixels(vidGrabber.getPixels());
  grayImage = colorImg;
  if (bLearnBakground == true)
  {
    grayBg = grayImage; // the = sign copys the pixels from grayImage into grayBg (operator overloading)
  }
  // take the abs value of the difference between background and incoming and then threshold:
  grayDiff.absDiff(grayBg, grayImage);
  grayDiff.threshold(threshold);
  
  // find contours which are between the size of 20 pixels and 1/3 the w*h pixels.
  // also, find holes is set to true so we will get interior contours as well....
  contourFinder.findContours(grayDiff, 1, (1280* 1024) / 3, 10, true);
 }

Schussposition und Übertragung

Der Blob wird dann in einem Rechteck modelliert und somit wird sein Mittelpunkt berechnet und durch eine ofxOsc-Schnittstelle per vordefiniertem Port an die Spiel-Software übertragen.

// check if there are blobs, get his bounding rectangle and get the center position (x, y) because there should be only 1 blob for the IR-Led
if (contourFinder.nBlobs) {
  double x, y;
  x = contourFinder.blobs[0].boundingRect.getCenter().x;
  y = contourFinder.blobs[0].boundingRect.getCenter().y;
  
  // Sender the position to the game via the opened port 4000
  ofxOscMessage m;
  m.setAddress("/position");
  m.addDoubleArg(x);
  m.addDoubleArg(1024-y);
  sender.sendMessage(m, false);
}

Spiel

Neben der Kommunikation und der Bildverarbeitung musste auch ein Spiel programmiert werden. In diesem Spiel sollten fliegende Objekte, in diesem Fall Hühner, zu sehen sein, die nach erfolgreichem Abschuss Punkte bringen.

Initialisierung

void ofApp::setup() {
//Load Pictures	
back.load("back.jpg");
huhni.load("huhni.gif");
blood.load("Blut.png");
//Give Start Position
x_pos = 300;
y_pos = 200;
size[0] = 128;
size[1] = 95;
//Give Running Direction speed and amplitude
direction = 1;
amplitude = ofRandom(80, 100);
speed = ofRandom(1, 2);
//Triggering Events Variables
hit = false;
count_blood = 0;
spawn = true; 
clicked = false;
score = 0;
height = ofGetScreenHeight();
width = ofGetScreenWidth();
verdana30.load("verdana.ttf", 30, true, true);
verdana30.setLineHeight(34.0f);
verdana30.setLetterSpacing(1.035);

Bilder von Hintergrund, das fliegende Huhn und die getroffene Anzeige werden geladen und alle Parameter mit Startwerten belegt.

Schussposition

 // check for waiting messages
 while (receiver.hasWaitingMessages()) {
    // get the next message
    ofxOscMessage m;
    receiver.getNextMessage(m);
    if (m.getAddress() == "/position") {
      positionX = m.getArgAsDouble(0);
      positionY = m.getArgAsDouble(1);
      ...
 }

Positionsberechnung

In der Update Funktion:

time = ofGetElapsedTimef();
//Get new position for Huhn
if (x_pos > width + size[0])
direction = -1;
if (x_pos < -size[0])
direction = 1;
x_pos = x_pos + direction * speed;
y_pos = 100 + amplitude*sin(speed * time);

time frägt die aktuelle Zeit ab. Je nachdem, ob das Huhn zu einem Rand herausgeflogen ist, wird direction gesetzt, was das Huhn wieder in die entgegengesetzte Richtung fliegen lässt. Nun werden die neuen Werte für x und y Position berechnet. X_Pos wird neu aufaddiert und um das Huhn in einem Sinus über den Bildschirm fliegen zu lassen, wird für die y_pos der Sinus angewendet. Der Wert 100 ist die y Startposition des Huhns, time die bereits abgefragte Zeit und Amplitude, ebenso wie Speed, eine zufällige Zahl. Damit verändert das Huhn jedes Mal seine Flugbahn und Geschwindigkeit.

Treffer ermitteln

//When shooted in update
if (clicked == true)
{
//See if hit was in picture
   if (direction == -1)
   {
     //If Picture is not turned
     if ((x_mouse > x_pos) && (x_mouse < x_pos + size[0]) && (y_mouse > y_pos) && (y_mouse < y_pos + size[1]) && (spawn == true))
     {
      hit = true;
      x_pos_blood = x_pos;
      y_pos_blood = y_pos;
      score = score + 10;
     }
   }
   else if (direction == 1) 
   {
     //If Picture is turned
     if ((x_mouse > x_pos - size[0]) && (x_mouse < x_pos) && (y_mouse > y_pos) && (y_mouse < y_pos + size[1]) && (spawn == true))
     {
      hit = true;
      x_pos_blood = x_pos - size[0]; 
      y_pos_blood = y_pos;
      score = score + 10;
     }
   }
clicked = false;
}

Wie bereits erwähnt kann das Huhn in zwei Richtungen fliegen, daher müssen bei einem Schuss auch zwei Optionen geprüft werden, die aber das selbe machen, außer dass bei direction == - die Bildgröße abgezogen werden muss, anstatt aufaddiert zu werden. In der If Abfrage wird geprüft ob die Koordinaten des Schusses innerhalb des Bildes liegen. Dafür wird die aktuelle Position abgefragt und die Bildgröße aufaddiert oder auch abgefragt. Damit man das Huhn nicht noch einmal treffen kann bis es wieder spawned, wird auch spawn abgefragt. Danach wird clicked wieder zurück gesetzt um einen neuen Schuss zu detektieren.

Oberfläche

back.draw(0, 0, width, height);
verdana30.drawString("Score: ", width - 400, height - 700);
verdana30.drawString(ofToString(score),width -200 , height-700);

In der Draw Funktion wird zuerst das Hintergrundbild gezeichnet und der Punktestand angezeigt.

//Huhn will be drawn and eventually turned
if (hit == false)
{
  if(spawn == true)
  huhni.draw(x_pos, y_pos, (-1) *direction * size[0], size[1]);
}

Wurde das Huhn nicht getroffen wird das Huhn einfach an der neu berechneten Position gezeichnet.

else 
{		
  spawn = false;
  if (count_blood < 60)
  {
    blood.draw(x_pos_blood, y_pos_blood, size[0], size[1]);			
  }
  count_blood++;
  if(count_blood == 60)
  {
   count_blood = 0;
   hit = false; 
   spawn = true; 
   x_pos = ofRandom(size[0], 0); 
   y_pos = ofRandom(100, 150);
   speed = ofRandom(1, 2);
   amplitude = ofRandom(80, 100);
 }
}

Wurde es aber getroffen, wird das Huhn nicht mehr angezeigt, sondern ein Blutfleck, der das Treffen des Huhns anzeigt. Count_blood ist ein Zähler, der die Länge für die Anzeige des Blutflecks angibt. Ist der Wert überschritten, werden alle Werte neu gesetzt und das Huhn mit diesen neuen Werten wieder gespawnt.

Bg.png

Links

Die Projektdateien sind unter folgendem Link zu finden: Infrarot-Shooter