libfreenectを使ったKinect v1の開発①


ようやくUbuntuでKinect v1の深度画像を表示することができたのでメモ.
とりあえず動いたことが確認できたので, 特別な処理を施したりはしていない.

使ったもの

libfreenect
OpenCV-3.4.4

libfreenectについて

Kinectの開発というと, OpenNIが主流な気がするが. こちらはOpenNIに比べてシンプルで値の取得が速いらしい. ただし, カラー画像, 深度画像, チルトモーターなどのセンサーデータをそのまま使うことしかできない.
深度画像, カラー画像など生のデータだけほしいのであればこれで十分.

ソースコード

これを参考にして, コードを書いた.
はじめは簡単. 初期化するだけ.

    freenect_context *ctx;
    freenect_device *dev;

    //コンテキストの情報を初期化. 成功すると0を返す.
    if (freenect_init(&ctx, NULL) < 0)
    {
            std::cout << "初期化に失敗しました." << std::endl; 
            return 1;
    }

    //デバイスに接続. 成功すると0を返す.
    if (freenect_open_device(ctx, &dev, 0) < 0)
    {
            std::cout << "Kinectに接続できません." << std::endl;
            return 1;
    }

次に, コールバック関数を呼び出す.

    //depth_cb内の処理は自分で書く
    freenect_set_depth_callback(dev, depth_cb);

今回, depth_cbはこのようにした.

void depth_cb(freenect_device *dev, void *depth, uint32_t timestamp) 
{
    cv::Mat d8;
    //深度画像データを入れる
    cv::Mat temp = cv::Mat(480, 640, CV_16UC1, depth);

    //8bitグレースケール画像に変換, 4000は測定可能な最大距離
        temp.convertTo(d8, CV_8UC1, -255.0f/4000, 255.0f);
    //データをコピー
        memcpy(img.data, d8.data, 640*480);
}

はじめはvoid型のdepthへのポインタがなんだかわからなかったので, 標準出力してみた.
するとメモリアドレスが表示された. depthへのポインタは深度画像データが入ったメモリのアドレスを指しているのだろう. OpenCV以外のライブラリを使いたい場合はこのdepthをうまく使えばいい.
depth_cbでは, まずtempに深度画像データを入れる. 次に, temp画像を8bitグレースケール画像に変換, それをd8に入れる. 最後に, d8に入ったデータをimgのデータにコピーする.

次に, depthデータをKinectから受け取る.

    //データを流す
    freenect_start_depth(dev);

これで, Mat型の変数にdepthデータが入る. これを表示や加工すればOK.

コード全体

#include<iostream>
#include<libfreenect.h>
#include<opencv2/opencv.hpp>

cv::Mat img;

void depth_cb(freenect_device *dev, void *depth, uint32_t timestamp) 
{
    cv::Mat d8;
    //深度画像データを入れる
    cv::Mat temp = cv::Mat(480, 640, CV_16UC1, depth);

    //8bitグレースケール画像に変換, 4000は測定可能な最大距離
        temp.convertTo(d8, CV_8UC1, -255.0f/4000, 255.0f);
    //データをコピー
        memcpy(img.data, d8.data, 640*480);
}

int main()
{
    img = cv::Mat(480, 640, CV_8UC1);
    freenect_context *ctx;
    freenect_device *dev;

    //コンテキストの情報を初期化. 成功すると0を返す.
    if (freenect_init(&ctx, NULL) < 0)
    {
            std::cout << "初期化に失敗しました." << std::endl; 
            return 1;
    }

    //デバイスに接続. 成功すると0を返す.
    if (freenect_open_device(ctx, &dev, 0) < 0)
    {
            std::cout << "Kinectに接続できません." << std::endl;
            return 1;
    }

    //depth_cb内の処理は自分で書く
    freenect_set_depth_callback(dev, depth_cb);

    //データを流す
    freenect_start_depth(dev);
    while(freenect_process_events(ctx) >= 0 ){
        cv::imshow("Depth", img);
        if(cv::waitKey(1) == 'q'){
            break;  
        }
    }
}

実行結果

実行結果を見ると, 奥行きが少しわかりにくいので加工処理をもう少し改善できないかこれから検討する.