からっぽのしょこ

読んだら書く!書いたら読む!同じ事は二度調べ(たく)ない

magickパッケージによるアニメーションの作成

はじめに

 R言語で画像処理を行うmagickパッケージを確認します。  この記事では、image_animate()image_write_gif()またはimage_write_video()を使ってアニメーション(gifファイル・mp4ファイル)を作成します。

【目次】

magickパッケージによるアニメーションの作成

 magickパッケージを利用してアニメーション(gif画像・mp4動画)を作成します。アニメーションに利用する画像のサイズが同じ場合と異なる場合で必要な処理が変わります。それぞれ確認します。
 この記事では、ggplot2パッケージとpatchworkパッケージを利用して2つのグラフ並べた図を作成し画像ファイルとして保存しておき、作成した画像をまとめてアニメーションを作成します。作図処理について不要であれば、「画像の作成」を読み飛ばしてください。また、この記事での作図コードやデータフレームは、あくまで参考図を作るためのものです。必要な部分を汲み取って利用してください。

 利用するパッケージを読み込みます。

# 利用パッケージ
library(tidyverse)
library(patchwork)
library(magick)

 この記事では、基本的にパッケージ名::関数名()の記法を使うので、パッケージを読み込む必要はありません。ただし、作図コードがごちゃごちゃしないようにパッケージ名を省略しているためggplot2と、patchworkパッケージの演算子を使うためpatchworkを読み込む必要があります。
 また、ネイティブパイプ演算子|>を使っています。magrittrパッケージのパイプ演算子%>%に置き換えても処理できますが、その場合はmagrittrも読み込む必要があります。

画像サイズが共通の場合

 まずは、アニメーションに利用する画像ファイルが全て同じサイズの場合の処理を確認します。

画像の作成

 アニメーションに利用する画像ファイルを作成します。ガウス-ガンマ分布の確率変数(サンプル)と、そのサンプルをパラメータとするガウス分布の関係を例とします。ガウス-ガンマ分布とガンマ分布の関係については「【R】ガウス-ガンマ分布から確率分布の生成 - からっぽのしょこ」を参照してください。

作図コード(クリックで展開)

 ガウス-ガンマ分布のパラメータ(ハイパーパラメータ)と、ガウス分布のパラメータとして利用する値(ガウス-ガンマの確率変数の値)を指定します。

# ガウス-ガンマ分布のパラメータを指定
m    <- 0
beta <- 2
a    <- 5
b    <- 6

# ガウス-ガンマ分布の確率変数として利用する値を指定
lambda_i <- seq(from = 0, to = 2.5, length.out = 101)
mu_i     <- seq(from = -1.5, to = 1.5, length.out = 101)

# ガウス分布のパラメータを格納
param_df <- tibble::tibble(
  mu = mu_i, 
  lambda = lambda_i
)

# フレーム数を設定
frame_num <- nrow(param_df)
param_df
## # A tibble: 101 × 2
##       mu lambda
##    <dbl>  <dbl>
##  1 -1.5   0    
##  2 -1.47  0.025
##  3 -1.44  0.05 
##  4 -1.41  0.075
##  5 -1.38  0.1  
##  6 -1.35  0.125
##  7 -1.32  0.15 
##  8 -1.29  0.175
##  9 -1.26  0.2  
## 10 -1.23  0.225
## # … with 91 more rows

 作図用に、ガウス分布のパラメータをデータフレームに格納しておきます。パラメータの数(データフレームの行数)をフレーム数とします。

 ガウス-ガンマ分布の(作図用の)確率変数の値を作成して、点ごとに確率密度を計算します。

# ガウス-ガンマ分布の確率変数の期待値を計算
E_mu     <- m
E_lambda <- a / b

# ガウス-ガンマ分布の確率変数の値を作成
mu_vals <- seq(
  from = E_mu - 1/sqrt(beta*E_lambda) * 4, 
  to = E_mu + 1/sqrt(beta*E_lambda) * 4, 
  length.out = 200
)
lambda_vals <- seq(from = 0, to = E_lambda * 3, length.out = 200)

