improc

Rによる画像処理:imagerパッケージの使い方

Rで画像処理をするにあたって imager というパッケージを用います。このページでは imager の基本的な使い方について扱います。まず始めにデジタル画像について説明します。

デジタル画像について

私たちが普段パソコンやスマホで扱っている画像や写真、つまりデジタル画像は、画素 (pixel) の集まりとして表現されています。それぞれのピクセルは画素値を持ち、多くの場合に1バイト(8ビット)で表現できる0から255の範囲の整数値を取っています。
グレースケール画像の場合であれば画素値はそのピクセルの明るさに対応するので、0であれば真っ黒、増えるに従って明るくなって行き、255で最大の明るさ、つまり真っ白を表現します。
画像の大きさに対応するだけのピクセルを縦横に並べた画素値の二次元配列としてグレースケール画像は表現できることになります。

カラー画像の場合、各ピクセルはRGB(赤緑青)の3つの画素値を持ちます。従って、R成分・G成分・B成分を表現する3個の二次元配列を用意することでカラー画像は表現できます。1つのカラー画像を、R画像、G画像、B画像の3つの画像に分解して表現するというイメージです。また、透過情報(透明度・アルファ情報)を持つ画像の場合はRGBの他にA成分が加わります。


デジタル画像の座標系としては、左上を原点としたものが採用されることが多いように思います(これは言語やライブラリに依存します)。プログラミングにおいては画像データは多次元の数値配列として格納されることが多いでしょう。たとえば横600px 縦400pxのカラー画像の場合、600 x 400 x 3の三次元の数値配列として画像を表現できます。画像の変数名をPとして、例えばx座標が25でy座標が2の位置にあるBlue成分の画素値は変数Pの P[10,20,3] の位置に格納されています。画素値は0から255の範囲の数値であることが多いですが、imager では0から1の範囲を取ります(真っ白な画像の場合、全てのピクセルの画素値が1になる)。

imagerについて

imager は R で画像処理プログラミングをするためのパッケージです。画像のリサイズや回転、線画の抽出、モルフォロジー変換、空間フィルタリング、FFT、色空間の変換など、画像処理のための基本的な機能を備えています。R で画像処理をやりたい場合に手軽に使い始めることができて便利なパッケージです。他方で、特徴点の抽出やマッチング、物体検出のアルゴリズムなど、コンピュータビジョンで典型的に使われるような機能(高次特徴の処理)はまだ実装されていないので、必要なものは自作する必要がありそうです(またはPythonやC++でOpenCVを使いましょう)。

R の画像処理パッケージとしては他にも EBImagemagick などがあります。一通り試してみましたがどれも機能的には大差はないように思いました。imager が最も使いやすそうで、ドキュメントも比較的充実しており、作者が認知科学系の人だったので、私は imager を使うことにしました。また OpenCV の R ラッパーも開発している人がいるようですが、まだあまり開発が進んでいないようなので選択肢からは外れました。

インストール

コンソールに以下を打ってインストールします。

install.packages("imager")
Mac の場合は XQuartz: https://www.xquartz.org をさらにインストールする必要があると imager のページに記載があります。

情報源

作者のサイトに情報が集まっています。最初に読むとすればこれこれでしょうか。関数のドキュメントはここで見れます。

画像の読み込みと表示

imager をインストールしたらまずは画像の読み込みと表示をしてみましょう。
(以下の解説は RStudio 環境で実行確認をしているので、別の環境だと表示のされ方などで多少の違いがあるかもしれません)

img = load.image( "myimage.png" )
plot( img )
myimage.png という画像ファイルを読み込んでそれを表示させる例です。何か適当な画層を用意して読み込ませてみましょう。読み込み可能なファイル形式は JPEG, PNG, BMP に対応しています(もし他の画像形式に対応したい場合は ImageMagick を、動画ファイルを読み込みたい場合は ffmpeg をインストールします)。

imagerには boats という予約変数があり、ボートの画像が予め用意されています。
読み込み不要ですぐ使えるので何か処理を試したい場合などに便利です。

plot( boats )

画像の周りに軸とラベルが表示されていますが、これを消したい場合には以下のどちらかのやり方があります。

