mengineer's blog

ニッチなネタばかりですが。

JAISDK ImageInfo内を直接アクセスする

かなり間が空きましたが、以前書いた下記ネタの続き。

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分)を読んでみましょう。

f:id:mengineer:20170929104448p:plain

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も変わることが判ります。

f:id:mengineer:20170929110509p:plain

PixelFormat 8Bitの場合

カメラの総画素数は、幅2560×高さ2048=5242880 [Pixel]です、
8Bitでは1Pixel=1ByteなのでImageSizeも5242880[Byte]になります、簡単ですね。

先頭から2560バイト分を読めば、そのまま先頭行のデータになります。

f:id:mengineer:20170929123719p:plain

Packedじゃ無い10Bit / 12Bitの場合

これらは1Pixel当たり2byteを使用しますので、ImageSizeは 5242880 × 2
= 10485760 [Byte]となります、先頭から2Byteずつ読めばOKです。

f:id:mengineer:20170929123737p:plain

10 Bit Monochrome Packedの場合

先ほどのPackedじゃ無い場合、10Bit / 12Bitに対して16Bitを使用しているため、
結果的に使っていない無駄なBitが生じます、下記の×印部分ですね。

10bitの場合 6Bitは未使用

f:id:mengineer:20170929130142p:plain

12bitの場合 4Bitは未使用

f:id:mengineer:20170929131107p:plain

これに対しPackedは、未使用のBitが無いようにデータを詰めたものです、
10 Bit Monochrome Packedでは、5Byte = 40Bit に4Pixel分を詰めています。

こんな感じです。

f:id:mengineer:20170929132939p:plain

ImageSizeは、(5242880 ÷ 4) × 5 = 6553600 [Byte]となります、
カメラからは同じ10bit出力でも、よりデータ量を小さく出来るわけです。

先頭から5Byteずつの固まりで読んで、各Pixel毎にBitシフトしたりして
データを取り出す必要が有るので、ちょっと手間が増えます。

12 Bit Monochrome Packedの場合

これも同じ考え方で3Byte = 24Bit に2Pixel分を詰め込みます、こんな感じ。

f:id:mengineer:20170929135831p:plain

ImageSizeは、(5242880 ÷ 2) × 3 = 7864320 [Byte]となります、

こちらも3Byteずつ読んで、Pixel毎にバラしていきます。

サンプルソフト

JAISDKのImageDelegeteSample というサンプルソフトに上記処理を追加しました、
先頭行(2,560個)を読んで、文字列に変換してクリップボードにコピーします。

ImageInfo 直接アクセス

主な変更点は、
・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ともテストパターン通り、右上がりに明るくなっていますね。

f:id:mengineer:20170929204217p:plain

念のため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)規格書で発見(下記引用)

f:id:mengineer:20170930102224p:plain

Mono12 PackedはPFNCに載っておらず、最終的に別のUSBカメラのマニュアルから
正解を発見しました、下記引用、JAIのGO-5000-USBのユーザーマニュアルより。
(これまでと並びが逆になっているので注意)

f:id:mengineer:20170930102808p:plain

ちなみに今回のSP-5000M-USBのマニュアルでは、下記の表記になっており、
当初これに合わせて並べ替えしたところ、おかしなデータになった次第。

f:id:mengineer:20170930103724p:plain

3Byteに2Pixel分を詰めるのは同じですが、1Pixel目のBit位置が異なってます、
後で調べて判ったのですが、GigEVisionでの12BitPackedが、この並びでした。

今回も色々ややこしいものだと痛感、何だかんだで悩むくらいなら、
PackedじゃないPixelFormatを使う&中間バッファ経由するのが一番簡単そうです。