# ガウス-ガンマ分布を計算
gaussian_gamma_df <- tidyr::expand_grid(
  mu = mu_vals, # 確率変数μ
  lambda = lambda_vals # 確率変数λ
) |> # 確率変数(μとλの格子点)を作成
  dplyr::mutate(
    N_dens = dnorm(x = mu, mean = m, sd = 1/sqrt(beta*lambda)), # μの確率密度
    Gam_dens = dgamma(x = lambda, shape = a, rate = b), # λの確率密度
    density = N_dens * Gam_dens # 確率密度
  )
gaussian_gamma_df
## # A tibble: 40,000 × 5
##       mu lambda N_dens   Gam_dens     density
##    <dbl>  <dbl>  <dbl>      <dbl>       <dbl>
##  1 -3.10 0      0      0          0          
##  2 -3.10 0.0126 0.0561 0.00000748 0.000000420
##  3 -3.10 0.0251 0.0703 0.000111   0.00000780 
##  4 -3.10 0.0377 0.0763 0.000521   0.0000398  
##  5 -3.10 0.0503 0.0781 0.00153    0.000119   
##  6 -3.10 0.0628 0.0774 0.00346    0.000268   
##  7 -3.10 0.0754 0.0751 0.00665    0.000500   
##  8 -3.10 0.0879 0.0719 0.0114     0.000822   
##  9 -3.10 0.101  0.0682 0.0181     0.00123    
## 10 -3.10 0.113  0.0641 0.0269     0.00172    
## # … with 39,990 more rows

 ガウス分布の確率密度はdnorm()、ガンマ分布の確率密度はdgamma()で計算できます。

 目安として描画する用に、ガウス分布のパラメータの期待値(ガウス-ガンマ分布の確率変数の期待値)による確率密度を計算します。

# ガウス分布の確率変数の値を作成
x_vals <- mu_vals

# パラメータの期待値によるガウス分布を計算
E_gaussian_df <- tidyr::tibble(
  x = x_vals, # 確率変数
  density = dnorm(x = x_vals, mean = E_mu, sd = 1/sqrt(E_lambda)) # 確率密度
)
E_gaussian_df
## # A tibble: 200 × 2
##        x density
##    <dbl>   <dbl>
##  1 -3.10 0.00667
##  2 -3.07 0.00723
##  3 -3.04 0.00782
##  4 -3.00 0.00846
##  5 -2.97 0.00914
##  6 -2.94 0.00987
##  7 -2.91 0.0106 
##  8 -2.88 0.0115 
##  9 -2.85 0.0124 
## 10 -2.82 0.0133 
## # … with 190 more rows


 ガウス-ガンマ分布とガウス分布のグラフを確認します。

作図コード(クリックで展開)

# ガウス-ガンマ分布を作図
gaussian_gamma_graph <- ggplot() + 
  geom_contour_filled(data = gaussian_gamma_df, mapping = aes(x = mu, y = lambda, z = density, fill = ..level..), 
                      alpha = 0.8) + # パラメータの生成分布
  geom_point(mapping = aes(x = E_mu, y = E_lambda), 
             color = "red", size = 5, shape = 4) + # パラメータの期待値
  labs(title = "Gaussian-Gamma Distribution", 
       subtitle = parse(text = paste0("list(m==", m, ", beta==", beta, ", a==", a, ", b==", b, ")")), 
       fill = "density", 
       x = expression(mu), y = expression(lambda))

# ガウス分布を作図
gaussian_graph <- ggplot() + 
  geom_line(data = E_gaussian_df, mapping = aes(x = x, y = density), 
            color = "red", size = 1, linetype = "dashed") + # 期待値による分布 
  labs(title = "Gaussian Distribution", 
       subtitle = parse(text = paste0("list(E(mu)==", E_mu, ", E(lambda)==", round(E_lambda, 3), ")")), 
       x = expression(x), y = "density") # ラベル

# グラフを並べて描画
graph <- gaussian_gamma_graph / gaussian_graph + 
  patchwork::plot_layout(guides = "collect")
graph


