【C++】CSVファイルに保存されたモーションキャプチャのデータを可視化する【GLFW/OpenGL】

スポンサーリンク
パソコンC++
スポンサーリンク

はじめに

研究室にモーションキャプチャのデータがあったので、せっかくなので可視化しようと思って作ってみました。

線を引こうかとも思ったのですが、あいにくモーションキャプチャのデータ点がはっきりと書いてなく調べるもの面倒だったので、データ点を表示するまでしか実装してません。

手抜き感があって申し訳ないですが、あまり時間的余裕がないので勘弁ください(m´・ω・`)m ゴメン…

コメントアウトをつけたので、ソースコードを参考にいい感じに作り変えてもらえばいいと思います。

Windows10(VisualStudioCommunity2019)での動作確認はしましたが、他では確認していません。

ソースコード

見にくかったらすみません。あと書き方が古いかも。

GLFWのバージョンをなんとなく4.0にしていますがなんとなくです。

#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include <sstream>
#include <gl/glew.h>
#include <GLFW/glfw3.h>



//ウィンドウサイズ
const int window_width = 960;
const int window_height = 540;
const int window_depth = 540;

//区切り文字
const char key_split = ',';

//入力ファイルパス(""でコンソール入力)
const std::string str_file_path = "";

//次元(1以下でコンソール入力)
const int dim_cap = 0;

//CSVファイル形式(0でコンソール入力)
/*
    csvファイル形式は次の形を想定
    pt:1
    t1,x11,y11,z11,x12,y12,z12,...,x1n,y1n,z1n
    t2,x21,y21,z21,x22,y22,z22,...,x2n,y2n,z2n
    ...
    tn,xn1,yn1,zn1,xn2,yn2,zn2,...,xnn,ynn,znn

    pt:2
    t1,x11,x12,.,x1n,y11,y12,.,y1n,z11,z12,.,z1n
    t2,x21,x22,.,x2n,y21,y22,.,y2n,z21,z22,.,z2n
    ...
    tn,xn1,xn2,.,xnn,yn1,yn2,.,ynn,zn1,zn2,.,znn
*/
const int csv_pt = 0;
const int pt_num = 2; //形式数

//データ点数
const int num_cap = 20; //motioncaptureのデータ点数



//自動分割(文字列,分割する文字)
std::vector<float> split_float(std::string input_split, char key_split){
    
    std::vector<float> result; //返り値
    std::istringstream iss_split(input_split); //stream
    std::string buf_split; //一時データ

    //最後まで分割
    while(std::getline(iss_split, buf_split, key_split)){

        //vectorの最後に追加
        result.push_back(std::stof(buf_split));
    }

    return result;
}

//データ読み込み(2次元vector)
int file_load(std::vector<std::vector<float>> &data_file){

    //csvファイル指定
    std::string str_path; //ファイルパス

    //ヘッダで指定があるか判定
    if(str_file_path.empty()){
        
        std::cout << "Enter file path" << std::endl;
        std::cin >> str_path; //コンソールから入力

    }else{

        str_path = str_file_path; //ヘッダで指定
    }

    //csv読み込み
    std::ifstream ifs_file(str_path); //入力ファイルストリーム

    //読み込みエラー処理
    if(!ifs_file){

        std::cerr << "Can't open the file" << std::endl;
        return -1;
    }

    std::string line_file; //1行の文字列

    //行が取り出せなくなるまでループ
    while(std::getline(ifs_file, line_file)){

        //分割 data_fileに追加しているのでdata_fileが元々空でない場合に注意
        data_file.push_back(split_float(line_file, key_split));
    }

    return 0;
}

//ファイル形式の統一(2次元vector,csvpt,次元):pt1に統一
void file_unification(std::vector<std::vector<float>> &data_file, int pt_file, int dim_file){

    //データのコピーを作成
    std::vector<std::vector<float>> copy_file = data_file;

    //csvptで分けて処理
    switch(pt_file){
        case 2:
            //コピーを使って書き換える
            for(int i = 0; i < copy_file.size(); i++){ //行の走査
                for(int j = 0; j < num_cap; j++){ //データ点数の走査
                    for(int k = 0; k < dim_file; k++){ //次元数の走査

                        data_file[i][j * dim_file + k + 1] = copy_file[i][j + k * num_cap + 1]; //書き換え
                    }
                }
            }
            break;
        default:
            break;
    }
}



//点の描画(次元,データ配列,サイズ:デフォルト引数)
void render_point(int dim_point, float *array_point, float size_point = 7.0f){
    
    //点の設定
    glVertexPointer(dim_point, GL_FLOAT, 0, array_point); //(次元,データ型,配列間隔,配列ポインタ)
    glPointSize(size_point); //サイズ
    glColor4f(1.0f, 1.0f, 1.0f, 1.0f); //色

    //描画設定
    glMatrixMode(GL_MODELVIEW); //モデルビュー行列
    glEnableClientState(GL_VERTEX_ARRAY); //頂点配列の有効化
    glDrawArrays(GL_POINTS, 0, 1); //(図形,頂点配列のindex,頂点数)
    glDisableClientState(GL_VERTEX_ARRAY); //頂点配列の無効化
}

//線の描画(次元,データ配列,サイズ:デフォルト引数)使ってない
void render_lines(int dim_lines, float *array_lines, float size_lines = 2.0f){

    //点の設定
    glVertexPointer(dim_lines, GL_FLOAT, 0, array_lines); //(次元,データ型,配列間隔,配列ポインタ)
    glPointSize(size_lines); //サイズ
    glColor4f(1.0f, 1.0f, 1.0f, 1.0f); //色

    //描画設定
    glMatrixMode(GL_MODELVIEW); //モデルビュー行列
    glEnableClientState(GL_VERTEX_ARRAY); //頂点配列の有効化
    glDrawArrays(GL_LINES, 0, 2); //(図形,頂点配列のindex,頂点数)
    glDisableClientState(GL_VERTEX_ARRAY); //頂点配列の無効化
}



//MotionCaptureデータの描画(2次元vector,次元)
//データ変更に合わせて変更するのは基本的にこの部分
void render_MotionCapture(std::vector<float> row_render, int dim_render){

    float *render_p = new float[dim_render]; //点の描画用
    float *render_l = new float[dim_render * 2]; //線の描画用 まだ使ってない

    for(int i = 0; i < num_cap; i++){ //データ点数の走査
        for(int j = 0; j < dim_render; j++){ //次元数の走査
            
            render_p[j] = row_render[i * dim_cap + j + 1]*100+300; //render関数に渡す配列の生成
        }
        //点の描画
        render_point(dim_render, render_p);
    }
}



//メイン関数
int main(){

    //すべて読み込んでから出力しているが,データ量によっては1行ずつ処理したほうが良い

    std::vector<std::vector<float>> data_csv; //csvデータ格納配列

    //ファイルロード
    if(file_load(data_csv)){

        std::cerr << "Can't file load" << std::endl;
    }



    //コンソール設定
    int dim; //次元
    if(dim_cap < 2 || dim_cap > 3){

        do{
            std::cout << "Enter dim" << std::endl;
            std::cin >> dim;
        }while(dim < 2 || dim > 3);

    }else{

        dim = dim_cap; //ヘッダに記述
    }

    int pt; //csvファイル形式
    if(csv_pt < 1 || csv_pt > pt_num){

        do{ //ptが決まるまでループ
            std::cout << "Enter csv pt" << std::endl;
            std::cin >> pt;
        } while(pt < 1 || pt> pt_num);

    }
    else{

        pt = csv_pt; //ヘッダに記述
    }

    //ファイル形式の統一
    if(pt != 1){
        file_unification(data_csv, pt, dim);
    }



    //GLFW初期化
    if(!glfwInit()){
        
        //エラー:初期化失敗(GL_FALSE)
        std::cerr << "Can't initialize glfw." << std::endl;
    
        return -1;
    }

    //ウィンドウ生成(width,height,title,monitor,share)
    GLFWwindow *const window_main = glfwCreateWindow(window_width, window_height, "MotionCaptureMonitor", NULL, NULL);
    
    if(!window_main){
        
        glfwTerminate(); //GLFWの終了処理
        
        return -1;
    }

    //バージョン4.0指定(なんとなく)
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);

    //コンテキストの作成
    glfwMakeContextCurrent(window_main); //処理対象の選択
    glfwSwapInterval(1); //垂直同期タイミング待ち

    //GLEW初期化
    if(glewInit()){

        //エラー:初期化失敗(!GLEW_OK)
        std::cerr << "Can't initialize glew." << std::endl;

        return -1;
    }

    //背景色(RGBA)の指定
    glClearColor(0.2f, 0.2f, 0.2f, 0.0f); //灰色(なんとなく)

    // 描画範囲の指定
    glMatrixMode(GL_PROJECTION); //射影行列
    glLoadIdentity(); //変換行列を単位行列で初期化
    glOrtho(0.0f, window_width, 0.0f, window_height, -window_depth, 0.0f); //変換の範囲指定(x,y,z)



    //ループ処理(ウィンドウが開いている間)
    while(!glfwWindowShouldClose(window_main)){ //ループ(GL_FALSE)

        //バッファのクリア
        glClear(GL_COLOR_BUFFER_BIT);

        //描画関数
        render_MotionCapture(data_csv.at(0), dim);

        //CSVデータの更新(処理済の行削除)
        data_csv.erase(data_csv.begin());

        //ダブルバッファのスワップ
        glfwSwapBuffers(window_main);

        //イベント待ち
        glfwPollEvents();

        //終了判定
        if(data_csv.size() == 0){
            std::cout << "Successful completion" << std::endl;
            break;
        }
    }



    //GLFWの終了処理
    glfwTerminate();

    return 0;
}

ソースコードの説明

このコードを少し書き換える場合に役立ちそうなところだけ抜粋して書きます。

split_float関数についてはこちらに書いてあります。

ヘッダ:グローバル変数

ソースコードではファイルパス、データの次元、ファイル形式がすべてコンソール入力になっていますが、指定することでコンソール入力を無くせます。

逆にモーションキャプチャのデータ点数や区切り文字は勝手に値を固定しています。異なる数値の場合はこの値を変更してください。

データ読み込み

file_load関数は2次元のvector配列を参照渡ししています。

ファイルパス(グローバル変数のstr_file_pathが空であればここで入力させる)を用いてcsvファイルを開き、floatに変換したデータを格納するところまでこの関数が行っています。

ファイル形式の統一

file_unification関数はcsvのファイル形式が異なる場合にファイル形式を変換する関数です。

おそらく一番オーソドックスであろう(time,x1,y1,z1,x2,y2,z2,…,xn,yn,zn)という形式が1として設定されており、後の描画でもこの形式を使って描画しています。

ほかの形式を使う場合にはここで1の形式に変換されるようにcaseを作ってください(csvが1の形式の場合は呼び出されない関数です)。

尚、caseを増やした際にはヘッダの形式数を増やすことも忘れずにお願いします。

関数の初めにデータを丸っとコピーしているcopy_file配列があるので、そちらのデータを使ってdata_file配列を書き換えるという方法が楽だと思います。

点、線の描画

こちらは単純です。どちらも引数は、データの次元、データ点の配列、太さです。

データ点の配列はfloat型の1次元配列(x1,y1,x2,y2の順で格納)です。vector配列を入れようとするとエラーが出ます。

データ点の配列は点であれば1組の座標(2次元であればxとy)、線の場合は2組の座標(始点のx,yと終点のx,y)です。

色を特定の点によって変えたい場合は引数に色情報も入れるといいのではないでしょうか。

MotionCaptureデータの描画

無駄に線を描画するための配列がありますが、気にせずに。

引数は1次元のvector配列(行ごとに描画するため一行ずつ取り出している)と次元です。

170行あたりのrender関数に渡す配列の生成の部分の後ろの「*100+300」は手に入ったデータがm単位だったので適当に決め打ちで値を入れています。

適宜データに合わせて数値を変えてください(さぼりましたすみません)。

メイン関数

あまりメイン関数については書くことがないですね。

ロードやファイル形式の統一などを先に行い、描画の設定もしたうえで、ループでは基本的に描画の処理のみを実行するようにしています。

OpenGLのバージョン4.0を指定していますがこれはなくても大丈夫です。なんとなく4.0を指定しました。

最後に

実行結果はこんな感じです。

あまり触れませんでしたが、GLFWの基本事項などについては時間があればまたまとめておきたいなと思います。

簡易実装で申し訳ないですが、参考になれば幸いです。

コメント

タイトルとURLをコピーしました