第14回 動画 (1)

本題に入る前に、必ず 連絡 の動画を見てください。
Google Colabへのリンク

画像の読み込み

動画の解説を参照

今回は静止画を元にして動画を作成します。
次項以降では第1~4回の画像の変形を応用し、パラメータを変えながら各フレーム (コマ) の画像を作り、それをまとめて動画にします。
特に「4. 変形する動画」では第4回で調べた4つの頂点の座標を使い、長方形の部分が動画全体にフィットしていく動画を作ります。
そのため、第4回の元画像をそのまま元画像として使います。
ここでは、元画像の画像変数 img への読み込みと、出力動画のサイズ決定を行います。
また、以下の項での処理のために、元画像から動画へのリサイズのために必要な行列を作ります。

元画像に対する出力動画の大きさの比はこのようになります。
v_ratio = w_video / w
warpAffine関数で画像をスケーリングするだけなら、このような行列で十分ですが、
\( M_{scale}=\begin{bmatrix} v_{ratio} & 0 & 0 \\ 0 & v_{ratio} & 0 \end{bmatrix} \)
M_scale = np.array([[v_ratio, 0, 0], [0, v_ratio, 0]], dtype=np.float32)
この行列の左側から、以下の項で行う「拡大」「回転」「スキュー」「射影変換」に対応する行列 (2行3列) をかければ「倍率をかけてから・・・する」という処理を一気に実行できます。
ただし、2行3列の行列どうしは掛け算ができないので \(M_{scale}\) を3行3列になるように変換します。
\( M_{scale}=\begin{bmatrix} v_{ratio} & 0 & 0 \\ 0 & v_{ratio} & 0 \\ 0 & 0 & 1 \end{bmatrix} \)
M_scale = np.vstack((M_scale, [0, 0, 1]))

import cv2
import numpy as np
from google.colab import files

file = files.upload()
filename = next(iter(file))
img = cv2.imread(filename)
h, w = img.shape[:2]

w_video = 800
h_video = int(h * (w_video / w))

v_ratio = w_video / w
M_scale = np.array([[v_ratio, 0, 0], [0, v_ratio, 0]], dtype=np.float32)
M_scale = np.vstack((M_scale, [0, 0, 1]))
これまでの課題では、多くの場合に元画像が大きいときに処理が重くならないように最初の段階で元画像をリサイズし、それを基準にしていろいろな処理を行っていました。
しかし、今回はなるべく動画の画質を良くしたいので画像変数にはリサイズの処理を行わず、そのまま使います。

1. ズームする動画

動画の解説を参照

動画出力に関わるコード
動画を作るには、まず書き込み用の出力先変数を作ります。
この「out」に1フレームずつ画像変数を追加していきます。
第3引数は1秒あたりのフレーム数 (fps : frames per second) です。ここでは1秒あたり30フレームにします。
fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
out = cv2.VideoWriter('ex1.mp4', fourcc, 30, (w_video, h_video))
for文などの繰り返しの中で、各フレームの画像変数 frame を作り、outに書きこみます。
out.write(frame)
すべてのフレームを書きこんだら、書き込み終了の処理を行います。
out.release()

画像の中心を基準とした拡大に関わるコード
まず、for文の中で動画にかける倍率 \(s\) を決めます。
このコードのようにすれば、 i が 0~179 の範囲で変わると、s は1~7の範囲で変化します。
s = 1 + i / 30
動画の中心、つまり \((w_{video}/2, h_{video}/2)\) を中心とした拡大を行いたいので、それに対応した変換行列を作ります。
\( M=\begin{bmatrix} s & 0 & (1-s)w_{video}/2 \\ 0 & s & (1-s)h_{video}/2 \end{bmatrix} \)
M = np.array([
      [s, 0, (1-s) * w_video / 2],
       [0, s, (1-s) * h_video / 2]
      ], dtype=np.float32)
これと前項で作った \(M_{scale}\) の積 \(MM_{scale}\) が「元画像から動画サイズになるように倍率をかけ、そのあとで動画の中心を基準として s 倍のスケールをかける」という変換行列になります。
M = np.dot(M, M_scale)
元画像にこの行列による変換を行ってできた画像を「そのフレームの画像」とします。
frame = cv2.warpAffine(img, M, (w_video, h_video))

fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
out = cv2.VideoWriter('ex1.mp4', fourcc, 30, (w_video, h_video))

for i in range(180):
  s = 1 + i / 30
  M = np.array([
      [s, 0, (1-s) * w_video / 2],
       [0, s, (1-s) * h_video / 2]
      ], dtype=np.float32)
  M = np.dot(M, M_scale)
  frame = cv2.warpAffine(img, M, (w_video, h_video))
  out.write(frame)

out.release()

課題1

元画像を1~7倍にズームする動画 ex1.mp4 を出力してください。
※ 動画はGoogle Colabのプレビューアでは再生できないので、ダウンロードして再生して確認してください。
※ 動画が再生できない場合はひとまず以降の内容に進んでください
※ PCで動画を再生できない場合は、チャットで自分あてに動画を添付して送れば、スマホでその動画を見られる可能性はあります (iPhone環境では確認済みです)。
出力例

2. 回転する動画

動画の解説を参照

