Hatena::ブログ(Diary)

Over&Out その後 このページをアンテナに追加 RSSフィード

2016-04-16

GLKView の描画内容を AVAssetWriter を用いて動画としてエクスポートする

すごい雑なメモですが、ちゃんと書くにはまだ理解が足りてなくて、かといってローカルに放置するとまた同じことを一から自分で調べそうな気がするので、とりあえずアップ。


※古いコードを流用している部分もあるため、一部ObjC、一部Swiftです。


やりたかったこと

標題の通り、「GLKView の描画内容を AVAssetWriter に渡して動画として書き出す」ということがやりたかった。リアルタイムに処理する必要があって、要件としては20fps。


AVAssetWriterInputPixelBufferAdaptor オブジェクトがあって、

@property (nonatomic, strong) AVAssetWriterInputPixelBufferAdaptor *pixelBufferAdaptor;

こう生成した CVPixelBufferRef があって、

CVPixelBufferRef pixelBuffer = NULL;
CVPixelBufferPoolCreatePixelBuffer(nil, self.pixelBufferAdaptor.pixelBufferPool, &pixelBuffer);

ここに GLKView への描画内容を反映すれば、

[self.pixelBufferAdaptor appendPixelBuffer:pixelBuffer withPresentationTime:timestamp];

こんな感じで AVAssetWriter で動画エクスポートできる。


・・・ということはわかっていたけど、CVPixelBuffer に GLKView の描画内容をどうやったら渡せるのかがわからなかった。


試した方法1: テクスチャキャッシュを利用して OpenGL の FBO を書き出す

ググッてこちらで見つけた方法。以下にコードがある。

`CVOpenGLESTextureCacheCreate` と `CVOpenGLESTextureCacheCreateTextureFromImage` なるメソッドを使用してテクスチャキャッシュなるものを生成して Frame Buffer Object (FBO) を云々する(処理内容を理解できていないので適切な説明ができない)。GPUImage と同様の方法らしい。


自分のプロジェクトに移植してみたがうまく動かず、上のコードと何が違うのかにらめっこしていたが、READMEをよく読むと 元コードもちゃんと動作してない と書いてあり、リファレンスコードなしであれこれ試行錯誤する時間もないので、いったん別の方法を考えることに。


試した方法2: GLKView のスナップショットを取得する

UIView のスナップショット取得はそれなりに重い印象があったのと、リアルタイム処理(20fps)の必要がありなるべくUIKitのレイヤーで処理したくなかったので当初は思いついても頭から除外していた選択肢。


ただ方法1が頓挫して、今日中に落とし所を見つけたかったので、少々フレームレートが落ちてもいいので試してみよう、ということでこっちでやることに。


下記によると、GLKView の描画内容は `renderInContext:` ではスナップショットとれないらしい。Core Graphics だからそりゃそうか。

You need to grab the view's framebuffer pixel data using OpenGL ES. You can't do it with renderInContext:.


で、同回答にリンクがあったのが下記。

GPUImage の作者 Brad Larson さんの回答。`glReadPixels()` でピクセルデータを取り出す方法と、テクスチャキャッシュを利用する方法が提示されている。後者は方法1で断念した方向なので厳しい・・・


前者の `glReadPixels()` を利用する方法は、下記に UIImage に変換するまでのコードがあった。

