第15回 動画の作成

videoライブラリを使うと、Webカメラの画像を画面に表示させることができる。
これと第12回で使った「GifAnimation」ライブラリを組み合わせると、それをgifアニメーションとして保存したり、それぞれの画像にこれまでやったような変換を加えることで、エッジのみの動画や色がだんだん変わっていくような動画を作ることができる。

※ 演習室BのPCのWebカメラにシールが貼られている場合は剥がし、カメラの上のスライドスイッチでカメラを露出させて使ってください。

今回のプログラムの最終的な機能

プログラムを実行すると、3つの動画の作成過程が順に表示される。
dataフォルダには3枚のgif動画が作られる。
動画作成の完了後に画面をクリックしてからキーボードの1~3のキーを押すと動画が切り替わる。
(プログラム実行時に自動的にすべての動画が作成されるので、クリックするのは実行画面にフォーカスをあてるためだけ)
キー 画像 意味 特徴
1 1カメラ.gif カメラに写った動画
2 2エッジ.gif カメラ画像にエッジ抽出を行った動画 ものの輪郭が白い線で表示される
3 3色相変化.gif フレームに応じて変化する値をもとの色相に加えた動画 動画1ループで赤→緑→青→…の変化を3回繰り返す
キーと表示される画像

1. カメラの映像の保存

概要

videoライブラリを使うと、mp4形式やmov形式の動画などを表示したり、Webカメラの映像をリアルタイムに表示したりできる。今回はWebカメラから取り込んだ映像をgifアニメーションのファイルとして保存する。

まず、以下のようにすれば使用可能なWebカメラのデバイスを文字列の配列として取得できる。
String[] cams = Capture.list()

for文などでその中身を表示させると、例えばこのようにデバイス名が確認できる。

この場合ならcams[0]~cams[3]のうちのどれかを「使用するデバイス名」として決め、以下のようにすればカメラからの映像が取得できるようになる。
Capture cam = new Capture(this, 横解像度, 縦解像度, 使用するデバイス名);
cam.start();

draw関数の中で以下の命令を実行すれば、その時点でWebカメラに写っているものが cam に入る。
cam.read();

あとは普通の画像の変数のように image関数で実行画面にそれを表示できる。
image(cam, 0, 0, width, height);


これをgif画像として保存するために GifMaker型の変数 (ここではgifMaker) を使う。
まず、以下のようにしてファイルの保存先を決め、
gifMaker = new GifMaker(this, 保存先のパス);

以下のようにしてgif動画がループするようにし、動画の速さを決める。
フレーム間の時間を小さくすると動画は速く、大きくすると動画は遅くなる。
gifMaker.setRepeat(0);
gifMaker.setDelay(フレーム間の時間);

gif動画のフレームごとの画像はaddFrame関数で追加する。
gifMaker.addFrame(画像変数);

必要なフレームをすべて追加したあとでfinish関数を実行すればgifファイルが保存される。
gifMaker.finish();


gif画像を実行画面で表示するためには、Gif型の変数 (ここではgif) を使う。
まず、以下のようにしてファイルを読み込み、ループ再生を開始する。
gif = new Gif(this, ファイルのパス);
gif.loop();

あとは普通の画像の変数のようにdraw関数の中でimage関数を使えば gifファイルが動画として再生される。
image(gif, 0, 0, width, height);

