第一回の勉強会では大学で使用しているマイクロフォーカスX線CT(ScanXmate-A130ss940, コムスキャンテクノ株式会社)で採用されているRAWデータの画像を読み書きする方法を勉強しました。今回は最終的にrawデータを読み込んで、リアルタイムで閾値処理をするプログラムを作成しました。
rawデータの読み込み方
今回扱うrawデータは640×480(307200)pixelですが、データの大きさは614400byteとピクセルの情報の2倍の大きさとなっています。これは2byteで1pixel分の情報を表していることを意味していてこの画像が16bitのrawデータであることがわかります(1byteが8bitです)。
16bitの画像を読み込むためには、2byte分の情報から1pixelの情報を知るために特別な処理が必要となります。画像にはビッグエンディアンとリトルエンディアンという記録方法の方式があります。例えば{1234ABCD}という4バイトのデータをデータの上位バイトからメモリに{12 34 AB CD}と並べる方式をビッグエンディアン、{CD AB 34 12}とデータの下位バイトから並べる方式をリトルエンディアンと言います。それぞれデータの読み込み方が異なってくるので注意が必要です。
$$d_1=b_1+b_2\times256$$
もしくは
$$d_1=b_1\times256+b_2$$
となります。
public static void readRawData() { File f = new File("○○○.raw"); try { DataInputStream dis = new DataInputStream(new FileInputStream(f)); //データの大きさだけバイト型の配列を用意 byte[] b = new byte[dis.available()]; //データを読み込む dis.read(b); //データの形式に合わせてint型の配列に画素地を入れる(今回は16bit) for (int i = 0; i < b.length / 2; i++) { rawdata[i] = (0xff & b[i * 2]) + (0xff & b[i * 2 + 1]) * 256; } } catch (Exception e) { e.printStackTrace(); } }
上記のように記述することによって16bitのrawデータを配列に格納することができました。配列に数値を格納できれば、閾値処理を行ったり、平滑化フィルタをかけたりなど様々な画像処理を行うことができるようになります。画像は画像の形式によって配列に格納する方法が変わってきます。例えばTIFF形式の画像ファイルの場合、ヘッダやフッタ(データの最初と最後)に画像に関する情報が入っている場合があり、このような画像では単純に配列に格納するだけでは読み込むことができません。
rawデータの表示方法
続けて表示方法です。描画を行うクラスを作成し、メインクラスでインスタンスを生成し、描画を行うます。NetBeansの場合はパッケージを右クリック→新規→JPanelフォームをクリックし、描画を行うパネルを作ります。そうすることでJPanelクラスをスーパークラスとするサブクラスが自動生成されます。自動生成したクラスの中に以下のコードを記述します。
public void paintComponent(Graphics g){ //背景を白く塗りつぶす g.setColor(Color.white); g.fillRect(0,0,width,heigth); //画像データを描画する for(int i = 0; i < height; i++){ for(int j = 0; j < width; j++){ //65536諧調になっているので256諧調に変換 int c = rawdata[i * width + j]/256; //色をセット(グレースケールなのでRGBすべてに同じ値を入れる) g.setColor(new Color(c,c,c)); //1pixelずつ色を塗る g.drawLine(j, i, j, i); } } }
上記のコードを記述し呼び出すことで、rawdata[]に格納していた画像のデータを描画することができます。次に呼び出し方についてです。
initComponentsメソッドが記述されているメソッドに以下のコードを書き足します。ちなみにinitComponentsメソッドはコンポーネントの作成に関するコードが記述されていてプログラムの中で一番最初に呼び出される部分になっています。
public StudyCt() { initComponents(); //インスタンスの生成 PanelDraw pDraw = new PanelDraw(); lbl1.setLayout(new BorderLayout()); //生成したパネルをラベルの上に張り付ける lbl1.add(pDraw); //外観を成形する pack(); }
これでおおまかな部分は完成です。後は閾値を変更した後、ボタンを押した後など随所でrepaintメソッドを呼び出すことで、再描画を行うことができます。
rawデータの書き出し方
16bitのrawデータとして画像を書き出します。読み込み方と方法は似ていて、おまじないの部分が多くなってしまいますが、以下のように記述します。理由は不明ですが書き出すときはsng形式で書き出すようです。
public void writeRaw(){ File f = new File("○○○.sng"); int bit = 16; try{ byte b1[] = new byte[width * height * bit / 8]; for(int i = 0; i < height; i++){ for(int j = 0; j < width; j++){ int buf = 0; if(rawdata[j][i]>65535)buf = 65535; else if(rawdata[j][i] < 0)buf = 0; else buf = (int)rawdata[j][i]; //1pixelのデータを2byte分に分けて配列に格納する b1[(i * 640+j)*2] = (byte)(buf%256); b1[(i * 640+j)*2+1] = (byte)(buf/256); } } FileOutputStream fos0 = new FileOutputStream(f); fos0.write(b1,0,widht*height*2); fos0.close(); }catch(IOException e){ } }
上記のように記述することで○○○.sngという画像ファイルを作成することができます。だいたいの部分が読み込むときと逆の操作を行っているのがわかると思います。配列に格納するときは条件分岐させて0以下65536以上の情報を持っているpixelがないようにしています。配列に格納できたら書き出してファイルを閉じます。これで完成です。
今回の勉強会で勉強したことを使って簡単なソフトを作ってみました。CTの画像を読み込んで、閾値処理を行い形状と拡大率がわかっている被写体に対して理想的な再構成画像とどれだけ違いがあるかを調べることができます。勉強会大変勉強になりました。