2011年12月31日土曜日

enchant.js物理シミュレーションプラグインPhySpriteを作ってみた

enchant.js用の物理シミュレーションプラグインを作りました。

物理シミュレーションにはBox2dWebを用いてます。

PhySprite.enchant.jsのダウンロード
ダウンロード
リファレンス


github



またBox2dWebのサイトから「Box2dWeb-○○○.js」の最新版をダウンロードして、「PhySprite.enchant.js」と同じフォルダに入れてください。



使い方


・プラグインの読み込み

index.htmlでenchant.jsの後に以下のファイルを読み込んでください
・Box2DWeb-○○○.js
・PhySprite.enchant.js
    
    <script type="text/javascript" src="enchant.js"></script>
    <!-- プラグイン読み込み -->
    <script type="text/javascript" src="Box2dWeb-2.1.a.3.js"></script>
    <script type="text/javascript" src="PhySprite.enchant.js"></script>


・ワールド生成
    
    //物理シミュレーションの世界を設定(y軸方向に重力 9.8[m/s^2])
    physicsWorld = new PhysicsWorld(0, 9.8);


・物理シミュレーション用Spriteの生成
    
    //ボール生成
    var phyBall = new PhyCircleSprite(8, DYNAMIC_SPRITE, 1.0, 0.5, 0.2, true);
    phyBall.image = game.assets["icon1.png"];
    phyBall.position = { x: 160, y: 10 };
    game.rootScene.addChild(phyBall); // シーンに追加

    //床生成
    var floor = new PhyBoxSprite(256, 16, STATIC_SPRITE, 1.0, 0.5, 0.3, true);
    floor.image = game.assets["floor.gif"];
    floor.position = { x: 160, y: 300 };
    game.rootScene.addChild(floor);


・毎フレームの処理
    
    //毎フレーム処理
    game.rootScene.addEventListener(enchant.Event.ENTER_FRAME, function (e) {
        physicsWorld.step(game.fps); //物理シミュレーション内の時間を進める
    });

PhyCircleSpriteは円、PhyBoxSpriteは四角の物理シミュレーションSpriteです
enchant.Spriteを継承しています

生成時の引数は
 
    PhyBoxSprite(width, height, staticOrDynamic, density, friction, restitution, isSleeping);
    PhyCircleSprite(radius, staticOrDynamic, density, friction, restitution, isSleeping);

引数の説明
width:Spriteの横幅.
height:Spriteの高さ.
radius:Spriteの半径.
staticOrDynamic:静止物体か動体か。STATIC_SPRITEの時、動かない物体。DYNAMIC_SPRITEの時、動く物体
density:Spriteの密度.
friction:Spriteの摩擦.
restitution:Spriteの反発.
isSleeping:Spriteが初めから物理演算を行うか.


では、サンプルプログラム1
 
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, user-scalable=no">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
    <title>PhySprite demo</title>
    <script type="text/javascript" src="enchant.js"></script>
    <!-- プラグイン読み込み -->
    <script type="text/javascript" src="Box2dWeb-2.1.a.3.js"></script>
    <script type="text/javascript" src="PhySprite.enchant.js"></script>
    <script type="text/javascript">
        enchant();
        window.onload = function () {
            var game = new Game(320, 320);
            game.fps = 24;
            game.preload("icon1.png", "floor.gif");

            game.rootScene.backgroundColor = "black";
            game.onload = function () {
                //物理シミュレーションの世界を設定(y軸方向に重力 9.8[m/s^2])
                var physicsWorld = new PhysicsWorld(0, 9.8);

                //ボール生成
                var phyBall = new PhyCircleSprite(8, DYNAMIC_SPRITE, 1.0, 0.5, 0.2, true);
                phyBall.image = game.assets["icon1.png"];
                phyBall.position = { x: 160, y: 10 };
                game.rootScene.addChild(phyBall); // シーンに追加

                //床生成
                var floor = new PhyBoxSprite(256, 16, STATIC_SPRITE, 1.0, 0.5, 0.3, true);
                floor.image = game.assets["floor.gif"];
                floor.position = { x: 160, y: 300 };
                game.rootScene.addChild(floor);

                game.rootScene.addEventListener(enchant.Event.ENTER_FRAME, function (e) {
                    physicsWorld.step(game.fps); //物理シミュレーション内の時間を進める
                });
            };
            game.start();   // ゲームスタート
        }
            
    </script>
    <style type="text/css">
        body
        {
            margin: 0px;
        }
    </style>
</head>
<body>
</body>
</html>


PhySprite.enchant.jsのサンプル2




リファレンス

2011年11月12日土曜日

Kinect OpenNIによるWindowsでのマルチキネクト - UDP

Kinectを複数使う場合、Macならマルチキネクト(Multi-Kinect)が可能です。

しかし、Windows環境ではまだドライバが対応しておらず、マルチキネクト(Multi-Kinect)を行うことができません。
できるみたいです。
USB端子の大元?が異なる端子で繋げば出来るみたい。
ex)デスクトップPCの前面パネルと後面パネルのUSB端子


ドライバが対応してないなら、違うPCで動かせばいいじゃない

っということで取得したKinectの画像を送受信してみます。


今回はUDPによって送受信を行います。

UDPはTCPと違い、相手に正しくデータが届くとは限りません。
届いたかどうかや誤りの確認をしていないからです。
そのかわり、高速でデータを送信することが可能となります。
よく、音声や画像のストリーム形式での配信に用いられるプロトコルです。


まず、Kinectから取得できるRGB画像と深度画像のサイズを計算してみます。

RGB画像
8bit (1byte)
3Channel    ->   921600byte   ->   900KB
480 * 640

深度画像
16bit (2byte)
1Channel    ->   614400byte   ->   600KB
480 * 640

合計 1500KB

RGB画像と深度画像のサイズは1500KB(約1.5MB)となります。


結構大きいですね・・・


では、このデータをUDPで送信することになりますが、UDPはこのような大きなサイズを一気に送ることはできません。
なので分割して送ります。

大体UDPで送ることができる最大サイズは60KBくらいなので、60KBで分割します。

RGB画像:900KB / 60KB = 15分割
深度画像:600KB / 60KB = 10分割


このことを頭において、プログラムを作ると



送信
/*
    送信
    ServerName    : IPアドレス
    PortNo        : ポート番号
    image        : 送信するRGBイメージ
    depth        : 送信する深度イメージ
*/
int sendKinectImage(char*ServerName,unsigned short PortNo,Mat &image,Mat &depth){
    HOSTENT*pHostent=gethostbyname(ServerName);
    if(pHostent==NULL){
        DWORD addr=inet_addr(ServerName);
        pHostent=gethostbyaddr((char*)&addr,4,AF_INET);
    }
    if(pHostent==NULL) return -1;//サーバーが見つかりません
  
    SOCKET s = socket(AF_INET,SOCK_DGRAM,0);
    if(s<0) return -2;//ソケットエラー
  
    SOCKADDR_IN sockaddrin;
    memset(&sockaddrin,0,sizeof(sockaddrin));
    memcpy(&(sockaddrin.sin_addr),pHostent->h_addr_list[0],pHostent->h_length);
    sockaddrin.sin_family = AF_INET;
    sockaddrin.sin_port = htons(PortNo);

    //RGBイメージ
    static const int sendSize = 61440;    //1度に送信する画像のバッファサイズ:60KB(61440byte)
    static char buff[sendSize + 2];            //送信するバッファ(ヘッダ部 + 画像データ)
    static const int divImageNum = (image.rows * image.step) / sendSize;    //RGB画像の分割数
    static const int divDepthNum = (depth.rows * depth.step) / sendSize;    //深度画像の分割数

    //RGBイメージ
    for(int i = 0;i < divImageNum;i++){
        //ヘッダ部の入力
        buff[0] = 'R';    //RGB画像だという識別記号
        buff[1] = i;    //分割された何番目か
        //画像データの入力
        memcpy(&buff[2],&image.data[sendSize * i],sendSize);
        sendto(s,buff,sendSize + 2,4,(LPSOCKADDR)&sockaddrin,sizeof(sockaddrin));

    }
    //Depthイメージ
    for(int i = 0;i < divDepthNum;i++){  
        //ヘッダ部の入力
        buff[0] = 'D';    //Depth画像だという識別記号
        buff[1] = i;    //分割された何番目か
        //画像データの入力
        memcpy(&buff[2],&depth.data[sendSize * i],sendSize);
        sendto(s,buff,sendSize + 2,4,(LPSOCKADDR)&sockaddrin,sizeof(sockaddrin));
    }
    closesocket(s);
    return 0;    //成功
}




送信するためにbuffという配列を用意します。

buffには画像に関する情報のヘッダ部と画像データを格納します。


ここでヘッダ部では
buff[0]にRGB画像か深度画像か判別する情報を入れます
つぎに
buff[1]にこのデータが何分割目のデータを格納します。
なぜこのようなことをするのかというと、UDPは送信したデータが相手に届くかわからず、また順番も送信した順になるとはかぎりません。
なので、受信側が分割されたデータを1つの画像に復元する際、必要になります。



データの送信はsendtoで行います


次に受信


受信

/*
    受信
    PortNo        : ポート番号
    image        : 受信したRGBイメージを格納する変数
    depth        : 受信した深度イメージを格納する変数
*/
int receiveKinectImage(unsigned short PortNo,Mat &image,Mat &depth){  
    SOCKET s = socket(AF_INET,SOCK_DGRAM,0);
    if(s<0) return -1;//ソケットエラー
  
    SOCKADDR_IN sockaddrin;
    memset(&sockaddrin,0,sizeof(sockaddrin));
    sockaddrin.sin_family = AF_INET;
    sockaddrin.sin_port = htons(PortNo);
    sockaddrin.sin_addr.S_un.S_addr = INADDR_ANY;
    if(SOCKET_ERROR == bind(s,(LPSOCKADDR)&sockaddrin,(int)sizeof(sockaddrin))){
        closesocket(s);
        return -2;//bindエラー
    }
    SOCKADDR_IN from;
    int fromlen=(int)sizeof(from);

    static const int receiveSize = 61440;    //1度に受信する画像のバッファサイズ        60KB(61440byte)
    static char buff[receiveSize + 2];            //受信するバッファ(ヘッダ部 + 画像データ)
    static const int divImageNum = (image.rows * image.step) / receiveSize;    //RGB画像の分割数
    static const int divDepthNum = (depth.rows * depth.step) / receiveSize;    //深度画像の分割数
    static int re;
    static char header;
    static char divNum;
    static int identificationNum;
    static char imageOrDepth;

    re = recvfrom(s,buff,receiveSize + 2,0,(SOCKADDR*)&from,&fromlen);//受信するまでここで停止
    if(re!=SOCKET_ERROR){//エラーで無ければ表示
        imageOrDepth = buff[0];    //RGB画像か深度画像か
        divNum = buff[1];        //分割番号

        if(imageOrDepth == 'R')        //RGBイメージ
            memcpy(&image.data[receiveSize * divNum],&buff[2],receiveSize);

        else if(imageOrDepth == 'D')    //Depthイメージ
            memcpy(&depth.data[receiveSize * divNum],&buff[2],receiveSize);
    }
  
    closesocket(s);
    return 0;
}


