メインコンテンツまでスキップ
バージョン: v24.05

GenDCデータの解析

このチュートリアルでは、GenDCセパレータライブラリの使い方を学びます。 デバイスのデータ形式がGenDC以外の場合(一般的なカメラが画像を取得する場合)、次のチュートリアルページ非GenDCバイナリデータの解析を参照してください。

デバイスがGenDC形式でない場合は、9つのうち6つが有効なコンポーネントかつ、4つの異なるセンサーデータを含むサンプルGenDCをダウンロードし、本チュートリアルで利用することができます。

前提条件

  • GenDCセパレータ(SDKパッケージに含まれています)
  • GenDCデータ(前のチュートリアルで取得したもの、またはこのページからダウンロードしたサンプル)

チュートリアル

前のチュートリアルでは、GenDCデータをバイナリファイルに保存する方法を学びました。今回はそのデータをロードし、コンテナを解析して、センサーの記述子からいくつかの情報を取得します。

注記

このチュートリアルで使用するGenDCには少なくとも1つの画像データコンポーネントが含まれていると仮定します。前のチュートリアルで保存したデータがない場合は、このページからサンプルデータをダウンロードしてください。

GenDC

GenDC(Generic Data Container)は、EMVA(European Machine Vision Association)によって定義されています。その名の通り、データの次元、メタデータ、画像シーケンス・バーストかどうかに関わらず、カメラデバイスが定義するあらゆる種類のデータを含むことができます。

フォーマットルールは公式ドキュメントで定義されていますが、GenDCセパレータはコンテナ全体を簡単に解析するのに役立ちます。

GenDCの概要を学びたい場合は、GenDCの概念と大まかな構造を説明するこのページをチェックしてください。

バイナリファイルの探索

前のチュートリアルで保存したバイナリファイルを使用する場合、ディレクトリ名はtutorial_save_gendc_XXXXXXXXXXXXXXで、バイナリファイルのプレフィックスはgendc0-です。

std::string directory_name = "tutorial_save_gendc_XXXXXXXXXXXXXX";
std::string prefix = "gendc0-";
備考

このチュートリアルの最後に提供される完全なコードは、コマンドラインオプションがついています。

-d tutorial_save_gendc_XXXXXXXXXXXXXXまたは--directory tutorial_save_gendc_XXXXXXXXXXXXXXオプションを設定してチュートリアルプログラムを実行すると、directory_nameが自動的に設定されます。

また、チュートリアルページの上部で提供されているサンプルデータoutput.binを使用する場合は、-uまたは--use-dummy-dataオプションを追加するだけで、ダウンロードされたoutput.binをプログラムが検索するようになります。

以下のスニペットでは、指定されたプレフィックスで始まるすべてのバイナリファイルをディレクトリから取得し、それらを録画された順番に並び替えています。

std::vector<std::string> bin_files;
for (const auto& entry : std::filesystem::directory_iterator(directory_name)) {
if (entry.path().filename().string().find(prefix) == 0 && entry.is_regular_file() && entry.path().extension() == ".bin") {
bin_files.push_back(entry.path().filename().string());
}
}

//バイナリファイルをセンサー0-0.bin、センサー0-1.bin、センサー0-2.bin...に再注文
std::sort(bin_files.begin(), bin_files.end(), [](const std::string& a, const std::string& b) {
return extractNumber(a) < extractNumber(b);
});

これでbin_files内のすべての順序付けられたバイナリファイルをforループで処理する準備が整いました。

for (const auto& filename : bin_files){

}

バイナリファイルを開いて読み込む

forループ内のバイナリファイルはfilenameで表されています。このバイナリファイルをifstreamで開きます。

std::filesystem::path jth_bin= std::filesystem::path(directory_name) / std::filesystem::path(filename);
std::ifstream ifs(jth_bin, std::ios::binary);

ifstreamをファイルの末尾に設定することで、バイナリファイル全体のサイズを取得します。

ifs.seekg(0, std::ios::end);
std::streampos filesize = ifs.tellg();

コンテンツをロードするためにifstreamをファイルの先頭に戻すことを忘れないでください。

ifs.seekg(0, std::ios::beg);
char* filecontent = new char[filesize];

バイナリファイルの解析

GenDCセパレータには、データにGenDC署名が含まれているかどうかを確認するisGenDCがあります。全データを解析する前に、データが実際にGenDC形式で保存されているかどうかを確認できます。

isGenDC(filecontent);

trueを返す場合、データからGenDC ContainerHeaderオブジェクトを作成できます。GenDCの用語(ContainerComponentPartなど)について学ぶには、このページをチェックしてください。

ContainerHeader gendc_descriptor = ContainerHeader(filecontent);

このオブジェクトには、GenDC Descriptorに書かれたすべての情報が含まれているので、Descriptorのサイズとデータのサイズを取得できます。

