<< 第1回、個人制作プレゼンテーション | top | JitterとProcessing+JMyronで映像を扱う >>
Flash ActionScript2.0 実践講座6:ビデオ映像を扱う
本日の予定
今回の講義は、前回の個人制作の第1回のプレゼン内容を受けて、Flashでライブビデオ映像をキャプチャして、その情報を処理する方法について解説します。作品に映像を用いたインタラクションを考えていて、Flashでシステムを作成したいと考えている場合は、今回の内容が参考になると思います。
さらに次回はJitterやProcessingを用いて同様の処理を行う方法について取り上げていく予定です。
本日のスライド
サンプルファイル
本日のサンプル一覧
※ 今回のサンプルを閲覧するには、FlashPlayer 8以上のプラグインがインストールされた、ビデオカメラがとりつけられたコンピュータが必要です。
目次
- 今日の内容
- ビデオカメラからのビデオのキャプチャー
- 応用1:ビデオミラー
- 応用2:ビデオ映像の解像度を変更
- 応用3:ビデオ映像を点描で表現
- 応用4:ビデオ in ビデオ
- 応用5:リプチンスキー・エフェクト
今日の内容
- Flash Player を実行するコンピュータに接続されたビデオカメラからのビデオをキャプチャする
- 取り込んだ映像のビットマップデータを利用する
- 「鏡」のようなエフェクト
- モザイク状にする
- ピクセル情報をもとに映像を再構成
- 複数の時間の共存
- ズビグ・リプチンスキー「四次元」のような表現にトライしてみる!
ビデオカメラからのビデオのキャプチャー
- まずはFlash Playerへビデオカメラの映像をキャプチャして取込む方法から
- ライブビデオ映像を扱うための基本
ファイル構成
- source/
- video_captuer.flp :プロジェクトファイル
- video_captuer.fla :Flashファイル
- as/ :ActionScript格納フォルダ
- VideoCaptuer.as :メインクラス
- deploy/
- video_captuer.swf:swfファイル
ビデオオブジェクトの生成
- video_captuer.flaのライブラリウィンドウのメニューから「新規ビデオ」を選択

- ビデオのタイプを「ビデオ(ActionScript制御)」に設定する

- ライブラリ内に生成されたビデオオブジェクトをステージ状にドラッグ&ドロップ
- インスタンス名を「my_video」に設定する

VideoCaptuerクラスのインスタンス化
- 下記のスクリプトを1フレーム目に記入する
import as.VideoCapture; var v:VideoCapture = new VideoCapture(this, my_video);
- コンストラクタの引数として、自身のターゲットパスとビデオオブジェクトのインスタンス名を渡している部分に注目!
VideoCaptuerクラスの設計
- カメラオブジェクトへリアルタイム映像を取得
my_camera = Camera.get();
- コンストラクタ関数の引数として指定されているビデオオブジェクトにカメラの映像を貼り付ける
my_video.attachVideo(my_camera);
- ビデオオブジェクトの位置と大きさの補正
VideoCaptuerクラス
import as.*;
class VideoCapture {
//自身のターゲットパス
private var target_mc:MovieClip;
//ビデオオブジェクトの場所
private var my_video:Video;
//とりつけられたカメラのオブジェクト
private var my_camera:Camera;
/*
コンストラクタ関数
*/
public function VideoCapture(target:MovieClip, video:Video) {
//Flashからわたされた引数を変数に格納
target_mc = target;
my_video = video;
//ビデオカメラの接続方法の設定のためのダイアログを表示
System.showSettings(3);
//ビデオのキャプチャ開始
captureVideo();
}
/*
パソコンに取り付けられたカメラの映像を取得して、ビデオオブジェクトに貼り付ける
*/
private function captureVideo():Void {
//カメラのをmy_cameraに設定
my_camera = Camera.get();
//ビデオオブジェクトにカメラの映像を貼り付ける
my_video.attachVideo(my_camera);
//ビデオオブジェクトの位置と大きさの補正
my_video._x = my_video._y=0;
my_video._width = Stage.width;
my_video._height = Stage.height;
}
}
完成! 
応用1:ビデオミラー
- ビデオ画像で鏡を作る
- 画面を縦に2つに割って、右半分の画面は左半分の画面の左右反転した画像にする

- 実際には…
- オリジナル映像の左半分にだけ、左右を反転した映像を合成する