ヘッダ部の情報を元に、RGB画像か深度画像のどちらにデータを格納するか選び
分割番号を元に、格納するデータの位置を決定します。



これでKinectの画像データを他のPCに送ることができます。



データの受信はrecvfromで行います
ちなみにrecvfromはデータを受信するまで、待ち状態になります。
利用する際は、受信は別スレッドで動作させましょう。


ここではUDPのくわしいプログラムは説明しません。

各自調べてください。



これでWindows環境でもマルチキネクト(Multi-Kinect) が可能に!?

骨格情報に関しては紹介しませんでしたが、同じような感じでできるんじゃないでしょうか。




ということで、サンプルプログラムどん

#include <winsock2.h>
#pragma comment(lib, "wsock32.lib")
#include <opencv2/opencv.hpp>
#ifdef _DEBUG
    //Debugモードの場合
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_core220d.lib")            // opencv_core
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_imgproc220d.lib")        // opencv_imgproc
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_highgui220d.lib")        // opencv_highgui
#else
    //Releaseモードの場合
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_core220.lib")            // opencv_core
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_imgproc220.lib")        // opencv_imgproc
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_highgui220.lib")        // opencv_highgui
#endif
#include <XnCppWrapper.h>
#pragma comment(lib,"C:/Program files/OpenNI/Lib/openNI.lib")

#define SAMPLE_XML_PATH "C:/Program Files/OpenNI/Data/SamplesConfig.xml"
using namespace cv;
using namespace xn;

//受信スレッド
DWORD WINAPI ThreadFunc(LPVOID vdParam);
int sendKinectImage(char*ServerName,unsigned short PortNo,Mat &image,Mat &depth);    //送信
int receiveKinectImage(unsigned short PortNo,Mat &image,Mat &depth);                //受信

//ポート番号
#define PORT 1000
//IPアドレス(送信するPCのIPアドレスを指定してください)
#define SEND_IPADDRESS "192.168.12.55"
//送信するか受信するか
enum Type{SEND,RECEIVE};  


Mat receiveImage(480,640,CV_8UC3);        //受信RGB画像
Mat receiveDepth(480,640,CV_16UC1);        //受信深度画像

int main()
{
    //OpenNI
    DepthGenerator depthGenerator;
    ImageGenerator imageGenerator;
    DepthMetaData depthMD;
    ImageMetaData imageMD;
    Context context;

    //OpenCV
    Mat image(480,640,CV_8UC3);            //RGB画像
    Mat depth(480,640,CV_16UC1);        //深度画像
  
    //OpenNIの初期化
    context.InitFromXmlFile(SAMPLE_XML_PATH);
    context.FindExistingNode(XN_NODE_TYPE_DEPTH, depthGenerator);
    context.FindExistingNode(XN_NODE_TYPE_IMAGE, imageGenerator);
  
    //RGB画像と振動画像のズレを補正
    depthGenerator.GetAlternativeViewPointCap().SetViewPoint(imageGenerator);

    //送信するか受信するか
    //Type sendOrReceive = SEND;    //送信する
    Type sendOrReceive = RECEIVE;    //受信する
  
    DWORD dwID;    //スレッドのID
    if(sendOrReceive == RECEIVE){
        //受信スレッド起動
        CreateThread(NULL , 0 , ThreadFunc , 0 , 0 , &dwID);
        cvNamedWindow("receiveImage");    //ウィンドウの準備
        cvNamedWindow("receiveDepth");
    }
  
    //ウィンドウの準備
    cvNamedWindow("image");
    cvNamedWindow("depth");
    int key = 0;
    while (key!='q'){
        context.WaitAndUpdateAll();
       
        imageGenerator.GetMetaData(imageMD);
        depthGenerator.GetMetaData(depthMD);
       
        memcpy(image.data,imageMD.Data(),image.step * image.rows);    //イメージデータを格納
        memcpy(depth.data,depthMD.Data(),depth.step * depth.rows);    //深度データを格納
       
        //BGRをRGBへ
        cvtColor(image,image,CV_RGB2BGR);

        //画面に表示
        imshow("image",image);
        imshow("depth",depth);
       
        if(sendOrReceive == SEND){            //送信          
            sendKinectImage(SEND_IPADDRESS,PORT,image,depth);
        }
        else if(sendOrReceive == RECEIVE){    //受信
            //受信した画像を表示する
            imshow("receiveImage",receiveImage);
            imshow("receiveDepth",receiveDepth);
        }

        key = waitKey(33);
    }
    context.Shutdown();
    return 0;
}

/*
    送信
    ServerName    : IPアドレス
    PortNo        : ポート番号
    image        : 送信するRGBイメージ
    depth        : 送信する深度イメージ
*/
int sendKinectImage(char*ServerName,unsigned short PortNo,Mat &image,Mat &depth){
    HOSTENT*pHostent=gethostbyname(ServerName);
    if(pHostent==NULL){
        DWORD addr=inet_addr(ServerName);
        pHostent=gethostbyaddr((char*)&addr,4,AF_INET);
    }
    if(pHostent==NULL) return -1;//サーバーが見つかりません
  
    SOCKET s = socket(AF_INET,SOCK_DGRAM,0);
    if(s<0) return -2;//ソケットエラー
  
    SOCKADDR_IN sockaddrin;
    memset(&sockaddrin,0,sizeof(sockaddrin));
    memcpy(&(sockaddrin.sin_addr),pHostent->h_addr_list[0],pHostent->h_length);
    sockaddrin.sin_family = AF_INET;
    sockaddrin.sin_port = htons(PortNo);

    //RGBイメージ
    static const int sendSize = 61440;    //1度に送信する画像のバッファサイズ:60KB(61440byte)
    static char buff[sendSize + 2];            //送信するバッファ(ヘッダ部 + 画像データ)
    static const int divImageNum = (image.rows * image.step) / sendSize;    //RGB画像の分割数
    static const int divDepthNum = (depth.rows * depth.step) / sendSize;    //深度画像の分割数

    //RGBイメージ
    for(int i = 0;i < divImageNum;i++){
        //ヘッダ部の入力
        buff[0] = 'R';    //RGB画像だという識別記号
        buff[1] = i;    //分割された何番目か
        //画像データの入力
        memcpy(&buff[2],&image.data[sendSize * i],sendSize);
        sendto(s,buff,sendSize + 2,4,(LPSOCKADDR)&sockaddrin,sizeof(sockaddrin));

    }
    //Depthイメージ
    for(int i = 0;i < divDepthNum;i++){  
        //ヘッダ部の入力
        buff[0] = 'D';    //Depth画像だという識別記号
        buff[1] = i;    //分割された何番目か
        //画像データの入力
        memcpy(&buff[2],&depth.data[sendSize * i],sendSize);
        sendto(s,buff,sendSize + 2,4,(LPSOCKADDR)&sockaddrin,sizeof(sockaddrin));
    }
    closesocket(s);
    return 0;    //成功
}
/*
    受信
    PortNo        : ポート番号
    image        : 受信したRGBイメージを格納する変数
    depth        : 受信した深度イメージを格納する変数
*/
int receiveKinectImage(unsigned short PortNo,Mat &image,Mat &depth){  
    SOCKET s = socket(AF_INET,SOCK_DGRAM,0);
    if(s<0) return -1;//ソケットエラー
  
    SOCKADDR_IN sockaddrin;
    memset(&sockaddrin,0,sizeof(sockaddrin));
    sockaddrin.sin_family = AF_INET;
    sockaddrin.sin_port = htons(PortNo);
    sockaddrin.sin_addr.S_un.S_addr = INADDR_ANY;
    if(SOCKET_ERROR == bind(s,(LPSOCKADDR)&sockaddrin,(int)sizeof(sockaddrin))){
        closesocket(s);
        return -2;//bindエラー
    }
    SOCKADDR_IN from;
    int fromlen=(int)sizeof(from);

    static const int receiveSize = 61440;    //1度に受信する画像のバッファサイズ        60KB(61440byte)
    static char buff[receiveSize + 2];            //受信するバッファ(ヘッダ部 + 画像データ)
    static const int divImageNum = (image.rows * image.step) / receiveSize;    //RGB画像の分割数
    static const int divDepthNum = (depth.rows * depth.step) / receiveSize;    //深度画像の分割数
    static int re;
    static char header;
    static char divNum;
    static int identificationNum;
    static char imageOrDepth;

    re = recvfrom(s,buff,receiveSize + 2,0,(SOCKADDR*)&from,&fromlen);//受信するまでここで停止
    if(re!=SOCKET_ERROR){//エラーで無ければ表示
        imageOrDepth = buff[0];    //RGB画像か深度画像か
        divNum = buff[1];        //分割番号

        if(imageOrDepth == 'R')        //RGBイメージ
            memcpy(&image.data[receiveSize * divNum],&buff[2],receiveSize);

        else if(imageOrDepth == 'D')    //Depthイメージ
            memcpy(&depth.data[receiveSize * divNum],&buff[2],receiveSize);
    }
  
    closesocket(s);
    return 0;
}
//受信スレッド
DWORD WINAPI ThreadFunc(LPVOID vdParam) {
    while (TRUE) {
        receiveKinectImage(PORT,receiveImage,receiveDepth);
    }
}






