二つの画像のピクセル値を引き算して、その結果を別の画像に保存するプログラムを書いてみた。
事の発端は、気象庁の衛星画像の可視画像と赤外画像の差分を採ってみたらどうなるんだろうというものです。
気象衛星画像のうち、可視画像は目で見たもので雲があれば白く、無ければ黒く写ります。一方で赤外画像は、(雲のてっぺんが高くて)雲の温度が低ければ白く、温度が高ければ黒く写ります。たとえば地上の霧は、可視画像では白く写りますが赤外画像では黒く(濃いグレー)写ります。一方で上空の薄い雲は可視画像では薄暗く、赤外画像では白く写ります。
つまり、地上の霧を抽出するには、可視画像と赤外画像の差分を良いのでは?というのが事の発端です。
早速、ネット上のいろいろなサイトを参考にプログラムを書いてみました。
Bitmapオブジェクトでもピクセル値を参照できますが、速度が大変に遅いようなのでアンマネージ配列にコピーしてからピクセル値を取得・計算しています。
7行目で作成したBitmapオブジェクトから、8行目のようにBitmapdataオブジェクトを発生させます。次に、14行目で作成したアンマネージ配列(byte[])に、15行目でMarshal.Copyを使ってコピーしています。
差分は25行目で計算していますが、マイナスは無いので26行目で下限値を0にしています。また、4バイト目はアルファ値なので255を強制的に入力しています。
そして、差分の結果を30行目でBitmapdataオブジェクトにコピーして、後処理をして終了という感じです。
static void Main(string[] args) { const string sFileA = @"C:\FileA.png"; //上記の例では可視画像 const string sFileB = @"C:\FileB.png"; //同じく赤外画像 const string sDiff = @"C:\Diff.png"; //差分結果 Bitmap bpA = new Bitmap(sFileA); BitmapData dtA = bpA.LockBits(new Rectangle(0, 0, bpA.Width, bpA.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); Bitmap bpB = new Bitmap(sFileB); BitmapData dtB = bpB.LockBits(new Rectangle(0, 0, bpB.Width, bpB.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); Bitmap bpDiff = new Bitmap(bpA.Width, bpA.Height); BitmapData datDiff = bpDiff.LockBits(new Rectangle(0, 0, bpDiff.Width, bpDiff.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); byte[] bA = new byte[bpA.Width * bpA.Height * 4]; Marshal.Copy(dtA.Scan0, bA, 0, bA.Length); byte[] btB = new byte[bpB.Width * bpB.Height * 4]; Marshal.Copy(dtB.Scan0, btB, 0, btB.Length); byte[] btDiff = new byte[bpDiff.Width * bpDiff.Height * 4]; for (int i = 0; i < bA.Length; i++) { byte Diff; if (bA[i] > btB[i]) { Diff = (byte)(bA[i] - btB[i]); } else { Diff = (byte)0; } btDiff[i] = Diff; if (i % 4 == 3) btDiff[i] = (byte)255; } Marshal.Copy(btDiff, 0, datDiff.Scan0, btDiff.Length); bpDiff.UnlockBits(datDiff); bpDiff.Save(sDiff); }
<参考にさせていただいたサイト>
- NonSoft Bitmap処理を高速化するサンプル(C#.NET)
- 端くれプログラマの備忘録 [C#] ビットマップにピクセル単位で高速にアクセスするには