Processingで赤外カラー合成画像生成ソフトを作ってみた

赤外線写真

 ちょっと前に「2000円で美し過ぎる赤外線写真を撮れるデジタル一眼カメラ用フィルタを自作」で、自作した赤外線フィルタを使って赤外線写真をとりました。そのとき、赤外線写真とカラー写真を加工して合成するフォルス・カラーなるレタッチ方法があるというので試してみることにしました。ネットでみる美しい赤外線写真の中にもこの技術を使った写真があるようです。

フォルス・カラーってなんじゃらほい?

 衛星写真で使われたりする技術みたいですが、実はイマイチ詳しい事はわかりませんでした。原理は「風景の簡易赤外線写真」というサイトの「赤外カラー合成画像」というところがわかりやすいです。

 図にすると下図のような感じです。
120329_falsecolor

 普通に撮ったカラー写真と、モノクロで撮影した赤外線写真を用意すれば、それぞれに簡単な画像処理して画像合成すればできてしまいそうですね。

フォルス・カラーをつくってみよう!

 じゃあ早速つくろうとして、ふと気づきました。RGBの色交換、RGBのレベル調整、画像合成が出来る画像処理ソフト意外に持ってない。WindowsだとJTrimで全部できてしまうみたいなのですが、MacだとJTrimに相当するソフトが見当たらないのです。探したら、GIMPとかpixelmatorといったソフト使えばできそうなのですがそんな重いソフトいくつも使って加工するのは、やりたいことに対して大げさ過ぎであんまスマートじゃないなーと思って悩んでました。悩んだ末、「そういや最近Processingで顔認識を使って写真をアヘ顔にするソフトをつくったな」と思い出し、「なければつくればいいじゃん!」というDo it myself精神でソフト自作してみることにしました。

ソフト開発

 今回も、アヘ顔自動生成ソフトをつくったときと同様Processingを使ってソフトつくることにしました。実際にプログラムすると、予想もしなかった苦難が連続して襲い来る・・・ということもなく色々Webで調べながら組んだらサクっと1時間程で完成してしまいました。Processing様々ですね。
 ただ、出来上がった最初の画像が物足りなかったのでマウスの動きと、簡単な操作で画質を調整できるようにしました。

作例

 ソフト使用した作例です。被写体が適当でイマイチですが、ちょっとそれっぽくなっているのではないでしょうか?

120329_Color
元画像(カラー)

120329_IR
元画像(赤外線モノクロ)

120329_FalseColor01
フォルス・カラー画像

120329_FalseColor02
フォルス・カラー画像(青強調)

ソースコード

 すみません、特に解説は無くソースベタ書きして終わります。あんまプログラム分からない人でも理解しやすいように、わざとクラスとかつかってません(べ、別にクラスわかんないとかそんなんじゃないんだからねっ!)。見辛いですが、順に読めばわかると思うので興味ある人は見て下さい。フォルス・カラーをとにかくつくれればよいという人は、Processingインストールして、ソースコードコピペして実行すればできるはずです。特にソースを弄る必要はありません。Macでしか試してませんが、多分Windowsでも動くかと。

PImage img;
PImage img_tmp;
PImage img_IR;
PImage img_IR_tmp;

int mode;

void setup() {
  size(100, 100);
  println("select normal(color) photo."); 
  String imgPath = selectInput();
  img = loadImage(imgPath);

  println("select IR monoochrome photo."); 
  imgPath = selectInput();
  img_IR = loadImage(imgPath);

  size(img.width, img.height);

  mode = 0;
  noStroke();
  background(0);

  img_tmp = createImage(img.width, img.height, RGB);
  img_IR_tmp = createImage(img_IR.width, img_IR.height, RGB);
}

