【C#,WPF】多ビット画像の取り扱い

一般的なカメラであれば、モノクロの画像であれば8bit(256諧調)、カラー画像であれば24bit(R,G,Bそれぞれ8bit)の画像が一般的なのですが、マシンビジョン用のカメラでは、1画素あたり10~14bitぐらいまでの諧調を持つカメラがあるのですが、この画像データをプログラム的に取り扱うには、通常のWindows Forms用のC#(Bitmapクラス)では出来ませんでした。。

かなり昔の評価になりますが、評価した結果はこちら↓です

Format16bppGrayScaleは使えるのか?実験してみた

http://imagingsolution.blog.fc2.com/blog-entry-65.html

 

この時に、「WPFなら出来ますよ!」というコメントを頂いていたのを、今更ながらに評価してみました。

 

WPFではSystem.Windows.Media.PixelFormatsクラスにGray16(16ビットのモノクログレースケール)、Bgr101010(R,G,B各10ビットのカラー画像)、Rgb48(R,G,B各16ビットのカラー画像)があり、個人的な使いそうな、この3つについて評価しました。

 

参考までに、Bgr101010のフォーマットでは32ビットの整数型(uint)に下位ビットからB,G,Rの順で輝度値が格納されています。

 

Rgb48のフォーマットは16ビットの整数型(ushort)にカラーデータがR,G,B,R,G,B・・・の順で格納されています。

一般的な24bitのカラー画像はB,G,R,B,G,R・・・の順なので、ここが少しはまりました。

評価方法

配列にカラーグラデーションのデータを格納し、これを画像ファイルに保存し、ファイルを読み込んで元の画像データに復元できるか?という内容で行いました。

画像データを復元したいので、ファイル形式は非圧縮、可逆圧縮であろうbmp,tiff,pngの3つのファイルで行いました。

(評価に用いた画像)

 

評価プログラム

評価に用いたプログラムはこんな感じ↓です。

ボタンをクリックすると、ボタンに示している画像ファイルを作成し、メニューのFile→Openで開くとファイルフォーマットとビット数が表示されるようになっていますが、基本的にデバッグ実行をしながら、各種パラメータをウォッチで見ながら確認を行いました。

 

評価に用いたプログラムはGitHubに置いておきましたので、もしよかったら評価してみてください。

https://github.com/ImagingSolution/ImageFileArrayConverter

 

結果

各ボタンをクリックして、エクスプローラでファイルのプロパティを確認したのが、以下の通りです。

 

保存 読み込み
bmp tif png bmp tif png
Gray16 ×

64bit

Bgr101010 ×

64bit

× ×
Rgb48 ×

64bit

表中の横棒(―)はファイル保存の時点でダメなので、ファイル読み込みの評価は行っておりません。

 

Bgr101010は全滅

Gray16はtifとpngファイルはOK

Rgb48もtifとpngファイルはOK

 

という結果になりました。

ただし、Gray16やRgb48では16ビット全体を輝度データとして用いる事は少なく、16ビット中の下位10ビット、12ビットなどに値を入れている事が多いのですが、この16ビット中の何ビットが有効か?を設定するのがSystem.Windows.Media.PixelFormat構造体のMasksプロパティっぽいのですが、このMasksプロパティは読み取り専用で設定が出来ない!

そのため、データとして、16ビット全体が有効(65536諧調もっている)であればいいのですが、そうでない場合、結局、どうしたらよいのか?分かりませんでした。。

 

ちなみにGray16、Bgr101010に関しては、有効ビットを設定するのにWin32APIのビットフィールドという機能を用いれば設定が可能です。

(参考)多ビット(10Bit、12Bit)画像データの表示、フォーマット

https://imagingsolution.net/imaging/imaging-programing/10bit-image-format/

 

※まだ、WPFを理解しきれていない部分が多いので、間違い等ありましたら、コメント頂けると幸いです。

【C#】画像の輝度値の取得

C#で画像ファイルの輝度値を取得するにはLockBits~UnlockBitsでポインタを取得して配列へコピーするなどするのが、C#では定番となっています。

例えば、下記のようなサンプルコード

var bmp = new Bitmap("01.png"); 

// Bitmapをロック 
var bmpData = bmp.LockBits( 
	new Rectangle(0, 0, bmp.Width, bmp.Height), 
	System.Drawing.Imaging.ImageLockMode.ReadWrite, 
	bmp.PixelFormat 
	); 

// メモリの幅のバイト数を取得 
var stride = Math.Abs(bmpData.Stride); 