参考
プログラミング色々


2011年11月11日金曜日

Kinect OpenNIによる人物領域の抽出 - OpenCV

OpenNIでは人物の領域をマスク画像として取得することができます。

そこで、OpenCVのMatを用いてマスク画像から人物のRGB画像、深度画像を取得してみます。

あと、ついでに背景も抽出してます。

まず、 「SamplesConfig.xml」を編集します。

「<Node type="User" name="User1"/>」を追加しましょう。



<OpenNI>
    <Licenses>
        <!-- Add licenses here
        <License vendor="vendor" key="key"/>
        -->
    </Licenses>
    <Log writeToConsole="false" writeToFile="false">
        <!-- 0 - Verbose, 1 - Info, 2 - Warning, 3 - Error (default) -->
        <LogLevel value="3"/>
        <Masks>
            <Mask name="ALL" on="true"/>
        </Masks>
        <Dumps>
        </Dumps>
    </Log>
    <ProductionNodes>
        <Node type="Depth" name="Depth1">
            <Configuration>
                <Mirror on="true"/>
            </Configuration>
        </Node>
        <Node type="Image" name="Image1" stopOnError="false">
            <Configuration>
                <Mirror on="true"/>
            </Configuration>
        </Node>
        <Node type="User" name="User1"/>
        <!--
        <Node type="Audio" name="Audio1"/>
        -->
    </ProductionNodes>
</OpenNI>

これでユーザ検出が可能となりました。


てことでサンプルどん

#include <opencv2/opencv.hpp>
#ifdef _DEBUG
    //Debugモードの場合
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_core220d.lib")            // opencv_core
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_imgproc220d.lib")        // opencv_imgproc
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_highgui220d.lib")        // opencv_highgui
#else
    //Releaseモードの場合
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_core220.lib")            // opencv_core
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_imgproc220.lib")        // opencv_imgproc
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_highgui220.lib")        // opencv_highgui
#endif
#include <XnCppWrapper.h>
#pragma comment(lib,"C:/Program files/OpenNI/Lib/openNI.lib")

#define SAMPLE_XML_PATH "C:/Program Files/OpenNI/Data/SamplesConfig.xml"
using namespace cv;
using namespace xn;

int main()
{
    //OpenNI
    DepthGenerator depthGenerator;
    ImageGenerator imageGenerator;
    UserGenerator userGenerator;
    DepthMetaData depthMD;
    ImageMetaData imageMD;
    SceneMetaData sceneMD;   
    Context context;

    //OpenCV
    Mat image(480,640,CV_8UC3);            //RGB画像
    Mat depth(480,640,CV_16UC1);        //深度画像
    Mat mask(480,640,CV_16UC1);            //プレイヤーマスク画像
    Mat player(480,640,CV_8UC3);        //人間画像
    Mat playerDepth(480,640,CV_16UC1);    //人間画像
    Mat background(480,640,CV_8UC3);    //背景画像
    Mat useMask(480,640,CV_16UC1);        //使用するマスク
   
    int maskSize = mask.step * mask.rows;    //マスク画像の配列数
   
    //OpenNIの初期化
    context.InitFromXmlFile(SAMPLE_XML_PATH);
    context.FindExistingNode(XN_NODE_TYPE_DEPTH, depthGenerator);
    context.FindExistingNode(XN_NODE_TYPE_IMAGE, imageGenerator);
    context.FindExistingNode(XN_NODE_TYPE_USER, userGenerator);
   
    //RGB画像と振動画像のズレを補正
    depthGenerator.GetAlternativeViewPointCap().SetViewPoint(imageGenerator);
   
    //ウィンドウの準備
    cvNamedWindow("image");
    cvNamedWindow("depth");
    cvNamedWindow("player");
    cvNamedWindow("playerDepth");
    cvNamedWindow("background");

    int key = 0;
    while (key!='q'){
        context.WaitAndUpdateAll();
       
        imageGenerator.GetMetaData(imageMD);
        depthGenerator.GetMetaData(depthMD);
        userGenerator.GetUserPixels(0,sceneMD);    //ユーザピクセル取得
       
        memcpy(image.data,imageMD.Data(),image.step * image.rows);    //イメージデータを格納
        memcpy(depth.data,depthMD.Data(),depth.step * depth.rows);    //深度データを格納
       
        //BGRをRGBへ
        cvtColor(image,image,CV_RGB2BGR);

        player = 0;                                //初期化
        playerDepth = 0;                        //初期化       
        memcpy(mask.data,sceneMD.Data(),maskSize); //マスクデータをコピー       
        mask.convertTo(useMask,CV_8UC1);        //マスクの変換
        image.copyTo(player,useMask);            //マスクを利用した人物抽出
        depth.copyTo(playerDepth,useMask);        //マスクを利用した人物奥行き抽出
        background = image - player;            //背景のみ取得

        //画面に表示
        imshow("image",image);
        imshow("depth",depth);
        imshow("player",player);
        imshow("playerDepth",playerDepth);
        imshow("background",background);

        key = waitKey(33);
    }
    context.Shutdown();
    return 0;
}


人物のRGBデータと深度データをマスクを用いて抽出しています。

マスク画像の取得は
・userGenerator
・sceneMD
この2つを用いて取得します。

取得したマスク画像を格納するOpenCVのMatクラスはMat mask(480,640,CV_16UC1);です。


では、OpenNIからマスク画像の取得方法です。


userGenerator.GetUserPixels(0,sceneMD);    //ユーザピクセル取得

これでsceneMDにマスク画像が格納されました。

次にsceneMDからmaskにデータを格納します。



memcpy(mask.data,sceneMD.Data(),maskSize);

sceneMD.Data()でマスクの配列が取得できるので、その値をそのままmaskにコピーしています。


もし、Matを使わずに取得したい場合は

g_UserGenerator.GetUserPixels(0,sceneMD);    //ピクセル取得
const XnLabel* pLabels = sceneMD.Data();
if(pLabels == NULL){
        return;
}
 これでpLabelにアクセスすることで可能です。

1ピクセルだけでいいなら
sceneMD(x,y)
 でいいのかな。




次にマスクから人物領域を取得する方法

player = 0;            //初期化
playerDepth = 0;    //初期化   
memcpy(mask.data,sceneMD.Data(),maskSize);    //マスクデータをコピー      mask.convertTo(useMask,CV_8UC1);            //マスクの変換
image.copyTo(player,useMask);                //マスクを利用した人物抽出
depth.copyTo(playerDepth,useMask);            //マスクを利用した人物奥行き抽出
background = image - player;                //背景のみ取得

まず最初に前回抽出した人物画像を初期化します。

次にマスクデータをコピー

そしてつぎ、コピーしたマスクデータのままではcopyToがうまくできないので変換します

そしてRGB画像と深度画像からそれぞれ人物領域のみを抽出し、コピーします。

で、あとはRGB画像から人物画像を引くと、背景のみ取得できます。



この方法ならピクセル一つ一つにアクセスしていないので高速に人物領域の画像を取得することができます。

2011年10月28日金曜日

9leap 「9Days Challenge #5」 & 「コミPo!チャレンジ」優秀賞をいただきました!


9月に開催された「9leap 9Days Challenge #5」と「9leap 9Days コミPo! Challenge」にて優秀賞をいただきました!


9leap 「9Days Challenge #5」 & 「コミPo!チャレンジ」結果発表!

優秀賞
「お天気バトル」by kassy708

現在地の天気がそのまま能力となるゲーム。晴れの日は攻撃力アップ・曇りの日は防御力アップ。全国のその日の天気を参照しているので、自分との相性を考えて敵に戦いを挑むことも…。外に出なくても気温や風の強さが分かるのもポイントかもw @kassy708さんには、賞品「GPS内蔵デジタルカメラ カシオ EX-H20G」を差し上げます。おめでとうございます!

優秀賞
まねまねしょーぶ!! by @kassy708

今回の優秀賞は「まねまねしょーぶ!」です。GPSチャレンジに引き続き二連覇となりました。数字や文字と違って、ポーズを覚えるのは実はなかなか難しい!まずは遊んでみてください。
優秀賞を受賞されたkassy708さんには、「Canon PowerShot G12コミPo! ダウンロード版および素材集」をお送りします。

やったあああああああ!!!!

いや、まさか2つも受賞するとは。。。


前からデジカメほしーって思ってたらまさか2つも手に入るとは

しかも「コミPo! Challenge」の方のカメラはすげー・・・

使いこなせるかな

というか手に余る

また、親にプレゼントしよう。


あと、コミPo!便利だけど、ゲーム作りに使ってもいいのかな・・・

絵が下手だから、これ使えると便利なんだけど。


そういや「9Days Challenge #5」が発表されたけど「9Days Challenge #4」ってどうなったんだろ


テレビとコラボしてるから時間かかってるのかな?

まあ、いいや


これからもいっぱいゲーム作ろう

やっぱりゲームは楽しんで作らないと!


次はenchant PROを使った「Open leap」をがんばろうかな

今作ってるandroidゲームを移植してみよう。

2011年10月5日水曜日

Kinect OpenNIによる3次元テクスチャマップ - 3次元描画

前回「Kinect OpenNIによる3次元ポイントクラウド - 3次元描画」でポイントクラウド(Point Cloud)にしてみました

しかし、ポイントクラウドでは近づいたときに点と点の隙間から奥の何もない空間が見えてしまい荒く見えてしまいます。

ということで、次はテクスチャマップ(Texture Maps)してみましょう。

video




テクスチャを貼るサンプル

//ポイントクラウド描画