void draw()
{
  
  float color_phase = mouseX / (float)img.width * 360;
  float color_gain = mouseY / (float)img.height * 5;
  
  img.loadPixels();
  colorMode(HSB, 360, 100, 100);
  for (int y = 0; y < img.height; y++)
  {
    for (int x = 0; x < img.width; x++)
    {
      int index = y * img.width + x;
      int pixel = img.pixels[index];

      img_tmp.pixels[index] = color(hue(pixel)+color_phase, saturation(pixel), brightness(pixel));
    }
  }
  img_tmp.updatePixels();

  img_tmp.loadPixels();
  colorMode(RGB, 256);
  for (int y = 0; y < img.height; y++)
  {
    for (int x = 0; x < img.width; x++)
    {
      int index = y * img.width + x;
      int pixel = img_tmp.pixels[index];
      switch(mode){
        case 0:
        img_tmp.pixels[index] = color(0, green(pixel), blue(pixel));
        break;
        case 1:
        img_tmp.pixels[index] = color(red(pixel), 0, blue(pixel));
        break;
        default:
        img_tmp.pixels[index] = color(red(pixel), green(pixel), 0);
        break;
      }
    }
  }
  img_tmp.updatePixels();

  img_IR.loadPixels();
  colorMode(RGB, 256);
  for (int y = 0; y < img_IR.height; y++)
  {
    for (int x = 0; x < img_IR.width; x++)
    {
      int index = y * img_IR.width + x;
      int pixel = img_IR.pixels[index];

      switch(mode){
        case 0:
        img_IR_tmp.pixels[index] = color(red(pixel), 0, 0);
        break;
        case 1:
        img_IR_tmp.pixels[index] = color(0, green(pixel), 0);
        break;
        default:
        img_IR_tmp.pixels[index] = color(0, 0, blue(pixel));
        break;
      }
    }
  }
  img_IR_tmp.updatePixels();

  img_tmp.loadPixels();
  img_IR_tmp.loadPixels();
  colorMode(RGB, 256);
  for (int y = 0; y < img_IR.height; y++)
  {
    for (int x = 0; x < img_IR.width; x++)
    {
      int index = y * img_IR.width + x;
      int img_pix = img_tmp.pixels[index];
      int img_IR_pix = img_IR_tmp.pixels[index];

      float r, g, b;

      switch(mode){
        case 0:
        r = min(red(img_pix) + color_gain*red(img_IR_pix), 255);
        g = min(green(img_pix) + green(img_IR_pix), 255);
        b = min(blue(img_pix) + blue(img_IR_pix), 255);
        break;
        case 1:
        r = min(red(img_pix) + red(img_IR_pix), 255);
        g = min(green(img_pix) + color_gain*green(img_IR_pix), 255);
        b = min(blue(img_pix) + blue(img_IR_pix), 255);
        break;
        default:
        r = min(red(img_pix) + red(img_IR_pix), 255);
        g = min(green(img_pix) + green(img_IR_pix), 255);
        b = min(blue(img_pix) + color_gain*blue(img_IR_pix), 255);
        break;
      }

      img_IR_tmp.pixels[index] = color(r, g, b);
    }
  }
  img_IR_tmp.updatePixels();

  image(img_IR_tmp, 0, 0);
}
 

void keyPressed() { 
  // save image
  if(key == 'p' || key == 'P') {
    save("screenshot.jpg"); 
    println("screen saved."); 
  }

  // exit
  if(key == ' ') {
    exit();
  }

  // red mode
  if(key == 'r' || key == 'R') {
    mode = 0;
  }

  // green mode
  if(key == 'g' || key == 'G') {
    mode = 1;
  }

  // blue mode
  if(key == 'b' || key == 'B') {
    mode = 2;
  }
}

プログラム使い方

 実行すると、ファイル選択のダイアログが出てくるので、最初にカラー画像を選択して下さい。選択した後、またダイアログが出てくるので、次に赤外線モノクロ画像を選択して下さい。すると合成されたフォルス・カラー画像が出てくるはずです。
 マウスを左右に動かす事で色相(色合いみたいなもの)の調節、マウスを上下に動かすことで合成する赤外線写真のゲインを調整することができるので、いい感じの画像になるまで調整してみて下さい。
 また、キーボードのRを押すと赤を強調、Gを押すと緑を強調、Bを押すと青を強調するモードに切り替わります。画像を保存したいときはPを押すとプログラムと同じフォルダに「screenshot.jpg」というファイルが保存されます。スペースボタンで終了します。
 もしメモリエラーが出たら、環境設定で最大メモリを増やしてやるか、画像ファイルを小さくしてやって下さい。

まとめ

 Processing使ってカラー赤外線写真を生成するソフトを作ったよ☆折角作ったので、色々な被写体で試してみようかなと思います。そして思いついたことをすぐ実現出来るProcessing凄過ぎるぜ!画像処理は一旦これで一区切りにして、次は物理シミュレータでも作ってみようかなと思います。ではまたー