// 画像データ格納用配列 
var data = new byte[stride * bmpData.Height]; 

// Bitmapデータを配列へコピー 
System.Runtime.InteropServices.Marshal.Copy( 
	bmpData.Scan0, 
	data, 
	0, 
	stride * bmpData.Height 
	);

// アンロック 
bmp.UnlockBits(bmpData);

このプログラムで、ほとんどの場合、問題ない事が多いのですが、モノクロ8Bitのpng画像ファイルを開くと、なぜか?32bitカラーの画像として認識してしまいます。

(評価に用いた画像) Deep Learningでは定番のMNISTの手書き文字に似せて作成した8bitモノクロ画像

(デバッグ画面)もとの画像は8bitモノクロ画像ですが、PixelFormatがFormat32bppArgbになっています。

 

8bitの画像が32bitとして認識してしまうという事は、無駄に画像データが4倍大きくなってしまうので、画像処理的には処理速度的にも不都合が起こります。

 

そこで、私は基本WindowsFormsを使っているのですが、クラスだけWPFのクラスを使う事で問題を解決します。

 

WPFのクラスを用いるにはプロジェクトの参照から

下記の2つを追加します。

PresentationCore
WindowsBase

 

これでWPFのクラスが使えるようになります。
そこで、最初に書いたBitmapクラスを用いたプログラムと同等のことをWPFのクラスを用いて作成すると

byte[] data; 

using (var fs = new System.IO.FileStream("01.png", System.IO.FileMode.Open, System.IO.FileAccess.Read)) 
{ 
	var bitmapFrame = System.Windows.Media.Imaging.BitmapFrame.Create( 
		fs, 
		System.Windows.Media.Imaging.BitmapCreateOptions.PreservePixelFormat, 
		System.Windows.Media.Imaging.BitmapCacheOption.Default 
		); 

	int stride = ((bitmapFrame.PixelWidth * bitmapFrame.Format.BitsPerPixel + 31) / 32) * 4; 

	// 画像データ格納用配列 
	data = new byte[stride * bitmapFrame.PixelHeight]; 
	
	// 輝度データを配列へコピー 
	bitmapFrame.CopyPixels(data, stride, 0); 
}

となります。

これでFormatもGray8となり、正しいサイズで画像データを取得できるようになります。

 

画像処理のためのC#テクニックへ戻る

関連記事

【C#】画像の輝度値の取得設定速度の比較

フーリエ変換アプリ

こちらのページでは、フーリエ変換のイメージをビジュアル的に見せるためのプログラムになります。

単に、フーリエ変換をしたい場合は、下記のプログラムをご使用下さい。

【C#】フーリエ変換(FFT, DFT)プログラム

フーリエ変換を教える時に、自分の中では糸(データ)を巻き取るようなイメージ↓(こんな感じ)

 

 

があるのですが、これを分かるように説明するのがなかなか難しく、その様子が分かるように、C#でプログラムを作成してみました。

 

動いている様子はこちら↓

 

実際のプログラムはこちら↓(.NET Framework4.5.2以上で動作すると思います)

FourierTransformAnimation.zip

 

上記ファイルを解凍後、FourierTransformAnimation.exeファイルをダブルクリックすると、プログラムが実行できますが、Windowsの警告画面が表示されるので、「詳細情報」をクリック後、プログラムを実行してください。

また、いくつかサンプルデータを入れてありますので、sampledataフォルダ内のcsvファイルを開いてお試しください。

フーリエ変換で用いている式は、いくつかあろうかと思いますが、下記の式に基づいて処理を行っています。

基本的な機能としては

●CSVファイルによる任意データ入力

●データの離散フーリエ変換

●離散フーリエ変~逆離散フーリエ変換までを動画表示

●窓関数

●離散フーリエ変換の結果をファイル保存

となります。

 

プログラムの説明

データファイルを開くと、離散フーリエ変換を行い、大きさと位相を表示します。

処理のアニメーションの時に用いる複素平面のグラフは、90°反時計周りに回転した状態が初期状態になっています。

離散フーリエ変換の途中途中に出てくる赤い線は、複素平面において、原点からデータの平均の位置までの線で、この線の大きさをMagnitudeグラフへ記載しています。(描画スケールが異なります。)

 

さらにこの線の傾き(12時方向が0°で反時計周りが正)をPhaseグラフへ記載しています。

 

 

逆離散フーリエ変換の時は、各周波数のデータを積算していきますが、前回までのデータの合計を太い青い線で、現在の周波数のデータを赤い線で表示しています。

 

 

