none
Bitmap画像を読み込み、Byte配列に入れて関数にポインタで渡す方法 RRS feed

  • 質問

  • プログラム初心者です。

    教えてください。よろしくお願いいたします。

    VS2005のC#でプログラムを作成しています。

    下の関数をC#の Form1のあるボタンを押したタイミングで呼び出しています。(DLL内の関数です)

    第1引数は、入力画像の画素データへのポインタを渡します。
    第2引数は、出力画像の画素データへのポインタを渡します。

    あるボタンを押した時に、400(W)×300(H)のBitmap画像またはTIFF画像を先に読み込んで
    関数の引数として渡したいのですが、読み込んだ画像をどんなふうに変換して引数に渡すのかが
    わかりません。(読み込んだ後、byte型に変換してポインタで渡す)


    // 読み込んだ画像(画素)データを渡してある処理をするDLL内の関数(私が作成した関数ではありません)
    short result = ImageConvert( ref byte inputImage , out byte outputImage )
    ※戻り値は、エラーNo.

    どなたか、ご教授のほどよろしくお願いいたします。

    2011年7月26日 7:25

回答

  • // 返ってくるのは 8bpp 固定ってことで良いのかな? 4 バイト境界の考慮も必要だと思うけど……。

    よくある方法は、Bitmap オブジェクトの LockBits メソッドを使ってビットマップの生データを表す BitmapData オブジェクトを取得し、その Scan0 プロパティを使用することです。ただし、この値は IntPtr 型(配列の先頭アドレスを指すポインタ)になっています。

    その ImageConvert メソッドなるものの(C# 側の)シグネチャは変更できないんでしょうか? C/C++ 的には自然でも、C# 的にはいかんともしがたいのですが。DllImport してる部分を変更しても良いのなら、引数を IntPtr に変更すれば Scan0 の値を直接使えますよ。


    • 回答の候補に設定 山本春海 2011年8月12日 4:40
    • 回答としてマーク 山本春海 2011年8月25日 6:46
    2011年7月26日 9:18
  • 外池と申します。

    確認なんですが・・・、データを渡す相手のDLL内の関数というのは、やはり、C#で作られたものなのでしょうか? (言い換えると、Managedなものでしょうか?)

    根本的には、Bitmapクラスには、Lockbitsというメソッドがあり、メモリ上の画像データのバイト列に直接アクセスするためのBitmapData型のオブジェクトを取得することができます。BitmapData型のオブジェクトにはScan0というプロパティーがあって、メモリ上の画像データの先頭へのポインターになっています。

    で、BitmapクラスのLockbitsメソッドのドキュメントには、おそらく、ご質問の内容にほぼ合致したサンプルプログラムが載っています。

    参考になれば幸いです。


    (ホームページを再開しました)
    • 回答の候補に設定 山本春海 2011年8月12日 4:41
    • 回答としてマーク 山本春海 2011年8月25日 6:46
    2011年7月26日 9:21
  • グレースケールですか。最初から質問文に含めておいて欲しかったですね。

    Hongliangさんや外池さんが紹介されている、BitmapクラスのLockBits()メソッドを使うとピクセルデータが取得できますが、この際に指定できるフォーマットはPixelFormatの中から選ぶことになります。

    残念ながら1chグレースケール8bppは存在しません。1px 2bytesのFormat16bppGrayScaleが一番適切でしょうか。1バイトずつ不要なデータがあるのでバイト配列を作り直す必要があります。もしくはFormat8bppIndexedを使用して、こちらはパレット形式なので256階調のグレースケールに戻す作業が必要あります。

    • 回答の候補に設定 山本春海 2011年8月12日 4:41
    • 回答としてマーク 山本春海 2011年8月25日 6:46
    2011年7月26日 11:56

すべての返信

  • byte配列の中はどのようなレイアウトになるのでしょうか?

    入力画像のメモリ確保は呼び出し元なのだとは思いますが、出力画像のメモリ確保は呼び出し元/呼ばれ側のどちらでしょうか? またそのメモリ解放はどちらでしょうか?

    これら条件がはっきりしないと作るにも作れないと思いますが。

    2011年7月26日 7:43
  • 佐祐理 様

    早々にご回答頂きありがとうございます。

    メモリについては、確保/開放ともに、呼び出し元が処理します。

    レイアウトについては、以下のように読み込んだ画像のWidth×Height分のメモリを確保します。

    byte[] inputImage = new byte[400*300];

    レイアウトについて、このような回答で正しいでしょうか。

    ※私はこのbyte配列に読み込んだBitmap画像データを変換して入れるという考え方をしています。

    よろしくお願いいたします。

     

     

    2011年7月26日 8:43
  • // 返ってくるのは 8bpp 固定ってことで良いのかな? 4 バイト境界の考慮も必要だと思うけど……。

    よくある方法は、Bitmap オブジェクトの LockBits メソッドを使ってビットマップの生データを表す BitmapData オブジェクトを取得し、その Scan0 プロパティを使用することです。ただし、この値は IntPtr 型(配列の先頭アドレスを指すポインタ)になっています。

    その ImageConvert メソッドなるものの(C# 側の)シグネチャは変更できないんでしょうか? C/C++ 的には自然でも、C# 的にはいかんともしがたいのですが。DllImport してる部分を変更しても良いのなら、引数を IntPtr に変更すれば Scan0 の値を直接使えますよ。


    • 回答の候補に設定 山本春海 2011年8月12日 4:40
    • 回答としてマーク 山本春海 2011年8月25日 6:46
    2011年7月26日 9:18
  • 外池と申します。

    確認なんですが・・・、データを渡す相手のDLL内の関数というのは、やはり、C#で作られたものなのでしょうか? (言い換えると、Managedなものでしょうか?)

    根本的には、Bitmapクラスには、Lockbitsというメソッドがあり、メモリ上の画像データのバイト列に直接アクセスするためのBitmapData型のオブジェクトを取得することができます。BitmapData型のオブジェクトにはScan0というプロパティーがあって、メモリ上の画像データの先頭へのポインターになっています。

    で、BitmapクラスのLockbitsメソッドのドキュメントには、おそらく、ご質問の内容にほぼ合致したサンプルプログラムが載っています。

    参考になれば幸いです。


    (ホームページを再開しました)
    • 回答の候補に設定 山本春海 2011年8月12日 4:41
    • 回答としてマーク 山本春海 2011年8月25日 6:46
    2011年7月26日 9:21
  • 1pxが1byteということはHongliangさんも触れられていますが8bppということになりますが、indexed colorなんでしょうか? もしそうならパレットとか渡さなくて大丈夫なんですかね…。それとも別形式?

    きちんと仕様が決まっているのかちょっと疑ってます。

    Hongliangさん、外池さんへ
    パレットの扱いが不明なのでScan0を直接流用できるのか不明ですが、現時点で提案されます? 固定パレットとかRGB2:3:2bitのカラーだったり、いろいろな可能性もありますが。

    2011年7月26日 9:28
  • Hongliang様

    回答頂きありがとうございます。

    わかる範囲でお答えします。

    的外れなことを言っていたら、申し訳ありません。(ただの情報提供という形になってしまうと思います)

    今回使用しているDLLは、DllImportで呼び出していません。

    COMのDLLで、プロジェクト側で参照先を追加して呼び出しています。

    あとは、DLL側の変更は一切できない状況です。

     

    2011年7月26日 9:52
  • 外池 様

    ご回答頂きまして、ありがとうございます。

    DLLの作成は、アンマネージで、C#で作られたものではありません。(VC++2005)

    BitmapクラスのLockbitsの情報ありがとうございます。

    調べてみます。

     

    2011年7月26日 9:56
  • 佐祐理 様

    ご回答頂きましてありがとうございます。

    入出力に使用する画像は全て1chグレースケールです。

    2011年7月26日 10:01
  • 使用しているのはCOM DLLで、インターフェイスにプリミティブ型へのポインタが入っている、という認識でよいでしょうか?

     

    これ多分、C#からは正攻法では呼び出しできないんじゃないでしょうか。

    たとえば、COM側でメモリブロックの先頭アドレスへのポインタを期待してBYTE*を使用しても、

    .NET側ではあくまでも「1つのbyte型の参照渡し」としか認識できないはずです。

    なので、マーシャラはbyte型1つぶんしか処理してくれません。

     

    直接の解決策でなくて申し訳ないですが、C++/CLIでラッパ書いた方が早いかもしれないです。

    2011年7月26日 10:38
  • グレースケールですか。最初から質問文に含めておいて欲しかったですね。

    Hongliangさんや外池さんが紹介されている、BitmapクラスのLockBits()メソッドを使うとピクセルデータが取得できますが、この際に指定できるフォーマットはPixelFormatの中から選ぶことになります。

    残念ながら1chグレースケール8bppは存在しません。1px 2bytesのFormat16bppGrayScaleが一番適切でしょうか。1バイトずつ不要なデータがあるのでバイト配列を作り直す必要があります。もしくはFormat8bppIndexedを使用して、こちらはパレット形式なので256階調のグレースケールに戻す作業が必要あります。

    • 回答の候補に設定 山本春海 2011年8月12日 4:41
    • 回答としてマーク 山本春海 2011年8月25日 6:46
    2011年7月26日 11:56
  • 外池です。

    呼び出そうとしているDLLの関数が、今回取り扱おうとしているビットマップ画像のフォーマットを直接取り扱えるのでは? と期待したのですが、ダメですかね? 直接取り扱えるなら、BitmapDataのScan0から始まるバイト列をそのまま渡してやれば良いと思った次第です。

    ただ、いずれにせよ、フォーマット変換が必要な場合でも、BitmapDataのScan0からデータを読み出すわけですから、使いますよね? そして、Heapメモリを確保してやって、そこにフォーマット変換しながらバイト列を書き出してやって、その上で、DLL関数をHeapに書き込んだバイト列を差すポインタを渡すことになろうかと思います。


    (ホームページを再開しました)
    2011年7月26日 12:34
  • >呼び出そうとしているDLLの関数が、今回取り扱おうとしているビットマップ画像のフォーマットを直接取り扱えるのでは? と期待したのですが、ダメですかね?

    そーなると Bits 以外の情報も渡さなければならないので、ちゃんと折り合いがつくのかどーか。。。

    2011年7月26日 13:27
    モデレータ
  • BitmapをImageConverterクラスのConvertToメソッドでByte配列に変換し,byte[] inputImage = new byte[400*300];で確保した配列にループを使って都合のいい形式になるよう変換しながらコピーしたらよいのではないでしょうか。
    普通に作ったBitmapオブジェクトは1ピクセル4byteだと思いますので,4byteおきにコピーすればよいと思います。
    私は主にVBを使うので,変なC#のコードかもしれませんが,次のようにしてはどうでしょうか。
       Bitmap Image = new Bitmap(400, 300);
       ImageConverter ImgConv = new ImageConverter();
       byte[] ByteArray = (byte[])ImgConv.ConvertTo(Image, typeof(byte[]));
       byte[] InputImage = new byte[400 * 300];
       for (int i = 0; i < 400 * 300; i++)
       {
        InputImage[i] = ByteArray[i * 4];
       }
    

    2011年8月1日 23:54
  • そのbyte[]はPNGやBMPファイルのバイト列であって、4バイトおきに読んだところで各ピクセルの情報にはなりませんよ。

    using(var ms = new MemoryStream()){
      Image.Save(ms); // 元の画像形式かPNG形式
      ByteArray = ms.ToArray();
    }

    と全く同じ処理です。 

    2011年8月2日 0:45
  • ごめんなさい。佐祐理さんのおっしゃるとおりでした。適当なことを投稿して申し訳ありません。

    やはりみなさまのおっしゃるとおり,BitmapData.Scan0を利用すべきでした。

    もともとの画像がグレースケールであれば,LockBitsメソッドでFormat32bppRgbを選択すればパレットを気にする必要がなくなり,Marshal.Copyでコピーした配列を4バイトおきに読むことで各ピクセル8ビットグレースケールのデータになると思うのですが...

     

    2011年8月2日 5:21