PeterMemo

主にC++,OpenGL,Siv3Dのプログラミング関連の内容です.

Siv3D 「Kinectを使ってブロック崩し」をしてみた

研究室にKinectがあったので使ってみました.また,Siv3Dでおなじみのブロック崩しに応用してみました.

 

Kinectとは

Kinectとは,マイクロソフトから発売されたジェスチャー音声認識によって操作ができるデバイスです.(Wikiより引用) Xboxなどに使われています.

 

1. インストール

簡単にインストール方法を説明します.

Download Kinect for Windows SDK 2.0 from Official Microsoft Download Center


上のurlに行ってもらい,緑の背景に白文字でContinueと書いてあるボタンをクリックします.そうするとexeをダウンロードできるので,exeを実行すればインストールされます.

 

2. サンプルを動かしてみる

とりあえずSiv3Dのリファレンスに行ってサンプルを動かしてみます.

github.com



Kinect v2 の,「デプスとボディを取得する」というサンプルを動かし,特にエラーなどが出なければインストール成功だと思います.

 

3. 機能紹介

Kinectの機能を紹介し,簡単なサンプルを載せておきます.

・カラー

たぶん普通のカメラです.以下にカラー画像を表示するプログラムを示します.

 

DynamicTexture colorTexture;
std::array<Optional<KinectV2Body>, 6> bodies;

while (System::Update()) {
    if (KinectV2::HasNewColorFrame()) {
        KinectV2::GetColorFrame(colorTexture);
    }
    if (KinectV2::HasNewBodyFrame()) { 
        KinectV2::GetBodyFrame(bodies);
    }
    colorTexture.draw();
} 

・デプス

リファレンスにあったサンプルで使用していた機能です.深度センサで距離を取得することができます.

 

DynamicTexture depthTexture;
std::array<Optional<KinectV2Body>, 6> bodies;

while (System::Update()) {
    if (KinectV2::HasNewDepthFrame()) {
        KinectV2::GetDepthFrame(depthTexture);
    }
    if (KinectV2::HasNewBodyFrame()) {
        KinectV2::GetBodyFrame(bodies);
    }
    depthTexture.draw();
} 


・関節検出

人間の関節を検出することができます.関節の種類は全部で25箇所あり,列挙型で定義がされています.KinectV2.hppファイルにそれぞれの部位の説明が書いてあります.

具体的な関節の座標の取得方法を説明します.下の例では左手のカラー画像上とデプス画像上の座標を取得してみます.

 

std::array<Optional<KinectV2Body>, 6> bodies;
Vec2 ColorLeftHand;
Vec2 DepthLeftHand;

KinectV2::GetBodyFrame(bodies);//ここでbodiesにデータが入る

for (const auto& body : bodies) {
    if (!body) { continue; }
    ColorLeftHand = body->joints[V2JointType::HandLeft].colorSpacePos; //左手のカラー座標を取得
    DepthLeftHand = body->joints[V2JointType::HandLeft].depthSpacePos; //左手のデプス座標を取得
}

ここで少し注意することがあります.例えばプログラム上で,左手がある場所に〇を書きたいときのことを考えます.もしカラー画像を出力しているときに,デプス画像上の左手の座標に〇を書いても思った場所に〇は書かれないので注意しましょう.カラー画像を出力しているときは,カラー画像上の座標を扱うようにしましょう.

ここではブロック崩しに使う機能をざっくり説明しましたが,ほかにも手の状態を検出したり,音声認識の機能があります.

 

4. ブロック崩しに応用

Kinectで両手の座標を取得し,バーを動かすことでブロック崩しに応用しました.基本的にサンプルプログラムを組み合わせただけです.

LeftHand = body->joints[V2JointType::HandLeft].colorSpacePos;
RightHand = body->joints[V2JointType::HandRight].colorSpacePos;

 この2行で両手の座標を取得し,バーの座標としています.起動直後はまだKinectが動いていないことがあるので,スペースキーを押すまで待機するようにしています.

 

# include <Siv3D.hpp>

//ブロックがいい感じに消える
struct Spark : IEffect{
    Array<std::pair<Vec2, Vec2>> m_particles;
    Spark(const Point& pos, const Point& size) : m_particles(50) {
        for (auto& particle : m_particles) {
            particle.second = Circular(Random(40.0), Random(TwoPi));
            particle.first = pos.movedBy(Random(-size.x / 2, size.x / 2), Random(-size.y / 2, size.y / 2));
        }
    }
    bool update(double t) override {
        for (const auto& particle : m_particles) {
            const Vec2 pos = particle.first + particle.second * t + 0.5* t*t * Vec2(0, 120);
            Triangle(pos, 8.0, particle.second.x).draw(HSV(pos.y - 40).toColorF(1.0 - t));
        }
    return t < 1.0;
    }
};

