コンピュータビジョン - 金貨を食べるための凸包検出制御ゲーム

金貨を食べる基本構造

(ゲーム構築ソースCSDN) 

cfg 構成ファイルには、ゲームに必要なフォント、画像、オーディオのパスが保存されます。

 

下の写真はリソースの画像ファイル内で走っている人の写真です

ゲームの主なコンポーネント:

①金貨を食べる悪役をヒーロークラスと定義

主な設定: 1) 主人公の画面の初期位置と初期方向を指定します。

                  2) 位置の移動とアクションの変更を通じて悪役の動きを記述する移動関数を定義します。

    def move(self, screensize, direction):
        assert direction in ['left', 'right']
        if direction != self.diretion:
            self.images = self.images_left.copy() if direction == 'left' else self.images_right.copy()
            self.image = self.images[0]
            self.diretion = direction
            self.switch_frame_count = 0
        self.switch_frame_count += 1
        if self.switch_frame_count % self.switch_frame_freq == 0:
            self.switch_frame_count = 0
            self.frame_index = (self.frame_index + 1) % len(self.images)
            self.image = self.images[self.frame_index]
        if direction == 'left':
            self.rect.left = max(self.rect.left-self.speed, 0)
        else:
            self.rect.left = min(self.rect.left+self.speed, screensize[0])
  1. 3) 悪役の変化を背景に描き込む

②落ちてくる金貨は食料クラスとして定義される

主な構成: 1) 食べ物の初期位置を指定し、合計 2 種類の金貨に分かれており、異なる金貨は異なるスコアを持ちます。

                  2) 一定の速度で落下させる更新関数を作成し、底まで到達しない場合は継続的に金貨の位置を更新します。

③ゲーム画面構成

主にゲーム インターフェイスを構築し、pygame.time を使用してインターフェイスをメイン ループ内に保持します。

④main.pyゲームはどのように動作するのでしょうか?

主な実現: 1) インターフェース、音楽、フォントの初期化

2) ヒーロー エンティティと食品エンティティの複数のグループを取得します

3) ランダムに食べ物を生成し、画面内の食べ物の位置を継続的に更新します(垂直移動)

4) ボタンの検出、<--- が検出された場合、悪役は左に 1 歩移動して位置を更新します。

     

   for event in pygame.event.get():

            if event.type == pygame.QUIT:

                pygame.quit()

                sys.exit()

        key_pressed = pygame.key.get_pressed()

        if key_pressed[pygame.K_a] or key_pressed[pygame.K_LEFT]:

            hero.move((600,400), 'left')

        if key_pressed[pygame.K_d] or key_pressed[pygame.K_RIGHT]:

            hero.move((600,400), 'right')
  1. 5) ヒーロー悪役が食べ物と衝突したことが検出された場合、得点が発生します。

     

   for food in food_sprites_group:

            if pygame.sprite.collide_mask(food, hero):

                game_sounds['get'].play()

                food_sprites_group.remove(food)

                score += food.score

                if score > highest_score: highest_score = score
  1. 6) スコア情報をもとに画面を更新

ドラム検出:

2.2.1 ゲーム 1 (金貨を食べる) アルゴリズムの概要 (opencv に基づく凸包検出)

指の凸包検出に基づいて、最初に画像をキャプチャする必要があり、キャプチャした画像に対して次の操作を実行する必要があります。

①バイラテラルフィルタリングを行う

ret、フレーム = Camera.read()

Frame = cv2.biliteralFilter(frame, 5, 50, 100) # 双方向フィルタリング

Frame = cv2.flip(frame,1) # 反転 0: X 軸に沿って反転 (垂直反転) 0 より大きい: Y 軸に沿って反転#Flip (水平反転) 0 未満: 最初に X 軸に沿って反転し、次に X 軸に沿って反転します。 Y 軸に沿って反転など。値は 180 度回転します。    

cv2.imshow('orig',frame)

バイラテラル フィルタリングは、平均または通常の重み付きフィルタリング (ガウス フィルタリングなど) に基づいており、距離重みと色重みの 2 つの重みを使用して画像上で重み付き平滑化を実行します。これにより、ノイズを除去し、エッジを保護できます。

バイラテラル フィルタリングのこの特徴は主に、フィルタを平滑化するときにピクセル間の幾何学的距離と色の距離の両方を考慮しているためです。画像が緩やかに変化する領域では、周囲のピクセルの輝度値に大きな差はなく、バイラテラル フィルターがガウス ローパス フィルターに変換され、元の輝度値が得られます。したがって、バイラテラル フィルターは、フィルター処理された画像を平滑化するだけでなく、画像のエッジも維持します