void drawTextureMaps(Mat &rgbImage,Mat &pointCloud_XYZ){
 static int x,y;
 static uchar *p[2];
 static Point3f *point[2];
 int channel = rgbImage.channels();
 p[0] = rgbImage.data;          //上の色
 p[1] = rgbImage.data + rgbImage.step;      //下の色
 point[0] = (Point3f*)pointCloud_XYZ.data;     //上の座標
 point[1] = &((Point3f*)pointCloud_XYZ.data)[KINECT_DEPTH_WIDTH]; //下の座標
 for(y = 0;y < KINECT_DEPTH_HEGIHT - 1;y++){
  for(x = 0;x < KINECT_DEPTH_WIDTH - 1;x++,p[1] += channel,point[1]++,p[0] += channel,point[0]++){ 
   //奥行きが取得できてなかったら何もしない
   if(point[0]->z == 0) 
    continue;
   //対角の奥行きが遠ければテクスチャを貼らない
   if(abs(point[0]->z - (point[1] + 1)->z) > THRESHOLD || abs((point[0] + 1)->z - point[1]->z) > THRESHOLD) 
    continue;  

   //テクスチャを貼る
   glBegin(GL_TRIANGLE_STRIP);
   //左上
   glTexCoord2f(0, 0);
   glColor3ubv(p[0]);
   glVertex3f(point[0]->x,point[0]->y,point[0]->z);
   //左下
   glTexCoord2f(1, 0);
   glColor3ubv(p[1]);
   glVertex3f(point[1]->x,point[1]->y,point[1]->z);
   //右上
   glTexCoord2f(0, 1);
   glColor3ubv(p[0]+channel);
   glVertex3f((point[0] + 1)->x,(point[0] + 1)->y,(point[0] + 1)->z);
   //右下
   glTexCoord2f(1, 1);
   glColor3ubv(p[1]+channel);
   glVertex3f((point[1] + 1)->x,(point[1] + 1)->y,(point[1] + 1)->z);   

   glEnd();
  }
  p[0] += channel,point[0]++;
  p[1] += channel,point[1]++;
 }
}


//対角の奥行きが閾値より遠ければテクスチャを貼らない

if(abs(point[0]->z - (point[1] + 1)->z) > THRESHOLD ||
 abs((point[0] + 1)->z - point[1]->z) > THRESHOLD)       
         continue;      
このif文は隣の画素が閾値より奥行き距離が離れていたらテクスチャを貼らないという処理です

ここで対角の距離を調べれば4点の奥行きがそれぞれ離れていないことがわかるんじゃないでしょうか。  


ちなみに

glBegin(GL_TRIANGLE_STRIP);

glEnd();
の間に書かれた点で面を貼ることができます。

OpenGLの詳しい説明は調べてください。

ポインタを使ってOpenCVのMatクラスにアクセスしてるので高速化できてるかと思いますが・・・

もし、もっと速度が欲しい時は

GPUを使った描画などで高速化してはどうでしょうか

glewを使うんですかね?

今度調べるかもしれません。



全体サンプル


#include <GL/glut.h>
#include <opencv2/opencv.hpp>
#ifdef _DEBUG
    //Debugモードの場合
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_core220d.lib")            // opencv_core
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_imgproc220d.lib")        // opencv_imgproc
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_highgui220d.lib")        // opencv_highgui
#else
    //Releaseモードの場合
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_core220.lib")            // opencv_core
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_imgproc220.lib")        // opencv_imgproc
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_highgui220.lib")        // opencv_highgui
#endif

#include <XnCppWrapper.h>
#pragma comment(lib,"C:/Program files/OpenNI/Lib/openNI.lib")

#define SAMPLE_XML_PATH "C:/Program Files/OpenNI/Data/SamplesConfig.xml"
using namespace cv;
using namespace xn;

//openNIのための宣言・定義
//マクロ定義
#define KINECT_IMAGE_WIDTH 640
#define KINECT_IMAGE_HEGIHT 480
#define KINECT_DEPTH_WIDTH 640
#define KINECT_DEPTH_HEGIHT 480

DepthGenerator depthGenerator;// depth context
ImageGenerator imageGenerator;//image context
DepthMetaData depthMD;
ImageMetaData imageMD;
Context context;

Mat image(480,640,CV_8UC3);
Mat depth(480,640,CV_16UC1);  
//ポイントクラウドの座標
Mat pointCloud_XYZ(480,640,CV_32FC3,cv::Scalar::all(0));

void retrievePointCloudMap(Mat &depth,Mat &pointCloud_XYZ);    //3次元ポイントクラウドのための座標変換
void drawTextureMaps(Mat &rgbImage,Mat &pointCloud_XYZ);        //テクスチャマップ描画

//openGLのための宣言・定義
//---変数宣言---
int FormWidth = 640;
int FormHeight = 480;
int mButton;
float twist, elevation, azimuth;
float cameraDistance = 0,cameraX = 0,cameraY = 0;
int xBegin, yBegin;
//---マクロ定義---
#define glFovy 45        //視角度
#define glZNear 1.0        //near面の距離
#define glZFar 150.0    //far面の距離
void polarview();        //視点変更


//テクスチャを貼る閾値
#define THRESHOLD 0.1

//描画
void display(){  
    // clear screen and depth buffer
    glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); 
    // Reset the coordinate system before modifying
    glLoadIdentity();   
    glEnable(GL_DEPTH_TEST); //「Zバッファ」を有効
    gluLookAt(0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0);   //視点の向き設定
    //wait and error processing
    context.WaitAnyUpdateAll();

    imageGenerator.GetMetaData(imageMD);
    depthGenerator.GetMetaData(depthMD);
    depthGenerator.GetAlternativeViewPointCap().SetViewPoint(imageGenerator);//ズレを補正
         
    memcpy(image.data,imageMD.Data(),image.step * image.rows);    //イメージデータを格納
    memcpy(depth.data,depthMD.Data(),depth.step * depth.rows);    //深度データを格納
      
    //3次元ポイントクラウドのための座標変換
    retrievePointCloudMap(depth,pointCloud_XYZ);

    //視点の変更
    polarview();  
    //テクスチャマップ
    drawTextureMaps(image,pointCloud_XYZ);
             
    //convert color space RGB2BGR
    cvtColor(image,image,CV_RGB2BGR);     
     
    imshow("image",image);
    imshow("depth",depth);
  
    glFlush();
    glutSwapBuffers();
}
//初期化
void init(){
    context.InitFromXmlFile(SAMPLE_XML_PATH); 
    context.FindExistingNode(XN_NODE_TYPE_DEPTH, depthGenerator); 
    context.FindExistingNode(XN_NODE_TYPE_IMAGE, imageGenerator);
}
// アイドル時のコールバック
void idle(){
    //再描画要求
    glutPostRedisplay();
}
//ウィンドウのサイズ変更
void reshape (int width, int height){
    FormWidth = width;
    FormHeight = height;
    glViewport (0, 0, (GLsizei)width, (GLsizei)height);
    glMatrixMode (GL_PROJECTION);
    glLoadIdentity ();
    //射影変換行列の指定
    gluPerspective (glFovy, (GLfloat)width / (GLfloat)height,glZNear,glZFar); 
    glMatrixMode (GL_MODELVIEW);
}
//マウスの動き
void motion(int x, int y){
    int xDisp, yDisp;  
    xDisp = x - xBegin;
    yDisp = y - yBegin;
    switch (mButton) {
    case GLUT_LEFT_BUTTON:
        azimuth += (float) xDisp/2.0;
        elevation -= (float) yDisp/2.0;
        break;
    case GLUT_MIDDLE_BUTTON:
        cameraX -= (float) xDisp/40.0;
        cameraY += (float) yDisp/40.0;
        break;
    case GLUT_RIGHT_BUTTON:
  cameraDistance += (float) xDisp/40.0;
        break;
    }
    xBegin = x;
    yBegin = y;
}
//マウスの操作
void mouse(int button, int state, int x, int y){
    if (state == GLUT_DOWN) {
        switch(button) {
        case GLUT_RIGHT_BUTTON:
        case GLUT_MIDDLE_BUTTON:
        case GLUT_LEFT_BUTTON:
            mButton = button;
            break;
        }
        xBegin = x;
        yBegin = y;
    }
}
//視点変更
void polarview(){
    glTranslatef( cameraX, cameraY, cameraDistance);
    glRotatef( -twist, 0.0, 0.0, 1.0);
    glRotatef( -elevation, 1.0, 0.0, 0.0);
    glRotatef( -azimuth, 0.0, 1.0, 0.0);
}
//メイン
int main(int argc, char *argv[]){
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGB);
    glutInitWindowSize(FormWidth, FormHeight);
    glutCreateWindow(argv[0]);
    //コールバック
    glutReshapeFunc (reshape);
    glutDisplayFunc(display);
    glutIdleFunc(idle);
    glutMouseFunc(mouse);
    glutMotionFunc(motion);
    init();
    glutMainLoop();
    context.Shutdown();
    return 0;
}

//テクスチャマップ描画
void drawTextureMaps(Mat &rgbImage,Mat &pointCloud_XYZ){
 static int x,y;
 static uchar *p[2];
 static Point3f *point[2];
 int channel = rgbImage.channels();
 p[0] = rgbImage.data;          //上の色
 p[1] = rgbImage.data + rgbImage.step;      //下の色
 point[0] = (Point3f*)pointCloud_XYZ.data;     //上の座標
 point[1] = &((Point3f*)pointCloud_XYZ.data)[KINECT_DEPTH_WIDTH]; //下の座標
 for(y = 0;y < KINECT_DEPTH_HEGIHT - 1;y++){
  for(x = 0;x < KINECT_DEPTH_WIDTH - 1;x++,p[1] += channel,point[1]++,p[0] += channel,point[0]++){ 
   //奥行きが取得できてなかったら何もしない
   if(point[0]->z == 0) 
    continue;
   //対角の奥行きが遠ければテクスチャを貼らない
   if(abs(point[0]->z - (point[1] + 1)->z) > THRESHOLD || abs((point[0] + 1)->z - point[1]->z) > THRESHOLD) 
    continue;  

   //テクスチャを貼る
   glBegin(GL_TRIANGLE_STRIP);
   //左上
   glTexCoord2f(0, 0);
   glColor3ubv(p[0]);
   glVertex3f(point[0]->x,point[0]->y,point[0]->z);
   //左下
   glTexCoord2f(1, 0);
   glColor3ubv(p[1]);
   glVertex3f(point[1]->x,point[1]->y,point[1]->z);
   //右上
   glTexCoord2f(0, 1);
   glColor3ubv(p[0]+channel);
   glVertex3f((point[0] + 1)->x,(point[0] + 1)->y,(point[0] + 1)->z);
   //右下
   glTexCoord2f(1, 1);
   glColor3ubv(p[1]+channel);
   glVertex3f((point[1] + 1)->x,(point[1] + 1)->y,(point[1] + 1)->z);   

   glEnd();
  }
  p[0] += channel,point[0]++;
  p[1] += channel,point[1]++;
 }
}
//3次元ポイントクラウドのための座標変換
void retrievePointCloudMap(Mat &depth,Mat &pointCloud_XYZ){
 static const int size = KINECT_DEPTH_HEGIHT * KINECT_DEPTH_WIDTH;
 static XnPoint3D proj[size] = {0};
 static int x,y;
 XnPoint3D *p = proj;
 unsigned short* dp = (unsigned short*)depth.data;
 for(y = 0; y < KINECT_DEPTH_HEGIHT; y++ ){
  for(x = 0; x < KINECT_DEPTH_WIDTH; x++, p++, dp++){ 
   p->X = x;
   p->Y = y;
   p->Z = *dp * 0.001f; // from mm to meters
  }
 }
 //現実座標に変換
 depthGenerator.ConvertProjectiveToRealWorld(size, proj, (XnPoint3D*)pointCloud_XYZ.data);
}  

