ページ

2015-08-10

PowerShell と ImageMagick (Magick.NET)で画像を加工する

WindowsでImageMagickを使おうとすると、cmdの引数の扱いがだるいと思っていたのだけど、ImageMagickライブラリの.NETラッパーであるところのMagick.NETをPowerShellから呼んでしまえば、便利に呼べることに気づいた。convertコマンドのパラメータの順序とか考えなくていいので、正直convert使うより楽かもしれない。

基本的にMagick.NETを呼んでいけばOK(気づいたPowerShell固有の罠は以下に書いておく)。本格的に処理するのであればC#でコード書いたほうがいいと思うけど、そこまででもないワンタイムバッチ処理にどうぞ。ただし「Verb-Noun -Arg abc」styleでない、.NETクラス直呼び出しの「なんちゃってPowerShell」なので、がっかりされる前に宣言しておく。コンソールで打って問題ない量だと思うけど。

なお動かしてる環境は、PowerShell v4 / x64。

準備

インストール

適当なフォルダを作り、NuGetでMagick.NETのx64 or x86を環境にあわせてもってくる。
PS C:\> mkdir c:\opt\magick
PS C:\> cd c:\opt\magick
PS C:\opt\magick> Invoke-WebRequest https://nuget.org/nuget.exe -OutFile nuget.exe
PS C:\opt\magick> .\nuget install Magick.NET-Q16-x64
NuGet的にはAnyCPUがあるので、何も考えずにAnyCPUを選びたくなるところではあるが、それはNG。というのも、AnyCPU版はx86とx64のDLLをEmbedded Resourceでかかえる形になっていて、必要に応じてロードしているっぽいのだが、これがAdd-Typeでうまくロードできない。x64やx86はプラットフォーム依存DLLがそのまま置いてあるのでOK。

アセンブリのロード

Add-Typeするだけ。これはさすがにスクリプト化しておいたほうが便利かもね。
PS C:\opt\magick> Add-Type -Path .\Magick.NET-*\lib\net40-client\Magick.NET-x64.dll

イメージの読み書き

イメージの読み込み

とにかくここから始まる。これはMagickImageインスタンスを作ればOK。
PS c:\temp> $image = New-Object ImageMagick.MagickImage c:\temp\sample.jpg
ただし、ワーキングディレクトリはPowerShellのものではないので、フルパスで指定するのが無難。クラス名はNew-O[TAB] MagickI[TAB]で補完できるのでおすすめ。

イメージの書き込み

後述の加工作業を行ってから、.Write(path)で書く。

PS C:\temp> $image.Write('c:\temp\sample-out.jpg')

基本的に「New-Object」「何かして」「Write」の組み合わせ。

フォーマット指示が必要な場合、Writeの前に入れる。
PS C:\temp> $image.Format = 'png'
PS C:\temp> $image.Write('c:\temp\sample-out.png')
とか、
PS C:\temp> $image.Format = 'jpeg'
PS C:\temp> $image.Quality = 99
PS C:\temp> $image.Write('c:\temp\sample-out-q99.jpg')
とか。

あとは
PS c:\temp> $image.Dispose()
で後片付け。インタラクティブ操作時はpowershellごと消してもいいと思うけど、ループ時とかは注意。

情報の表示

属性の表示

$imageと打てば、それだけで情報がでてくる。Width, Heightなど。これだけでもわりと便利。ただ、計算するプロパティが含まれているせいか、全部出すと重いのでループするのはおすすめしない。表示はこんな感じ(全部ではない):


EXIF情報の表示

$image.GetExifProfile().Values でOK。
PS C:\temp> $image.GetExifProfile().Values
 DataType                       IsArray                           Tag Value
 --------                       -------                           --- -----
    Ascii                         False              ImageDescription
    Ascii                         False                          Make SONY
    Ascii                         False                         Model NEX-6
    Short                         False                   Orientation 1
 Rational                         False                   XResolution 350
 Rational                         False                   YResolution 350
    Short                         False                ResolutionUnit 2
    Ascii                         False                      Software NEX-6 v1.01
    Ascii                         False                      DateTime 2015:05:24 10:46:23
    Short                         False              YCbCrPositioning 2
Undefined                          True                       Unknown {80, 114, 105, 110...}
 Rational                         False                  ExposureTime 0.01
 Rational                         False                       FNumber 13
    Short                         False               ExposureProgram 2
    Short                         False               ISOSpeedRatings 100
(以下略)
PS C:\temp> $image.GetExifProfile().Values.Where({$_.Tag -eq 'FocalLengthIn35mmFilm'}).Value
24

1ファイル系の処理

1ファイルに対する処理をこの画像を使って行っていく。


回転(変換と保存)

回転はRotate()。

PS c:\temp> $image = New-Object ImageMagick.MagickImage c:\temp\sample.jpg
PS C:\temp> $image.Rotate(90)
PS C:\temp> $image.Write('c:\temp\sample-out.jpg')


(多分)EXIFにしたがって回転してくれるAutoOrient()ってのもある。便利ね。

切り出し