ガウス-ガンマ分布とガウス分布

 {patchwork}/演算子を使うと上下にグラフを並べて描画できます。
 上の図では、ガウス-ガンマ分布の等高線上にパラメータの期待値を赤色のバツ印で示しています。また下の図では、パラメータの期待値によるガウス分布を赤色の破線で示しています。

 パラメータのサンプルごとに、サンプルの点とサンプルによる分布のグラフを重ねて描画し、画像ファイルとして保存します。

# 保存用のフォルダを作成
dir_path  <- "tmp_folder"
dir.create(path = dir_path)

# パラメータのサンプルごとに作図
for(i in 1:frame_num) {
  # i番目のパラメータを取得
  mu     <- mu_i[i]
  lambda <- lambda_i[i]
  
  # パラメータラベルを作成
  hyparam_text <- paste0(
    "list(", 
    "m==", m, ", beta==", beta, ", a==", a, ", b==", b, 
    ", E(mu)==", E_mu, ", E(lambda)==", round(E_lambda, 3), 
    ")"
  )
  param_text <- paste0(
    "list(mu==", round(mu, 2), ", lambda==", round(lambda, 2), ")"
  )
  
  # ガウス-ガンマ分布を作図
  gaussian_gamma_graph <- ggplot() + 
    geom_contour_filled(data = gaussian_gamma_df, mapping = aes(x = mu, y = lambda, z = density, fill = ..level..), 
                        alpha = 0.8) + # パラメータの生成分布
    geom_point(mapping = aes(x = E_mu, y = E_lambda), 
               color = "red", size = 5, shape = 4) + # パラメータの期待値
    geom_point(data = param_df[i, ], mapping = aes(x = mu, y = lambda), 
               color = "gold", size = 5) + # パラメータのサンプル
    labs(title = "Gaussian-Gamma Distribution", 
         subtitle = parse(text = hyparam_text), 
         fill = "density", 
         x = expression(mu), y = expression(lambda))
  
  # ガウス分布を計算
  gaussian_df <- tibble::tibble(
    x = x_vals, # 確率変数
    density = dnorm(x = x_vals, mean = mu, sd = 1/sqrt(lambda)) # 確率密度
  )
  
  # ガウス分布を作図
  gaussian_graph <- ggplot() + 
    geom_line(data = E_gaussian_df, mapping = aes(x = x, y = density), 
              color = "red", size = 1, linetype = "dashed") + # 期待値による分布 
    geom_line(data = gaussian_df, mapping = aes(x = x, y = density), 
              color = "gold", size = 1) + # サンプルによる分布
    coord_cartesian(ylim = c(0, 0.7)) + # 描画範囲
    labs(title = "Gaussian Distribution", 
         subtitle = parse(text = param_text), 
         x = expression(x), y = "density") # ラベル
  
  # グラフを並べて描画
  graph <- gaussian_gamma_graph / gaussian_graph + 
    patchwork::plot_layout(guides = "collect")
  
  # ファイルを書き出し
  file_path <- paste0(dir_path, "/", stringr::str_pad(i, width = 3, pad = "0"), ".png") # widthはframe_numの桁数
  ggplot2::ggsave(filename = file_path, plot = graph, width = 800, height = 800, units = "px", dpi = 100)
  
  # 途中経過を表示
  print(paste0(i, " (", round(i/frame_num*100, 1), "%)"))
}

 保存用のフォルダは空である必要があります。frame_num枚の画像(グラフ)が保存されます。

動画の作成

 続いて、複数枚の画像ファイルを1つの動画ファイルにします。

 ファイルパスを作成します。

# ファイル名を取得
file_name_vec <- list.files(dir_path)

# ファイルパスを作成
file_path_vec <- paste0(dir_path, "/", file_name_vec)
file_path_vec[1:5]
## [1] "tmp_folder/001.png" "tmp_folder/002.png" "tmp_folder/003.png"
## [4] "tmp_folder/004.png" "tmp_folder/005.png"

 list.files()dir_pathフォルダ内のファイル名を取得して、ファイルパスを作成します。

 画像ファイルを読み込んで、gifファイルに変換します。