サンプルダウンロード

github

2011年10月4日火曜日

新作ゲーム投稿

CAB - 法則性パズル【就活対策!】


新作ゲーム投稿しました

IT業界へ就職活動するときによく出会う問題ですね。

今回は9days Challenge「パズルゲームチャレンジ」に参加

今回の商品は「Sony Tablet」だそうで。

かなりほしぃ


もうすぐ応用情報技術者試験受けるので、あまり時間をかけれませんでした。



そろそろ勉強しないとやばいので、今日から本気だす。

2011年9月30日金曜日

9leap優秀賞受賞

かけらあつめが9leap優秀賞を受賞しました。

4Gamer.net
スマートフォン向けオリジナルゲームコンテスト“9leap”の前期審査会で優秀作品が決定

ファミ通App
【ゲーム付き】オリジナルゲーム開発コンテスト“9leap”前期審査会が開催 未来のクリエイターが作ったゲームはコレだ!



9leapとは
“9leap”とは、プログラマを目指す学生からJavaSprictベースのスマホ向けゲームを募集するコンテストで、期間は2011年5月~12月 と長期間に渡って行われている。審査は2011年5月~8月までの前期、9月1日から12月31日までの後期に分けられていて、それぞれの期間で優秀作品 を発表。受賞作品の開発者には、賞品として最新モデルのMacBook ProまたはMacBook Airが贈呈される。

前期後期を通じてもっとも高い評価を得た3作品に最優秀賞が贈られ、受賞作品開発者3名は2012年3月5日から9日にかけて開催予定の世界最大のゲー ム開発者会議“Game Developers Conference”の視察旅行に無料で参加できるほか、IGDA日本の主催するイベントに登壇できる権利も得ることができる内容だ。
ファミ通Appより


やったああああああああああああああああああああああああああ!!!


それほどプレイ数稼げてるわけでもないので諦めてましたけど受賞できました。


審査員の方のコメント
「万人に受け入れられそうで、加速度センサーを使った操作も簡単。かつ、グラフィックの美しさを評価しました」(宝珠山氏)
ということでした。

グラフィックに力入れたので評価されてうれしいです。


そしてMacがもらえる!!!

すでに持ってるけど!!!


むう、どうしよう。

Windows機に変えてもらってもいいけど、どうせならMBAのほうがいいなぁ

iPadとandroidタブレット・・・は欲張りすぎか


まあ、MBAもらって母親にあげようかな

最近iPad2買ったけどPCが古すぎて同期せずに使ってるんだよな。

もうすぐ誕生日だし。



よし、後期もはりきってこー!

2011年9月29日木曜日

Kinect OpenNIによる3次元ポイントクラウド - 3次元描画 高速化

前回の「Kinect OpenNIによる3次元ポイントクラウド - 3次元描画」でOpenGLによる3次元ポイントクラウドの方法を紹介しました。

しかし、cv::Matのアクセス方法によりちょっと速度が遅くなっているので、高速化を図りたいと思います。

cv::Matのアクセスで最も早いのはポインタへのアクセスです。

なので画素のアクセスはすべてポインタアクセスで行うことにします。


   
//ポイントクラウド描画
void drawPointCloud(Mat &rgbImage,Mat &pointCloud_XYZ){
    static int x,y;
    glPointSize(2);
    glBegin(GL_POINTS);
    uchar *p = rgbImage.data;
    Point3f *point = (Point3f*)pointCloud_XYZ.data;
    for(y = 0;y < 480;y++){
        for(x = 0;x < 640;x++,p += 3,point++){ 
            if(point->z == 0)
                continue;
            glColor3ubv(p);
            glVertex3f(point->x,point->y,point->z);
        }
    }
    glEnd();
}

    
//3次元ポイントクラウドのための座標変換
void retrievePointCloudMap(Mat &depth,Mat &pointCloud_XYZ){
    static const int size = 480 * 640;
    static XnPoint3D proj[size] = {0};
    static int x,y;
    XnPoint3D *p = proj;
    unsigned short* dp = (unsigned short*)depth.data;
    for(y = 0; y < 480; y++ ){
        for(x = 0; x < 640; x++, p++, dp++){  
            p->X = x;
            p->Y = y;
            p->Z = *dp * 0.001f; // from mm to meters
        }
    }
    //現実座標に変換
    depthGenerator.ConvertProjectiveToRealWorld(size, proj, (XnPoint3D*)pointCloud_XYZ.data);
}   

今まで画素のアクセスに
Point3f &point = ((Point3f*)(pointCloud_XYZ.data + pointCloud_XYZ.step.p[0]*y))[x];
やら
SEQ = y * step + x * channel;
rgbImage.data[SEQ + 0]
やらしていたので、 計算コストがかかってました

ということで、どうせ画素の配列は1次元配列で並んでるのならポインタをスライドさせていこうということです。


ポインタに慣れていない人にはわかりづらいかもしれませんが、慣れている人なら逆にこっちのほうがわかりやすいでしょう。

簡単に説明すると
    uchar *p = rgbImage.data;
    Point3f *point = (Point3f*)pointCloud_XYZ.data;
 でRGBデータと3次元位置データの先頭アドレスを格納します。

    for(x = 0;x < 640;x++,p += 3,point++){  }
のp += 3,point++でポインタをとなりのデータの場所に移動してるわけです


ということでサンプルどん

#include <GL/glut.h>
#include <opencv2/opencv.hpp>
#ifdef _DEBUG
    //Debugモードの場合
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_core220d.lib")            // opencv_core
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_imgproc220d.lib")        // opencv_imgproc
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_highgui220d.lib")        // opencv_highgui
#else
    //Releaseモードの場合
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_core220.lib")            // opencv_core
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_imgproc220.lib")        // opencv_imgproc
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_highgui220.lib")        // opencv_highgui
#endif

#include <XnCppWrapper.h>
#pragma comment(lib,"C:/Program files/OpenNI/Lib/openNI.lib")

#define SAMPLE_XML_PATH "C:/Program Files/OpenNI/Data/SamplesConfig.xml"
using namespace cv;
using namespace xn;

//openNIのための宣言・定義
//マクロ定義
#define KINECT_IMAGE_WIDTH 640
#define KINECT_IMAGE_HEGIHT 480
#define KINECT_DEPTH_WIDTH 640
#define KINECT_DEPTH_HEGIHT 480

DepthGenerator depthGenerator;// depth context
ImageGenerator imageGenerator;//image context
DepthMetaData depthMD;
ImageMetaData imageMD;
Context context;

Mat image(480,640,CV_8UC3);
Mat depth(480,640,CV_16UC1);  
//ポイントクラウドの座標
Mat pointCloud_XYZ(480,640,CV_32FC3,cv::Scalar::all(0));

void retrievePointCloudMap(Mat &depth,Mat &pointCloud_XYZ);    //3次元ポイントクラウドのための座標変換
void drawPointCloud(Mat &rgbImage,Mat &pointCloud_XYZ);        //ポイントクラウド描画

//openGLのための宣言・定義
//---変数宣言---
int FormWidth = 640;
int FormHeight = 480;
int mButton;
float twist, elevation, azimuth;
float cameraDistance = 0,cameraX = 0,cameraY = 0;
int xBegin, yBegin;
//---マクロ定義---
#define glFovy 45        //視角度
#define glZNear 1.0        //near面の距離
#define glZFar 150.0    //far面の距離
void polarview();        //視点変更

