none
プログラム内に大量の画像データを保持する場合のメモリ管理 RRS feed

  • 質問

  • こんにちは。

    次のようなアプリケーションを作成しています。

    カメラから画像を取得し、一旦Queue<Bitmap>にデータを保存、別スレッドでQueue<Bitmap>からデータを一つずつPNG形式でファイルに保存

    Bitmapの形式はFormat24bppRgb形式の1600x1200です。


    耐久試験として、PNGに保存せずにQueue<Bitmap>配列にデータを追加し続けてみたところ、40万件以上のデータを保存し、なおかつデータを取り出して保存することができてしまいました。

    Bitmap1つ分のデータ量は1600x1200x3=約5.5MB、32bitアプリなので最大2GB(約370件)分のデータを保存した時点でアプリが終了すると思うのですが、内部ではどのようにデータを管理しているのでしょうか。

    よろしくお願いします。

    2012年12月3日 2:41

回答

  • scan0パラメーター付きのBitmapコンストラクターは画像データとして渡されたscan0を使います。ドキュメントにも書かれていますが、

    呼び出し元が、scan0 パラメーターによって指定されるメモリのブロックを割り当てたり、解放したりします。関連する Bitmap が解放されるまで、メモリはリリースされません。
    The caller is responsible for allocating and freeing the block of memory specified by the scan0 parameter. However, the memory should not be released until the related Bitmap is released.

    翻訳が不適切なので英文も引用しましたが、Bitmapが解放されるまでscan0のメモリーを解放してはいけません(should not)。
    一方、質問者さんのコードでは bmp.UnlockBits(bmpData) によりscan0が解放されてしまい不正な状態のBitmapインスタンスになってしまっています。本来310件しか保存できないはずのBitmapが40万件以上保存できたからくりはここにあると思います。

    質問者さんのコードで、そもそもなぜbmpから新たなBitmapを作成するのか、直接  data.copyImage = bmp; としなかったのか、私、気になります!


    • 編集済み 佐祐理 2012年12月4日 1:02
    • 回答としてマーク sshrmyv 2012年12月4日 1:33
    2012年12月4日 0:59