- (UIImage*)snapshotRenderBuffer {

    // Bind the color renderbuffer used to render the OpenGL ES view
    // If your application only creates a single color renderbuffer which is already bound at this point, 
    // this call is redundant, but it is needed if you're dealing with multiple renderbuffers.
    // Note, replace "_colorRenderbuffer" with the actual name of the renderbuffer object defined in your class.
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);

    NSInteger dataLength = backingWidth * backingHeight * 4;
    GLubyte *data = (GLubyte*)malloc(dataLength * sizeof(GLubyte));

    // Read pixel data from the framebuffer
    glPixelStorei(GL_PACK_ALIGNMENT, 4);
    glReadPixels(0.0f, 0.0f, backingWidth, backingHeight, GL_RGBA, GL_UNSIGNED_BYTE, data);

    // Create a CGImage with the pixel data
    // If your OpenGL ES content is opaque, use kCGImageAlphaNoneSkipLast to ignore the alpha channel
    // otherwise, use kCGImageAlphaPremultipliedLast
    CGDataProviderRef ref = CGDataProviderCreateWithData(NULL, data, dataLength, NULL);
    CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
    CGImageRef iref = CGImageCreate(
                                    backingWidth, backingHeight, 8, 32, backingWidth * 4, colorspace, 
                                    kCGBitmapByteOrder32Big | kCGImageAlphaNoneSkipLast,
                                    ref, NULL, true, kCGRenderingIntentDefault);

    // (sayeth abd)
    // This creates a context with the device pixel dimensions -- not points. 
    // To be compatible with all devices, you're meant to keep everything as points and a scale factor;  but,
    // this gives us a scaled down image for purposes of saving.  So, keep everything in device resolution,
    // and worry about it later...
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(backingWidth, backingHeight), NO, 0.0f);
    CGContextRef cgcontext = UIGraphicsGetCurrentContext();
    CGContextSetBlendMode(cgcontext, kCGBlendModeCopy);
    CGContextDrawImage(cgcontext, CGRectMake(0.0, 0.0, backingWidth, backingHeight), iref);

    // Retrieve the UIImage from the current context
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    // Clean up
    free(data);

    return image;
}

(結局 Core Graphics を経由するのか。。)


で、最終的に以下の回答に行きつく。


あれ、`snapshot` なんてメソッドあったっけ?と思って調べてみたら、GLKView に元から用意されていた・・・!

@property (readonly, strong) UIImage *snapshot;

最終的にうまくいった方法

guard let cisnapshot = CIImage(image: snapshot) else { fatalError() }
drawImage = cisnapshot.imageByCompositingOverImage(drawImage)

glView.ciContext.render(
    drawImage,
    toCVPixelBuffer: pixelBuffer,
    bounds: drawImage.extent,
    colorSpace: CGColorSpaceCreateDeviceRGB())

上記コードの前提

  • GLKView の `snapshot` メソッドで取得した UIImage が `snapshot` に入っている
  • `drawImage` は諸々の処理を経由した CIImage オブジェクト

2014-10-14

一泊二日のopenFrameworksセミナーに参加してきました


先週末、『デジタルアートセミナー#3 openFrameworksで学ぶ、クリエイティブ・コーディング』という一泊二日のセミナーに参加してきました。(※参加者のTLでは「oFセミナー」という呼称の方が一般的でした)


自分にとっての openFrameworks (以下 oF)は、 去年真鍋さんと仕事したいがために少しかじってみた ものの、さらっと基礎をなでただけで結局一度も実案件で使うことも自分で何かつくってみることもなかった、という程度の縁しかありません。


それでも参加したのは、講師陣と内容がすごく興味深かったからです。


2014年の今に boost ライブラリについてがっつり2時間半教えてくれるセミナーはなかなかないだろうし、iOS 8 から CIKernel が追加されて GLSL で Core Image のカスタムフィルタをつくれるようになった ので、GLSL についてプロ中のプロから直接学べるなら是非ともという感じだし、プロジェクションマッピングは流行り始めてから数年経つけど自分ではやったことないので要素技術とかポイントは把握しておきたいし、映像制作ソフトの連携や映像解析の話も興味ある分野だったり。


つまるところ oF 抜きにしても今回のセミナー内容は自分にとって学びたい内容ばかりだったので、募集開始直後1分以内ぐらいに全力で応募した次第です。


で、実際に参加してみた結果、期待以上でした。「これが聞けただけでも参加した甲斐があった!」と思った話が何度あったことか。これで宿泊費込みで1.5万円は安すぎるので、次回あったら10万円でも参加したいというレベルです。(実際に講師/TAの方々に正規フィーを払ったらそれでも済まないだろうけど。。)