# 画像ファイルを読み込み
pic_data_vec <- magick::image_read(path = file_path_vec)

# gif画像を作成
gif_data <- magick::image_animate(image = pic_data_vec, fps = 1, dispose = "previous")
gif_data

image_animate関数によるアニメーション

 image_read()で画像を読み込んで、image_animate()でgifファイルに変換します。fps引数に1秒当たりのフレーム数を指定します。

 gif画像またはmp4動画として書き出します。

# gifファイルを書き出し
magick::image_write_gif(image = gif_data, path = "GaussianGamma.gif", delay = 0.1)

# mp4ファイルを書き出し
magick::image_write_video(image = gif_data, path = "GaussianGamma.mp4", framerate = 10)

image_animate関数とimage_write_gif関数によるアニメーション

 image_write_gif()でgifファイル、image_write_video()でmp4ファイルとして書き出します。delay引数は1フレーム当たりの表示秒数、framerate引数は1秒当たりのフレーム数を指定できます。1秒当たり10フレームとするのであれば、delay引数に1/10framerate引数に10を指定します。
 ファイルの書き出し時に、保存先のファイルパスを出力します。(書き出しと言いつつ、レンダリングしてますよね?image_animate()との役割の違いがよく分かりません。)
 動画ファイルは貼れないので省略します。

 全ての画像サイズが同じ場合は、image_animate()を使わなくてもアニメーションを作成できます(質に違いがあるかも?)。

# gifファイルを書き出し
magick::image_write_gif(image = pic_data_vec, path = "GaussianGamma.gif", delay = 0.1)

# mp4ファイルを書き出し
magick::image_write_video(image = pic_data_vec, path = "GaussianGamma.mp4", framerate = 10)

image_write_gif関数によるアニメーション

 詳しくは、次で確認します。

画像サイズが異なる場合

 次は、アニメーションに利用する画像ファイルが異なるサイズの場合の処理を確認します。gifファイルと同様なのでmp4ファイルについては省略します。

画像の作成

 アニメーションに利用する画像ファイルを作成します。フレームごとに x, yが1ずつ大きくまたは小さくなる座標上の z = x + yのヒートマップを例とします。

作図コード(クリックで展開)

 フレーム数を指定して、x軸とy軸の値を作成しz軸の値を計算します。

# フレーム数を指定
frame_num <- 10

# 座標の下限を指定
lower_x <- 4
lower_y <- 3

# 値を作成
x_vals <- seq(from = 1, to = lower_x+frame_num, by = 1)
y_vals <- seq(from = 1, to = lower_y+frame_num, by = 1)

# 作図用のデータフレームを作成
df <- tidyr::expand_grid(x = x_vals, y = y_vals) |> # 格子点を作成
  dplyr::mutate(z = x + y)
df
## # A tibble: 182 × 3
##        x     y     z
##    <dbl> <dbl> <dbl>
##  1     1     1     2
##  2     1     2     3
##  3     1     3     4
##  4     1     4     5
##  5     1     5     6
##  6     1     6     7
##  7     1     7     8
##  8     1     8     9
##  9     1     9    10
## 10     1    10    11
## # … with 172 more rows

 x軸の値をx_vals、y軸の値をy_valsを作成して、x_vals, y_valsの格子状の点(全ての組み合わせ)をexpand_grid()で作成しx軸とy軸の値を足します。


 ヒートマップを確認します。

作図コード(クリックで展開)

# ヒートマップを確認
ggplot(data = df, mapping = aes(x = x, y = y, fill = z)) + # ヒートマップ
  geom_tile(show.legend = FALSE) + 
  geom_text(mapping = aes(label = paste0("(", x, ", ", y, ")")), color = "white", size = 5) + # 座標ラベル
  scale_fill_gradient(low ="gold", high = "red") + # グラデーション
  scale_x_continuous(breaks = x_vals) + 
  scale_y_reverse(breaks = y_vals) + # y軸を反転
  coord_equal(clip = "off", expand = FALSE) + # 余白を非表示
  labs(title = "Heatmap", 
       subtitle = parse(text = paste0("list(x==list(1, cdots, ", lower_x+frame_num, ")", 
                                      ", y==list(1, cdots, ", lower_y+frame_num, "))")))