int32_t descriptor_size = gendc_descriptor.getDescriptorSize();
int64_t container_data_size = gendc_descriptor.getDataSize();

コンテナ全体のサイズは、このDescriptorSizeとDataSizeの合計です。元のデータのオフセットとして合計を追加することで次のコンテナ情報をロードすることができます。

ContainerHeader next_gendc_descriptor= ContainerHeader(filecontent + descriptor_size + data_size);

このチュートリアルでは、どのComponentに画像センサデータが含まれているかを検出し、センサデータの情報(チャンネル数、データの次元、バイト深度など)を取得し、最終的にOpenCVで画像を表示します。

まず、getFirstComponentIndexByTypeID()を使用して最初の有効な画像データComponentを見つける必要があります。これにより、パラメータと一致するデータタイプの最初の有効なデータComponentのインデックスが返されます。-1を返す場合、有効なデータがセンサー側に設定されていないことを意味します。

ここに、GenICamによって定義されたいくつかのデータタイプがあります。

データタイプキーデータタイプID値
未定義0
強度1
赤外線2
紫外線3
距離4
......
メタデータ0x8001

参照: GenICam標準機能命名規約の4.13ComponentIDValue

画像(すなわち輝度)データを取得するために、データタイプID値として1を使用します。

// 最初の有効な画像コンポーネントを取得
int32_t image_component_index = gendc_descriptor.getFirstComponentIndexByTypeID(1);

これで、画像データを含むComponentのヘッダー情報にアクセスできます。

ComponentHeader image_component = gendc_descriptor.getComponentByIndex(image_component_index);

Componentには1つ以上のPartがあります。forループを使用してそれらを反復処理できます。画像ComponentPartの数は通常、カラーチャンネルの数です。例えば、モノクロ空間画像では1、一般的なRGBなどのカラーピクセルフォーマットでは3です。