では以下、それぞれのセッションについて。


セッション1 : C++テクニック(boostライブラリの使い方)

講師 : 堀口淳史、藤本直明

openFrameworksを本格的に使う上で避けて通れないC++のテクニックを学びます。

今回は、boostライブラリの使い方について学びます。


oF も C++ も初心者レベルだったので参加前はついていけるか不安だったのですが、基礎の基礎からじっくり丁寧に解説してくれて、libstdc++ と libc++ の違い とか、「oFで動くMacのboostバイナリ」をコンパイルするためのビルドオプションがどういう理由で決まってくるのかとか、あとboostの使い方自体も、非常に勉強になりました。


詳細な講義メモはこちらにあります。


セッション2: Shaderテクニック(GLSL)

講師 : 藤本直明、神田竜、他

GLSL(Shader)と呼ばれるOpenGLの機能を解説し、それを応用した映像表現を学びます。今回は、3Dを中心としたシェーディング手法を中心に解説していきます。


冒頭に書きましたが、iOS 8 から CIKernel で自前フィルタを GLSL で書けるようになったし、GPUImage とかで自前フィルタ追加したい場合もシェーダを自分で書ける必要があるしで、わりと以前から自分の中でシェーダはちゃんと勉強したい項目のひとつでした。


で、やっぱりこのセッションも超勉強になりました。


自分が興味があったところはフラグメントシェーダ(ピクセルシェーダとも呼ばれる)のあたりで、よく分かってなかった Vertex Shader とかのあたりもすごく腹落ちしたし、ライティングについても ライトの種類から拡散光の求め方まで順序立てて教えてもらえて、これまた勉強したいと思っていた3Dプログラミングの勉強もできた感じです。


f:id:shu223:20141011213048j:image:w600


詳細な講義メモはこちらにあります。


セッション3: 自己紹介&ショートセッション

講師/TA陣が自分の案件とその中で使ってる技術Tipsを紹介してくれるセッション。流れ解散制で、自分は深夜2時に退室したのですが、実に深夜3時半までセッションは続いたそうです。


オフレコな話が多かったので詳細レポートはできませんが、「あの案件の裏側はこうなってたのか!!!」みたいな話が盛りだくさんでこれまた勉強になりすぎる内容でした。


あと、みなさん技術+デザインとか演出のセンス+アイデアがすごくて、iOSしかできない自分はより精進せねば・・・と改めて思ったのでした。


セッション4: プロジェクションマッピング

openFrameworksでのプロジェクションマッピングの基礎を学び、実際に数名ずつのグループで簡単な制作を行います。 また、エッジブレンディングやメッシュワープなど、実践的なプロジェクションマッピングを行う上でのテクニックについても紹介します。


個人的にはあまりプロジェクションマッピングにはそんなに興味がなくて、そしてやったこともなく、でもすごい流行ってるのでちょっと気になる、ちょっと自分で体験してみたりはしたい・・・みたいな存在だったので、まさにうってつけのセッションでした。


f:id:shu223:20141012125651j:image:w600


講義メモ:


このセミナー全体を通して感じたことですが、プロジェクションマッピング(とかAR)は、もはや興味があるとかないとかのレベルじゃなくて、リアルな場での表現手段としてもはや当たり前なものになってるんだなーと。「これはプロジェクションマッピングの作品です」みたいな感じじゃなくて、演劇の背景だったり、TVのスタジオで世界観をつくる使い方だったりで表現のいち手法になってる感じ。


セッション5: openFrameworksと映像制作ソフトの連携

ライゾマ比嘉さんのセッション。


AfterEffectsやCinema4Dなどの映像ソフトとopenFrameworksを連携させる映像制作手法の紹介を行います。 (AfterEffectsやCinema4D、Ableton Live、Adobe Premiereなどをお持ちの方は、持参して頂けると手元で試せるのでよいかもしれません)


