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の用語(Container、Component、Partなど)について学ぶには、このページをチェックしてください。
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ループを使用してそれらを反復処理できます。画像ComponentのPartの数は通常、カラーチャンネルの数です。例えば、モノクロ空間画像では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::Matimg
にmemcpy
でコピーして表示することができます。
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
を使用します。
コンポーネントインデックス | センサータイプ | 有効性 | SourceId | TypeId |
---|---|---|---|---|
0 | 画像 | 有効 | 0x1001 | 1 (Intensity) |
1 | 音声 | 有効 | 0x2001 | 0x8001 (Metadata) |
2 | アナログ1 | 有効 | 0x3001 | 0x8001 (Metadata) |
3 | アナログ2 | 有効 | 0x3002 | 0x8001 (Metadata) |
4 | アナログ3 | 有効 | 0x3003 | 0x8001 (Metadata) |
5 | PMOD | 有効 | 0x4001 | 0x8001 (Metadata) |
6 | 追加 | 無効 | 0x0001 | 0x8001 (Metadata) |
7 | 該当なし | 無効 | 0x5001 | 0x8001 (Metadata) |
8 | 該当なし | 無効 | 0x6001 | 0x8001 (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データには、識別子0x3001
、0x3002
、および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;
完全なコード
このチュートリアルで使用される完全なコードはこちらです。