int part_count = image_component.getPartCount();
for (int idx = 0; idx < part_count; idx++) {
PartHeader part = image_component.getPartByIndex(idx);
int part_data_size = part.getDataSize();

画像データをコピーするには、各Partのデータを格納するバッファを作成する必要があります。

uint8_t* imagedata;
imagedata = new uint8_t [part_data_size];
part.getData(reinterpret_cast<char *>(imagedata));

現在、画像データは1D配列形式のimagedataにあります。プレビュー画像を表示するためは2Dにしなければならないので、次の情報を確認する作業が必要です。

  • 高さ
  • カラーチャンネル
  • バイト深度

getDimension()は、幅と高さを含むベクターを返します。

std::vector <int32_t> image_dimension = part.getDimension();

バイト深度を決定するには、データの総サイズと上記の取得された次元値から計算できます。

int32_t bd = part_data_size / WxH;

これで、データを1D配列imagedataから画像形式のcv::Matimgmemcpyでコピーして表示することができます。

cv::Mat img(image_dimension[1], image_dimension[0], CV_8UC1);
std::memcpy(img.ptr(), imagedata, datasize);
cv::imshow("First available image component", img);

cv::waitKeyEx(1);

CvMatの型システムは、バイト深度とカラーチャンネルによって異なります。一般的にフォーマットはCV_<bit-depth><signed/unsigned>C<チャンネル数>です。詳細は公式ドキュメントを参照してください。

このページの上部で提供されているサンプルデータを使用すると、次のような画像が表示されます。

サンプル画像.

サンプルデータの例

前のセクションでは、GenDCセパレータAPIを使用してGenDCデータを解析する一般的なアイデアを学びました。今度は、このページの上部(またはこちら)で提供されているサンプルデータを使用して、いくつかの非画像データを処理できます。

サンプルデータ構造

画像センサデータで行ったように、1. チャンネル数 2. データの次元 3. データのバイト深度を取得することでユーザアプリケーションで使用可能になります。Pythonのチュートリアルでは視覚化のためのコードも提供しています Visualize GenDC data

サンプルデータに入っているすべての非画像データのデータタイプはメタデータであるため、TypeIdで目的のComponentを見つけることはできません。各コンポーネントにアクセスするためには別の情報が必要です。

通常、各Componentにはデータを検出するための一意のSourceIdがあります。今回は、ターゲットComponentを探すためにgetFirstComponentIndexBySourceIdを使用します。

コンポーネントインデックスセンサータイプ有効性SourceIdTypeId
0画像有効0x10011 (Intensity)
1音声有効0x20010x8001 (Metadata)
2アナログ1有効0x30010x8001 (Metadata)
3アナログ2有効0x30020x8001 (Metadata)
4アナログ3有効0x30030x8001 (Metadata)
5PMOD有効0x40010x8001 (Metadata)
6追加無効0x00010x8001 (Metadata)
7該当なし無効0x50010x8001 (Metadata)
8該当なし無効0x60010x8001 (Metadata)

オーディオデータの取得

目的のComponentのインデックスがわかると、画像Componentと同様にComponentHeaderオブジェクトを作成できます。

int audio_component_index = gendc_descriptor.getFirstComponentIndexBySourceId(0x2001);
ComponentHeader audio_component = gendc_descriptor.getComponentByIndex(audio_component_index);

このContainerには2つのパーツがあり、それぞれにオーディオデータの左チャンネル右チャンネルが含まれています。

int audio_part_count = audio_component.getPartCount();
備考

一部のオーディオセンサーは、LchとRchを交互に同じPartに格納するインターリーブオーディオを使用します。 この場合、audio_component.getPartCount()は1を返しますが、データを使用するには2次元に再構築する必要があります。

画像Componentと同様に、各Partのデータサイズと次元を確認します。

for (int idx = 0; idx < audio_part_count; idx++) {
PartHeader part = audio_component.getPartByIndex(idx);
int part_data_size = part.getDataSize();
std::vector <int32_t> audio_dimension = part.getDimension();

ここで、audio_dimension{800}です。通常、GenDCの画像Componentは1フレームのデータを格納しますが、他のセンサーデータはその画像データを取得するのにかかる時間内に取得されたサンプルを格納します。例えば、画像が60fpsで取得されている場合、1/60秒以内に取得されたサンプルのみが格納されます。このオーディオデータは48kHzでサンプリングされ、1/60秒内に800サンプルが取得されるため、次元は800になります。

このオーディオデータの型を知るためには、データのサイズと次元で計算できるバイト深度が必要です。

int32_t bd = part_data_size / 800;

part_data_sizeが1600であるため、バイト深度は2であり、型はint16_tとなります。

このデータを前述のPythonチュートリアルコードで視覚化すると、以下のプロットが確認できます。

サンプル画像

アナログデータの取得

このGenDCデータには、識別子0x30010x3002、および0x3003を持つ3つのアナログセンサがあります。getFirstComponentIndexBySourceId()を使用してComponentのインデックスを見つけることができます。

int analog_component_index = gendc_descriptor.getFirstComponentIndexBySourceId(0x3001);
ComponentHeader analog_component = gendc_descriptor.getComponentByIndex(analog_component_index);

各アナログセンサのComponentには1つのPartが含まれており、これはgetPartCount()で確認できます。

int analog_part_count = analog_component.getPartCount();

データサイズ、次元(サンプル数)、バイト深度を知るためにPartを取得できます。

for (int idx = 0; idx < analog_part_count; idx++) {
PartHeader part = analog_component.getPartByIndex(idx);
int part_data_size = part.getDataSize();
std::vector <int32_t> analog_dimension = part.getDimension();
}

このデータを前述のPythonチュートリアルコードで視覚化すると、以下のプロットが確認できます。

サンプル画像

PMODデータの取得

このGenDCデータには1つのPMOD加速度センサがあり、これは加速度計でx、y、zの座標情報を記録します。したがって、Partsの数は3です。

int pmod_component_index = gendc_descriptor.getFirstComponentIndexBySourceId(0x4001);
ComponentHeader pmod_component = gendc_descriptor.getComponentByIndex(pmod_component_index);
int pmod_part_count = pmod_component.getPartCount();
備考

一部の加速度センサーはインターリーブ構造を使用し、X、Y、Zの座標を交互に同じPartに格納します。 この場合、pmog_component.getPartCount()は1を返しますが、データを使用するには3次元(または1つのダミーチャンネルを追加して4次元)に再構築する必要があります。

データサイズ、次元(サンプル数)、バイト深度を知るためにPartを取得できます。

for (int idx = 0; idx < pmod_part_count; idx++) {
PartHeader part = pmod_component.getPartByIndex(idx);
int part_data_size = part.getDataSize();
std::vector <int32_t> pmod_dimension = part.getDimension();
...

Partにはそれぞれx、y、zのデータが含まれており、前述のPythonチュートリアルコードで視覚化すると、以下のプロットが確認できます。

サンプル画像

Typespecificの使用

GenDCのTypeSpecificフィールドに格納されたデバイス固有のデータにアクセスしたいときはgetTypeSpecificByIndex()を使う必要があります。

例えば、以下のGenDCデータには、8バイトのTypeSpecific3の下位4バイトにframecountデータが含まれています。

TypeSpecificはN=1、2、3...から始まり、インデックスは0、1、2...なので、TypeSpecific3のインデックスは2です。

int64_t typespecific3 = part.getTypeSpecificByIndex(2);
int32_t framecount = static_cast<int32_t>(typespecific3 & 0xFFFFFFFF);
std::cout << "Framecount: " << framecount<< std::endl;

完全なコード

このチュートリアルで使用される完全なコードはこちらです。