これがガウシアンフィルタリングなど他の手法との最大の違いであり、エッジ保存とノイズ除去の目的を達成するために、空間情報とグレー類似性を考慮しながら、画像の空間的近接性と画素値の類似性を組み合わせた折衷処理です。 。シンプルで反復的ではなく、ローカルです。

今回検出したいのは肌なので、まず肌色の閾値を設定し、同時に画像に対して閾値処理を行います。

lower = np.array([0, 48, 80], dtype = "uint8")

upper = np.array([20, 255, 255], dtype = "uint8")

#肌の色のしきい値を設定します。光や肌の色に応じて調整できます。

SkinRegionHSV = cv.inRange(hsvim, 下位, 上位)

ぼかし = cv.blur(skinRegionHSV, (2,2))

ret,thresh = cv.threshold(blurred,0,255,cv.THRESH_BINARY)

cv.imshow("スレッシュ", スレッシュ)

②画像グレースケール、平均値フィルター(ノイズ除去、画像処理を容易にする)

③肌色検出

検出後は手だけの二値画像が得られますが、これは期待通りの効果が得られますが、実際には周囲のさまざまなノイズを除去するのは難しく、現実にはノイズが多く存在します。

 

二値化画像を取得したら、次に指の凸包を検出し、指の輪郭を描画します。

#等高線描画