//描画
void display(){  
    // clear screen and depth buffer
    glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); 
    // Reset the coordinate system before modifying
    glLoadIdentity();   
    glEnable(GL_DEPTH_TEST); //「Zバッファ」を有効
    gluLookAt(0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0);   //視点の向き設定
    //wait and error processing
    context.WaitAnyUpdateAll();

    imageGenerator.GetMetaData(imageMD);
    depthGenerator.GetMetaData(depthMD);
    depthGenerator.GetAlternativeViewPointCap().SetViewPoint(imageGenerator);//ズレを補正
         
    memcpy(image.data,imageMD.Data(),image.step * image.rows);    //イメージデータを格納
    memcpy(depth.data,depthMD.Data(),depth.step * depth.rows);    //深度データを格納
      
    //3次元ポイントクラウドのための座標変換
    retrievePointCloudMap(depth,pointCloud_XYZ);

    //視点の変更
    polarview();  
    //ポイントクラウド
    drawPointCloud(image,pointCloud_XYZ);
             
    //convert color space RGB2BGR
    cvtColor(image,image,CV_RGB2BGR);     
     
    imshow("image",image);
    imshow("depth",depth);
  
    glFlush();
    glutSwapBuffers();
}
//初期化
void init(){
    context.InitFromXmlFile(SAMPLE_XML_PATH); 
    context.FindExistingNode(XN_NODE_TYPE_DEPTH, depthGenerator); 
    context.FindExistingNode(XN_NODE_TYPE_IMAGE, imageGenerator);
}
// アイドル時のコールバック
void idle(){
    //再描画要求
    glutPostRedisplay();
}
//ウィンドウのサイズ変更
void reshape (int width, int height){
    FormWidth = width;
    FormHeight = height;
    glViewport (0, 0, (GLsizei)width, (GLsizei)height);
    glMatrixMode (GL_PROJECTION);
    glLoadIdentity ();
    //射影変換行列の指定
    gluPerspective (glFovy, (GLfloat)width / (GLfloat)height,glZNear,glZFar); 
    glMatrixMode (GL_MODELVIEW);
}
//マウスの動き
void motion(int x, int y){
    int xDisp, yDisp;  
    xDisp = x - xBegin;
    yDisp = y - yBegin;
    switch (mButton) {
    case GLUT_LEFT_BUTTON:
        azimuth += (float) xDisp/2.0;
        elevation -= (float) yDisp/2.0;
        break;
    case GLUT_MIDDLE_BUTTON:
        cameraX -= (float) xDisp/40.0;
        cameraY += (float) yDisp/40.0;
        break;
    case GLUT_RIGHT_BUTTON:
  cameraDistance += (float) xDisp/40.0;
        break;
    }
    xBegin = x;
    yBegin = y;
}
//マウスの操作
void mouse(int button, int state, int x, int y){
    if (state == GLUT_DOWN) {
        switch(button) {
        case GLUT_RIGHT_BUTTON:
        case GLUT_MIDDLE_BUTTON:
        case GLUT_LEFT_BUTTON:
            mButton = button;
            break;
        }
        xBegin = x;
        yBegin = y;
    }
}
//視点変更
void polarview(){
    glTranslatef( cameraX, cameraY, cameraDistance);
    glRotatef( -twist, 0.0, 0.0, 1.0);
    glRotatef( -elevation, 1.0, 0.0, 0.0);
    glRotatef( -azimuth, 0.0, 1.0, 0.0);
}
//メイン
int main(int argc, char *argv[]){
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGB);
    glutInitWindowSize(FormWidth, FormHeight);
    glutCreateWindow(argv[0]);
    //コールバック
    glutReshapeFunc (reshape);
    glutDisplayFunc(display);
    glutIdleFunc(idle);
    glutMouseFunc(mouse);
    glutMotionFunc(motion);
    init();
    glutMainLoop();
    context.Shutdown();
    return 0;
}

//ポイントクラウド描画
void drawPointCloud(Mat &rgbImage,Mat &pointCloud_XYZ){
 static int x,y;
 glPointSize(2);
 glBegin(GL_POINTS);
 uchar *p = rgbImage.data;
 Point3f *point = (Point3f*)pointCloud_XYZ.data;
 for(y = 0;y < KINECT_DEPTH_HEGIHT;y++){
  for(x = 0;x < KINECT_DEPTH_WIDTH;x++,p += 3,point++){ 
   if(point->z == 0)
    continue;
   glColor3ubv(p);
   glVertex3f(point->x,point->y,point->z);
  }
 }
 glEnd();
}
//3次元ポイントクラウドのための座標変換
void retrievePointCloudMap(Mat &depth,Mat &pointCloud_XYZ){
 static const int size = KINECT_DEPTH_HEGIHT * KINECT_DEPTH_WIDTH;
 static XnPoint3D proj[size] = {0};
 static int x,y;
 XnPoint3D *p = proj;
 unsigned short* dp = (unsigned short*)depth.data;
 for(y = 0; y < KINECT_DEPTH_HEGIHT; y++ ){
  for(x = 0; x < KINECT_DEPTH_WIDTH; x++, p++, dp++){ 
   p->X = x;
   p->Y = y;
   p->Z = *dp * 0.001f; // from mm to meters
  }
 }
 //現実座標に変換
 depthGenerator.ConvertProjectiveToRealWorld(size, proj, (XnPoint3D*)pointCloud_XYZ.data);
}   


サンプルダウンロード

github


追記:テクスチャマップ
Kinect OpenNIによる3次元テクスチャマップ - 3次元描画

2011年9月27日火曜日

Kinect OpenNIによる3次元ポイントクラウド - 3次元描画

前回の続き
前回:Kinect OpenNIによる3次元ポイントクラウド - 3次元座標取得

前回の話でKinectからの奥行き情報から3次元座標を取得しました。

次はその3次元座標を目に見える形に描画しましょう。

ここでは3D描画にOpenGLというグラフィックスライブラリを用います。

OpenGLの導入方法を説明しません。


video







ということでサンプルどーん
#include <GL/glut.h>
#include <opencv2/opencv.hpp>
#ifdef _DEBUG
    //Debugモードの場合
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_core220d.lib")            // opencv_core
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_imgproc220d.lib")        // opencv_imgproc
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_highgui220d.lib")        // opencv_highgui
#else
    //Releaseモードの場合
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_core220.lib")            // opencv_core
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_imgproc220.lib")        // opencv_imgproc
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_highgui220.lib")        // opencv_highgui
#endif

#include <XnCppWrapper.h>
#pragma comment(lib,"C:/Program files/OpenNI/Lib/openNI.lib")

#define SAMPLE_XML_PATH "C:/Program Files/OpenNI/Data/SamplesConfig.xml"
using namespace cv;
using namespace xn;

//openNIのための宣言・定義
//マクロ定義
#define KINECT_IMAGE_WIDTH 640
#define KINECT_IMAGE_HEGIHT 480
#define KINECT_DEPTH_WIDTH 640
#define KINECT_DEPTH_HEGIHT 480

DepthGenerator depthGenerator;// depth context
ImageGenerator imageGenerator;//image context
DepthMetaData depthMD;
ImageMetaData imageMD;
Context context;

Mat image(480,640,CV_8UC3);
Mat depth(480,640,CV_16UC1);  
//ポイントクラウドの座標
Mat pointCloud_XYZ(480,640,CV_32FC3,cv::Scalar::all(0));

void retrievePointCloudMap(Mat &depth,Mat &pointCloud_XYZ);    //3次元ポイントクラウドのための座標変換
void drawPointCloud(Mat &rgbImage,Mat &pointCloud_XYZ);        //ポイントクラウド描画

//openGLのための宣言・定義
//---変数宣言---
int FormWidth = 640;
int FormHeight = 480;
int mButton;
float twist, elevation, azimuth;
float cameraDistance = 0,cameraX = 0,cameraY = 0;
int xBegin, yBegin;
//---マクロ定義---
#define glFovy 45        //視角度
#define glZNear 1.0        //near面の距離
#define glZFar 150.0    //far面の距離
void polarview();        //視点変更

//描画
void display(){  
    // clear screen and depth buffer
    glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); 
    // Reset the coordinate system before modifying
    glLoadIdentity();   
    glEnable(GL_DEPTH_TEST); //「Zバッファ」を有効
    gluLookAt(0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0);   //視点の向き設定
    //wait and error processing
    context.WaitAnyUpdateAll();

    imageGenerator.GetMetaData(imageMD);
    depthGenerator.GetMetaData(depthMD);
    depthGenerator.GetAlternativeViewPointCap().SetViewPoint(imageGenerator);//ズレを補正
         
    memcpy(image.data,imageMD.Data(),image.step * image.rows);    //イメージデータを格納
    memcpy(depth.data,depthMD.Data(),depth.step * depth.rows);    //深度データを格納
      
    //3次元ポイントクラウドのための座標変換
    retrievePointCloudMap(depth,pointCloud_XYZ);

    //視点の変更
    polarview();  
    //ポイントクラウド
    drawPointCloud(image,pointCloud_XYZ);
             
    //convert color space RGB2BGR
    cvtColor(image,image,CV_RGB2BGR);     
     
    imshow("image",image);
    imshow("depth",depth);
  
    glFlush();
    glutSwapBuffers();
}
//初期化
void init(){
    context.InitFromXmlFile(SAMPLE_XML_PATH); 
    context.FindExistingNode(XN_NODE_TYPE_DEPTH, depthGenerator); 
    context.FindExistingNode(XN_NODE_TYPE_IMAGE, imageGenerator);
}
// アイドル時のコールバック
void idle(){
    //再描画要求
    glutPostRedisplay();
}
//ウィンドウのサイズ変更
void reshape (int width, int height){
    FormWidth = width;
    FormHeight = height;
    glViewport (0, 0, (GLsizei)width, (GLsizei)height);
    glMatrixMode (GL_PROJECTION);
    glLoadIdentity ();
    //射影変換行列の指定
    gluPerspective (glFovy, (GLfloat)width / (GLfloat)height,glZNear,glZFar); 
    glMatrixMode (GL_MODELVIEW);
}
//マウスの動き
void motion(int x, int y){
    int xDisp, yDisp;  
    xDisp = x - xBegin;
    yDisp = y - yBegin;
    switch (mButton) {
    case GLUT_LEFT_BUTTON:
        azimuth += (float) xDisp/2.0;
        elevation -= (float) yDisp/2.0;
        break;
    case GLUT_MIDDLE_BUTTON:
    case GLUT_RIGHT_BUTTON:
        cameraX -= (float) xDisp/40.0;
        cameraY += (float) yDisp/40.0;
        break;
    }
    xBegin = x;
    yBegin = y;
}
//マウスの操作
void mouse(int button, int state, int x, int y){
    if (state == GLUT_DOWN) {
        switch(button) {
        case GLUT_RIGHT_BUTTON:
        case GLUT_MIDDLE_BUTTON:
        case GLUT_LEFT_BUTTON:
            mButton = button;
            break;
        }
        xBegin = x;
        yBegin = y;
    }
}
//視点変更
void polarview(){
    glTranslatef( cameraX, cameraY, cameraDistance);
    glRotatef( -twist, 0.0, 0.0, 1.0);
    glRotatef( -elevation, 1.0, 0.0, 0.0);
    glRotatef( -azimuth, 0.0, 1.0, 0.0);
}
//メイン
int main(int argc, char *argv[]){
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGB);
    glutInitWindowSize(FormWidth, FormHeight);
    glutCreateWindow(argv[0]);
    //コールバック
    glutReshapeFunc (reshape);
    glutDisplayFunc(display);
    glutIdleFunc(idle);
    glutMouseFunc(mouse);
    glutMotionFunc(motion);
    init();
    glutMainLoop();
    context.Shutdown();
    return 0;
}