前項の拡大処理のかわりに、動画の中心を基準として i° 回転する変換
\( M=\begin{bmatrix} \cos(i) & -\sin(i) & \frac{w_{video}}{2}(1-\cos(i))+\frac{h_{video}}{2}\sin(i) \\ \sin(i) & \cos(i) & -\frac{w_{video}}{2}\sin(i)+\frac{h_{video}}{2}(1-\cos(i)) \end{bmatrix} \)
を作り、これと \(M_{scale}\) の積 \(MM_{scale}\) が「元画像から動画サイズになるように倍率をかけ、そのあとで動画の中心を基準として i°回転させる」という変換行列になります。
\(M\) は複雑に見えますが、OpenCVでその行列を作るためのコードは単純です。 第2回の「4. 画像を反時計回りに30°回転させる (はみ出しを考慮しない)」と同様です。
M = cv2.getRotationMatrix2D((w_video / 2, h_video /2), i, 1)

out = cv2.VideoWriter('ex2.mp4', fourcc, 60, (w_video, h_video))

for i in range(360):
  M = cv2.getRotationMatrix2D((w_video / 2, h_video /2), i, 1)
  M = np.dot(M, M_scale)
  frame = cv2.warpAffine(img, M, (w_video, h_video))
  out.write(frame)

out.release()

課題2

元画像を反時計回りに360°回転させる動画 ex2.mp4 を出力してください。
出力例

3. スキューする動画

動画の解説を参照

第3回の「1. 画像をx方向に30°スキューさせる」と「2. 画像をx方向に-30°スキューさせる」では、 元画像の絵がはみ出さないように出力画像の横幅を変えて、横方向の基準位置をずらしました。
動画ではフレームごとに画面サイズを変えることはできないので、上下が均等にはみ出すように横のずらし方を変えます。
\(M_x= \begin{bmatrix} 1 & \tan\theta & -\frac{h_{video}}{2}\tan\theta \\ 0 & 1 & 0 \end{bmatrix} \)
M_x = np.array([[1, t, -(h_video * t) // 2], [0, 1, 0]], dtype=np.float32)
y方向のスキューも同様にします。
\(M_y= \begin{bmatrix} 1 & 0 & 0 \\ \tan\theta & 1 & -\frac{w_{video}}{2}\tan\theta \end{bmatrix} \)
M_y = np.array([[1, 0, 0], [t, 1, -(w_video * t) // 2]], dtype=np.float32)
あとはこれまでと同様にして元画像→動画のスケーリングと組み合わせるだけです。
out_x = cv2.VideoWriter('ex3_x.mp4', fourcc, 60, (w_video, h_video))
out_y = cv2.VideoWriter('ex3_y.mp4', fourcc, 60, (w_video, h_video))

for i in range(-60, 61):
  t = np.tan(np.deg2rad(i))
  M_x = np.array([[1, t, -(h_video * t) // 2], [0, 1, 0]], dtype=np.float32)
  M_y = np.array([[1, 0, 0], [t, 1, -(w_video * t) // 2]], dtype=np.float32)
  M_x = np.dot(M_x, M_scale)
  M_y = np.dot(M_y, M_scale)
  frame_x = cv2.warpAffine(img, M_x, (w_video, h_video))
  frame_y = cv2.warpAffine(img, M_y, (w_video, h_video))
  out_x.write(frame_x)
  out_y.write(frame_y)

out_x.release()
out_y.release()

課題3

元画像をx方向, y方向に -60~60°の範囲でスキューさせる動画 ex3_x.mp4, ex3_y.mp4 を出力してください。
出力例 (X方向スキュー)
出力例 (Y方向スキュー)

4. 変形する動画

動画の解説を参照

第4回では、選択した範囲の左上・右上・右下・左下の座標 corners が出力画像の左上・右上・右下・左下の座標 new_corners に移動するような変換を行いました。
ここでは元の状態から変換済みの (選択部分が動画と同じ形の長方形になった) 状態に滑らかに変わるようにしたいので、
corners → corners
new_corners → goal_corners
として、動画の進行度合いによってそれらの中間の点 (内分点) dividing_corners を求めることにします。
for文で i を 0~179 の範囲で変化させると
ratio = 1 - i / 180
は 0~1の範囲で変化します。これを使ってこのような dividing_corners を計算すると、corners から goal_corners に向かって移動する点の座標が得られます。
dividing_corners = corners * ratio + goal_corners * (1 - ratio)
この点を「移動先」として変換行列を作ります。
M = cv2.getPerspectiveTransform(corners, dividing_corners)
あとはこれまでと同様にして元画像→動画のスケーリングと組み合わせるだけです。
out = cv2.VideoWriter('ex4.mp4', fourcc, 60, (w_video, h_video))

corners = np.array(
    [
        [?, ?],
        [?, ?],
        [?, ?],
        [?, ?]
    ], dtype=np.float32)

goal_corners = np.array(
    [
        [0, 0],
        [w, 0],
        [w, h],
        [0, h]
    ], dtype=np.float32)

for i in range(180):
  ratio = 1 - i / 180
  dividing_corners = corners * ratio + goal_corners * (1 - ratio)
  M = cv2.getPerspectiveTransform(corners, dividing_corners)
  M = np.dot(M_scale, M)
  frame = cv2.warpPerspective(img, M, (w_video, h_video))
  out.write(frame)

out.release()

課題4

元画像の特定の部分が全体に広がるように滑らかに変形する動画 ex4.mp4 を出力してください。
※ cornersで設定する座標は、第4回のex1.txtの値を使ってください。
出力例

提出

動画の解説を参照

今回作成したノートブックを「imgprc2024@gmail.com」と共有してください。
※ 第2回で共有した、画像共有用の「画像処理」フォルダの中に「第14回」フォルダを作り、その中に今回の元画像と出力動画を入れてください。
※ 課題の再提出の際は、ノートブックの再度の共有はせずにチャットで連絡してください。