ヒートマップ

 geom_tile()でヒートマップを描画します。格子点を渡す必要があります。
 この図を最大の描画範囲として、座標(描画範囲)を変化させます。

 lower_x+1, lower_y+1のサイズから、lower_x+frame_num, lower_y+frame_numのサイズまでを描画し、画像ファイルとして保存します。

# 保存用のフォルダを作成
dir_path  <- "tmp_folder"
dir.create(path = dir_path)

# フレームごとに作図
for(i in 1:frame_num) {
  # i番目のデータを抽出
  tmp_df <- df |> 
    dplyr::filter(x <= lower_x+i, y <= lower_y+i)
  
  # ヒートマップを作成
  g <- ggplot(data = tmp_df, mapping = aes(x = x, y = y, fill = z)) + 
    geom_tile(show.legend = FALSE) + # ヒートマップ
    geom_text(mapping = aes(label = paste0("(", x, ", ", y, ")")), color = "white", size = 5) + # 座標ラベル
    scale_fill_gradient(low ="gold", high = "red") + # グラデーション
    scale_x_continuous(breaks = x_vals) + 
    scale_y_reverse(breaks = y_vals) + # y軸を反転
    coord_equal(clip = "off", expand = FALSE) + # 余白を非常時
    labs(title = "Heatmap", 
         subtitle = parse(text = paste0("list(x==list(1, cdots, ", lower_x+i, ")", 
                                        ", y==list(1, cdots, ", lower_y+i, "))")))
  
  # ファイルを書き出し
  file_path <- paste0(dir_path, "/", stringr::str_pad(i, width = 2, pad = "0"), ".png") # widthはframe_numの桁数
  ggplot2::ggsave(
    filename = file_path, plot = g, 
    width = (lower_x+i)*100, height = (lower_y+i)*100, units = "px", dpi = 100
  )
  
  # 途中経過を表示
  print(paste0(i, " (", round(i/frame_num*100, 1), "%)"))
}

 保存用のフォルダは空である必要があります。frame_num枚の画像(グラフ)が保存されます。

動画の作成:加工処理なし

 続いて、複数枚の画像ファイルをそのまま使って1つの動画ファイルにします。

 ファイルパスを作成します。

# ファイル名を取得
file_name_vec <- list.files(dir_path)

# ファイルパスを作成
file_path_vec <- paste0(dir_path, "/", file_name_vec)
file_path_vec[1:5]
## [1] "tmp_folder/01.png" "tmp_folder/02.png" "tmp_folder/03.png"
## [4] "tmp_folder/04.png" "tmp_folder/05.png"

 ファイル名の値が小さいほど、座標(サイズ)が小さいグラフ(画像)です。

 サイズが小さい順に画像ファイルを読み込んで、gifファイルに変換します。

# 画像ファイルを読み込み
pic_data_vec <- magick::image_read(path = file_path_vec)

# gif画像を作成
gif_data <- magick::image_animate(image = pic_data_vec, fps = 1, dispose = "previous")

# gifファイルを書き出し
magick::image_write_gif(image = gif_data, path = "heatmap.gif", delay = 0.4)

image_animate関数とimage_write_gif関数によるアニメーション:小→大

 image_read()で画像を読み込んで、image_animate()でgifファイルに変換して、image_write_gif()で書き出します。
 この方法でアニメーションを作成した場合、最初のサイズよりも大きい場合は、トリミングされ全てのフレームでサイズが統一されます。

 image_animate()で処理せずにgifファイルを作成します。

# 画像ファイルを読み込み
pic_data_vec <- magick::image_read(path = file_path_vec)

# gifファイルを書き出し
magick::image_write_gif(image = pic_data_vec, path = "heatmap.gif", delay = 0.4)
## Images are not the same size. Resizing to 500x400...