使用方法

ファイルを開く

ファイルメニューより、File → Open(*.csv) をクリックし、CSVファイルを選択します。

CSVファイルのフォーマットは1行に1データを縦に記載します。

【例】

1.2
1.107165359
0.914844142
0.80157706
0.872515202
1.061803399
1.193716632
1.145793725
0.962523737
0.814044703
0.838196601
1.012558104
1.175261336

入力データに虚数成分がある場合は、実数と虚数をカンマ(,)でつなげて以下のようにします。

【例】

1,0
0.999390827,0.034899497
0.99756405,0.069756474
0.994521895,0.104528463
0.990268069,0.139173101
0.984807753,0.173648178
0.978147601,0.207911691
0.970295726,0.241921896
0.961261696,0.275637356
0.951056516,0.309016994
0.939692621,0.342020143
0.927183855,0.374606593
0.913545458,0.406736643
0.898794046,0.438371147
0.882947593,0.469471563

データ数がどこまでいけるか?評価していませんが、アニメーション表示をするなら50~100個ぐらいまでが目安です。

アニメーション表示しなければ、そこそこいける?!

 

アニメーションの開始/停止

Youtubeの動画のように、処理の様子を動かすには、ファイルメニューの Animation → Start/Stop をクリックします。スペースキーを押しても同様の動きをします。

 

アニメーションのステップ実行

処理を1つ1つのデータで行う場合は、アニメーションを停止させ、矢印キーの右()を押します。

 

アニメーション速度の調整

アニメーション速度を速くする場合は、矢印キーの上()、遅くする場合は、矢印キーの下()を押します。

 

窓関数処理

基本的に全データを1周期分を想定していますが、この1周期に窓関数(Hamming、Hanning、Blackman)を通します。

ファイルメニューの Window → Hamming、Hanning、Blackman をクリックします。

 

フーリエ変換のイメージ

フーリエ変換では、データを複素平面へ巻き取るようなイメージでになります。

 試しにCosθのデータ全体を1回転で巻き取るようにすると

 

最終的に複素平面の原点から、データが巻き取られた点(複素平面上の点)の平均の位置までの線の大きさが、その周波数の大きさで、線の傾きが位相となります。

 

このデータを巻き取るときの回転の速度が、データ全体を0回転、1回転、2回転・・・で巻き取るようにすると、それぞれの周波数(0,1,2・・・)の大きさと位相が取得できます。

 

試しにCosθのデータ全体を2回転で巻き取るようにしてみると、複素平面上の点の平均は0となります。

もともとのデータにない周波数成分(Cos2θの成分)は、複素平面上で平均を取ると、データが相殺されて0になるのが面白い!

 

逆変換の場合は、取得した各周波数ごとの大きさと位相からコサイン波形を生成し、足し合わせた結果が変換後の実部となります。

ここで、ちょっと違和感があるであろう コサイン波形 と書きましたが、サイン波形を足し合わせた結果は、虚部となります。

 

おそらくこんな書き方をする人はいないのですが、データの個数をN、周波数tの時の大きさをAt、位相をφtとすると、逆変換後の実部(Re)と虚部(Im)は以下のようになります。

 

$$Re(x)\quad =\quad \sum _{ t=0 }^{ N-1 }{ { A }_{ t }Cos(\frac { 2\pi t }{ N } x+{ \varphi }_{ t }) } \\ Im(x)\quad =\quad \sum _{ t=0 }^{ N-1 }{ { A }_{ t }Sin(\frac { 2\pi t }{ N } x+{ \varphi }_{ t }) } $$

 

以下の画像は周波数1の結果を逆変換したものになります。

これだけを見ると、実部のグラフのcosθの振幅は小さくない?!

と思われたかもしれませんが、フーリエ変換を行う元々のデータに虚数部分が含まれない場合、大きさが同じで位相がマイナスの関係になった周波数が存在します。

こんな感じ↓

 

この2つの波形を足し合わせる事で、逆変換では元の波形に戻ります。

この部分の詳細を知りたい場合は、複素共役で調べてもらうと分かるかも?しれません。

 

このフーリエ変換を発明したジョゼフ・フーリエさんは、自分自身をミイラのように包帯でグルグル巻きになるという、ちょっと変わった趣味の持ち主だったそうですが、このグルグル巻きになった時にフーリエ変換が思いついたのではないのかな?(詳細は不明)

 

フーリエ変換へ戻る