切り出しはCrop()。Gravityはどこからひっぱってくるかの指示。
PS c:\temp> $image = New-Object ImageMagick.MagickImage c:\temp\sample.jpg
PS c:\temp> $image.Crop(200, 200, [ImageMagick.Gravity]::Center)
PS C:\temp> $image.Write('c:\temp\sample-out.jpg')

リサイズ

Resize()かResample()かScale()かThumbnail()。この使い分けは難しいのだけど、とりあえず無難そうなResizeだけ説明。

そのResize()もパラメータをいくつか選べる。
PS C:\temp> $image.Resize

OverloadDefinitions
-------------------
void Resize(int width, int height)
void Resize(ImageMagick.MagickGeometry geometry)
void Resize(ImageMagick.Percentage percentage)
void Resize(ImageMagick.Percentage percentageWidth, ImageMagick.Percentage percentageHeight)

一番簡単なやつがwidth, heightを指定するやつ(New-ObjectとWriteはここの説明では省略)
PS C:\temp> $image.Resize(200, 100)
ただし、アスペクト比を維持するので、(200, 100)にはならない。ここに入る大きさになる。



パーセンテージ指定が次に簡単(書き方がイマイチ...)
PS C:\temp> image.Resize((New-Object ImageMagick.Percentage 50))


で、もっともいろいろできるのが、MagickGeometry指定パターン。ImageMagickをコマンドで使っている人ならおなじみの、例の記法が使える。例えば、アスペクト比を無視する!を付与したスタイル。
PS C:\temp> $image.Resize((New-Object ImageMagick.MagickGeometry '200x200!'))


このあたりの挙動はImageMagickそのものの話なので、詳しくは公式ドキュメント(ImageMagick v6 Examples --
Resize or Scaling (General Techniques)
)を参照。なお、日本語でまとめてくれてる人もいる(ImageMagickでリサイズする方法

複数ファイル系の処理

ここからはこの2つの画像を例に説明。

結合

結合は、MagickImageCollectionを作ってimageを追加し、AppendHorizontally()かAppendVertically()を呼ぶ。
PS C:\temp> $image1 = New-Object ImageMagick.MagickImage c:\temp\sample.jpg
PS C:\temp> $image2 = New-Object ImageMagick.MagickImage c:\temp\sample2.jpg

PS C:\temp> $col = New-Object ImageMagick.MagickImageCollection
PS C:\temp> $col.Add($image1)
PS C:\temp> $col.Add($image2)

PS C:\temp> $image = $col.AppendVertically() # あるいは $col.AppendHorizontally()  
PS C:\temp> $image.Write('c:\temp\sample-out.jpg')

重ねる

結合とだいたい同じノリだけども、重ねることも可能。
PS C:\temp> $image1 = New-Object ImageMagick.MagickImage c:\temp\sample.jpg
PS C:\temp> $image2 = New-Object ImageMagick.MagickImage c:\temp\sample2.jpg

PS C:\temp> $col = New-Object ImageMagick.MagickImageCollection
PS C:\temp> $col.Add($image1)
PS C:\temp> $col.Add($image2)

PS C:\temp> $image = $col.Evaluate([ImageMagick.EvaluateOperator]::Add)
PS C:\temp> $image.Write('c:\temp\sample-out.jpg')

ここまで書いておいて例が悪いな。

etc, etc...

あとはBlurとか、さまざまなフィルタがあるのだけど、きりないからこのへんでやめ。ImageMagickは加工は得意なので、できることはたくさんある。

他のものを知りたかったら、$image.で[TAB]してみたり、MagickNetのドキュメントとか、ImageMagickのコマンドラインオプション(ImageMagick: Command-line Options)を参照。

おまけ: 統計情報の表示

最後に一応、需要はなさそうだけど、これをやるために使い始めたので説明。平均とかの取り方。

Statistics()を呼んで、Composite()あるいはGetChannel(チャンネル)を呼ぶ。
先に
$image.Grayscale([ImageMagick.PixelIntensityMethod]::Lightness)
を呼んでおくのも便利かも。
PS C:\temp> $stat = $image.Statistics()
PS C:\temp> $stat.Composite()

Channel           : Composite
Depth             : 1
Entropy           : 0.966856332282486
Kurtosis          : -1.25816857805541
Maximum           : 65535
Mean              : 27115.482213115
Minimum           : 0
Skewness          : 0.0779930273889325
StandardDeviation : 17685.6919467067
Sum               : 27115.482213115
SumCubed          : 45922010656938.6
SumFourthPower    : 2.14361048522477E+18
SumSquared        : 1049386363.99743
Variance          : 312783699.633806

PS C:\temp> $stat.GetChannel([ImageMagick.PixelChannel]::Blue)

Channel           : Blue
Depth             : 8
Entropy           : 0.967346949241304
Kurtosis          : -1.26933689965117
Maximum           : 65535
Mean              : 28404.2418122061
Minimum           : 0
Skewness          : 0.0995661483530123
StandardDeviation : 18559.3929293196
Sum               : 28404.2418122061
SumCubed          : 52904690206274
SumFourthPower    : 2.59600361552516E+18
SumSquared        : 1151252018.83115
Variance          : 1151252018.83115

0 件のコメント:

コメントを投稿