plot( boats, axes = FALSE, xlab = "", ylab = "" )
plot( as.raster( boats ) )
詳しくは ?plot.cimg や ?as.raster.cimg とコンソールに打つと情報が得られます。
ただ、軸があって特に困ることもないですし、imager::save.image という関数を使えば画像だけが保存できるので、普段は気にする必要はないでしょう。

cimg クラス

コンソールに画像の変数名を打つと情報が表示されます。

boats
# Image. Width: 256 pix Height: 384 pix Depth: 1 Colour channels: 3

Width と Height は画像のピクセルサイズです。
Depth は動画のフレーム数です。静止画を読み込んだ場合は 1 になります。
Colour channels (cc) は色のチャネル数です。グレースケール画像の場合は 1 ですが、カラー画像の場合は 3(RGB) または 4(RGBA) になります。

grayscale() という関数を使うことで画像をグレースケール化できます。

grayimg = grayscale( boats )
plot( grayimg )

コンソールに画像名を打ち込んでみましょう。 Colour channels (cc) の値が 1 になっているのが確認できます。
grayimg
# Image. Width: 256 pix Height: 384 pix Depth: 1 Colour channels: 1

imager では、画像データには cimg というクラスが与えられています。

class( boats )
# [1] "cimg"

この cimg クラスは、実体としてはただの四次元の数値配列です。

dim( boats )
#[1] 256 384   1   3
順にx, y, z(Depth), cc(Color channels)となっています。

画素のデータを取得するには次のようにします。

boats[ , , 1, 1 ] # R チャネルの画素データ
# [,1]      [,2]      [,3]      [,4]      [,5]      [,6]
# [1,] 0.3882353 0.3899486 0.3901242 0.3890260 0.3904318
# [2,] 0.3858633 0.4150829 0.4410514 0.4317388 0.4324542
# [3,] 0.3849406 0.4154990 0.4472226 0.4389835 0.4373150
# [4,] 0.3852481 0.4108869 0.4399946 0.4424267 0.4424585
# ...
x と y を指定していないので全ての値が表示されます。
boats[ , , 1, 2 ] # G チャネルの画素データ
boats[ , , 1, 3 ] # B チャネルの画素データ
grayimg[ , , 1, 1 ] # グレースケール画像の場合

x と y を指定すればその位置の画素値を取得できます。

boats[ 10, 20, 1, 1 ] # x = 10, y = 20 座標の R の画素値
# [1] 0.4308576

boats[ 1:3, 1:3, 1, 1 ] # x = 1~3, y =1~3 領域の R の画素値
# [,1]      [,2]      [,3]
# [1,] 0.3882353 0.3899486 0.3901242
# [2,] 0.3858633 0.4150829 0.4410514
# [3,] 0.3849406 0.4154990 0.4472226

つまり、R の数値配列と同様のやり方で画像データを扱えるということです。

色チャネル

特定の色チャネルのみのデータを抜き出すには以下のようにします。

r = R( boats ) # R チャネルの画素データ
g = G( boats ) # G チャネルの画素データ
b = B( boats ) # B チャネルの画素データ
plot( imlist( red = r, green = g, blue = b ), layout = "row" )
# imlist は cimg のリストを作るための関数です。

# 同じことを次のようにも書けます。
r = imsub( boats, cc == 1 )
g = imsub( boats, cc == 2 )
b = imsub( boats, cc == 3 )

# また次のようにも書けます。
r = channel( boats, 1 )
g = channel( boats, 2 )
b = channel( boats, 3 )

# 色チャネルが4つある画像の場合は透明度チャネルも取得できます。
a = imsub( img, 4 )
どのやり方の場合でも関数の返り値は cimg オブジェクトです。g = G( boats ) とした場合、変数 g は色チャネルが1つだけの画像、つまりはグレースケール画像であると見なせます。従って、plot 関数を使って表示するとグレースケールの画像が表示されることになります。

画素値の操作

range 関数を使って画像の画素値の最小値と最大値を調べてみましょう。imager では最小0、最大1で画素値を表現します。

range( boats )
#[1] 0.01608021 0.99999999

range( grayscale( boats ) )
# [1] 0.08454238 0.99697984

当然、画素値を変化させると画像が変化します。簡単な例で確認します。

boatsLight = boats^(1/3)
boatsDark = boats^3
layout( t(1:3) ) # レイアウトの設定(横に3つ並べる)
plot( boatsLight )
plot( boats )
plot( boatsDark )
layout( 1 ) # レイアウト設定を初期化する