受講前は、「AfterEffects持ってないなー」「Cinema4Dって何??」ぐらいの感じだったのですが、動画内のサッカーボールのトラッキングを After Effects でやって、そのキーフレームを取り出すことでいかにも映像解析がすごい的に見せる みたいな方法は目からウロコで感動しました。


f:id:shu223:20141013174310j:image:w600


あと、ofxPDF というアドオンで、PDFベクタ形式のデータを読み取ってアウトラインを順番に取り出してアニメーション表示するやつは、すごくいい感じなので iOS ネイティブにも移植しようかと。Xcode 6 の Asset Catalog でも PDF ベクター形式をサポート したので、PDF 形式自体が iOS 界隈で一般的になってきそうだし。


f:id:shu223:20141013174237j:image:w400


講義メモ:


セッション6: 映像解析によるインタラクション

openFrameworksとOpenCV(ofxOpenCV、ofxCv)を組みあわせることで、映像を用いたインタラクティブな表現の可能性が大きく拡がります。このセッションでは、実例を紹介しながら映像とのリアルタイムなインタラクションの手法を探ります。

田所先生のイントロ、ひつじさんのオプティカルフロー+ドロネー変換、ライゾマ登本さんによるアドバンストな話、の豪華3本立て構成でした。


動画の中から、人間の目ではわからないような微細な変化を検出するアルゴリズム EVM(Eulerian Video Magnification)、トラッキングしながらリアルタイムに学習もしていく TLD(Tracking Learning Detection)アルゴリズム等々、非常に勉強になりました。


f:id:shu223:20141013191301j:image:w600

(EVMの処理フロー)


講義メモ:


あと、講義の後に、登本さんにとあるライゾマ案件のしくみについて直接聞いてみたところ、自分が用意してた答えとは全然違ってて、聞かないとわからなかったであろう方法だったので、それもほんと質問してよかったなと。


おわりに

最高でした!主催のみなさま、講師のみなさま、TAのみなさま、セミナーハウスのみなさまどうもありがとうございました!!


2014-10-13

【oFセミナーメモ5】映像解析

デジタルアートセミナー#3 openFrameworksで学ぶ、クリエイティブ・コーディング』の最終セッション『映像解析によるインタラクション』のメモです。

openFrameworksとOpenCV(ofxOpenCV、ofxCv)を組みあわせることで、映像を用いたインタラクティブな表現の可能性が大きく拡がります。このセッションでは、実例を紹介しながら映像とのリアルタイムなインタラクションの手法を探ります。


田所先生のイントロ、ひつじさんのオプティカルフロー+ドロネー変換、ライゾマ登本さんによるアドバンストな話、の3本立て構成でした。


過去のセッションのレポートはこちら。


田所先生のイントロ

講義資料: Session 6: 映像解析によるインタラクション


オプティカル・フロー

映像内の物体の動きを、ベクトル場で表したもの


f:id:shu223:20141013191045j:image:w500


2種類のアルゴリズム
  • Gunner Farneback

- 密なオプティカルフロー

- ofxCv::FlowFarneback

  • Pyramidal LK

- 疎な特徴集合に対するオプティカルフロー

- ofxCv::FlowPyrLK


ひつじさん

講義資料: オプティカルフローを使ったモーショングラフィックス生成|ひつじ|note



ダンサーさんの踊っている映像を使用して、それをリアルタイムにカットアップしながらオプティカルフローを使ったグラフィックを載せているというプログラムです。


  • YCAMのダンサーの映像にオプティカルフローをかける・・・特徴点を抽出
  • ofxDelaunay ドロネー変換

ドロネー図 - Wikipedia


f:id:shu223:20141013191122p:image


登本さん

Kinectで3Dモデリング

