【OpenCV C++ 案例实战一】实现双人篮球游戏

【OpenCV C++ 案例实战一】实现双人篮球游戏

  • 概述
  • 一、 预处理
    • 1.1定义结构体
    • 1.2背景处理
    • 1.3篮球处理
  • 二、项目实现
    • 2.1篮球放置(角色放置同理)
    • 2.2篮球移动(角色移动同理,但是需要通过键盘监听)
    • 2.3角色与篮球的碰撞检测
    • 2.4角色移动
  • 三、项目总结
    • 3.1 复习内容
    • 3.2项目亮点
    • 3.3项目不足
  • 四、源码下载


概述

本小游戏类似于4399中的火柴人打羽毛球游戏,在背景图上,左右双方通过键盘来进行接球操作。
实现方法比较简单,其中的知识点包括霍夫圆检测,键盘事件响应,背景填充等
做这个小游戏的目的主要是为了用于OpenCV学习的阶段性展示。

演示视频如下所示:

basketBallGame

一、 预处理

1.1定义结构体

为了增强代码的可读性以及后续对程序的修改,我们对项目中的一些重要对象定义结构体。
包括:游戏角色、背景图、篮球。

// 定义篮球的结构体(把篮球看作圆形。x,y为篮球位置;d为篮球直径)
typedef struct ballLoc {
    double x;
    double y;
    double d;
};
// 人物结构体(人物的高度,宽度,位置)
typedef struct kun {
    double heigth;
    double width;
    double x;
    double y;
};
// 背景图结构体(背景图的宽度,高度)
typedef struct backgroundI {
    int bg_width;
    int bg_height;
};

1.2背景处理

背景图使用 resize() 函数调整到合适的大小
【OpenCV C++ 案例实战一】实现双人篮球游戏_第1张图片

1.3篮球处理

篮球如下图所示,为了让篮球能够完美地贴合到背景图中,我们这里的先使用霍夫圆检测,把篮球单独从图像之中抠出来备用。
【OpenCV C++ 案例实战一】实现双人篮球游戏_第2张图片篮球处理分为以下步骤:
1. 预处理(灰度化、高斯模糊)
2. 霍夫圆检测
3. 在检测出来的圆中根据特征提取出我们需要的圆
4. 将提取出来的圆单独定义为一张图像,调整大小并返回给主函数

实现代码如下:

// 篮球处理
Mat ballDetect(Mat image) {
    // 1. 预处理(灰度化,滤波)
    Mat gray_image,gauss_image;
    cvtColor(image, gray_image, COLOR_BGR2GRAY);
    GaussianBlur(gray_image, gauss_image, Size(3, 3), 0, 0);

    // 2. 霍夫圆检测
    // 圆的表示:前两个参数为圆心坐标,第三个参数为半径
    vector<Vec3f> ball_circles;
    HoughCircles(gauss_image, ball_circles, HOUGH_GRADIENT, 1, 50, 80, 80, 50, 200);

    // 3. 找到需要的篮球的圆(最大的圆)
    Vec3f ball_circle = ball_circles[0];
    for (int i = 0; i < ball_circles.size(); i++)
    {
        if (ball_circles[i][2] > ball_circle[2]) {
            ball_circle = ball_circles[i];
        }
    }

    // 4. 这里的(x,y)为通过圆参数计算出来的正方形的左上角坐标,d为正方形的边长
    double x = ball_circle[0] - ball_circle[2];
    double y = ball_circle[1] - ball_circle[2];
    double d = ball_circle[2]*2;
    Rect rect(x, y,d ,d);
    
    // 单独把篮球提取出成一张图像
    Mat det_ball;
    image(rect).copyTo(det_ball);

    resize(det_ball, det_ball, Size(ball_01.d, ball_01.d));
    
    return det_ball;
}

二、项目实现

2.1篮球放置(角色放置同理)

如果使用常规的ball(ROI).copyTo(background(ROI)); 需要把感兴趣区域设置为圆形,但是由于没有看懂这块是怎么用的(我不会!)。所以使用数学的方法暴力进行背景填充。

如下图所示,给出一个圆形和他的外接正方形(正方形边长与圆直径相同),将中心视作图像的原点,那么我们就可以通过判断 x,y的坐标与圆半径r的关系得出这个点是否落在圆内。

但是由于图形的默认坐标是从左上角算起。即 (0,0)点 为图像的左上点。所以我们在遍历图像像素点的过程中应当使用额外的参数来进行判断。
【OpenCV C++ 案例实战一】实现双人篮球游戏_第3张图片实现代码如下:
圆形区域(篮球)像素点不变;圆形区域外的像素用背景图的对应像素进行替换

