Motion sensing game (Ping Pong Game)
Here is the source code of the project based on OpenCV anc C++.
Before you run this code on Linux, you should install the OpenCV library first.
#include<opencv/highgui.h>
#include<opencv2/opencv.hpp>
#include<string>
#include<sstream>
#include<stdlib.h>
#include<time.h>
#include<stdio.h>
using namespace std;
using namespace cv;
// The length and width of window frame
const int FRAME_LENGTH = 640, FRAME_WIDTH = 480;
// The stating point of BALL
const int BALL_START_X = 320, BALL_START_Y = 26;
// the constant value of PI
const double PI = acos(-1);
// The speed of the ball
double SPEED = 35;
// The redius of the ball
int RADIUS = 20;
// the diagonal points of the board
int recX1 = 225, recY1 = 460;
int recX2 = 415, recY2 = 480;
const int boardLength = recX2 - recX1;
const int boardWidth = recY2 - recY1;
// The initial min and max of HSV filter values
int H_MIN = 0;
int H_MAX = 256;
int S_MIN = 0;
int S_MAX = 256;
int V_MIN = 0;
int V_MAX = 256;
// The maximum number of object to be detected
const int MAX_NUM_OBJECT = 50;
// Minimum and maximum area of object
const int MIN_OBJECT_AREA = 20 * 20;
const int MAX_OBJECT_AREA = FRAME_LENGTH * FRAME_WIDTH / 1.5;
// Name of different windows
const string originalImage = "Original Image";
const string hsvImage = "HSV Image";
const string thresholdImage = "Threshold image";
const string trackbarName = "Trackbars";
// The flag to control whether tracking object
bool trackObject = true;
// Scoreboard
int scoreBoard = 0;
// GameOver
bool gameOver = false;
// Select the game mode
int choice = 0;
// acceleration
int acc = 0;
// This function will be called when a trackbar position is changed
void onTrackbar(int, void *){
}
// Convert the integer to string
string intToString(int);
// Create trackbars
void createTrackbars();
// Draw crosshair to locate the position of the target
void drawObject(int ,int , Mat&);
// Reduce noise
void morphOps(Mat&);
// Locate the position of the object
void trackFilteredObject(int& ,int&, Mat, Mat&);
// Depict the board
void depictBoard(Mat, Point, Point);
// Depict Generator to generate the ball
void depictGenerator(Mat);
// Limit the board in case of the board out of border
void limitBoard(int&, int&, int);
// Class of BALL
class NewBall{
private:
// The driction of the ball at the beginning
double angle;
// the moving vector of the ball for each frame
int Vx, Vy;
// the center point of the ball
Point point;
public:
NewBall();
void reset();
// Movement the ball like baseball
void baseballMoving(Point, Point);
// Movement the ball like ping pong ball
void pingpongMoving(Point, Point);
// Depict the ball
void depictBall(Mat);
void accelerate();
};
NewBall::NewBall(){
// the center point of the ball
point = Point(BALL_START_X, BALL_START_Y);
// Initialize the random seed
srand(time(NULL));
// The driction of the ball at the beginning
// Filter the angle of the driection let it between PI/4 and PI/4*3
angle = (rand() % 51 + 25) / 100.0 * PI;
// the moving vector of the ball for each frame
Vx = (int)(cos(angle) * SPEED);
Vy = (int)(sin(angle) * SPEED);
}
void NewBall::accelerate(){
Vx = (int)(cos(angle) * SPEED);
Vy = (int)(sin(angle) * SPEED);
}
void NewBall::reset(){
// the center point of the ball
point = Point(BALL_START_X, BALL_START_Y);
// Initialize the random seed
// The driction of the ball at the beginning
angle = (rand() % 51 + 25) / 100.0 * PI;
// the moving vector of the ball for each frame
Vx = (int)(cos(angle) * SPEED);
Vy = (int)(sin(angle) * SPEED);
}
// Depict the ball on the frame
void NewBall::depictBall(Mat img){
int lineType = 8;
circle(img, point, RADIUS, Scalar(0,0,255), CV_FILLED, lineType);
}
// Movement of the ball
void NewBall::baseballMoving(Point a, Point b){
if(point.x - RADIUS < 0 || point.x + RADIUS > FRAME_LENGTH){
Vx = -Vx;
}
if(point.y + RADIUS > FRAME_WIDTH + 5){
// the ball will dispear and then appear in the original position
gameOver = true;
}
if((point.y + RADIUS > FRAME_WIDTH - boardWidth - 10) && (point.x >= a.x - 12 && point.x <= b.x + 12)){
cout << a.x - 12<< " " << b.x + 12<< endl;
scoreBoard++;
if(scoreBoard == 5 && acc == 0){
SPEED += 5;
}else if (scoreBoard == 10 && acc == 1){
SPEED += 5;
}else if (scoreBoard == 15 && acc == 2){
SPEED += 5;
}else if (scoreBoard == 20 && acc == 3){
SPEED += 5;
}
Vy = -Vy;
}
if(point.y - RADIUS < 0){
this->reset();
}
point.x = point.x + Vx;
point.y = point.y + Vy;
}
// Movement of the ball
void NewBall::pingpongMoving(Point a, Point b){
if(point.x - RADIUS < 0 || point.x + RADIUS > FRAME_LENGTH){
Vx = -Vx;
}
if(point.y + RADIUS > FRAME_WIDTH + 5){
// the ball will dispear and then appear in the original position
gameOver = true;
}
if((point.y + RADIUS > FRAME_WIDTH - boardWidth - 10) && (point.x >= a.x - 12 && point.x <= b.x + 12)){
cout << a.x - 12<< " " << b.x + 12<< endl;
cout << Vx << " " << Vy << endl;
scoreBoard++;
if(scoreBoard == 5 && acc == 0){
SPEED += 5;
this->accelerate();
}else if (scoreBoard == 10 && acc == 1){
SPEED += 5;
this->accelerate();
}else if (scoreBoard == 15 && acc == 2){
SPEED += 5;
this->accelerate();
acc++;
}else if (scoreBoard == 20 & acc == 3){
SPEED += 5;
this->accelerate();
acc++;
}
Vy = -Vy;
}
if(point.y - RADIUS < 0){
Vy = -Vy;
}
point.x = point.x + Vx;
point.y = point.y + Vy;
}
int main(int argc, char*argv[]){
// two boolean variables to control the two functions, one is to track object and the other
//reduce noise
bool useMorphOps = true;
//Matrix to store each frame from the webcam feed
Mat cameraFeed;
//Martrix storage for HSV image
Mat HSV;
//Matrix sotrage for binary threshold image
Mat threshold;
//x and y values for the current location of the object
int x = 0, y = 0;
//The position of the object on the last frame
int lastX = 0, lastY = 0;
//Create slider bars for HSV filtering
createTrackbars();
//video capture object to acquire webcam feed
VideoCapture cap(0);
char name[] = "camera";
if(!cap.isOpened()){
cout << "Camera can not opened." << endl;
return -1;
}
//Window of original image
namedWindow(originalImage, WINDOW_AUTOSIZE);
//Window of HSV image
namedWindow(hsvImage, WINDOW_AUTOSIZE);
//Window of threshold image
namedWindow(thresholdImage, WINDOW_AUTOSIZE);
// move windows to its proper location
moveWindow(originalImage, 20, 20);
moveWindow(hsvImage, 20, 600);
moveWindow(thresholdImage, 700, 20);
// flag to control the movement of the ball
bool ballMoveFlag = 0;
NewBall ball;
while(1){
bool bSuccess = cap.read(cameraFeed);
if(!bSuccess){
cout << "Can not read a frame from video stream" << endl;
break;
}
//Convert the original image to HSV
cvtColor(cameraFeed, HSV, COLOR_BGR2HSV);
//Select a range of H,S,V properties and then it can filter the
//specific color
inRange(HSV, Scalar(H_MIN, S_MIN, V_MIN), Scalar(H_MAX,S_MAX,V_MAX), threshold);
//Reduce noise
if(useMorphOps){
morphOps(threshold);
}
// press the button to go on the game
if(waitKey(30) == 27){ // press the ESC button
cout << "You exit the game" << endl;
break;
}
if(waitKey(30) == 32){ // press the space button
ballMoveFlag = 1;
ball.reset();
gameOver = false;
acc = 0;
SPEED = 35;
}
if(waitKey(30) == 49){
choice = 1;
}
if(waitKey(30) == 50){
choice = 2;
}
//On the last frame keep the location of the frame
lastX = x;
lastY = y;
//Parameters x and y record the current position of the objcet
if(trackObject){
trackFilteredObject(x, y, threshold, cameraFeed);
int diffX = x - lastX, diffY = y - lastY;
//Control the movement of the rectangle and do not let it out of the boarder
limitBoard(recX1, recX2, diffX);
}
// Depict the image of the board and the ball
depictGenerator(cameraFeed);
depictBoard(cameraFeed, Point(recX1, recY1), Point(recX2, recY2));
ball.depictBall(cameraFeed);
putText(cameraFeed,"Your Score " + intToString(scoreBoard), Point(350,50),1,2,Scalar(0,255,255),2);
if(ballMoveFlag == false){
putText(cameraFeed, "Press space bar to start", Point(30,400),2,1,Scalar(0,0,255),4);
}
if(choice != 1 && choice != 2){
putText(cameraFeed, "Press 1 to select baseball mode", Point(30,200),2,1,Scalar(0,0,255),4);
putText(cameraFeed, "Press 2 to select pingpong mode", Point(30,300),2,1,Scalar(0,0,255),4);
}
if(ballMoveFlag == 1){
// Generate the ball
//Move the center fo the circle to the next position
if(choice == 1){
ball.baseballMoving(Point(recX1, recY1), Point(recX2, recY2));
}else if(choice == 2){
ball.pingpongMoving(Point(recX1, recY1), Point(recX2, recY2));
}
if(gameOver == true){
choice = 0;
ballMoveFlag = 0;
scoreBoard = 0;
}
}
//If the ball is out of boarder, GameOver
if(gameOver == true && choice == 0 && ballMoveFlag == 0){
putText(cameraFeed, "GAME OVER", Point(30,100),2,1,Scalar(0,0,255),4);
}
imshow(originalImage, cameraFeed);
imshow(hsvImage, HSV);
imshow(thresholdImage, threshold);
}
return 0;
}
// Limit the board in case of the board out of border
void limitBoard(int &recX1, int &recX2, int diffX){
if(recX1 + diffX < 0){
recX1 = 0;
recX2 = boardLength;
}else if(recX2 + diffX > FRAME_LENGTH){
recX2 = FRAME_LENGTH;
recX1 = FRAME_LENGTH - boardLength;
}else{
recX1 += diffX;
recX2 += diffX;
}
}
// Depict the generator
void depictGenerator(Mat img){
int thickness = CV_FILLED;
int lineType = 8;
Point a(300,0);
Point b(340,5);
rectangle(img, a, b, Scalar(0,255,0), thickness, lineType);
}
void depictBoard(Mat img, Point a, Point b){
int thickness = CV_FILLED;
int lineType = 8;
rectangle(img, a, b, Scalar(255,0,0), thickness, lineType);
}
void createTrackbars(){
namedWindow(trackbarName, 0);
moveWindow(trackbarName, 800, 500);
createTrackbar("H_MIN", trackbarName, &H_MIN, H_MAX, onTrackbar);
createTrackbar("H_MAX", trackbarName, &H_MAX, H_MAX, onTrackbar);
createTrackbar("S_MIN", trackbarName, &S_MIN, S_MAX, onTrackbar);
createTrackbar("S_MAX", trackbarName, &S_MAX, S_MAX, onTrackbar);
createTrackbar("V_MIN", trackbarName, &V_MIN, V_MAX, onTrackbar);
createTrackbar("V_MAX", trackbarName, &V_MAX, V_MAX, onTrackbar);
}
// Draw crosshair to locate the position of the target
void drawObject(int x,int y, Mat &frame){
circle(frame,Point(x, y), 20, Scalar(255,0,0), 2);
if(y - 25 > 0){
line(frame, Point(x, y), Point(x, y-25), Scalar(255,0,0), 2);
}else{
line(frame, Point(x, y), Point(x, 0), Scalar(255,0,0), 2);
}
if(y + 25 < FRAME_WIDTH){
line(frame, Point(x,y), Point(x,y+25),Scalar(255,0,0), 2);
}else{
line(frame, Point(x,y), Point(x,FRAME_WIDTH),Scalar(255,0,0), 2);
}
if(x - 25 > 0){
line(frame, Point(x,y), Point(x-25, y), Scalar(255,0,0), 2);
}else{
line(frame, Point(x,y), Point(0, y), Scalar(255,0,0), 2);
}
if(x + 25 < FRAME_LENGTH){
line(frame, Point(x,y), Point(x+25,y), Scalar(255,0,0),2);
}else{
line(frame,Point(x,y), Point(FRAME_LENGTH,y), Scalar(255,0,0),2);
}
putText(frame, intToString(x)+","+intToString(y), Point(x,y+30),1,1,Scalar(255,0,0),2);
}
// Locate the position of the object
void trackFilteredObject(int& x,int& y, Mat threshold, Mat& cameraFeed){
Mat temp;
threshold.copyTo(temp);
// these two vectors needed for output of findContours
vector< vector<Point> > contours;
vector< Vec4i > hierarchy;
// find contours of filtered image using openCV findContours function
findContours(temp, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);
// use moments method to find our filtered object
double refArea = 0;
bool objectFound = false;
if(hierarchy.size() > 0){
int numObjects = hierarchy.size();
// if number of objects greater than MAX_NUM_OBJECTS we have noisy filter
if(numObjects < MAX_NUM_OBJECT){
for(int index = 0; index >= 0; index = hierarchy[index][0]){
Moments moment = moments((cv::Mat)contours[index]);
double area = moment.m00;
if(area > MIN_OBJECT_AREA && area < MAX_OBJECT_AREA && area > refArea){
x = moment.m10 / area;
y = moment.m01 / area;
objectFound = true;
refArea = area;
}else{
objectFound = false;
}
if(objectFound == true){
putText(cameraFeed, "Tracking Object", Point(0,50),2,1,Scalar(0,255,0),2);
drawObject(x,y,cameraFeed);
}
}
}
else{
putText(cameraFeed,"TOO MUCH NOISE! ADJUST FILTER", Point(0,50),1,2,Scalar(0,0,255),2);
}
}
}
// Convert the integer to string
string intToString(int number){
std::stringstream ss;
ss << number;
return ss.str();
}
void morphOps(Mat &thresh){
Mat erodeElement = getStructuringElement( MORPH_RECT, Size(3,3));
Mat dilateElement = getStructuringElement( MORPH_RECT, Size(8,8));
erode(thresh, thresh, erodeElement);
erode(thresh, thresh, erodeElement);
dilate(thresh, thresh, dilateElement);
dilate(thresh, thresh, dilateElement);
}
浙公网安备 33010602011771号