//ポイントクラウド描画
void drawPointCloud(Mat &rgbImage,Mat &pointCloud_XYZ){
    int SEQ = 0;//配列番号
    int channel = rgbImage.channels();
    int step = rgbImage.step;
    glPointSize(2);   //点の大きさ
    glBegin(GL_POINTS);  //今から点を描画しますよっと
    for(int y = 0;y < KINECT_DEPTH_HEGIHT;y++){
        for(int x = 0;x < KINECT_DEPTH_WIDTH;x++){
            //Point3f &point = &pointCloud_XYZ.at<Point3f>(y,x);
            Point3f &point = ((Point3f*)(pointCloud_XYZ.data + pointCloud_XYZ.step.p[0]*y))[x];
            if(point.z == 0)  //奥行きがとれてなければ描画しない
                continue;
            SEQ = y * step + x * channel;
            glColor3f(rgbImage.data[SEQ + 0] / 255.0f,rgbImage.data[SEQ + 1] / 255.0f,rgbImage.data[SEQ + 2] / 255.0f);
            glVertex3f(point.x,point.y,point.z);
        }
    }
    glEnd();  //描画終了
}
//3次元ポイントクラウドのための座標変換
void retrievePointCloudMap(Mat &depth,Mat &pointCloud_XYZ){
    static XnPoint3D proj[KINECT_DEPTH_HEGIHT * KINECT_DEPTH_WIDTH] = {0};
    int SEQ = 0;    //配列番号
    for(int y = 0; y < KINECT_DEPTH_HEGIHT; y++ ){
        for(int x = 0; x < KINECT_DEPTH_WIDTH; x++ ){  
            proj[SEQ].X = (XnFloat)x;
            proj[SEQ].Y = (XnFloat)y;
            proj[SEQ].Z = ((unsigned short*)(depth.data + depth.step.p[0]*y))[x] * 0.001f; // from mm to meters
            SEQ++;
        }
    }
    //現実座標に変換
    depthGenerator.ConvertProjectiveToRealWorld(KINECT_DEPTH_HEGIHT*KINECT_DEPTH_WIDTH, proj, (XnPoint3D*)pointCloud_XYZ.data);
}   


ほとんどOpenGLの設定です。

ここでポイントクラウド(Point Cloud)の処理を行っているのは
retrievePointCloudMap()と
drawPointCloud()だけですね。


OpenGLの説明をするとOpenGLの話だけになってしまいそうなので、基本的なところだけ説明します。


とりあえずdisplay()の中だけ説明。

void display(){  
    // clear screen and depth buffer
    glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); 
    // Reset the coordinate system before modifying
    glLoadIdentity();   
    glEnable(GL_DEPTH_TEST); //「Zバッファ」を有効
    gluLookAt(0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0);   //視点の向き設定
    //wait and error processing
    context.WaitAnyUpdateAll();

    imageGenerator.GetMetaData(imageMD);
    depthGenerator.GetMetaData(depthMD);
    depthGenerator.GetAlternativeViewPointCap().SetViewPoint(imageGenerator);//ズレを補正
         
    memcpy(image.data,imageMD.Data(),image.step * image.rows);    //イメージデータを格納
    memcpy(depth.data,depthMD.Data(),depth.step * depth.rows);    //深度データを格納
      
    //3次元ポイントクラウドのための座標変換
    retrievePointCloudMap(depth,pointCloud_XYZ);

    //視点の変更
    polarview();  
    //ポイントクラウド
    drawPointCloud(image,pointCloud_XYZ);
             
    //convert color space RGB2BGR
    cvtColor(image,image,CV_RGB2BGR);     
     
    imshow("image",image);
    imshow("depth",depth);
  
    glFlush();
    glutSwapBuffers();
}

glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); 
画面を初期化して消してます

glLoadIdentity(); 
視点を初期に戻します(まあ、簡単に言うとだけど)

glEnable(GL_DEPTH_TEST);
「Zバッファ」を有効にします。
これにより、奥の座標にあるものは手前のものに隠れて見えなくなります。

gluLookAt(0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0);
視点の向きをZ方向に向けます。

glFlush();
今までの処理をすべて実行する。
OpenGLでは関数を呼んでも、すぐには行われずいくつか溜まってから処理を行います。
なので、ここで溜まった処理を実行するわけです。

glutSwapBuffers();
描画を行います。
この描画はダブルバッファモードのときだけです。
詳しくは調べてください。


といった感じで描画してきます。

では点を描画している drawPointCloud()の説明を行います。


void drawPointCloud(Mat &rgbImage,Mat &pointCloud_XYZ){
    int SEQ = 0;//配列番号
    int channel = rgbImage.channels();
    int step = rgbImage.step;
    glPointSize(2);   //点の大きさ

    glBegin(GL_POINTS);  //今から点を描画しますよっと
    for(int y = 0;y < KINECT_DEPTH_HEGIHT;y++){
        for(int x = 0;x < KINECT_DEPTH_WIDTH;x++){
            //Point3f &point = &pointCloud_XYZ.at<Point3f>(y,x);
            Point3f &point = ((Point3f*)(pointCloud_XYZ.data + pointCloud_XYZ.step.p[0]*y))[x];
            if(point.z == 0)    //奥行きがとれてなければ描画しない
                continue;
            SEQ = y * step + x * channel;
            glColor3f(rgbImage.data[SEQ + 0] / 255.0f,rgbImage.data[SEQ + 1] / 255.0f,rgbImage.data[SEQ + 2] / 255.0f);
            glVertex3f(point.x,point.y,point.z);
        }
    }
    glEnd();  //描画終了
}


drawPointCloud(Mat &rgbImage,Mat &pointCloud_XYZ)
引数にRGBイメージと3次元座標を格納したMatクラスを入れます


glPointSize();
描画する点の大きさを設定します。

glBegin(GL_POINTS);
いまからglEnd()まで点を描画する処理を行いますよっということを意味する

で、for文の中

glColor3f();
点の色をr,g,bで設定

 glVertex3f();
引数のx,y,zに点を描画


とまあ、こんな感じで3次元ポイントクラウドを表現します。

サンプルプログラムはマウスで視点を変更することができます。

もっといろいろしてみたい!っという人はOpenGLを勉強してください。


んー、次はテクスチャマップでもしようかな?



追記:高速化目指しました。
Kinect OpenNIによる3次元ポイントクラウド - 3次元描画 高速化


追記:テクスチャマップ
Kinect OpenNIによる3次元テクスチャマップ - 3次元描画


2011年9月26日月曜日

Kinect OpenNIによる3次元ポイントクラウド - 3次元座標取得





Kinect Hackといえば点で部屋を3次元復元する動画が有名ですよね。


誰もが最初にしてみたいと思うはず!たぶん!


ということでOpenNIを使ってKinectセンサで取得した奥行き情報から3次元位置を求める方法を紹介します。

これも実はOpenNIを使えば簡単にできます。



この関数でできます。
DepthGenerator.ConvertProjectiveToRealWorld(座標数,奥行き情報の配列,現実座標の配列);


ということでサンプルプログラム


#include <opencv2/opencv.hpp>
#ifdef _DEBUG
    //Debugモードの場合
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_core220d.lib")            // opencv_core
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_imgproc220d.lib")        // opencv_imgproc
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_highgui220d.lib")        // opencv_highgui
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_objdetect220d.lib")    // opencv_objdetect
#else
    //Releaseモードの場合
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_core220.lib")            // opencv_core
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_imgproc220.lib")        // opencv_imgproc
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_highgui220.lib")        // opencv_highgui
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_objdetect220.lib")    // opencv_objdetect
#endif

#include <XnCppWrapper.h>
#pragma comment(lib,"C:/Program files/OpenNI/Lib/openNI.lib")

#define SAMPLE_XML_PATH "C:/Program Files/OpenNI/Data/SamplesConfig.xml"
using namespace cv;
using namespace xn;

//マクロ定義
#define KINECT_IMAGE_WIDTH 640
#define KINECT_IMAGE_HEGIHT 480
#define KINECT_DEPTH_WIDTH 640
#define KINECT_DEPTH_HEGIHT 480

DepthGenerator depthGenerator;// depth context
ImageGenerator imageGenerator;//image context
DepthMetaData depthMD;
ImageMetaData imageMD;

void retrievePointCloudMap(Mat &depth,Mat &pointCloud_XYZ);

int main()
{
    Context context;
    EnumerationErrors errors;

    context.InitFromXmlFile(SAMPLE_XML_PATH); 
    context.FindExistingNode(XN_NODE_TYPE_DEPTH, depthGenerator); 
    context.FindExistingNode(XN_NODE_TYPE_IMAGE, imageGenerator);

    Mat image(480,640,CV_8UC3);
    Mat depth(480,640,CV_16UC1);  
    //ポイントクラウドの座標
    Mat pointCloud_XYZ(480,640,CV_32FC3,cv::Scalar::all(0));

    int key = 0;
    bool isWarp=false;
    while (key!='q')
    {
        //wait and error processing
        context.WaitAnyUpdateAll();

        imageGenerator.GetMetaData(imageMD);
        depthGenerator.GetMetaData(depthMD);
        depthGenerator.GetAlternativeViewPointCap().SetViewPoint(imageGenerator);//ズレを補正
         
        memcpy(image.data,imageMD.Data(),image.step * image.rows);    //イメージデータを格納
        memcpy(depth.data,depthMD.Data(),depth.step * depth.rows);    //深度データを格納
      
        //3次元ポイントクラウドのための座標変換
        retrievePointCloudMap(depth,pointCloud_XYZ);
             
        //convert color space RGB2BGR
        cvtColor(image,image,CV_RGB2BGR);     
     
        imshow("image",image);
        imshow("depth",depth);
        key = waitKey(33);
    }
    context.Shutdown();
    return 0;
}
//3次元ポイントクラウドのための座標変換
void retrievePointCloudMap(Mat &depth,Mat &pointCloud_XYZ){
    static XnPoint3D proj[KINECT_DEPTH_HEGIHT * KINECT_DEPTH_WIDTH] = {0};
    int SEQ = 0;    //配列番号
    for(int y = 0; y < KINECT_DEPTH_HEGIHT; y++ ){
        for(int x = 0; x < KINECT_DEPTH_WIDTH; x++ ){  
            proj[SEQ].X = (XnFloat)x;
            proj[SEQ].Y = (XnFloat)y;
            proj[SEQ].Z = ((unsigned short*)(depth.data + depth.step.p[0]*y))[x] * 0.001f; // from mm to meters
            SEQ++;
        }
    }
    //現実座標に変換    depthGenerator.ConvertProjectiveToRealWorld(KINECT_DEPTH_HEGIHT*KINECT_DEPTH_WIDTH, proj, (XnPoint3D*)pointCloud_XYZ.data);
}   