キャリブレーション

  • Kinect3台
  • それぞれのカメラの位置がわかってない
  • そのままだと 2.5d
  • 光る玉を3台のKinectから見える位置で振る
    • kinectのcolorの方で見ると、暗い部屋との対比で、簡単に中心位置を割り出せる
  • カメラ位置の推定に、OpenCV の estimateAffine3D を使用

ofxEvm

Eulerian Video Magnification

動画の中から、人間の目ではわからないような微細な変化を検出する


Eulerian Video Magnification - YouTube


EVMのアルゴリズム

f:id:shu223:20141013191301j:image:w600

(上記動画内の解説図)


論文と MATLAB
  • 論文のコードは MATLAB で公開されてることが多い
  • EVM も MATLAB でコードが公開されてる
  • MATLAB が使われるのは、その世界ではみんな使ってるので、referされやすいから。Pythonも増えてきた。
  • MatLabからのC++エクスポート機能はつかってない。自分でC++で書き直している。

SVGでAR
  • カメラを固定して、映っている対象物の頂点の位置を示したSVGファイル をつくる
  • → その位置に別の映像をオーバーレイするとAR的なことができる
  • CameraCalibrate3D を使用 する
  • 最終的なテクスチャをズームしたり変位させたり回転させたりすることでカメラが動いてるっぽくも見える

TLD Tracker

Tracking Learning Detection



ofxTldTracker

  • ライセンスはLGPL

OpenCV 3.0 にTLDアルゴリズムのとラッカーが追加される

404 Not Found の TrackerTLD


【oFセミナーメモ4】openFrameworksと映像制作ソフトの連携

デジタルアートセミナー#3 openFrameworksで学ぶ、クリエイティブ・コーディング』のライゾマ比嘉さんのセッション『openFrameworksと映像制作ソフトの連携』のメモ。

講師 : 比嘉了

AfterEffectsやCinema4Dなどの映像ソフトとopenFrameworksを連携させる映像制作手法の紹介を行います。


講義資料はこちらで公開されています。markdown形式。


過去のセッションのレポートはこちら。


事例紹介

(ダンスのやつ。動画メモし忘れました)

  • 手と頭につけてるマーカーで軌跡を書く
  • 他の部分は動的生成ではなく、CINEMA 4Dであらかじめ用意していたもの

Illustratorとの連携:ofxPDF

ベクタデータのうち、SVGよりPDFの方がロードが10倍ぐらい速いことがあったので、それからはPDFを使っている。そのPDFをoFで取り扱うアドオン。


普通に描画
ofxPDF pdf;
pdf.loadPDF("tiger.pdf");
pdf.draw();

テキストアニメーション

f:id:shu223:20141013174237j:image:w400

(アニメーションの途中の様子です)


void draw()
{
    float app_time = ofGetElapsedTimef();
    float animation_time = fmodf(app_time, 2) / 2.;
    
    cout << "app_time: = " << app_time << ", animation_time: " << animation_time << endl;
    
    ofSetColor(0);
    
    // PDFのパスを順番に取り出して ofPolyline で描画する
    for (int i = 0; i < pdf.getNumPath(); i++)
    {
        ofPath& path = pdf.getPathAt(i);
        
        vector<ofPolyline>& polys = path.getOutline();
        for (int k = 0; k < polys.size(); k++)
        {
            ofPolyline poly = polys[k];
            
            poly = poly.getResampledByCount(100);
            
            int target_size = poly.size() * animation_time;
            poly.resize(target_size);
            
            poly.draw();
        }
    }
}

順番に見ていくと、


1. パスを取り出す

ofPath& path = pdf.getPathAt(i);

2. パスのアウトラインを取り出す

vector<ofPolyline>& polys = path.getOutline();

ofPolyline がベクタで得られる


3. ofPolylineをリサイズしながら描画する

for (int k = 0; k < polys.size(); k++)
{
    ofPolyline poly = polys[k];

    poly = poly.getResampledByCount(100);

    int target_size = poly.size() * animation_time;
    poly.resize(target_size);

    poly.draw();
}

各アウトラインが、長さ0から元の長さに戻っていく。