メインクラス「MirrorVideo.as」でやりたいこと
- 初期化
- ビデオをキャプチャ
- ビデオ画像をビットマップデータにする
- 新規ビットマップデータの生成
- 新規ムービクリップの生成
- ビットマップデータをムービクリップに貼り付ける
- ビデオの位置とサイズの補正
- 画面のアップデート用のタイマーをスタート
- 定期的に画面を更新
- ビデオ画像をビットマップデータに読み込み
- ビデオ画像の行の数(幅)の半分だけ実行
- ピクセルの色情報を取り出し
- 左右を反転して貼り付け
MirrorVideoクラス
import as.*;
import flash.display.BitmapData;
import flash.display.*;
/*
ビデオ画面で「合わせ鏡」を作る!
*/
class MirrorVideo {
private var target_mc:MovieClip;
private var my_video:Video;
private var my_camera:Camera;
private var fps:Number = 30;
private var updateTimer:Number;
private var myBitmapData:BitmapData;
private var videoData_mc:MovieClip;
public function MirrorVideo(target:MovieClip, video:Video) {
target_mc = target;
my_video = video;
System.showSettings(3);
captureVideo();
createBitmapData();
}
/*
ビデオをキャプチャ
*/
private function captureVideo():Void {
my_camera = Camera.get();
my_video.attachVideo(my_camera);
my_video._visible = false;
}
/*
ビットマップデータを貼りつけたムービクリップの生成
画面の書き換え用のタイマーをスタート
*/
private function createBitmapData():Void {
//新規ビットマップデータの生成
myBitmapData = new BitmapData(my_camera.width, my_camera.height, false);
//新規ムービクリップの生成
videoData_mc = target_mc.createEmptyMovieClip("videoData", target_mc.getNextHighestDepth());
//先程生成したビットマップデータを貼り付ける
videoData_mc.attachBitmap(myBitmapData, 1);
//ビデオの位置とサイズの補正
videoData_mc._x = videoData_mc._y=0;
videoData_mc._width = Stage.width;
videoData_mc._height = Stage.height;
//画面のアップデート用のタイマーをスタート
clearInterval(updateTimer);
updateTimer = setInterval(this, "update", 1000/fps);
}
/*
画面の更新
*/
private function update():Void {
var pc:Number;
//ビットマップデータにビデオ画像を読み込む
myBitmapData.draw(my_video);
//ビデオ画像の行の数(幅)の半分だけ実行
for (var col:Number = 0; col<myBitmapData.width/2; col++) {
//ビデオ画像の列の数(高さ)だけ実行
for (var row:Number = 0; row<myBitmapData.height; row++) {
//ピクセルの色情報を取り出し
pc = myBitmapData.getPixel(col, row);
//左右を反転して貼り付ける
myBitmapData.setPixel(myBitmapData.width-col, row, pc);
}
}
}
}
完成! 
応用2:ビデオ映像の解像度を変更
- より複雑な処理をするにあたり…
- 処理をするピクセル数が多いと計算量が多すぎてコマ落ちしてしまう
- キャプチャした映像の解像度を下げて、高速に計算させる工夫が必要
- まずは解像度を落して、映像をモザイク状にする
ResizeVideoクラス
import as.*;
import flash.display.*;
import flash.geom.*;
/*
ビデオ映像の解像度を変更→モザイク状に
*/
class ResizeVideo {
private var target_mc:MovieClip;
private var my_video:Video;
private var my_camera:Camera;
private var fps:Number = 30;
private var updateTimer:Number;
private var myBitmapData:BitmapData;
private var transformBitmap:BitmapData;
private var videoData_mc:MovieClip;
private var transform_mc:MovieClip;
private var rows:Number;
private var cols:Number;
private var cell_width:Number;
private var cell_height:Number;
private var transform_matrix:Matrix;
private var cellAarry = Array();
/*
コンストラクタ
*/
public function ResizeVideo(target:MovieClip, video:Video) {
target_mc = target;
my_video = video;
cols = 20;
rows = 15;
cell_width = Stage.width/cols;
cell_height = Stage.height/rows;
System.showSettings(3);
captureVideo();
createBitmapData();
transformVideo();
}
/*
ビデオのキャプチャ
*/
private function captureVideo():Void {
my_camera = Camera.get();
my_video.attachVideo(my_camera);
my_video._visible = false;
}
/*
ビットマップデータを貼りつけたムービクリップの生成
*/
private function createBitmapData():Void {
myBitmapData = new BitmapData(my_camera.width, my_camera.height, false);
myBitmapData.draw(my_video);
videoData_mc = target_mc.createEmptyMovieClip("videoData", target_mc.getNextHighestDepth());
videoData_mc.attachBitmap(myBitmapData, 1);
}
/*
ビデオの解像度を変更
新規に、ビットマップデータを貼りつけたムービクリップの生成
画面の書き換え用のタイマーをスタート
*/
private function transformVideo():Void {
//ビットマップデータを貼りつけたムービクリップの幅と高さを指定のセルの数まで縮小
videoData_mc._width = cols;
videoData_mc._height = rows;
//ムービークリップのデータをマトリクスに変換
transform_matrix = videoData_mc.transform.matrix;
//解像度を変換後のデータ表示用ビットマップを生成
transformBitmap = new BitmapData(cols, rows, false);
//解像度を変換後のデータ表示用ムービクリップを生成
transform_mc = target_mc.createEmptyMovieClip("transform_mc", target_mc.getNextHighestDepth());
transform_mc.attachBitmap(transformBitmap, 1);
transform_mc._width = Stage.width;
transform_mc._height = Stage.height;
//画面のアップデート用のタイマーをスタート
clearInterval(updateTimer);
updateTimer = setInterval(this, "update", 1000/fps);
}
/*
画面の更新
*/
private function update():Void {
//解像度を変更したマトリクスに合致するようにビデオのデータを読み込み
transformBitmap.draw(my_video, transform_matrix);
}
}完成! 
応用3:ビデオ映像を点描で表現
- 解像度を調整したビデオ映像のピクセル情報を取り出す
- 各ピクセルのRGBの値を取得する
- その値を元に、円を描く
- 円の色は対応するピクセルのRGBの値を同じに
- 円のサイズはピクセルの明度に対応させる
PixelateVideoクラス
import as.*;
import flash.display.*;
import flash.geom.*;
/*
ビデオ映像を元にして、ピクセルを点描する
*/
class PixelateVideo {
private var target_mc:MovieClip;
private var my_video:Video;
private var my_camera:Camera;
private var fps:Number = 10;
private var updateTimer:Number;
private var myBitmapData:BitmapData;
private var transformBitmap:BitmapData;
private var videoData_mc:MovieClip;
private var rows:Number;
private var cols:Number;
private var cell_width:Number;
private var cell_height:Number;
private var transform_matrix:Matrix;
private var cellAarry = Array();
/*
コンストラクタ
*/
public function PixelateVideo(target:MovieClip, video:Video) {
target_mc = target;
my_video = video;
cols = 30;
rows = 20;
cell_width = Stage.width/cols;
cell_height = Stage.height/rows;
System.showSettings(3);
captureVideo();
createBitmapData();
transformVideo();
createCells();
}
/*
ビデオのキャプチャ
*/
private function captureVideo():Void {
my_camera = Camera.get();
my_video.attachVideo(my_camera);
my_video._visible = false;
}
/*
ビットマップデータを貼りつけたムービクリップの生成
*/
private function createBitmapData():Void {
myBitmapData = new BitmapData(my_camera.width, my_camera.height, false);
myBitmapData.draw(my_video);
videoData_mc = target_mc.createEmptyMovieClip("videoData", target_mc.getNextHighestDepth());
videoData_mc.attachBitmap(myBitmapData, 1);
}
/*
ビデオの解像度を変更
新規に、ビットマップデータを貼りつけたムービクリップの生成
*/
private function transformVideo():Void {
videoData_mc._width = cols;
videoData_mc._height = rows;
transform_matrix = videoData_mc.transform.matrix;
transformBitmap = new BitmapData(cols, rows, false);
transformBitmap.draw(my_video, transform_matrix);
}
/*
ピクセル情報を表現するムービクリップをタイル状に敷き詰める
画面の書き換え用のタイマーをスタート
*/
private function createCells():Void {
//変換後のビットマップの縦横のピクセル数だけ、ムービクリップを生成していく
for (var c:Number = 0; c<cols; ++c) {
for (var r:Number = 0; r<rows; ++r) {
var cell_mc:MovieClip = target_mc.createEmptyMovieClip("cell_mc", target_mc.getNextHighestDepth());
//対応するビットマップデータのピクセルの位置にムービクリップを移動
cell_mc._x = c*cell_width+cell_width*0.5;
cell_mc._y = r*cell_height+cell_height*0.5;
//生成したムービクリップを配列"cellAarry"に格納
cellAarry.push(cell_mc);
}
}
clearInterval(updateTimer);
updateTimer = setInterval(this, "update", 1000/fps);
}
/*
画面の更新
*/
private function update():Void {
var pixelColor:Number;
var cellRed:Number, cellGreen:Number, cellBlue:Number, bright:Number;
transformBitmap.draw(my_video, transform_matrix);
var count = 0;
//変換後のビットマップの縦横のピクセル数だけ繰り返す
for (var c:Number = 0; c<cols; c++) {
for (var r:Number = 0; r<rows; r++) {
//ビットマップデータのピクセル値を取り出す
pixelColor = transformBitmap.getPixel(c, r);
//取得した16進数の色情報を10進数のRGB分解した色情報に変換する
cellRed = pixelColor >> 16 & 0xFF;
cellGreen = pixelColor >> 8 & 0xFF;
cellBlue = pixelColor & 0xFF;
//ピクセルの明度を算出(0〜100にノーマライズ)
bright = Math.max(Math.max(cellRed, cellGreen), cellBlue)/255*100;
//配列"cellAarry"に格納したムービクリップをとりだす
var cell_mc:MovieClip = cellAarry[count];
//ムービクリップの内容を消去
cell_mc.clear();
//指定の場所に色の付いた点を描く
//(終端がラウンドした太い線を1pxだけ描いて円にする)
cell_mc.lineStyle(cell_height, pixelColor, 100, false, "normal", "round");
cell_mc.lineTo(0, 1);
//算出した明度の情報をもとに円の大きさを決定
cell_mc._xscale = cell_mc._yscale=bright;
count++;
}
}
}
}完成! 
さらに応用:文字を貼りつけたムービクリップで表現 
応用4:ビデオ in ビデオ
- ビデオを構成するそれぞれのピクセル情報が、同じビデオ画像になっている (フラクタル構造?)
つくり方
- タイル状に敷き詰めたムービクリップ内に、解像度を変更した後のビットマップデータを貼っていく
- それぞれのビットマップデータの色と透明度を調整して全体の映像を生成する
VideoInVideoクラス
import as.*;
import flash.display.*;
import flash.geom.*;
class VideoInVideo {
private var target_mc:MovieClip;
private var my_video:Video;
private var my_camera:Camera;
private var fps:Number = 15;
private var updateTimer:Number;
private var myBitmapData:BitmapData;
private var transformBitmap:BitmapData;
private var videoData_mc:MovieClip;
private var rows:Number;
private var cols:Number;
private var cell_width:Number;
private var cell_height:Number;
private var transform_matrix:Matrix;
private var cellAarry = Array();
public function VideoInVideo(target:MovieClip, video:Video) {
target_mc = target;
my_video = video;
cols = 20;
rows = 20;
cell_width = Stage.width/cols;
cell_height = Stage.height/rows;
System.showSettings(3);
captureVideo();
createBitmapData();
transformVideo();
createCells();
}
/*
ビデオのキャプチャ
*/
private function captureVideo():Void {
my_camera = Camera.get();
my_video.attachVideo(my_camera);
my_video._visible = false;
}
/*
ビットマップデータを貼りつけたムービクリップの生成
*/
private function createBitmapData():Void {
myBitmapData = new BitmapData(my_camera.width, my_camera.height, false);
myBitmapData.draw(my_video);
videoData_mc = target_mc.createEmptyMovieClip("videoData", target_mc.getNextHighestDepth());
videoData_mc.attachBitmap(myBitmapData, 1);
}
/*
ビデオの解像度を変更
新規に、ビットマップデータを貼りつけたムービクリップの生成
*/
private function transformVideo():Void {
videoData_mc = target_mc.createEmptyMovieClip("videoData", target_mc.getNextHighestDepth());
videoData_mc.attachBitmap(myBitmapData, 1);
videoData_mc._visible = false;
videoData_mc._width = cols;
videoData_mc._height = rows;
transform_matrix = videoData_mc.transform.matrix;
transformBitmap = new BitmapData(cols, rows, false);
transformBitmap.draw(my_video, transform_matrix);
}
/*
ピクセル情報を表現するムービクリップをタイル状に敷き詰める
画面の書き換え用のタイマーをスタート
*/
private function createCells():Void {
for (var c:Number = 0; c<cols; ++c) {
for (var r:Number = 0; r<rows; ++r) {
var cell_mc:MovieClip = target_mc.createEmptyMovieClip("cell_mc", target_mc.getNextHighestDepth());
cell_mc._x = c*cell_width;
cell_mc._y = r*cell_height;
//ムービクリップの内部に、
//解像度を調整した後のビデオ画像のビットマップデータを貼り付けている
cell_mc.attachBitmap(transformBitmap, 1);
cell_mc._width = cell_width;
cell_mc._height = cell_height;
cellAarry.push(cell_mc);
}
}
clearInterval(updateTimer);
updateTimer = setInterval(this, "update", 1000/fps);
}
/*
画面の更新
*/
private function update():Void {
var pixelColor:Number;
var cellRed:Number, cellGreen:Number, cellBlue:Number, bright:Number;
transformBitmap.draw(my_video, transform_matrix);
var count = 0;
for (var c:Number = 0; c<cols; c++) {
for (var r:Number = 0; r<rows; r++) {
pixelColor = transformBitmap.getPixel(c, r);
cellRed = pixelColor >> 16 & 0xFF;
cellGreen = pixelColor >> 8 & 0xFF;
cellBlue = pixelColor & 0xFF;
bright = Math.max(Math.max(cellRed, cellGreen), cellBlue)/255*100;
var cell_mc:MovieClip = cellAarry[count];
//ムービクリップの透明度を明度に対応させる
cell_mc._alpha = bright;
//ムービクリップの色を取得したピクセル値にあわせて変更
var trans:Transform = new Transform(cell_mc);
var colorTrans:ColorTransform = new ColorTransform(1, 1, 1, 1, cellRed, cellGreen, cellBlue, 0);
trans.colorTransform = colorTrans;
count++;
}
}
}
}
完成! 
応用5:リプチンスキー・エフェクト
- ズビグ・リプチンスキー「四次元」1988年