//数字のエフェクト
struct TextEffect : IEffect{
    const Font m_font;
    const int32 m_value;
    const Vec2 m_from;
    const double N = 1.0;
    const bool flg;
    TextEffect(const Font& font, const int32 value, const Vec2& from, bool f) :
        m_font(font) , m_value(value) , m_from(from) , flg(f) {}
    bool update(const double t) override {
        // N 秒で消える
        if (t >= N) { return false; }
        const double alpha = N - t;
        if (flg) {
           m_font(L"+", m_value).drawCenter(m_from + Vec2(0, -40 * t), HSV(60 - m_value).toColorF(alpha));
        }
        else {
            m_font(L"-", m_value).drawCenter(m_from + Vec2(0, -40 * t), HSV(160 - m_value).toColorF(alpha));
        }
        return true;
    }
};

void Main(){

    //カラー画像の大きさに合わせる

    Window::Resize(1920, 1080);
    //Kinect用

    if (!KinectV2::IsAvailable()) {
        exit(1);
    }

    if (!KinectV2::Start()) {
        exit(1);
    }

    DynamicTexture colorTexture;
    std::array<Optional<KinectV2Body>, 6> bodies;
    Vec2 RightHand = Vec2(1920 / 2, 1080 / 2);
    Vec2 LeftHand = Vec2(1920 / 2, 1080 / 2);
    const double HandRadius = 50;
    Effect effect;
    const Font font(40, Typeface::Heavy);
    int Score = 0;
    int Combo = 0;
    int ComboM = 0;
    const Sound sound(Wave(0.1s, [](double t) { return Fraction(t * 440)*0.5 - 0.25; }));
    const Size blockSize(40, 20);
    const double speed = 8.0;
    Rect bar(180, 20);
    Rect bar2(180, 20);//このブロック崩しは両手を使うのでバーが二つ必要
    Circle ball(960, 540, 8);
    Vec2 ballSpeed(0, -speed);
    Array<Rect> blocks;
    bool start = false;

    for (auto p : step({ Window::Width() / blockSize.x , 5 })) {
        blocks.emplace_back((p*blockSize).moveBy(0, 60), blockSize);
    }

    while (System::Update()&&!start) {
        if (KinectV2::HasNewColorFrame()) {
            KinectV2::GetColorFrame(colorTexture);
        }
        if (KinectV2::HasNewBodyFrame()) {
            KinectV2::GetBodyFrame(bodies);
        }
        colorTexture.draw();
        if (Input::KeySpace.clicked) {
        start = true;
        }
    }

    while (System::Update()) {
        if (KinectV2::HasNewColorFrame()) {
            KinectV2::GetColorFrame(colorTexture);
        }
        if (KinectV2::HasNewBodyFrame()) {
            KinectV2::GetBodyFrame(bodies);
        }
        colorTexture.draw();
        if (Input::KeySpace.clicked) {
            ball.center = Vec2(960, 540);
            ballSpeed = Vec2(0, -speed);
        }
        ball.moveBy(ballSpeed);
        for (const auto& body : bodies) {
            if (!body) {
                continue;
            }
            LeftHand = body->joints[V2JointType::HandLeft].colorSpacePos;
            RightHand = body->joints[V2JointType::HandRight].colorSpacePos;
        }
        Circle(LeftHand, HandRadius).draw(Palette::Green);
        Circle(RightHand, HandRadius).draw(Palette::Green);
        bar.setCenter((int)LeftHand.x,(int)LeftHand.y);
        bar2.setCenter((int)RightHand.x, (int)RightHand.y);
        for (auto it = blocks.begin(); it != blocks.end(); ++it) {
             if (it->intersects(ball)) {
                Combo++;//2コンボ以上でエフェクト
                if (Combo >= 2) {
                    effect.add<TextEffect>(font, Combo, it->pos, true);
                 }
                Score += Combo; 
                (it->bottom.intersects(ball) || it->top.intersects(ball) ? ballSpeed.y : ballSpeed.x) *= -1; effect.add<Spark>(it->center, blockSize);
                blocks.erase(it);
                sound.playMulti();
                break;
            }
        }
        for (auto const& block : blocks) {
            block.stretched(-1).draw(HSV(block.y - 40));
        }
        if (ball.y < 0 && ballSpeed.y <  0) {
            ballSpeed.y *= -1;
        }
        if ((ball.x < 0 && ballSpeed.x < 0) || (Window::Width() < ball.x && ballSpeed.x > 0)) {
            ballSpeed.x *= -1;
        }
        if (ballSpeed.y > 0 && bar.intersects(ball)) {
            Combo = 0;
            ballSpeed = Vec2((ball.x - bar.center.x) / 8, -ballSpeed.y).setLength(speed);
        }
        if (ballSpeed.y > 0 && bar2.intersects(ball)) {
            Combo = 0;
            ballSpeed = Vec2((ball.x - bar2.center.x) / 8, -ballSpeed.y).setLength(speed);
        }

        ball.draw();
        bar.draw();
        bar2.draw();
        effect.update();//エフェクト表示
        font(L"score : ", Score).draw(Vec2(0, 0), Palette::Black);//スコア表示
    }
}

 

このブロック崩しが意外と好評で,大学の文化祭の展示の一部になりました.最終的なプログラムは少し長いのでgithubに置いておきます.プログラムの中でいろいろと画像を使用していますが,権利的な問題が気になったので,ご自身で用意してもらえると助かります.

 


 最後まで見ていただき,ありがとうございました.質問があればいつでも受け付けています.明日は@ongaeshi さんの記事です.よろしくお願いします.