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 |