課題 1

  1. Processing 4を起動する。
  2. 以下のコードをコピー&ペーストする。
    import processing.video.*;
    import gifAnimation.*;
    String[] fName = {"1カメラ", "2エッジ", "3色相変化"};
    GifMaker gifMaker;
    Gif gif;
    Capture cam;
    int count = 0;
    int selected = -1;
    int gLength = 60; // 1つの動画のフレーム数
    // X方向、Y方向のソーベルフィルタのマスクをまとめた2重配列
    int[][] mask = {{-1, 0, 1, -2, 0, 2, -1, 0, 1}, {-1, -2, -1, 0, 0, 0, 1, 2, 1}};
    
    void setup() {
      size(800, 600);
      textFont(createFont("MS Pゴシック", 48));
      String[] cams = Capture.list();
      // 使えるWebカメラのリストを表示
      for (int i=0; i<cams.length; i++) {
        println(i+":" + cams[i]);
      }
      cam = new Capture(this, width, height, cams[0]);
      cam.start();
    }
    
    void draw() {
      if (cam.available() && count < gLength*3) {
        cam.read(); // カメラの画像の読み込み
        int k = count/gLength; // 対応する出力画像の番号
        // それぞれの動画の最初のフレームのときの例外処理(保存開始)
        if (count % gLength == 0) {
        }
        background(0);
        // 各フレームでエッジ抽出を行う(課題2)
        if (k==1) {
        }
        // 各フレームで色相を変える(課題3)
        if (k==2) {
        }
        image(cam, 0, 0, width, height); // camの画像を画面に表示する
        // 作成中のgif画像にcamをフレームとして追加する
        // それぞれの動画の最後のフレームでの例外処理(保存終了)
        if (count % gLength == gLength-1) {
          println(k+"完了");
        }
        count++;
        fill(255, 0, 0);
        text(fName[k] + ":" + count%gLength, 30, height-30);
        fill(255);
      }
      if (selected >= 0) {
        background(0);
        image(gif, 0, 0, width, height);
        fill(255, 0, 0);
        text(fName[selected], 30, height-30);
        fill(255);
      }
    }
    
    void keyPressed() {
      int k = key-'0';
      if (k>=1 && k<=3) {
        selected = k-1;
        gif = new Gif(this, "data/" + fName[k-1] + ".gif");
        gif.loop();
      }
    }

  3. 「img15」という名前で保存する。
  4. ライブラリ「gifAnimation」をインポートする (第12回で導入済みの場合はこのステップは不要)。
    (「スケッチ」→「ライブラリをインポート」→「ライブラリを追加」を選ぶ)
    (「Filter」に「gif」と入力する)
    (検索に引っかかったものを選び、「Install」をクリックする)

  5. ライブラリ「Video Library for Processing 4」をインポートする (これが完了すればエラーが消える)。
    (「スケッチ」→「ライブラリをインポート」→「ライブラリを追加」を選ぶ)
    (「Filter」に「video」と入力する)
    (検索に引っかかったものから「Video Library for Processing 4」を選び、「Install」をクリックする)
    (インストールが完了してからウインドウを閉じると、エラーが消えている)

  6. 実行してコンソールに表示されるものを確認し、使用するカメラを選択する。
    (「cam = new Capture(this, width, height, cams[0]);」の [] の中の値を変え、画面にWebカメラの画像が映るようにする)

  7. 選んだカメラに応じて実行画面のサイズを適宜変更する。
    (カメラが4:3のタイプの場合は変更の必要なし)
    (カメラが16:9の横長のタイプの場合はsetup関数の「size(800, 600);」を「size(800, 450);」にするとちょうどよくなる)

  8. draw関数の
        // それぞれの動画の最初のフレームのときの例外処理(保存開始)
        if (count % gLength == 0) {
    の下に
          gifMaker = new GifMaker(this, "data/" + fName[k] + ".gif");
          gifMaker.setRepeat(0);
          gifMaker.setDelay(60);
    を追加する。

  9. draw関数の「// 作成中のgif画像にcamをフレームとして追加する」の左に以下のコードを追加する。
    gifMaker.addFrame(cam);

  10. draw関数の
        // それぞれの動画の最後のフレームでの例外処理(保存終了)
        if (count % gLength == 0) {
    の下に
          gifMaker.finish();
    を追加する。

  11. プログラムを実行する。
    • カメラ画像とともに、左下に「1カメラ」「2エッジ」「3色相変化」と保存中のフレーム番号0~59が表示される。
    • 3番目の動画の作成が完了してから実行画面をクリックして1キー~3キーを押すと、それぞれの動画が表示される。
    • (2, 3の動画には課題2, 3で変更を加えるが、この時点ではカメラに写ったままの状態)

2. エッジ抽出

概要

第07回で行ったエッジ抽出をそれぞれのフレームに行えば、背景が黒で被写体のエッジが白い線になった動画が作れる。
エッジ抽出にはいろいろな方法があったが、ここではX, Y方向のソーベルフィルタの抽出結果を合成したものを作る。

課題 2

  1. draw関数の「if (k==1) {」の下に以下のコードをコピー&ペーストする。
          PImage img = createImage(width, height, ARGB); // 画面と同じサイズの黒画像
          // 端以外のピクセルについての繰り返し
          for (int j=1; j<height-1; j++) {
            for (int i=1; i<width-1; i++) {
              float[] e = new float[2]; // 「マスク」×「ピクセル明度」の足し上げ用変数
              for (int m=0; m<2; m++) { // マスクのタイプについての繰り返し
                for (int l=0; l<9; l++) { // マスク内の位置についての繰り返し
                  int x = l%3-1; // マスク内横位置
                  int y = l/3-1; // マスク内縦位置
                  // カメラ画像の(i+x, j+y)のピクセルの明度を float br に入れる
                  // brとm番目のマスクのl番目の位置の値をかけたものをe[m]に加える
                }
              }
              // imgの(i, j)のピクセルにe[0]とe[1]を2乗して加え、平方根をとった値の明度をもつ色を設定する
            }
          }
          cam.pixels = img.pixels;

  2. draw関数の「// カメラ画像の(i+x, j+y)のピクセルの明度を float br に入れる」「// brとm番目のマスクのl番目の位置の値をかけたものをe[m]に加える」の下にそれぞれ対応したコードを追加する。
    • カメラ画像はcam。
    • 明度はbrightness関数で得られる。
    • m番目のマスクのl番目の位置の値は mask[m][l]。

  3. draw関数の「// imgの(i, j)のピクセルにe[0]とe[1]を2乗して加え、平方根をとった値の明度をもつ色を設定する」の下に対応したコードを追加する。
    • 色はcolor関数で作る。
    • 平方根はsqrt関数で得られる。

  4. プログラムを実行する。
    • カメラ画像とともに、左下に「1カメラ」「2エッジ」「3色相変化」と保存中のフレーム番号0~59が表示される。
    • 3番目の動画の作成が完了してから実行画面をクリックして1キー~3キーを押すと、それぞれの動画が表示される。
    • 2キーで表示される動画は黒背景に白エッジのものになる。

3. 色相を変える

概要

第13回では課題1で色相を120°、240°変えたが、同様のことをカメラの画像に行えば色合いの変わった動画を作れる。

さらに、変化させる値をフレームに応じて変えれば、だんだん色が変わっていくようにできる。

課題 3

  1. draw関数の「if (k==2) {」の下に以下のコードをコピー&ペーストする。
          // 色設定モードをHSBにする
          for (int j=0; j<height; j++) {
            for (int i=0; i<width; i++) {
              color c = cam.pixels[i+j*width];
              float H = hue(c);
              float S = saturation(c);
              float B = brightness(c);
              // float a に色相の変化値を入れる (動画1周で色立体を3周するように)
              // Hをaだけ増やし、0~360の範囲に収まるように調整する
              cam.pixels[i+j*width] = color(H, S, B);
            }
          }
          // 色設定モードをRGBに戻す

  2. draw関数の「// 色設定モードをHSBにする」「// 色設定モードをRGBに戻す」の左にそれぞれ対応したコードを追加する。
    • 色設定モードを変えるのはcolorMode関数。
    • 引数はモード名とそれぞれの要素の最大値。

  3. draw関数の「// float a に色相の変化値を入れる (動画1周で色立体を3周するように)」の下に対応したコードを追加する。
    • 色はcolor関数で作る。
    • この動画の作成中は count の値は gLength*2 ~ gLength*3-1 の範囲で変化する。
    • countをgLengthで割った余りは 0~gLength-1 の範囲で変化する。
    • aが1フレームで 360*3/gLength ずつ大きくなれば、ちょうど動画1ループで a が 360*3 増える。

  4. draw関数の「// Hをaだけ増やし、0~360の範囲に収まるように調整する」の下に対応したコードを追加する。
    • a は 0~360*3の範囲で変化するので、第13回でやったような「大きすぎたら360引く」という方法は使えない。
    • H, a はどちらも正なので、H+a を 360で割った余りは0~360の範囲に収まる。

  5. プログラムを実行する。
    • カメラ画像とともに、左下に「1カメラ」「2エッジ」「3色相変化」と保存中のフレーム番号0~59が表示される。
    • 3番目の動画の作成が完了してから実行画面をクリックして1キー~3キーを押すと、それぞれの動画が表示される。
    • 3キーで表示される動画では1ループ中に赤→緑→青→…の色変化が3回おこる。

提出

提出先フォーム

※ コードはTeamsのClass Notebookに保存してください。
7/30(火) → 7/31(水) までに提出すれば出席がつきます。ただし、あまりにも進捗が少ない場合は提出としてカウントされません。
※ 締切後はこちらから提出してください (通常と同様に点数がつきますが、出席はつきません)。
戻る