すべての返信

  • 試験が間違っていただけでは…本当に40万枚以上の異なる画像で試しましたか?

    例えば少数毎のBitmapを何回Queue<Bitmap>に入れようとも1枚当たり数10バイトしか消費しません。

    2012年12月3日 4:22
  • 試験が間違っていただけでは…本当に40万枚以上の異なる画像で試しましたか?

    例えば少数毎のBitmapを何回Queue<Bitmap>に入れようとも1枚当たり数10バイトしか消費しません。

    毎回新しく撮影した画像をQueueに格納しています。

    >例えば少数毎のBitmapを何回Queue<Bitmap>に入れようとも1枚当たり数10バイトしか消費しません。

    現象から察するにこのようなことだとは思うのですが、理屈がよく分からなくて…。

    200万画素の画像データをプログラム内では数10バイトで表現しているということではないですよね。
    2012年12月3日 7:33
  • Queue<Bitmap> queue = new Queue<Bitmap>();
    for (int i = 0; i < 400000; ++i) {
        queue.Enqueue(new Bitmap(1600, 1200, PixelFormat.Format24bppRgb));
    }

    カメラからの入力ではなく、この単純なコードでやってみてください。

    // カメラからのは同じBitmapインスタンスに上書きしていってるだけじゃないかなぁ。

    2012年12月3日 7:57
  • /// <summary>
    /// 撮影画像情報
    /// </summary>
    public class QueueImage
    {
        public Bitmap copyImage;
        /* 略 */;
    }
    Queue<QueueImage> queue = new Queue<QueueImage>();
    // 画像の詳細コピーを作成 bmpはカメラ画像
    QueueImage data = new QueueImage();
    BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, bmp.PixelFormat);
    data.copyImage = new Bitmap(bmp.Width, bmp.Height, bmpData.Stride, bmp.PixelFormat, bmpData.Scan0);
    bmp.UnlockBits(bmpData);
    // 画像などをキュー配列に格納
    lock (((ICollection)queue).SyncRoot)
    {
        queue.Enqueue(data);
    }

    >Hongliangさんのコードでは、i=310で仮想メモリが1935MBとなりArgumentExceptionが発生しました。

    おっしゃるとおり、Bitmapインスタンスが上書きされていたようです。

    画像データ以外の情報も必要なので、上記コードのように自作クラスをQueueに格納していますが、これに問題があると思うのでいろいろ試してみます。

    なにかおかしい点があればご教授ください。

    また、カメラのシャッターを切るタイミングでBackgroundWorkerインスタンスを作成し、別スレッドでカメラ画像の取得→Queueに格納、という流れになっています。

    2012年12月4日 0:43
  • scan0パラメーター付きのBitmapコンストラクターは画像データとして渡されたscan0を使います。ドキュメントにも書かれていますが、

    呼び出し元が、scan0 パラメーターによって指定されるメモリのブロックを割り当てたり、解放したりします。関連する Bitmap が解放されるまで、メモリはリリースされません。
    The caller is responsible for allocating and freeing the block of memory specified by the scan0 parameter. However, the memory should not be released until the related Bitmap is released.

    翻訳が不適切なので英文も引用しましたが、Bitmapが解放されるまでscan0のメモリーを解放してはいけません(should not)。
    一方、質問者さんのコードでは bmp.UnlockBits(bmpData) によりscan0が解放されてしまい不正な状態のBitmapインスタンスになってしまっています。本来310件しか保存できないはずのBitmapが40万件以上保存できたからくりはここにあると思います。

    質問者さんのコードで、そもそもなぜbmpから新たなBitmapを作成するのか、直接  data.copyImage = bmp; としなかったのか、私、気になります!


    • 編集済み 佐祐理 2012年12月4日 1:02
    • 回答としてマーク sshrmyv 2012年12月4日 1:33
    2012年12月4日 0:59
  • Bitmapが解放されるまでscan0のメモリーを解放してはいけません(should not)。
    一方、質問者さんのコードでは bmp.UnlockBits(bmpData) によりscan0が解放されてしまい不正な状態のBitmapインスタンスになってしまっています。本来310件しか保存できないはずのBitmapが40万件以上保存できたからくりはここにあると思います。

    質問者さんのコードで、そもそもなぜbmpから新たなBitmapを作成するのか、直接  data.copyImage = bmp; としなかったのか、私、気になります!

    画像を取得してQueueに格納するスレッドとは別に、Queue配列からデータを取り出しPNG画像を出力するスレッド(画像保存スレッド)が動いています。

    data.copyImage = bmp;でQueueに格納していた場合、画像保存スレッド内のdata.copyImage.Save()、またはカメラ制御クラス内の画像生成部分でArgumentExceptionが発生してしまいます。

    単純にdata.copyImage = new Bitmap(bmp); とするとフォーマットがFormat32bppArgbとなるのを避けたかったのですが、今試してみたところ順調に仮想メモリが増えていったので、とりあえずこれで行こうと思います。

    しかし使用中している仮想メモリが1GBを超えるとnew Bitmap()が非常に重くなりますね。このあたりでカメラの取得をストップしたほうがいいのかもしれません。

    ひとまず主題については解決したので解決済みとさせていただきます。

    皆様ありがとうございました。

    2012年12月4日 1:32
  • new Bitmap(bmp) の代わりに (Bitmap)bmp.Clone() ではどうですか? 確かこの方法ならフォーマットは維持されたと思います。とはいえ、「ArgumentExceptionが発生」し、コピーを行うことで解消するのは何か別の問題が隠れていて、本質的ではない対策に思います。

    ちなみに私なら画像保存スレッドを用意したりせず、1枚1Taskで並列に書き出しさせます…。数十万枚という規模ではこの方法はやばいのかな…?

    2012年12月4日 2:14