image_write_gif関数によるアニメーション:小→大

 こちらの方法では、全てのフレームが最初のサイズにリサイズされます。最初のサイズよりも大きい場合は小さく、小さい場合は大きくなります。リサイズの際にアスペクト比が変わることもあります。(アスペクト比によっては?リサイズされずにエラーになることもあった気がするのですが、再現できませんでした。)

 今度は、サイズが大きい順に画像ファイルを読み込んで、gifファイルに変換します。

# gif画像を作成
file_path_vec |> 
  rev() |> 
  magick::image_read() |> 
  magick::image_animate() |> 
  magick::image_write_gif(path = "heatmap.gif", delay = 0.4)

image_animate関数とimage_write_gif関数によるアニメーション:大→小

 rev()file_path_vecを逆順に並べ替えて画像を読み込み、image_animate()を使ってgif画像を作成します。pic_data_vecに対してrev()を使っても処理できます。
 最初のサイズよりも小さい場合は、透過背景?白背景?によって全てのフレームでサイズが統一されます。

 image_animate()を使わずにgifファイルを作成します。

# gif画像を作成
file_path_vec |> 
  rev() |> 
  magick::image_read() |> 
  magick::image_write_gif(path = "heatmap.gif", delay = 0.4)
## Images are not the same size. Resizing to 1400x1300...

image_write_gif関数によるアニメーション:大→小

 こちらは先ほどと同様に、全てのフレームが最初のサイズにリサイズされます。先ほどよりも最初のサイズが大きいので、全体サイズも大きくなっています。

動画の作成:加工処理あり

 続いて、複数枚の画像ファイルに関して、サイズを統一する処理を行って1つの動画ファイルにします。

リサイズ

 画像サイズを合わせるように変形してからアニメーションを作成します。サイズ変更については「magick::image_scale関数による画像変形 - からっぽのしょこ」を参照してください。

 全ての画像の情報を取得します。

# 画像情報を取得
pic_info_df <- file_path_vec |> 
  magick::image_read() |> 
  magick::image_info()
pic_info_df
## # A tibble: 10 × 7
##    format width height colorspace matte filesize density
##    <chr>  <int>  <int> <chr>      <lgl>    <int> <chr>  
##  1 PNG      500    400 sRGB       FALSE    20452 39x39  
##  2 PNG      600    500 sRGB       FALSE    29291 39x39  
##  3 PNG      700    600 sRGB       FALSE    39958 39x39  
##  4 PNG      800    700 sRGB       FALSE    52067 39x39  
##  5 PNG      900    800 sRGB       FALSE    65956 39x39  
##  6 PNG     1000    900 sRGB       FALSE    82704 39x39  
##  7 PNG     1100   1000 sRGB       FALSE    98024 39x39  
##  8 PNG     1200   1100 sRGB       FALSE   114962 39x39  
##  9 PNG     1300   1200 sRGB       FALSE   135178 39x39  
## 10 PNG     1400   1300 sRGB       FALSE   156548 39x39

 image_info()で画像サイズなどを得られます。

 横サイズ(横方向ピクセル数)と縦サイズ(縦方向ピクセル数)の最大値を取得します。

# 縦横の最大サイズを取得
max_w <- max(pic_info_df[["width"]])
max_h <- max(pic_info_df[["height"]])
max_w; max_h
## [1] 1400
## [1] 1300


 リサイズ後の画像情報を確認しておきます。

# 最大サイズに変形して確認
file_path_vec |> 
  magick::image_read() |> 
  magick::image_scale(geometry = paste0(max_w, "x", max_h)) |> 
  magick::image_info()
