かなり間が空きましたが、以前書いた下記ネタの続き。
JAISDK PixelFormatを指定して平均値取得
JAISDK Pixel値の取得
これらの時は、下記のようにm_ConversionBufferという中間バッファを経由して、
データを参照しましたが、PixelFormatがPackedの場合に下位ビットが欠落する?
ような現象が出ました。(上記記事参照)
void HandleImage(ref Jai_FactoryWrapper.ImageInfo ImageInfo) { 中略 // Allocate conversion buffer once if (m_ConversionBuffer.ImageBuffer == IntPtr.Zero) error = Jai_FactoryWrapper.J_Image_Malloc(ref ImageInfo, ref m_ConversionBuffer); // Then we convert into 48bpp image format error = Jai_FactoryWrapper.J_Image_FromRawToImage(ref ImageInfo, ref m_ConversionBuffer, Jai_FactoryWrapper.EColorInterpolationAlgorithm.BayerStandardMultiprocessor, 4096, 4096, 4096); 後略 }
なので、今回は直接ImageInfoの中身を参照します、判りやすくするために、
今回は白黒カメラを使用し、テストパターン出力状態にします。
カメラ SP-5000M-USB 画像サイズ 幅:2560 × 高さ:2048 [pixel]
下図のような、左から右に明るくなっていくテストパターンです、
この先頭行(2560Pixel分)を読んでみましょう。
PixelFormatとImageInfoのサイズ
その前に、ImageInfoのバッファサイズを調べてみました。
ImageInfo.ImageSizeで、画像データのサイズ(単位:バイト)が判ります、
このカメラには下記5種類のPixelFormatが有り、各々でサイズも異なってきます、
また同じBit数でもPacked無しとPackedで、サイズは異なります。(詳細は後述)
PixelFormat | ImageSize[Byte] |
---|---|
8 Bit Monochrome | 5242880 |
10 Bit Monochrome | 10485760 |
10 Bit Monochrome Packed | 6553600 |
12 Bit Monochrome | 10485760 |
12 Bit Monochrome Packed | 7864320 |
また、このサイズはPayloadSizeと同じになるようでJAISDK上でも確認出来ます、
下記でPixelFomatを変えると、PayloadSizeも変わることが判ります。
PixelFormat 8Bitの場合
カメラの総画素数は、幅2560×高さ2048=5242880 [Pixel]です、
8Bitでは1Pixel=1ByteなのでImageSizeも5242880[Byte]になります、簡単ですね。
先頭から2560バイト分を読めば、そのまま先頭行のデータになります。
Packedじゃ無い10Bit / 12Bitの場合
これらは1Pixel当たり2byteを使用しますので、ImageSizeは 5242880 × 2
= 10485760 [Byte]となります、先頭から2Byteずつ読めばOKです。
10 Bit Monochrome Packedの場合
先ほどのPackedじゃ無い場合、10Bit / 12Bitに対して16Bitを使用しているため、
結果的に使っていない無駄なBitが生じます、下記の×印部分ですね。
10bitの場合 6Bitは未使用
12bitの場合 4Bitは未使用
これに対しPackedは、未使用のBitが無いようにデータを詰めたものです、
10 Bit Monochrome Packedでは、5Byte = 40Bit に4Pixel分を詰めています。
こんな感じです。
ImageSizeは、(5242880 ÷ 4) × 5 = 6553600 [Byte]となります、
カメラからは同じ10bit出力でも、よりデータ量を小さく出来るわけです。
先頭から5Byteずつの固まりで読んで、各Pixel毎にBitシフトしたりして
データを取り出す必要が有るので、ちょっと手間が増えます。
12 Bit Monochrome Packedの場合
これも同じ考え方で3Byte = 24Bit に2Pixel分を詰め込みます、こんな感じ。
ImageSizeは、(5242880 ÷ 2) × 3 = 7864320 [Byte]となります、
こちらも3Byteずつ読んで、Pixel毎にバラしていきます。
サンプルソフト
JAISDKのImageDelegeteSample というサンプルソフトに上記処理を追加しました、
先頭行(2,560個)を読んで、文字列に変換してクリップボードにコピーします。
主な変更点は、
・32~48行 Packed用の構造体を定義
・270行目~ Pixelformat毎にImageInfo.ImageBufferをアクセスして文字列に変換
・105行目 Stopボタンが押されたらクリップボードにコピーします。
Packedで無い場合、8Bitもしくは16Bit単位でデータを読んでいるだけです。
Packedの場合は対応する構造体でキャストし、各Pixel毎の値を計算しています、
例えば12 Bit Monochrome Packedでは、下記Mono12pを使用(ソース42~48行目)
// Mono12 Packed : 12bit * 2pixel = 24bit -> 3byte struct Mono12p { public byte bData1; public byte bData2; public byte bData3; }
データを取り出しているのは、332行目からの下記処理、
3Byte(24bit)から2Pixel分のデータを取得しています。
一画素目の下位8bitが1Byte目、残り上位4bitは2Byte目の下位4bitなので、
8bit左シフトしてORすればOK、二画素目は下位4Bitが2Byte目の上位4bit、
残りの上位8Bitが3Byte目なので、同様の手法でシフトしてORしています。
// Cast IntPtr to pointer Mono12p Mono12p* mono12pArray = (Mono12p*)ImageInfo.ImageBuffer; // Get Top Row data for (i = 0; i < 2560; i += 2) { usvalue = (ushort)mono12pArray->bData1; usvalue |= (ushort)((mono12pArray->bData2 & 0x0F) << 8); sClipBd += usvalue.ToString() + "\r\n"; usvalue = (ushort)((mono12pArray->bData2 & 0xF0) >> 4); usvalue |= (ushort)(mono12pArray->bData3 << 4); sClipBd += usvalue.ToString() + "\r\n"; mono12pArray++; }
10 Bit Monochrome Packedでは、これが5Byte(40Bit)から4Pixelを取得する、
となります、少しややこしくなりますが手法は同じ、303行目~を参照して下さい。
下記は取得データ(クリップボード経由文字列)をExcelでグラフ化したものです、
各PixelFormatともテストパターン通り、右上がりに明るくなっていますね。
念のためX軸はX座標です、Y軸MAXは 8Bit:255, 10Bit:1023, 12Bit:4095となります。
また以前のネタで、12Bit Packed時 4095→4080,4087等飽和しない:下位Bit落ち?
と言ってた件も、今回のサンプルでは、無事4095まで達することを確認出来ました。
まとめ
・今回の方法で、直接Jai_FactoryWrapper.ImageInfoのバッファを参照出来る、
但しPackedの場合は、データ並びを理解して、自前で並べ替える必要が有る。
・以前の中間バッファ(m_ConversionBuffer)を使う方法だと、
SDK側で処理するので簡単、但しPacked時に下位Bitが欠落する場合有り。
単純に画像のPixel値だけを取得するなら、前者の方法でも充分良さそうです。
以下余談。
今回一番苦労したのは、Packedでのデータの並び順でした、Mono10 Packedは、
GenICamのPFNC(Pixel Format Naming Conversion)規格書で発見(下記引用)
Mono12 PackedはPFNCに載っておらず、最終的に別のUSBカメラのマニュアルから
正解を発見しました、下記引用、JAIのGO-5000-USBのユーザーマニュアルより。
(これまでと並びが逆になっているので注意)
ちなみに今回のSP-5000M-USBのマニュアルでは、下記の表記になっており、
当初これに合わせて並べ替えしたところ、おかしなデータになった次第。
3Byteに2Pixel分を詰めるのは同じですが、1Pixel目のBit位置が異なってます、
後で調べて判ったのですが、GigEVisionでの12BitPackedが、この並びでした。
今回も色々ややこしいものだと痛感、何だかんだで悩むくらいなら、
PackedじゃないPixelFormatを使う&中間バッファ経由するのが一番簡単そうです。