ECE597 Project Interactive Pong
This project is part of the ECE597 32-bit Embedded Linux class at Rose-Hulman Institute of Technology in Terre Haute, IN.
Contents
Project Goal
The goal of this project is to implement a simple game of "Pong" on the Beagle Board that allows a user to play the game by gesturing with their hands rather than using a mouse or keyboard.
Team Members
Elliot Simon
Mitch Garvin
Matt Luke
Jian Li
Code
OpenCV (C++)
void loadTemplateImage()
Get the color of the hand, so that we can track the movement of the hand automatically.
{ IplImage *tempimage = cvLoadImage("/home/dica/workspace/cvex1/sshou.bmp",1); cvCvtColor( tempimage, hsv, CV_BGR2HSV ); int _vmin = vmin, _vmax = vmax; cvInRangeS( hsv, cvScalar(0,smin,MIN(_vmin,_vmax),0), cvScalar(180,256,MAX(_vmin,_vmax),0), mask ); cvSplit( hsv, hue, 0, 0, 0 ); selection.x = 1; selection.y = 1; selection.width = 320-1; selection.height= 240-1; cvSetImageROI( hue, selection ); cvSetImageROI( mask, selection ); cvCalcHist( &hue, hist, 0, mask ); float max_val = 0.f; cvGetMinMaxHistValue( hist, 0, &max_val, 0, 0 ); cvConvertScale( hist->bins, hist->bins, max_val ? 255. / max_val : 0., 0 ); cvResetImageROI( hue ); cvResetImageROI( mask ); track_window = selection; track_object = 1; cvZero( histimg ); int bin_w = histimg->width / hdims; for(int i = 0; i < hdims; i++ ) { int val = cvRound( cvGetReal1D(hist->bins,i)*histimg->height/255 ); CvScalar color = hsv2rgb(i*180.f/hdims); cvRectangle( histimg, cvPoint(i*bin_w,histimg->height), cvPoint((i+1)*bin_w,histimg->height - val), color, -1, 8, 0 ); } cvReleaseImage(&tempimage); }
int main( int argc, char** argv )
create window, get the color of the hand and then track the movement of the hand
{ CvCapture* capture = 0; if( argc == 1 || (argc == 2 && strlen(argv[1]) == 1 && isdigit(argv[1][0]))) capture = cvCaptureFromCAM( argc == 2 ? argv[1][0] - '0' : 0 ); else if( argc == 2 ) capture = cvCaptureFromAVI( argv[1] ); if( !capture ) { fprintf(stderr,"Could not initialize capturing...\n"); return -1; } cvNamedWindow( "CamShiftDemo", 1 ); cvNamedWindow( "Histogram", 1 ); for(;;) { IplImage* frame = 0; int i, bin_w, c; frame = cvQueryFrame( capture ); if( !frame ) break; if( !image ) { /* allocate all the buffers */ image = cvCreateImage( cvGetSize(frame), 8, 3 ); image->origin = frame->origin; hsv = cvCreateImage( cvGetSize(frame), 8, 3 ); hue = cvCreateImage( cvGetSize(frame), 8, 1 ); mask = cvCreateImage( cvGetSize(frame), 8, 1 ); backproject = cvCreateImage( cvGetSize(frame), 8, 1 ); hist = cvCreateHist( 1, &hdims, CV_HIST_ARRAY, &hranges, 1 ); histimg = cvCreateImage( cvSize(320,200), 8, 3 ); cvZero( histimg ); loadTemplateImage(); } cvCopy( frame, image, 0 ); cvCvtColor( image, hsv, CV_BGR2HSV ); if( track_object ) { int _vmin = vmin, _vmax = vmax; cvInRangeS( hsv, cvScalar(0,smin,MIN(_vmin,_vmax),0), cvScalar(180,256,MAX(_vmin,_vmax),0), mask ); cvSplit( hsv, hue, 0, 0, 0 ); if( track_object < 0 ) { float max_val = 0.f; cvSetImageROI( hue, selection ); cvSetImageROI( mask, selection ); cvCalcHist( &hue, hist, 0, mask ); cvGetMinMaxHistValue( hist, 0, &max_val, 0, 0 ); cvConvertScale( hist->bins, hist->bins, max_val ? 255. / max_val : 0., 0 ); cvResetImageROI( hue ); cvResetImageROI( mask ); track_window = selection; track_object = 1; cvZero( histimg ); bin_w = histimg->width / hdims; for( i = 0; i < hdims; i++ ) { int val = cvRound( cvGetReal1D(hist->bins,i)*histimg->height/255 ); CvScalar color = hsv2rgb(i*180.f/hdims); cvRectangle( histimg, cvPoint(i*bin_w,histimg->height), cvPoint((i+1)*bin_w,histimg->height - val), color, -1, 8, 0 ); } } cvCalcBackProject( &hue, backproject, hist ); cvAnd( backproject, mask, backproject, 0 ); cvCamShift( backproject, track_window, cvTermCriteria( CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 10, 1 ), &track_comp, &track_box ); track_window = track_comp.rect; if( backproject_mode ) cvCvtColor( backproject, image, CV_GRAY2BGR ); if( !image->origin ) track_box.angle = -track_box.angle; cvEllipseBox( image, track_box, CV_RGB(255,0,0), 3, CV_AA, 0 ); } if( select_object && selection.width > 0 && selection.height > 0 ) { cvSetImageROI( image, selection ); cvXorS( image, cvScalarAll(255), image, 0 ); cvResetImageROI( image ); } cvShowImage( "CamShiftDemo", image ); cvShowImage( "Histogram", histimg ); } } return 0; }
PyPong.py
#! /usr/bin/env python # PyPong - an arcade Pong game programmed in Python # Created by pymike, released to the Public Domain import sys, os import random import pygame from pygame.locals import * HUMAN = 1 COM = 2 class Player(pygame.sprite.Sprite): def __init__(self): pygame.sprite.Sprite.__init__(self, self.containers) self.image = pygame.Surface((16, 64)) self.image.fill((255, 255, 255), (2, 0, 12, 64)) self.image.fill((255, 255, 255), (0, 2, 16, 60)) self.rect = self.image.get_rect(midleft = (16, 240)) self._rect = Rect(self.rect) def update(self): self._rect = Rect(self.rect) key = pygame.key.get_pressed() if key[K_UP]: self.rect.move_ip(0, -5) if key[K_DOWN]: self.rect.move_ip(0, 5) if self.rect.bottom > 480: self.rect.bottom = 480 if self.rect.top < 0: self.rect.top = 0 class Computer(pygame.sprite.Sprite): def __init__(self, ball): pygame.sprite.Sprite.__init__(self, self.containers) self.image = pygame.Surface((16, 64)) self.image.fill((255, 255, 255), (2, 0, 12, 64)) self.image.fill((255, 255, 255), (0, 2, 16, 60)) self.rect = self.image.get_rect(midleft = (640-32, 240)) self._rect = Rect(self.rect) self.ball = ball self.speed = 4 self.max_speed = self.speed def update(self): self._rect = Rect(self.rect) if abs(self.ball.vy) < self.max_speed: self.speed = abs(self.ball.vy) else: self.speed = self.max_speed if self.ball.rect.centery > self.rect.centery: self.rect.move_ip(0, self.speed) if self.ball.rect.centery < self.rect.centery: self.rect.move_ip(0, -self.speed) if self.rect.bottom > 480: self.rect.bottom = 480 if self.rect.top < 0: self.rect.top = 0 def set_ball(self, ball): self.ball = ball class Ball(pygame.sprite.Sprite): def __init__(self): pygame.sprite.Sprite.__init__(self, self.containers) self.image = pygame.Surface((12, 12)) self.image.fill((255, 255, 255), (2, 0, 8, 12)) self.image.fill((255, 255, 255), (0, 2, 12, 8)) self.rect = self.image.get_rect(midleft = (320, 240)) self._rect = Rect(self.rect) self.vx = random.choice([5, -5]) self.vy = random.choice([-2, -1, 1, 2]) self.scored = 0 def update(self): self._rect = Rect(self.rect) self.rect.move_ip(self.vx, self.vy) if self.rect.bottom > 480: self.rect.bottom = 480 self.vy = -self.vy if self.rect.top < 0: self.rect.top = 0 self.vy = -self.vy if self.rect.left < 0: self.kill() self.scored = COM if self.rect.right > 640: self.kill() self.scored = HUMAN def collide(self, bat): if self.rect.colliderect(bat.rect) and not self._rect.colliderect(bat._rect): if self._rect.right <= bat._rect.left and self.rect.right > bat.rect.left: self.rect.right = bat.rect.left self.vx = -self.vx if self._rect.left >= bat._rect.right and self.rect.left < bat.rect.right: self.rect.left = bat.rect.right self.vx = -self.vx self.vx += 1 if self.rect.centery < bat.rect.centery: self.vy = ((self.rect.centery-bat.rect.centery)/(bat.rect.height/2))*4.5 if self.vy >= -1: self.vy = -1 if self.rect.centery > bat.rect.centery: self.vy = ((self.rect.centery-bat.rect.centery)/(bat.rect.height/2))*4.5 if self.vy <= 1: self.vy = 1 if self._rect.bottom <= bat._rect.top and self.rect.bottom > bat.rect.top: self.rect.bottom = bat.rect.top self.vy = -self.vy if self._rect.top >= bat._rect.bottom and self.rect.top < bat.rect.bottom: self.rect.top = bat.rect.bottom self.vy = -self.vy class Menu: def __init__(self, screen): self.screen = screen self.font = pygame.font.SysFont("Comic Sans MS", 100) self.font2 = pygame.font.SysFont("Comic Sans MS", 50) self.font3 = pygame.font.SysFont("Comic Sans MS", 25) self.all = pygame.sprite.RenderUpdates() Computer.containers = self.all Ball.containers = self.all self.ball = Ball() self.com1 = Computer(self.ball) self.com2 = Computer(self.ball) self.com1.rect.left = 16 self.clock = pygame.time.Clock() self.timer = 0 def loop(self): while 1: self.clock.tick(60) self.all.update() self.timer += 1 if self.timer >= 50: self.timer = 0 for e in pygame.event.get(): if e.type == QUIT: pygame.quit() sys.exit() if e.type == KEYDOWN: if e.key == K_ESCAPE: pygame.quit() sys.exit() if e.key == K_SPACE: game = Game(self.screen) game.loop() self.screen.fill((0, 0, 0)) self.ball.collide(self.com1) self.ball.collide(self.com2) self.all.draw(self.screen) ren = self.font.render("PyPong", 1, (255, 255, 255)) self.screen.blit(ren, (320-ren.get_width()/2, 20)) ren = self.font3.render("March 2008", 1, (255, 255, 255)) self.screen.blit(ren, (320-ren.get_width()/2, 220)) ren = self.font3.render("by PyMike", 1, (255, 255, 255)) self.screen.blit(ren, (320-ren.get_width()/2, 250)) ren = self.font2.render("Press Space", 1, (255, 255, 255)) if self.timer <= 25: self.screen.blit(ren, (320-ren.get_width()/2, 350)) pygame.display.flip() class Game: def __init__(self, screen): self.screen = screen self.all = pygame.sprite.RenderUpdates() Player.containers = self.all Computer.containers = self.all Ball.containers = self.all self.ball = Ball() self.player = Player() self.com = Computer(self.ball) self.clock = pygame.time.Clock() self.p1score = 0 self.p2score = 0 self.win_score = 21 self.font = pygame.font.SysFont("Comic Sans MS", 50) self.font2 = pygame.font.SysFont("Comic Sans MS", 30) self.won = False self.served = False self.done = False def handle_input(self): for e in pygame.event.get(): if e.type == QUIT: self.pygame.quit() sys.exit() if e.type == KEYDOWN: if e.key == K_ESCAPE: self.done = True if e.key == K_SPACE: self.served = True self.ball = Ball() self.com.set_ball(self.ball) def render(self): self.screen.fill((0, 0, 0)) self.all.draw(self.screen) ren = self.font.render("%s:%s" % (self.p1score, self.p2score), 1, (255, 255, 255)) self.screen.blit(ren, (320-ren.get_width()/2, 10)) if self.won == HUMAN: ren = self.font.render("Player 1 won!", 1, (255, 255, 255)) self.screen.blit(ren, (320-ren.get_width()/2, 240-ren.get_height()/2)) if self.won == COM: ren = self.font.render("Com won!", 1, (255, 255, 255)) self.screen.blit(ren, (320-ren.get_width()/2, 240-ren.get_height()/2)) if not self.served and not self.won: ren = self.font2.render("Press Space to Serve", 1, (255, 255, 255)) self.screen.blit(ren, (320-ren.get_width()/2, 240-ren.get_height()/2)) pygame.display.flip() def update(self): self.clock.tick(60) if not self.won and self.served: self.all.update() self.ball.collide(self.player) self.ball.collide(self.com) if self.p1score >= self.win_score: self.won = HUMAN if self.p2score >= self.win_score: self.won = COM if not self.ball.alive() and not self.won: if self.ball.scored == HUMAN: self.p1score += 1 if self.ball.scored == COM: self.p2score += 1 self.ball = Ball() self.com.set_ball(self.ball) self.served = False if self.won or not self.served: self.ball.kill() def loop(self): self.done = False while not self.done: self.update() self.handle_input() self.render() def main(): pygame.init() os.environ["SDL_VIDEO_CENTERED"] = "1" pygame.display.set_caption("PyPong") screen = pygame.display.set_mode((640, 480)) menu = Menu(screen) menu.loop() if __name__ == "__main__": main()
System Setup
Currently the hand-tracking and Pong games run in different processes. Hand-tracking requires a picture called "sshou.bmp" in the /home/user-name directory that is used as a reference for the histogram that tracks hands. The hand-tracking writes to a temporary pipe in /usr/yval, which the Pong game then reads the latest value from. To run, start the hand-tracking program in a root shell, then the Pong game in a separate root shell. Press [Spacebar] to begin a game, then [Spacebar] to release a ball. Moving a hand up and down in front of the camera causes the user's paddle to move and reflect that. The user's paddle will not move if there is not a ball "in play".
Task List
Below is an estimated list of tasks to complete Task list will be updated as necessary
Task | Status | Description |
---|---|---|
Pong on the Beagle Board | Complete | Port a simple game of classic pong onto the Beagle Board with keyboard/mouse input |
Webcam interface with the Beagle Board and OpenCV | Complete | Get the Beagle Board to recognize the webcam and perform basic functionalities from OpenCV (connect to camera, snapshot, etc.) |
Refactor Pong | Complete | Refactor Pong to work with a different input (camera) |
Track hands with camera and OpenCV | Complete | Track user's hands using OpenCV library functions on the Beagle Board |
Interface tracking camera with pong | Complete | Join the tracking and game into one system such that users can play pong with their hands |
Files
File:PyPong.tgz File:Cvex1.zip