Matlabで時間軸方向に連続する動画データを可視化する際の問題点と解決法

Matlabで時間軸方向に連続する動画データを可視化する。この際に必要になる動画の各フレームに対する処理と、動画データにカラーマップを適用する処理をまとめた。

ソースは以下で公開
[gist](https://gist.github.com/gkhtnk/9352e9f46b09e37c04642d5786f3649e)

使ったもの

* Matlab R2016a student
* [Image Processing Toolbox](https://jp.mathworks.com/help/images/index.html)が必要

# 問題点

matlabの[im2frame](https://jp.mathworks.com/help/matlab/ref/im2frame.html)を用いると、イメージをムービーフレーム(構造体配列)に変換することができる。

```Matlab
f = im2frame(X);
```

ムービーフレームを作製しておくと動画の再生([movie](https://jp.mathworks.com/help/matlab/ref/movie.html))や書き込み([writeVideo](https://jp.mathworks.com/help/matlab/ref/videowriter.writevideo.html))に使うことができる。

そこでim2frameを用いて、動画データ(`m x n x c x t`)をムービーフレームに変換したい。


しかしこの関数の引数`X`は
1. **インデックス付きイメージ**か**トゥルーカラー イメージ**だけしか許されていない(see also: [イメージの種類](https://jp.mathworks.com/help/matlab/creating_plots/image-types.html))
2. ndims>3の動画データ(`m x n x c x t`)は許されていない
の2点の制約があるため動画データを直接変換することはできない。

# 解決法
動画データの種類によって変わる

## 動画データ(**トゥルーカラー**)からムービーフレームへの変換
動画データ(`m x n x 3 x t`)の各フレームが**トゥルーカラー イメージ**の場合はそれぞれをim2frameに代入するだけでよい。
普通にfor文を用いて以下のように処理してもよいが、

```Matlab
%% Test RGB Data (with for loop)
NofFrame = 300;
MovieData = rand(64, 64, 3, NofFrame);
for fi = 1:NofFrame
  frame(fi) = im2frame(MovieData(:, :, :, fi));
end
movie(frame, 1, 30);
```

[arrayfun](https://jp.mathworks.com/help/matlab/ref/arrayfun.html)でスライスするとスッキリとなる。

```Matlab
%% Test RGB Data
NofFrame = 300;
MovieData = rand(64, 64, 3, NofFrame);

frame = arrayfun(@(fi) im2frame(MovieData(:, :, :, fi)), 1:NofFrame);
movie(frame, 1, 30);
```

データはランダムなカラーノイズ


## 動画データ(**インデックス付き**)からムービーフレームへの変換
動画データ(`m x n x 1 x t`)のインデックスに対応するmapを添える。

```Matlab
%% Test Ind Data
NofFrame = 300;
NofScale = 256;
MovieData = randi(NofScale, 64, 64, 1, NofFrame);

frame = arrayfun(@(fi) im2frame(MovieData(:, :, :, fi), hot(NofScale)),  1:NofFrame);
movie(frame, 1, 30);
```

## 動画データ(**未整形**)からムービーフレームへの変換
動画データ(`m x n x 1 x t`)が未整形の場合は[mat2gray](https://jp.mathworks.com/help/images/ref/mat2gray.html), [gray2ind](https://jp.mathworks.com/help/images/ref/gray2ind.html) を使って動画の各フレームを**インデックス付きイメージ**に変換する。

```Matlab
%% Test Mat Data (convert to Ind Data)
NofFrame = 300;
NofScale = 256;
MovieData = rand(64, 64, 1, NofFrame);

% convert mat -> (gray) -> ind
MovieData = gray2ind(mat2gray(MovieData , [min(MovieData(:)), max(MovieData(:))]), NofScale);

frame = arrayfun(@(fi) im2frame(MovieData(:, :, :, fi), cool(NofScale)), 1:NofFrame);
movie(frame, 1, 30);
```

[ind2rgb](https://jp.mathworks.com/help/images/ref/ind2rgb.html)を用いると、**インデックス付きイメージ**から**トゥルーカラー イメージ**に変換できるが、この関数は画像(`m x n`)に対してのみしかうまく動作しない。（色チャネルを結合する際のインデックスの振り方のせい）
どうしても**トゥルーカラー**の動画データがほしい場合は以下のようにrgbを分けて出力し、catで3次元目を結合する。

```Matlab
%% Test Mat Data (convert to RGB Data)
% ind2rgb function doesn't work for 3-or-more dimentional FrameData
NofFrame = 300;
NofScale = 256;
MovieData = rand(64, 64, 1, NofFrame);

% convert mat -> (gray) -> (ind) -> (r, g, b) -> rgb
[r, g, b] = ind2rgb(gray2ind(mat2gray(MovieData , [min(MovieData(:)), max(MovieData(:))]), NofScale), spring(NofScale);
MovieData = cat(3, r, g, b);

frame = arrayfun(@(fi) im2frame(MovieData(:, :, :, fi)), 1:NofFrame);
movie(frame, 1, 30);
```


## まとめ
* 動画データをムービーフレームに変換することができる。
* arrayfunを用いることでfor文を書かずにデータをスライスできる。
* mat2gray, gray2indを連続して使うことでデータを変換できる。


## 参考
[im2frame](https://jp.mathworks.com/help/matlab/ref/im2frame.html) | [movie](https://jp.mathworks.com/help/matlab/ref/movie.html) | [writeVideo](https://jp.mathworks.com/help/matlab/ref/videowriter.writevideo.html) | [arrayfun](https://jp.mathworks.com/help/matlab/ref/arrayfun.html) | [mat2gray](https://jp.mathworks.com/help/images/ref/mat2gray.html) | [gray2ind](https://jp.mathworks.com/help/images/ref/gray2ind.html) | [ind2rgb](https://jp.mathworks.com/help/images/ref/ind2rgb.html)