After Effects との連携:ofxAfterEffectsKeyframeParser

サッカーのボールの軌跡を追う

AEにはそういう機能が入っている: tracker

-> キーフレームとして入る

-> ofxAfterEffectsKeyframeParser で読む


f:id:shu223:20141013174310j:image:w600


画像解析不要!


Cinema 4Dとの連携:ofxAlembic

https://github.com/perfume-dev/ofxAlembic

  • メッシュアニメーション
  • パーティクル
  • polyline
  • カメラワーク

などが読み書きできる


f:id:shu223:20141013174340j:image:w600

f:id:shu223:20141013174400j:image:w600

(それぞれ ofxAlembic に付属のサンプルを実行したもの。アニメーションします)


2014-01-08

シェルスクリプトでmp4からアニメーションgifを生成する

mp4からアニメーションgifを生成したい、というケースが最近ちょくちょくありまして。


たとえば「GitHub の README に動く様子を載せたい」場合、YouTubeやVimeoの埋め込みタグをREADME.mdに載せてもプレイヤーを表示してくれないので、アニメーションgifをつくって載せています。

元の動画はQuickTimeでシミュレータのキャプチャ動画を撮ったものだったり、iPhoneのカメラで撮ったものだったりするのですが、アニメーションgifへの変換はどういう方法があるのか知らなかったので、

  • QuickTimeで動画を短くトリムする
  • Photoshopで動画を読み込んで適当にフレーム飛ばしてAnimation Gifに書き出す

という非常に面倒なことをやっていました。


で、ふと下記記事を見ていると、

「90sのmp4から12fpsで320*180のアニメgifを作る」スクリプトが例に挙げられています。


高速化云々以前に、そんな便利なことができたのか!と。


というわけで試してみました。


準備

まず、自分の環境にはffmpegが入ってなかったのでインストール。

$ brew install ffmpeg

あとアニメーションgifを生成する gifsicle もインストールする必要があるのですが、brew installしようとすると

gifsicle: Unsatisfied dependency: XQuartz

Homebrew does not package XQuartz. Installers may be found at:

https://xquartz.macosforge.org

と怒られたので、エラー文の言う通りに https://xquartz.macosforge.org に行って、インストーラから XQuart をインストール。


で、あらためて gifsicle をインストール。

$ brew install gifsicle

シェルスクリプト作成

$ touch gen_animegif.sh
$ vi gen_animegif.sh

で以下のように編集(shellわからないので、元記事のコードをほぼそのままお借りしています)。

#!/bin/sh

# Task 1
SRC="$1"
[[ ! -f "$SRC" ]] && echo 'no exists' && exit 1
INI=$SECONDS
mkdir ./temp

# Task 2-3
ffmpeg -loglevel panic -ss 0 -i "$SRC" -r 12 -an -f image2 -s 320x180 "./temp/%03d.gif"
gifsicle -O3 --batch ./temp/*.gif

# Task 4
gifsicle ./temp/*.gif > output.gif
rm -rf ./temp
echo "done with `expr $SECONDS - $INI`s" && exit 0

実行してみる

$ sh ./gen_animegif.sh xxxx.mp4

23.9MB の mp4 動画ファイルが、コマンド一発で1.7MBのgifアニメに!


movファイルも試してみました。

$ sh ./gen_animegif.sh xxxx.mov

24MB → 5.1MBに!


問題点

上記shellだと、mp4(iPhoneで撮影)は出力ファイルにおける動画の向きが90度回転してしまい、mov(QuickTimeでスクリーンキャプチャ)は元々縦長だった動画が横長になって上下方向につぶれた感じになってしまいました。きちんと向きを処理する必要がありそうです。


まとめ

シェルスクリプト、他にも知らずに損してることがたくさんありそうです。勉強します。


2009 | 08 |
2011 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2012 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2013 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2014 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2015 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2016 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 11 | 12 |
2017 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2018 | 02 |