PowerShellで簡単な画像処理する

画像処理できれば自動化できそうなことを目視でやっている人がいたが、OpenCVやら特別なソフトやらを入れさせるのは難易度が高いし、
C++やPythonのコードを書かせるのは絶対に受け入れてもらえないので、簡単に画像処理を行える方法はないかと思って調べてみた。
結果的に、やはりPowerShellが一番良さそうだった。
ただし、実行速度はだいぶ遅い。

ちなみに、PowerShellはC#のコードを直接実行できたりもするので、そこまでいくともはやなんでもPowerShellでできてしまえるのではないかと思えてしまう。
[OpenCVSharp（OpenCVのC#版）](https://github.com/shimat/opencvsharp)というライブラリもあるので、
PowerShellの中にOpenCVSharpを使うC#のコードを書いて実行すれば、実質PowerShellからOpenCVを使うこともできるらしい。（ググっても古い情報しか出てこなくて上手くいかなかったので未確認）

PowerShellのバージョンは5.0を使用

## PowerShellで画像を読み込む

[System.Drawing](https://msdn.microsoft.com/ja-jp/library/system.drawing(v=vs.110).aspx)という.NET Frameworkにあるクラスを使うと画像が扱えるらしい。
ただし、なぜか画像ファイルのパスはフルパスで指定しないとエラーになるので、普通にフルパスで指定するかpwd使うなどする必要がある。

```powershell
Add-Type -AssemblyName System.Drawing
# 画像読み込み
$src_image = [System.Drawing.Image]::FromFile((pwd).Path + "\lenna.png")
# JPG形式で保存
$src_image.Save((pwd).Path + "\test_png2jpg.jpg", [System.Drawing.Imaging.ImageFormat]::Jpeg)
# オブジェクトを破棄
$src_image.Dispose()
```

※仕様がよくわからないが、ISEだと最初のアセンブリ読み込み無くても動いたけど、普通のPowerShellだと読み込み必須だった。

カレントディレクトリに「lenna.png」の画像を置いた状態で上記コードを実行すると、jpgに変換された画像が出力される。
たった4行（実質3行）で画像読み込み→フォーマット変換して保存ができてしまった。
これだけでも、特定のフォルダ内にあるbmpで撮られた無駄に容量のでかいスクショすべてをjpgに変換するみたいな、存在してしまっていることが悲しくなるような作業を自動化できる。

リサイズや回転も割と簡単にできるらしい。
[PowerShell で画像の回転、リサイズを行う](https://qiita.com/miyamiya/items/d1a975fb6618d46eda0c)

ただ、フォーマット変換やリサイズしたいだけならフリーソフトを探すのが普通だと思う。
本命は次の処理

## ピクセル単位でアクセスする

画像処理でよくやる、横方向と縦方向の二重ループを回して全画素に対して色のGetやSetを行うループをPowerShellで行う。

```powershell
Add-Type -AssemblyName System.Drawing
# 全画素ループしてRとB入れ替えるだけの関数
function Test-PixelControl($src_image, $dst_image){
  $image_width = $src_image.Width
  $image_height = $src_image.Height
  0..($image_width-1) | %{
    $x = $_
    0..($image_height-1) | %{
      $y = $_
      $a = $src_image.GetPixel($x, $y).A
      $r = $src_image.GetPixel($x, $y).R
      $g = $src_image.GetPixel($x, $y).G
      $b = $src_image.GetPixel($x, $y).B
      $color = [System.Drawing.Color]::FromArgb($a, $b, $g, $r)
      $dst_image.SetPixel($x, $y, $color)
    }
  }
  return $dst_image
}
# 画像読み込み
$src_image = [System.Drawing.Image]::FromFile((pwd).Path + "\lenna.png")
# 出力用オブジェクト生成
$dst_image = New-Object System.Drawing.Bitmap($src_image.Width, $src_image.Height)
# 画像処理の関数を実行
$dst_image = (Test-PixelControl $src_image $dst_image)
# 画像の保存
$dst_image.Save((pwd).Path + "test_out.png", [System.Drawing.Imaging.ImageFormat]::Png)
# オブジェクトを破棄
$src_image.Dispose()
$dst_image.Dispose()
```

ここでやっていることは読み込んだ画像のRとBを入れ替えた画像を出力しているだけだが、これができれば簡単な画像解析や合成もできるようになる。
例えば、以下のような作業がそこそこ簡単に自動化・効率化できる。

- 大量の画像にヘッダーやフッターやロゴを付ける作業
- 大量の画像から特定の範囲を切り取る作業
- 大量の画像の白い画素を透過する作業（パワポ用の画像とか）
- 定期的に同じ画面のスクショを取って、前回のスクショから変化が起きたら何かする作業
  - 前回と今回の2枚の画像を取っておいて、差分を取れば変化が検知できる

## PowerShellでガンマ補正

サンプルにグレースケールとかネガポジ反転のコード書くのは芸が無い気がするので、ガンマ補正の処理を書いてみる。
[ガンマ補正の参考ページ](http://imagingsolution.blog107.fc2.com/blog-entry-67.html)

```powershell
Add-Type -AssemblyName System.Drawing
# ガンマ補正する関数
function Correct-Gamma($src_image, $dst_imag, $gamma){
  # ルックアップテーブル（LUT）作成
  $lut = 0..255
  0..255 | %{
    $lut[$_] = [Math]::Round([Math]::Pow(($_ / 255), 1 / $gamma) * 255)
  }

  $image_width = $src_image.Width
  $image_height = $src_image.Height
  0..($image_width-1) | %{
    $x = $_
    0..($image_height-1) | %{
      $y = $_
      $a = $src_image.GetPixel($x, $y).A
      $r = $src_image.GetPixel($x, $y).R
      $g = $src_image.GetPixel($x, $y).G
      $b = $src_image.GetPixel($x, $y).B
      # LUT使って変換した輝度値を出力
      $color = [System.Drawing.Color]::FromArgb($a, $lut[$r], $lut[$g], $lut[$b])
      $dst_image.SetPixel($x, $y, $color)
    }
  }
  return $dst_image
}

$src_image = [System.Drawing.Image]::FromFile((pwd).Path + "\lenna.png")
$dst_image = New-Object System.Drawing.Bitmap($src_image.Width, $src_image.Height)
$dst_image = (Correct-Gamma $src_image $dst_image 1.6)
$dst_image.Save((pwd).Path + "\test_gamma.png", [System.Drawing.Imaging.ImageFormat]::Png)
# オブジェクトを破棄
$src_image.Dispose()
$dst_image.Dispose()
```

実行結果
Before（左）、After（右）
<img src="https://qiita-image-store.s3.amazonaws.com/0/116209/827a1a29-f2e8-e8b2-aac6-19e7eeaff13f.png" width=20%>　<img src="https://qiita-image-store.s3.amazonaws.com/0/116209/de7b5863-bb4e-892c-f0e5-dd5bf885501e.png" width=20%>

明るくなった。

## 参考リンク

[PowerShell で画像の回転、リサイズを行う](https://qiita.com/miyamiya/items/d1a975fb6618d46eda0c)
[PowerShell と C# で画像処理（ピクセル操作）](https://qiita.com/saggie/items/2cc1cc89830c1184ab75)