PG日誌

各記事はブラウザの横幅を1410px以上にすると2カラムの見出しが表示されます。なるべく横に広げてみてください。

OpenCvSharpで画像データを高速に設定、取得する

C#のOpenCVライブラリのOpenCVSharp3でMatに高速でデータ読み書きする方法の紹介です。

高速にデータの読み書きをするためにMat.Data : IntPtr を利用してデータの読み書きを行いたいと思います。

使用しているライブラリは、OpenCvSharp3(ver. 3.4.1.20180319)です。

MatのSet, Atメソッドは超遅い

先ず初めに、OpenCVSharpでSetやAtで各ピクセルにアクセスしてみます。

結論として速度がかなり遅いです。試しに全ピクセルにアクセスする以下のコードを実行してみます。

// 16ビットモノクロ画像をバイト配列をMatへ読み書きしたい
int width = 1920;
int height = 1080;
ushort[] source_array = new short[1920 * 1080];

var mat = new Mat(Size(width, height), MatType.CV_16U);

// Setメソッドを使ってピクセルごとに値を設定
for (int y = 0; y < height; y++)
{
    for (int x = 0; x < width; x++)
    {
        mat.Set(y, x, source_array[y * width + x]);
    }
}
// 460msくらいかかる

// Atメソッドを使ってピクセルごとに値を取得
for (int y = 0; y < height; y++)
{
    for (int x = 0; x < width; x++)
    {
        byte b = mat.At<byte>(y, x);
    }
}
// 750msくらいかかる

Atの方が1.5倍くらい遅く0.75秒もかかってます。うーん遅い。

Mat.Dataを使って高速に操作する

C++ならDataは配列になっていてインデックスやポインタでそのままアクセス可能ですがC#のOpenCvSharpの場合DataがIntPtr型になっています。(unsafe コンテキスト内でポインタ操作すれば結構簡単にここへもアクセスできますが、通常のC#で操作する方法を考えたいと思います。

処理の流れですが、DataのIntPtrそこで一旦ローカルの配列にデータを取得してMarshal系の操作でポインタの中身をマネージコードとやり取りします。

Dataのポインタには連続したメモリ領域が割り当てられているのでサイズさえ合っていればC#らしくない乱暴な操作でも大丈夫です。

// 16ビットモノクロ画像をバイト配列をMatへ読み書きしたい
int width = 1920;
int height = 1080;
ushort[] source_array = new short[1920 * 1080];


// -x-x-x- メモリ操作でデータを設定 -x-x-x- 

// Marshal.Copyはushort型を受け取らないので先ずは型を変換する(メモリが倍必要になります)
char[] char_array = new char[source_array.Length];
Buffer.BlockCopy(source_array, 0, char_array, 0, char_array.Length * 2/*byte数*/);

// ポインタへ中身をコピー
Marshal.Copy(char_array, 0, mat.Data, char_array.Length);
// 2~3ms程度で処理が終わる

// -x-x-x- メモリ操作でデータを取得 -x-x-x- 

// 上記と同じくushortは受け取らないので受ける配列と変換する配列を用意する
char[] char_array_dest = new char[mat.Rows * mat.Cols];
ushort[] short_array_dest = new ushort[char_array_dest.Length];

// ポインタからマネージ配列へデータをコピー
Marshal.Copy(mat.Data, char_array_dest, 0, char_array_dest.Length);
// 実際に使用する型へ変換
Buffer.BlockCopy(char_array_dest, 0, short_array_dest, 0, short_array_dest.Length/*配列数*/);
// 2~3msで処理が終わる

どちらの操作も、2~3msで処理が完了します。これならある程度、実用的な速度課と思います。

最後に、この操作で画像を操作するとデータの並び順(R→G→B→Aなど)や、書き出す形式(Bmp32, PNG Gray16など)を意識しないといけません。従って適切に画像ファイルで視覚化するのであれば、上記処理の前後でデータの加工が必要になります。(特に有効データ長が10bitや12bitの情報を16bitに書き出すとビューワー上では真っ暗という状況になります。