今回
void retrievePointCloudMap(Mat &depth,Mat &pointCloud_XYZ);

という関数を作りました。

これは引数に、深度画像と変換した現実座標を格納するMatクラスを渡しています。

現実座標を格納するMatクラスは
Mat pointCloud_XYZ(480,640,CV_32FC3,cv::Scalar::all(0));

と宣言しています。

CV_32FC3で32 ビット 3チャンネル(x,y,z float型)を意味します。


なので、このプログラムでは深度データ取得後

retrievePointCloudMap(depth,pointCloud_XYZ);
を呼ぶことにより。

depthからの情報でpointCloud_XYZに変換された3次元座標の情報を格納することができます。



3次元座標にアクセスするにはOpenCV2のcv::Matから画素を取り出すで紹介した方法で取り出します。


for(int y = 0 ; y < pointCloud_XYZ.rows; y++){
    for(int x = 0 ; x < pointCloud_XYZ.cols; x++){
        Point3f &point = pointCloud_XYZ.at<Point3f>(y,x);
        printf("%f %f %f\n",point.x,point.y,point.z);  //座標を表示
    }
}

または
for(int y = 0 ; y < pointCloud_XYZ.rows; y++){
    for(int x = 0 ; x < pointCloud_XYZ.cols; x++){
        Point3f &point = ((Point3f*)(data + step.p[0]*y))[x];
        printf("%f %f %f\n",point.x,point.y,point.z);  //座標を表示
    }
}



次回は取り出した3次元位置座標をOpenGLで3D描画する方法を紹介しようかと思います。

ポイントクラウドというやつですね。
次回:Kinect OpenNIによる3次元ポイントクラウド - 3次元描画

2011年9月22日木曜日

9leap投稿

まねまねしょーぶ!!




今回は「9leap 9Days コミPo! Challenge」というのに投稿です。

http://9leap.net/info/comipo



「コミPo!」っというのは3Dモデルを使って簡単に漫画を作れるようにしたソフトウェアです。

このソフトを使って出力した画像を用いてゲーム制作というのが今回のお題です。


「コミPo!」

これ、なかなか便利

簡単に思った構図ができるので面白いです


漫画の構図考える時にぱっと作ってみてトレスしてもそれっぽいのができるんじゃないでしょうか

絵が描けない人にはいい良さ気なソフトになりそうです。


でも、漫画描きたい人は絵を描くプロセスを楽しみそうだから絵を描く人にとっては邪道?

OpenCV2.xのcv::Matから画素を取り出す

OpenCV2.xから実装されたcv::Matクラス

まだ、あまり2.xの参考書がないので画素アクセスの方法がわからなくて当初困りました。

ということでcv::Matの画素アクセスの方法について紹介します。



まず、簡単な方法から
1.「Mat::at」を使う

// グレースケール画像(8bit)画像
for(int y = 0 ; y < image.rows; y++){
    for(int x = 0 ; x < image.cols; x++){
        image.at( y, x ) = 255;
    }
}

// RGB画像(24bit)画像
for(int y = 0 ; y < image.rows; y++){
    for(int x = 0 ; x < image.cols; x++){
        Vec3b &p = image.at<uchar>( y, x );
        p[ 0 ] = 0;
        p[ 1 ] = 255;
        p[ 2 ] = 255;
    }
}


と、簡単ですね。


しかし、Mat::atはどうやらオーバーヘッドが大きく、処理が遅いという欠点があります。

おそらくMat::at内でエラー処理などを行っているからでしょうか。

では次に画素に直接アクセスしてみます。


2.「Mat.data」を使う

Mat.dataには画素が格納されています。
ここに直接アクセスしてみましょう。


// グレースケール画像(8bit)画像
for(int y = 0 ; y < image.rows; y++){
    for(int x = 0 ; x < image.cols; x++){
        image.data[y * image.cols + x] = 255;
    }
}

// RGB画像(24bit)画像
for(int y = 0 ; y < image.rows; y++){
    for(int x = 0 ; x < image.cols; x++){
        image.data[y * image.cols + x * image.step + 0] = 0;
        image.data[y * image.cols + x * image.step + 1] = 255;
        image.data[y * image.cols + x * image.step + 2] = 255;
    }
}


これである程度の速度は確保できたでしょう。

ちなみにここに出てくる変数名を説明しておきます。

image.rows imageの高さ
image.cols imageの幅
image.step imageの幅×チャンネル数






です。



しかし、初めて扱う型の画像だとアクセスする場合どうすればいいのかわからない場合があります。

例)Mat point3D( 480, 640, CV_32FC3);
私はこの時は3次元座標を格納したい時に使いました。
しかし、どうやってアクセスすればいいのかわからなかったのです。
この時とった解決策を紹介します。

3.Mat:atの中身をパクる
Mat:atの中身を見てみましょう。

template<typename _Tp> inline _Tp& Mat::at(int i0, int i1)
{
    CV_DbgAssert( dims <= 2 && data && (unsigned)i0 < (unsigned)size.p[0] &&
        (unsigned)(i1*DataType<_Tp>::channels) < (unsigned)(size.p[1]*channels()) &&
        CV_ELEM_SIZE1(DataType<_Tp>::depth) == elemSize1());
    return ((_Tp*)(data + step.p[0]*i0))[i1];
}


なんかごちゃごちゃ書いてますが必要そうなのはreturnの後の式だけっぽいですね。

point3Dの値をMat::atで取得しようとした場合
for(int y = 0 ; y < point3D.rows; y++){
    for(int x = 0 ; x < point3D.cols; x++){
        Point3f &point = point3D.at<Point3f>(y,x);
        point.x = 0;
        point.y = 0.5;
        point.z = 1000;
    }
}

となる


しかし速度が遅い


ということでMat:atの中身をパクってみよう

for(int y = 0 ; y < point3D.rows; y++){
    for(int x = 0 ; x < point3D.cols; x++){
        Point3f &point = ((Point3f*)(data + step.p[0]*y))[x];
        point.x = 0;
        point.y = 0.5;
        point.z = 1000;
    }
}

すると速度が上がりました


ということでMat::atを使わずMatのアクセス速度を高速化することに成功しました!!





他にも調べるとアクセス高速化の方法はいっぱいあるので、自分のプログラムにあったものを探してください。



参考
Miyabiarts.net

OpenNIでRGB画像と深度画像のズレを直す

KinectはRGB画像と深度画像を取得するセンサの位置が物理的に異なるため、取得する画像にはズレが存在します。

そのズレを直す関数がOpenNIには備わっています

それは
「DepthGenerator.GetAlternativeViewPointCap().SetViewPoint()」という関数です。
これを「DepthGenerator.GetMataData()」の後に呼ぶだけです。

#include <opencv2/opencv.hpp>
#ifdef _DEBUG
    //Debugモードの場合
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_core220d.lib")            // opencv_core
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_imgproc220d.lib")        // opencv_imgproc
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_highgui220d.lib")        // opencv_highgui
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_objdetect220d.lib")    // opencv_objdetect
    //以下、必要に応じて追加
    //#pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_ml220d.lib")            // opencv_ml
    //#pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_features2d220d.lib")    // opencv_features2d
    //#pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_video220d.lib")        // opencv_video
    //#pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_calib3d220d.lib")        // opencv_calib3d
    //#pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_flann220d.lib")        // opencv_flann
    //#pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_contrib220d.lib")        // opencv_contrib
    //#pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_legacy220d.lib")        // opencv_legacy
    //#pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_gpu220d.lib")            // opencv_gpu
#else
    //Releaseモードの場合
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_core220.lib")            // opencv_core
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_imgproc220.lib")        // opencv_imgproc
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_highgui220.lib")        // opencv_highgui
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_objdetect220.lib")    // opencv_objdetect
    //以下、必要に応じて追加
    //#pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_ml220.lib")            // opencv_ml
    //#pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_features2d220.lib")    // opencv_features2d
    //#pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_video220.lib")        // opencv_video
    //#pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_calib3d220.lib")        // opencv_calib3d
    //#pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_flann220.lib")        // opencv_flann
    //#pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_contrib220.lib")        // opencv_contrib
    //#pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_legacy220.lib")        // opencv_legacy
    //#pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_gpu220.lib")            // opencv_gpu
#endif

#include <XnCppWrapper.h>
#pragma comment(lib,"C:/Program files/OpenNI/Lib/openNI.lib")

#define SAMPLE_XML_PATH "C:/Program Files/OpenNI/Data/SamplesConfig.xml"
using namespace cv;
using namespace xn;

int main()
{
    Context context;
    EnumerationErrors errors;

    context.InitFromXmlFile(SAMPLE_XML_PATH);
   
    DepthGenerator depthGenerator;// depth context
    context.FindExistingNode(XN_NODE_TYPE_DEPTH, depthGenerator);
   
    ImageGenerator imageGenerator;//image context
    context.FindExistingNode(XN_NODE_TYPE_IMAGE, imageGenerator);

    DepthMetaData depthMD;
    ImageMetaData imageMD;

    Mat image(480,640,CV_8UC3);
    Mat depth(480,640,CV_16UC1);
    int key = 0;
    bool isWarp=false;
    while (key!='q')
    {
        //wait and error processing
        context.WaitAnyUpdateAll();

        imageGenerator.GetMetaData(imageMD);
        depthGenerator.GetMetaData(depthMD);
        depthGenerator.GetAlternativeViewPointCap().SetViewPoint(imageGenerator);//ズレを補正
           
        memcpy(image.data,imageMD.Data(),image.step * image.rows);    //イメージデータを格納
        memcpy(depth.data,depthMD.Data(),depth.step * depth.rows);    //深度データを格納
               
        //convert color space RGB2BGR
        cvtColor(image,image,CV_RGB2BGR);       
       
        imshow("image",image);
        imshow("depth",depth);
        key = waitKey(33);
    }
    context.Shutdown();
    return 0;
}


これで、ズレを補正された深度画像を取得することができます。

参考
Kinect Tips
画像処理ソリューション