輪郭、階層 = cv.findContours (thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
輪郭 = max(輪郭, key=lambda x: cv.contourArea(x))
cv.drawContours (img, [輪郭], -1, ( 255,255,0), 2)
cv.imshow("輪郭", img)

 

次のステップでは、opencv に付属している凸包検出関数convexHullを使用して指の数を検出しますここで、まず凸包の概念を簡単に紹介します。

凸包は、計算幾何学 (グラフィックス) の概念です。2 次元ユークリッド空間では、凸包はすべての点を単に包むゴムバンドとして想像できます。大まかに言えば、2 次元平面上に点セットが与えられた場合、凸包は最も外側の点を接続することによって形成される凸多角形であり、点セット内のすべての点を含むことができます。

例: 平面上に 13 個の点 p0 ~ p12 があると仮定すると、この多角形がすべての点を「包む」ことができるように、いくつかの点を通る多角形が作成されます。この多角形が凸状である場合、それを「凸包」と呼びます。以下に示すように: 

ハル = cv.convexHull(輪郭)
cv.drawContours (img, [ハル], -1, (0, 255, 255), 2)
cv.imshow("ハル", img)

オブジェクト上の凹みは凸欠陥と呼ばれ、 cv.convexityDefect() 関数は凸欠陥の発見に役立ちます。各行に値[始点、終点、最遠点、最遠点までのおおよその距離]が含まれる配列を返します。

ハル = cv.convexHull (輪郭、returnPoints=False)
欠陥 = cv.convexityDefects (輪郭、ハル)

 

主なタスクは指の数を数えることであり、凸包検出と凸欠陥検出を通じて、隣接する 3 つの点が三角形を形成できる場合、指先の位置と指の間のハンドオーバー窪みの位置を決定できるためです。角度が 90 度未満の場合、ここには 2 つあると考えられます。

それでは、ここでの問題は、三角形の 3 つの辺がある場合、対角線の角度が 90 度未満であるかどうかをどのように判断するかということです? 数学的知識を通じて、余弦の法則を使用して c の対角線の角度の数を見つけることができます。 

 

 

この2つの公式を使うとθの度数が求まり、90度未満であれば指が2本あるとみなして、そのθが4つあれば指が5本あるとみなすことができます。

指が検出された後は、主に金貨を食べるヒーロー クラス (スポーツ悪役) を通じて制御を実装し、それをグローバル変数として設定し、ゲームと指検出の 2 つのスレッドを同時に実行します。指の動きを検出し、パスする ヒーローの内蔵移動機能により、ヴィランの移動方向を制御します。ここでは方向は2つしかなく、検出された指が1か2の場合は悪役は左に移動し、それ以外の場合は悪役は右に移動します。

グローバルヒーロー

if(cnt<=2):

hero.move(cfg.SCREENSIZE, 'left')

それ以外:

hero.move(cfg.SCREENSIZE, 'right')

デモンストレーション効果:

 

 コード (メインプログラム、構成ファイルを除く):


# coding: utf-8

# In[1]:


import threading
import time
import os

import cv2
import numpy as np
import copy
import time
from modules import *


import os
import cfg
import cv2 as cv
import numpy as np
import sys
import pygame
import random
from modules import *
import cv2
cnt1=1
def main_gesture(): 

    cap_region_x_begin = 0.3 # 起点/总宽度
    cap_region_y_end = 0.8
    threshold = 60 # 二值化阈值
    blurValue = 41 # 高斯模糊参数
    bgSubThreshold = 50
    learningRate = 0
 
    
    isBgCaptured = 0 
    triggerSwitch = False 
    camera = cv2.VideoCapture(1)  
    camera.set(10, 200) 
    t1=time.time()
    i=1
    while camera.isOpened():
        ret, frame = camera.read()
        frame = cv2.bilateralFilter(frame, 5, 50, 100) # 双边滤波
        frame = cv2.flip(frame,1) # 翻转 0:沿X轴翻转(垂直翻转)  大于0:沿Y轴翻转(水平翻转)  小于0:先沿X轴翻转,再沿Y轴翻转,等价于旋转180°
        cv2.imshow('orig',frame) 
        if isBgCaptured == 1: 
            img=frame
            img = img[0:int(cap_region_y_end * frame.shape[0]),int(cap_region_x_begin * frame.shape[1]):frame.shape[1]] # 剪切右上角矩形框区域
            hsvim = cv.cvtColor(img, cv.COLOR_BGR2HSV)
            lower = np.array([0, 48, 80], dtype = "uint8")
            upper = np.array([20, 255, 255], dtype = "uint8")
            skinRegionHSV = cv.inRange(hsvim, lower, upper)
            blurred = cv.blur(skinRegionHSV, (2,2))
            ret,thresh = cv.threshold(blurred,0,255,cv.THRESH_BINARY)
            cv.imshow("thresh", thresh)
            _,contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
            
            try:
                contours = max(contours, key=lambda x: cv.contourArea(x))
            except ValueError as e:
                cv.imshow('final_result',img)
                continue
                
     
            cv.drawContours(img, [contours], -1, (255,255,0), 2)
          
            hull = cv.convexHull(contours)
            cv.drawContours(img, [hull], -1, (0, 255, 255), 2)

            hull = cv.convexHull(contours, returnPoints=False)
            defects = cv.convexityDefects(contours, hull)
            if defects is not None:
                cnt = 0
                for i in range(defects.shape[0]): # calculate the angle
                    s, e, f, d = defects[i][0]
                    start = tuple(contours[s][0])
                    end = tuple(contours[e][0])
                    far = tuple(contours[f][0])
                    a = np.sqrt((end[0] - start[0]) ** 2 + (end[1] - start[1]) ** 2)
                    b = np.sqrt((far[0] - start[0]) ** 2 + (far[1] - start[1]) ** 2)
                    c = np.sqrt((end[0] - far[0]) ** 2 + (end[1] - far[1]) ** 2)
                    angle = np.arccos((b ** 2 + c ** 2 - a ** 2) / (2 * b * c)) # cosine theorem
                    if angle <= np.pi / 2: # angle less than 90 degree, treat as fingers
                        cnt += 1
                        cv.circle(img, far, 4, [0, 0, 255], -1)
                        
                t2=time.time()
                if cnt > 0:
                    cnt = cnt
                    if(t2-t1<=2):
                        cnt=cnt1   
                    else:
                        cnt1=cnt
                        t1=t2
                    if(cnt<=2):
                        ttt="<<-"
                    else:
                        ttt="->>"
                    cv.putText(img, ttt, (200, 80), cv.FONT_HERSHEY_SIMPLEX,3, (0,0, 255) , 1, cv.LINE_AA)     
            
            cv.imshow('final_result',img)
            global hero
            if(cnt<=2):
                hero.move(cfg.SCREENSIZE, 'left')
            else:
                hero.move(cfg.SCREENSIZE, 'right')
            
                
        k = cv2.waitKey(10)
        if k == ord('b'): 
            bgModel = cv2.createBackgroundSubtractorMOG2(0, bgSubThreshold)
            isBgCaptured = 1
            print('!!!Background Captured!!!')
        elif k == ord('r'): # 按下'r'会重置背景
            bgModel = None
            triggerSwitch = False
            isBgCaptured = 0
            print('!!!Reset BackGround!!!')
        elif k == ord('n'):
            triggerSwitch = True
            print('!!!Trigger On!!!')
        elif k==ord('q'):
            camera.release()
            break
    
          

import os
import cfg
import sys
import pygame
import random
from modules import *


'''游戏初始化'''
def initGame():
    # 初始化pygame, 设置展示窗口
    pygame.init()
    screen = pygame.display.set_mode((600,400))
    pygame.display.set_caption('catch coins —— 吃金币游戏')
    # 加载必要的游戏素材
    game_images = {}
    for key, value in cfg.IMAGE_PATHS.items():
        if isinstance(value, list):
            images = []
            for item in value: images.append(pygame.image.load(item))
            game_images[key] = images
        else:
            game_images[key] = pygame.image.load(value)
    game_sounds = {}
    for key, value in cfg.AUDIO_PATHS.items():
        if key == 'bgm': continue
        game_sounds[key] = pygame.mixer.Sound(value)
    # 返回初始化数据
    return screen, game_images, game_sounds


'''主函数'''
def main():
    # 初始化
    screen, game_images, game_sounds = initGame()
    # 播放背景音乐
    pygame.mixer.music.load(cfg.AUDIO_PATHS['bgm'])
    pygame.mixer.music.play(-1, 0.0)
    # 字体加载
    font = pygame.font.Font(cfg.FONT_PATH, 40)
    # 定义hero
    global hero
    hero = Hero(game_images['hero'], position=(375, 320))
    # 定义食物组
    food_sprites_group = pygame.sprite.Group()
    generate_food_freq = random.randint(10, 20)
    generate_food_count = 0
    # 当前分数/历史最高分
    score = 0
    highest_score = 0 if not os.path.exists(cfg.HIGHEST_SCORE_RECORD_FILEPATH) else int(open(cfg.HIGHEST_SCORE_RECORD_FILEPATH).read())
    # 游戏主循环
    clock = pygame.time.Clock()
    while True:
        # --填充背景
        screen.fill(0)
        screen.blit(game_images['background'], (0, 0))
        cfg.SCREENSIZE=(600,400)
        # --倒计时信息
        countdown_text = 'Count down: ' + str((90000 - pygame.time.get_ticks()) // 60000) + ":" + str((90000 - pygame.time.get_ticks()) // 1000 % 60).zfill(2)
        countdown_text = font.render(countdown_text, True, (0, 0, 0))
        countdown_rect = countdown_text.get_rect()
        countdown_rect.topright = [cfg.SCREENSIZE[0]-30, 5]
        screen.blit(countdown_text, countdown_rect)
        # --按键检测
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
        key_pressed = pygame.key.get_pressed()
        if key_pressed[pygame.K_a] or key_pressed[pygame.K_LEFT]:
            hero.move((600,400), 'left')
        if key_pressed[pygame.K_d] or key_pressed[pygame.K_RIGHT]:
            hero.move((600,400), 'right')
        # --随机生成食物
        generate_food_count += 1
        if generate_food_count > generate_food_freq:
            generate_food_freq = random.randint(10, 20)
            generate_food_count = 0
            food = Food(game_images, random.choice(['gold',] * 10 + ['apple']), cfg.SCREENSIZE)
            food_sprites_group.add(food)
        # --更新食物
        for food in food_sprites_group:
            if food.update(): food_sprites_group.remove(food)
        # --碰撞检测
        for food in food_sprites_group:
            if pygame.sprite.collide_mask(food, hero):
                game_sounds['get'].play()
                food_sprites_group.remove(food)
                score += food.score
                if score > highest_score: highest_score = score
        # --画hero
        hero.draw(screen)
        # --画食物
        food_sprites_group.draw(screen)
        # --显示得分
        score_text = f'Score: {score}, Highest: {highest_score}'
        score_text = font.render(score_text, True, (0, 0, 0))
        score_rect = score_text.get_rect()
        score_rect.topleft = [5, 5]
        screen.blit(score_text, score_rect)
        # --判断游戏是否结束
        if pygame.time.get_ticks() >= 90000:
            break
        # --更新屏幕
        pygame.display.flip()
        clock.tick(cfg.FPS)
    # 游戏结束, 记录最高分并显示游戏结束画面
    fp = open(cfg.HIGHEST_SCORE_RECORD_FILEPATH, 'w')
    fp.write(str(highest_score))
    fp.close()
    return showEndGameInterface(screen, cfg, score, highest_score)

import threading
import time
import os
from multiprocessing import Process,Value
#线程条件变量
threading.Thread(target=main).start()  
threading.Thread(target=main_gesture).start() 

おすすめ

転載: blog.csdn.net/cangzhexingxing/article/details/124124094