## # A tibble: 10 × 7
##    format width height colorspace matte filesize density
##    <chr>  <int>  <int> <chr>      <lgl>    <int> <chr>  
##  1 PNG     1400   1120 sRGB       FALSE        0 39x39  
##  2 PNG     1400   1167 sRGB       FALSE        0 39x39  
##  3 PNG     1400   1200 sRGB       FALSE        0 39x39  
##  4 PNG     1400   1225 sRGB       FALSE        0 39x39  
##  5 PNG     1400   1244 sRGB       FALSE        0 39x39  
##  6 PNG     1400   1260 sRGB       FALSE        0 39x39  
##  7 PNG     1400   1273 sRGB       FALSE        0 39x39  
##  8 PNG     1400   1283 sRGB       FALSE        0 39x39  
##  9 PNG     1400   1292 sRGB       FALSE        0 39x39  
## 10 PNG     1400   1300 sRGB       FALSE        0 39x39

 image_scale()で画像サイズを指定して変形できます。geometry引数にサイズの上限を"横サイズx縦サイズ"の形式で指定すると、それぞれの値を超えない範囲でアスペクト比を保って変形します。
 アスペクト比が変わらないので、全ての画像が指定したサイズになるわけではありません。アスペクト比を保たずに指定したサイズにする場合は、"横サイズx縦サイズ!"を指定します。

 画像サイズを調整して、gifファイルを作成します。

# 最大サイズに変形してgif画像を作成
file_path_vec |> 
  #rev() |> 
  magick::image_read() |> 
  magick::image_scale(geometry = paste0(max_w, "x", max_h)) |> 
  #magick::image_animate(fps = 1, dispose = "previous") |> 
  magick::image_write_gif(path = "heatmap.gif", delay = 0.4)
## Images are not the same size. Resizing to 1400x1120...

image_write_gif関数によるアニメーション:サイズ調整

 全ての画像を同じサイズにできていないので、image_write_gif()によってアスペクトを保たないサイズに変形されています。image_animate()を使う場合はトリミングされます。

背景画像を挿入

 最大サイズの背景画像を用意しておき各画像を重ねることでサイズを統一してからアニメーションを作成します。

 背景画像を読み込んで、最大サイズに変形します。

# 背景用の画像を読み込み
background_data <- magick::image_read(path = "../data/picture/background.png") |> 
  magick::image_scale(geometry = paste0(max_w, "x", max_h, "!"))
background_data |> 
  magick::image_info()
background_data
## # A tibble: 1 × 7
##   format width height colorspace matte filesize density
##   <chr>  <int>  <int> <chr>      <lgl>    <int> <chr>  
## 1 PNG     1400   1300 sRGB       FALSE        0 47x47

背景用の画像

 この例では真っ白の画像を使います。

 1枚ずつ背景画像に重ねます(本当はベクトルのまま処理したかった)。

# 画像ごとに背景を挿入
for(i in seq_along(file_path_vec)) {
  # i番目のファイルパスを抽出
  file_path <- file_path_vec[i]
  
  # 画像を読み込み
  pic_data <- magick::image_read(path = file_path)
  
  if(i == 1) {
    # 背景画像に重ねる
    pic_data_vec <- magick::image_mosaic(image = c(background_data, pic_data))
  } else {
    # 背景画像に重ねる
    pic_back_data <- magick::image_mosaic(image = c(background_data, pic_data))
    
    # ベクトルに格納
    pic_data_vec <- c(pic_data_vec, pic_back_data)
  }
}
pic_data_vec[1]

背景を付けた画像

 画像データのベクトルをimage_mosaic()に渡すと重ねた画像を作成します。
 加工した画像データをpic_data_vecに格納していきます。

 gifファイルを作成します。

# gif画像を作成
pic_data_vec |> 
  magick::image_animate(fps = 1, dispose = "previous") |> 
  magick::image_write_gif(path = "heatmap.gif", delay = 0.4)

image_write_gif関数によるアニメーション:背景を挿入

 各画像が左上で固定されたアニメーションになります。

 この記事では、magickパッケージによるアニメーションの作成を解説しました。

参考

おわりに

 上手くいかない例を載せると記事が冗長になりますね。もう少しパッケージの関数を見ていけばもっと上手く処理できそうな気もしますが、それはまた次の機会とします。
 patchworkを使うとgganimateを使えないのが残念と思いつつ使えたらやりたいことがさらに増えて先に進めないしまぁいいかと思ってたのですが、うっかりmagickを使ってグラフを動かせるようになってしまいました。

【次の内容】

www.anarchive-beta.com