// 画球(主要用来分离背景)
// 有坐标位置,有背景图
void drawBall(Mat & ball) {                    
    int r = ball_01.d / 2;
    for (int i = -r, ix = 0; i < ball_01.d - r; i++, ix++) {
        for (int j = -r, iy = 0; j < ball_01.d - r; j++, iy++) {
            if (i * i + j * j > r * r) {
                for (int c = 0; c < 3; c++) {
                    ball.at<Vec3b>(iy, ix)[c] = background.at<Vec3b>(ball_01.y + iy,  ball_01.x + ix)[c];
                }
            }
        }
    }
}

2.2篮球移动(角色移动同理,但是需要通过键盘监听)

在主函数中通过while(1){} ,不断循环绘制篮球(通过篮球坐标),由于我们定义了篮球位置的结构体,所以如果想要改变篮球的位置,修改其坐标即可。

		// x向着左边越界
        if (ball_01.x <= 5) {
            ball_01.x += 1;
            b_x = -b_x;
        }
        // x右边界越界
        else if (ball_01.x >= (bg_width - ball_01.d - 4)) {
            ball_01.x -= 1;
            b_x = -b_x;

        }
        // 上边界
        else if (ball_01.y <= 5) {
            ball_01.y += 1;
            b_y = -b_y;
        }
        // 下边界
        else if (ball_01.y >= (bg_height - ball_01.d - 4)) {
            ball_01.y -= 1;
            b_y = -b_y;
            break;
        }

篮球撞墙后的变向思路如下图所示:
【OpenCV C++ 案例实战一】实现双人篮球游戏_第4张图片

2.3角色与篮球的碰撞检测

角色与篮球碰撞后,篮球的运动方向改变,且角色状态改变。

这里只是通过篮球和角色的位置以及他们各自的高度宽度进行检测,因此如果当篮球的大小设置的过于大时,会出现bug,感觉后续可以通过opencv视觉的方法进行替换。代码如下:

      // 检测哥哥与篮球碰撞
        if (((ball_01.y + ball_01.d) >= kun01.y &&
            (ball_01.x + ball_01.d) >= kun01.x &&
            (ball_01.x + ball_01.d) <= (kun01.x + kun01.width))
            ||
            ((ball_01.y + ball_01.d) >= kun_right.y &&
            (ball_01.x + ball_01.d) >= kun_right.x &&
            (ball_01.x + ball_01.d) <= (kun_right.x + kun_right.width)
            ))
        {
            drawKun(background, kunImage2, 0);
            drawKun(background, kunImage2, 1);
            b_y = -b_y;
        }

2.4角色移动

角色移动通过监听键盘响应实现。
使用 int key = waitKeyEx(1); 获取到按键,再通过按键内容进行响应。

		int key = waitKeyEx(1);
        if (key == 2424832)
        {
            //键盘←键
            if (kun01.x > 10) {
                kun01.x -= 10;
                drawKun(background, kunImage2, 0);
            }
        }
        if (key == 2555904)
        {
            //键盘→键
            if (kun01.x < ((bg01.bg_width - 10 - kun01.width)/2)) {
                kun01.x += 10;
                drawKun(background, kunImage2, 0);
            }
        }
        if (key == 97)
        {
            //键盘a键
            if (kun_right.x > ((bg01.bg_width - 10 - kun_right.width)/2)) {
                kun_right.x -= 10;
                drawKun(background, kunImage2, 1);
            }
        }
        if (key == 100)
        {
            //键盘d键
            if (kun_right.x < (bg01.bg_width - 10 - kun_right.width)) {
                kun_right.x += 10;
                drawKun(background, kunImage2, 1);
            }
        }

三、项目总结

3.1 复习内容

  1. 复习了霍夫圆检测
  2. 复习了opencv中圆的表示方法:Vec3f(前两个参数表示圆心位置,第三个参数表示圆半径)
    vector<Vec3f> ball_circles;
    HoughCircles(gauss_image, ball_circles, HOUGH_GRADIENT, 1, 50, 80, 80, 50, 200);
    
  3. 复习了鼠标监听方法
    int key = waitKeyEx(1);
    	key = 100:d
    	key = 97:a
    	key = 2555904:键盘→键
    	key = 2424832:键盘←键
    

3.2项目亮点

可以尝试用数学的角度去解决一些图像中的问题。
例如正方形中最大圆的检测以及碰撞中球的运动轨迹变化

3.3项目不足

  1. 学一下自定义(圆)掩膜层可以更方便高效的解决篮球在背景图的放置问题。
  2. 利用opencv视觉方法可以解决因为通过坐标判断人物与篮球碰撞带来的bug。

四、源码下载

https://download.csdn.net/download/weixin_46221106/87212956

你可能感兴趣的:(OpenCV,C++,项目实战,opencv,c++,游戏)