- Zbig Rybczynski "The Fourth Dimension" 1988
- この「ねじれた」映像はどうのようにして作られているのか?
- 複数の時間軸が一つの画面に共存している
- 映像の行ごとに1フーレムずつ時間が後(もしくは前に)にずれていく

- その結果として、カメラの前で動くと、その軌跡が時間の経過とともに残っていく
- このエフェクトをFlashで再現してみる!
Rybczynskiクラス
import as.*;
import flash.display.*;
import flash.geom.*;
class Rybczynski {
private var target_mc:MovieClip;
private var my_video:Video;
private var my_camera:Camera;
private var fps:Number = 30;
private var updateTimer:Number;
private var myBitmapData:BitmapData;
private var transformBitmap:BitmapData;
private var videoData_mc:MovieClip;
private var transform_mc:MovieClip;
private var rows:Number;
private var cols:Number;
private var cell_width:Number;
private var cell_height:Number;
private var bitmapAarry = Array();
private var transform_matrix:Matrix;
public function Rybczynski(target:MovieClip, video:Video) {
target_mc = target;
my_video = video;
cols = 60;
rows = 60;
cell_width = Stage.width/cols;
cell_height = Stage.height/rows;
System.showSettings(3);
_quality = "LOW";
captureVideo();
transformVideo();
}
private function captureVideo():Void {
my_camera = Camera.get();
my_video.attachVideo(my_camera);
my_video._visible = false;
myBitmapData = new BitmapData(my_camera.width, my_camera.height, false);
myBitmapData.draw(my_video);
}
private function transformVideo():Void {
videoData_mc = target_mc.createEmptyMovieClip("videoData", target_mc.getNextHighestDepth());
videoData_mc.attachBitmap(myBitmapData, 1);
videoData_mc._visible = false;
videoData_mc._width = cols;
videoData_mc._height = rows;
transform_matrix = videoData_mc.transform.matrix;
transformBitmap = new BitmapData(cols, rows, false);
transform_mc = target_mc.createEmptyMovieClip("transform_mc", target_mc.getNextHighestDepth());
transform_mc.attachBitmap(transformBitmap, 1);
transform_mc._width = Stage.width;
transform_mc._height = Stage.height;
clearInterval(updateTimer);
updateTimer = setInterval(this, "update", 1000/fps);
}
private function update():Void {
var pixelColor:Number;
var cellRed:Number, cellGreen:Number, cellBlue:Number, bright:Number;
var tmpBitmap:BitmapData = new BitmapData(cols, rows, false);
tmpBitmap.draw(my_video, transform_matrix);
bitmapAarry.push(tmpBitmap);
if (bitmapAarry.length>rows) {
bitmapAarry.shift();
}
for (var c:Number = 0; c<cols; c++) {
for (var r:Number = 0; r<rows; r++) {
pixelColor = bitmapAarry[r].getPixel(c, r);
transformBitmap.setPixel(c, r, pixelColor);
}
}
}
}
完成! 
<< 第1回、個人制作プレゼンテーション | top | JitterとProcessing+JMyronで映像を扱う >>


Comments
コメントはありません
Add Comment
トップページに戻る