# 上記の内容はこのようにすれば1行で書けます
plot( imlist( light = boats^(1/3), original = boats, dark = boats^3 ), layout = "row" )

次は画像を2値化する例です。画素値が 0.5 より大きければそのピクセルの画素値は 1 にし、0.5以下なら 0 にすることで、白黒画像が作れます。

gBoats = grayscale( boats )
gBoats[ gBoats > .5] = 1
gBoats[ gBoats <= .5 ] = 0
plot( gBoats )

ところで、先ほどの例では画像を明るくするのに boats^(1/3) という計算をしましたが、画素値を大きくしたいのであれば値の足し算をするのがもっと単純なやり方です。やってみましょう。

light = grayscale( boats ) + .4
original = grayscale( boats )
layout( t(1:3) )
plot( light )
plot( original )
layout( 1 )

# 一行で書くならこのようにします
plot( imlist( light = grayscale(boats) + .4, original = grayscale(boats) ), layout = "row" )

画像が何も変わっていません。これは、plot 関数で 画像を表示する際の仕様によるものです。
plot 関数は入力された画像の画素値を自動で正規化(rescale)した上で表示します。画素値を全て一律で増加させても、正規化によって表示された時の見た目が同じになってしまうのです。

例えば、画素値に2を加えた画像を表示させてみましょう。画素の最大値は1ですから、2を足せばどのピクセルの画素値も1より大きくなり、真っ白な画像になると予想されます。しかし実際にはそうなりません。

plot( grayscale( boats ) + 2 ) # 元と同じ
plot( grayscale( boats ) + 2, rescale = F ) # エラーが出る
このように plot 関数はデフォルトでスケーリングが有効化されているため、単純に値を足して画素値全体をシフトさせるだけだと画像の見た目は変わりません。また、スケーリングをオフにした場合に、画素値が1を超えるデータを表示させようとするとエラーが出ます。値を足した後に画素値が 1 を超えたピクセルの画素値を 1 にして、画素値が 0 から 1 の範囲に収まるようにしておく必要があります。
gBoats = grayscale( boats ) + .4
gBoats[ gBoats > 1 ] = 1 # you need to clamp values
plot( imlist( light = gBoats, original = grayscale( boats ) ), layout = "row" )

画像内の全てのピクセルが明るくなりました。

次は画像全体ではなく一部分のみを加工する例です。
特定の領域のピクセルの画素値を書き換えることで画像内に線を引いています。

gBoats = grayscale( boats )
gBoats[ 20:24, 1:384, 1, 1 ] = 1 # vertical white line
gBoats[ 1:256, 30, 1, 1 ] = 0 # horizontal black line
plot( gBoats )
imager::save.image( gBoats, "saved.png" )

作成した画像は save.image 関数を使って保存できます。
R の base パッケージにも同じ名前の関数があるので、ダブルコロンを使って imager の方の関数を指定しています。

画素値の分布の可視化

簡単な分析例として、画素値の分布のヒストグラムを作成してみます。

layout( t(1:4) )
hist( grayscale( boats ), main = "Luminance" )
hist( R( boats ), main = "Red" )
hist( G( boats ), main = "Green" )
hist( B( boats ), main = "Blue" )

ggplot2 を使っていい感じに可視化してみましょう。
まずは as.data.frame という関数を使うことで画像(cimgクラスのデータ)をデータフレームの形式に変換します。

df = as.data.frame( boats )
head( df )
#   x y cc    value
# 1 1 1  1 99.00000
# 2 2 1  1 98.39513
# 3 3 1  1 98.15985
# 4 4 1  1 98.23828
# 5 5 1  1 98.43990
# 6 6 1  1 98.31671

あとは好きなように可視化します。

df$color = as.factor( df$cc )
levels( df$color ) = c( "R", "G", "B" )

plot( ggplot( df, aes( value, fill = color ) ) +
        geom_histogram( bins = 30, size = .2, color = "black" ) + facet_wrap( ~ color ) )

plot( ggplot( df, aes( x = value, fill = color ) ) +
        geom_histogram( binwidth = 1/256, alpha = .5, position = "identity" ) )

plot( ggplot( df, aes( x = value, fill = color ) ) + geom_density( alpha = .3 ) )

コメント

Copied title and URL