.jpg -> .wav -> .mp3 -> .wav ->.jpg
流れ
画像を音に変換する。その音をmp3に変換、再度、音から画像に戻す。
.wmaは.mp3とほとんど同じ。
.oggは元の画像と変化なし
#!/usr/bin/env python #coding:utf-8 import Image, wave, subprocess input_file = "diana.jpg" output_file = "diana_out.jpg" img = Image.open(input_file) raw_data = img.tostring() # 生データを引き抜く wav = wave.open("dianaXXXX.wav", "wb") # モノラル、16ビット、サンプリングレートの設定 wav.setnchannels(1) wav.setsampwidth(2) wav.setframerate(44100) # 画像の生データを音にぶっこむ wav.writeframesraw(raw_data) wav.close() # 外部のffmpegで.wavを.mp3に変換し、もう一度.wavに戻す subprocess.call(['ffmpeg', '-i', 'dianaXXXX.wav', '-ab', '64', '-ar', '44100', 'dianaXXXX.mp3'], shell=False) subprocess.call(['ffmpeg', '-i', 'dianaXXXX.mp3', 'dianaXXXX.wav'], shell=False) wave2 = wave.open("dianaXXXX.wav", "rb") # 音の生データを引き抜く raw_data2 = wave2.readframes(wave2.getnframes()) # 音の生データを画像にぶっこむ im2 = Image.fromstring("RGB", img.size, raw_data2) im2.save(output_file)
アウトロ
少し前に画像を音にしてスピーカーから出力し、マイクで再度拾って画像にフィルタを掛けるというのをやった。それの応用で、mp3に変換し可聴周波数以外が切り取られることで、聞く限界がもたらす画像フィルタとかです多分。
映像にも以上の操作をしてみたのだが、元の映像の画質も悪いせいかノイズまみれで汚かったので今回はパス。気が向いたらそのうち。
動画を一枚の画像に敷き詰め、グリッチしてまた動画に戻す
僕は.modや.sidなどの音楽ファイルをバイナリを壊してグリッチ音を作ったりするのですが、映像などには全く明るくないので以下の内容はすでに何方かがやっていたりする話なのかもしれない。
さて以下の文章を読みました。
要約 : Apple iPhone5のページのJPEGとJSとcanvasタグによるビデオの実装がやばいという話
上の内容からちょっとした疑問がわきまして、動画を一枚の画像に敷き詰め、その「敷き詰め画像」をグリッチしてまた動画に戻したらどうなるか?。
1.まず動画(http://archive.org/details/BflOggGX1963)を準備します。お猿さんがシェイクスピアを叩いています。
2.ffmpegなどで動画から画像の連番を書き出します。30秒ほど抜き出しましょう、全部で900枚です。
3.「敷き詰め画像」を作ります。コードはpythonです。
import Image import math v_width = 400 v_height = 308 f_sum = 900 f_xy = int( math.sqrt( f_sum ) ) i_width = v_width * f_xy i_height = v_height * f_xy img = Image.new("RGB", (i_width, i_height)) x=0 y=0 for i in range(f_sum): im = Image.open("%(#)04d" % {"#":i} + ".jpg") img.paste(im, (v_width * x, v_height * y)) x = (x + 1) % f_xy if x is 0: y += 1 img.save("kansei.jpg")
こんな感じの画像(縮小版)ができます。実際はものすごくでかいのでショボイ画像ビュアーで開いたりするとハングします。
4.ここでyoupyのglitchmonkeyで使われている方法でグリッチします。
import base64 j_file = "kansei.jpg" file = open(j_file, "rb").read() e_base64 = base64.encodestring(file) g_text = e_base64.replace("Qe", "ud") d_base64 = base64.decodestring(g_text) open("glitch_kansei.jpg", "wb").write(d_base64)
敷き詰め画像は以下のグリッチ画像(縮小版)になる。
5.グリッチ画像を動画にするために連番画像を作ります。
import Image import math v_width = 400 v_height = 308 f_sum = 900 f_xy = int(math.sqrt( f_sum )) i_width = v_width * f_xy i_height = v_height * f_xy im = Image.open("glitch_kansei.jpg") x=0 y=0 for i in range(f_sum): t_im = im.crop((v_width * x, v_height * y, v_width * x + v_width, v_height *y + v_height)) t_im.save("%(#)04d" % {"#":i} + ".jpg") x = (x + 1) % f_xy if x is 0: y += 1
6.ffmpegなどで連番画像を動画にしてyoutubeにポストしました。
感想
う〜ん、、、。
.jpg以外、例えば.pngや.gifにしたりして色々ためしてみるといいかもしれませんね、photoshopやgimpなどのフィルタをかけてみるのもいいかもしれません、以上。
周波数ドメインの一行数式音楽
今回は、周波数ドメインの一行数式音楽
one-line algorithmic music in frequency domain (chrome, safari) (11/4 大幅改良)
説明
一行数式音楽は時間ドメインです。それは数式で、波形編集ソフトでよく見られる波を生成する。一方、周波数ドメインの一行数式音楽は数式で、以下の図のようなスペクトルを作ったりする実数部と虚数部を生成している。
(図はaudio data apiのサイトから拝借)
感想
意外と個性のあるサウンドで良い感じではないでしょうか。ただ僕自身がFFTをよく理解してないために実装に不安が残ります。しかし例えば1を入力するとすべての周波数成分を持つインパルスぽい音が生成されたり、そこそこ望んだものが出るので良いのではと思っています。間違っていたらお教えください。(7/4 追記)バッファーサイズをどのくらいとるか、またラッピングをどれくらいにするかとか、考察の余地は結構あり、仕様を定めるのは難しそうだ。
動作環境
web audio apiに対応したブラウザで、chromeと最新のsafari。firefoxは時間切れでそのうち対応します。
おわりに
さて、これを思いついたのは、前回の日記で書いた一行数式カメラで色空間をRGBからHSVにかえてみたら思わぬ視覚効果を得ることができたから(様子は追記しました)。ふと、音にはそういったものはないけれど時間的な見方ではなく周波数的な見方ならどうなるかとやってみた結果、こうなりました。いろいろやってみるものだなあ。(7/4 多少書き換え)
一行数式カメラ
今回は一行数式カメラ(または一行数式写真)。
デモ (firefoxモバイル、カメラ機能のない他ブラウザ)
スクリプトを使ったカメラみたいな感じです。
どんな写真が取れるのか、例えば
(t|222>>(t>>81))+(t%4)
(t+5*(t%6)*7*(t%2))+(t%4)
(t+10*(t%16)|256>>(t%8))+(t%4)
(以下追記)
RGBをHSVに変換してから。
t+99*(t%2)
他はココらへんに垂れ流しています。
感想
数式を発見する楽しさはあります。画像フィルタプログラムとか全然知らないのでこれだけでも嬉しい。すぐに飽きるかも知れません。
動作環境
webアプリなので、ブラウザでwebカメラが使えるもの。
スマフォはopera mobile、firefox, chrome?。ノートなどはchrome(設定あり)やopera?などが動くと思います。
技術的な話
RGBAを数式に紛れ込ませて直接いじることも考えたのですが、数式が複雑化するので今回はカウンタの増減をいじっている。
おわりに
前にワンライナーで音楽を作る記事を訳したのですがそれにヒントを得ています。他にもちょいアレンジしてサウンドファイルの再生位置をワンライナーでコントロールするというのをあります。ココ。
追記7/6 デモの実装に不備があり、それに伴い今までの数式では結果が異なってしまいます。上記の数式は修正しましたが、同じ結果にするには既存のをカッコしt%4を加えてください。以下のように
(数式)+(t%4)
androidで目まゆ鼻口顔輪郭
ビデオを見るとわかるのですが、Android携帯で顔(目まゆ口顔輪郭)をトラッキングしています。
正直、かなりのレイテンシがあります。
顔を検出するとフレームレートは落ちて毎秒1フレームぐらいです。
何とかしたいのですがね、多少修正してスピード出したりはしているのですが。
使ったライブラリはOpenCV2.4(http://bit.ly/d0RNZm)。
それからasmlib-opencv(http://bit.ly/NIUjBV)です。
opencvはandroid用が準備されていてすぐに使えます。
しかしもう一方は結構移植が大変で、そこの部分は後でまとめてココに書き足します。
それではソース(opencvのtutorial-3-nativenのsrcとjniを置き換えてください)
https://dl.dropbox.com/u/78146942/src/asm_androiddemo.zip
ライセンスはGPLです(ライブラリがそうなもので)。
OpenCVとasmlib-opencvとPortAudio
完全に自分用のメモで読みづらい。
これはOpenCVとasmlib-opencvとPortAudioを使ったもので、カメラの画像を解析し目とか顔の座標位置で音が鳴るだけ、ちょっとした実験。
僕の環境はVisual Studio 2010 Expressなんですが、この3つはどのような環境でも使えるはずなので、特定の環境の話は最後にまとめて書きます。
ちなみにasmlib-opencvはオープンソースのQt Creatorのサンプルがあります。これを使いたかったがうまくインストールできなかった。
OpenCV
http://opencv.willowgarage.com/wiki/
今、多分2.4でその前までのバージョンと違って?簡単に使えます。
ネットの情報に右往左往し無駄に時間を使ってしまわないように、ちゃんとバージョンを確認したほうがいいです(自分に言い聞かす)。
まずインクルードのディレクトリを設定し、自身の環境に適したライブラリにパスを通します、すると準備はできてしまいます。
以下のサイトがとても参考になりましたし、ここのサンプルコードで動くのを確認しました。
http://imagingsolution.net/program/opencv/opencv2-4-dynamic-downlaod-install/
OpenCVの使い方はasmlib-opencvの解説の中で。
asmlib-opencv
http://code.google.com/p/asmlib-opencv/
このライブラリで顔や目鼻の輪郭を線でなぞるわけです。
今回使用するのはsrc以下のlibフォルダに入っているものすべて。
それからsrc以下のdateフォルダのhaarcascade_frontalface_alt.xmlとmuct76.modelの2つです。haarcascade_frontalface_alt.xmlは映像から顔を抜き出すのに使用し、muct76.modelは輪郭などに使うものだと思います。
ただここが厄介です。libフォルダ以下のファイルはこのまま使えませんでした。
このライブラリはOpenCV2用のものなのです(OpenCVは1と2で全然書き方が違っているので注意が必要なのです)。
しかしさらにこの2.4とも差異が発生しています。
それはインクルードが以下の様な書き方に変更されました、多分。
#include "opencv2\opencv.hpp"
GUIだとか使用するものに合わせてインクルードせず、上記で大概済むようになっているようです。
そのためにasmlib-opencvのインクルードの殆どを修正しなければなりません。
とはいえ、ファイル数も少ないしファイルあたり多くて2行です。
根気よくOpenCVのインクルードファイルを上記のように修正してください。
このasmlib-opencvは自分でモデルを組むことができるようなのですが、まだそこまで把握できていません。
今回は映像から画像をとって、画像から顔を抜き出し、目鼻などの輪郭を測り、そこに線や円を描画します。
src以下のdemoにあるmain.cppの一部から流れを確認。
まずASMモデルを読み込みます。そいつはmuct76.modelです。
何が行われているのか残念ながらわかりません。
asmModel.loadFromFile(modelPath);
次に顔の分類器を読み込みます。そいつはhaarcascade_frontalface_alt.xmlです。
何が行われているのか残念ながらわかりません。
cv::CascadeClassifier faceCascade; faceCascade.load(faceCascadePath)
続いてWEBカメラの機能をオンにします。そして映像から画像を取得。
Mat img, imgT; cv::VideoCapture capture; capture.open(0); capture >> imgT;
顔を検出します
objCascadeClassfifier.detectMultiScale(img,,,);
これとASMモデルをフィットさせる。フィットした座標の位置の配列を返す。
vector < ASMFitResult > fitResult = asmModel.fitAll(img, faces, verboseL);
結果を画像に描画します
asmModel.showResult(img, fitResult);
以上です。
流れはそれほど難しくありません、中で何やってるかは全然わかりませんが。
今後少しづつ理解していければ、、、。
PortAudio
http://www.portaudio.com/
これは比較的古い環境のプロジェクトファイルしかありません。
しかしcmakeが用意されているので一発で自分の環境にあったプロジェクトファイルを作ることができした。
CMakeList.txtがあるフォルダでコマンドプロンプトでcmake .とすることでプロジェクトファイルが生成される。
さて次にパスを通します。以上です。サイン波が数秒間鳴るサンプルがあるのでコンパイルしてみましょう。
流れの確認してみよう。
初期化
PortAudioStream *stream; Pa_Initialize();
ストリームの開放
Pa_OpenStream(&stream,,,);
バッファ
buffer = new double[bs*ch];
コールバック
int paCallback(void *, void *outputBuffer, unsigned long framesPerBuffer, PaTimestamp, void *_ss) { //実際の処理 }
ストリームの開始
PaError err = Pa_StartStream( stream );
ストリームの停止
PaError err = Pa_StopStream(stream);
ストリームを閉じる
PaError err = Pa_CloseStream( stream );
終了
Pa_Terminate();
ソース(GPL)
https://dl.dropbox.com/u/78146942/src/cv_asm_pa_demo.zip
以下、書き途中。
Androidで写真を撮って、それを音データに変換し、スピーカーで再生すると同時にマイクで録音し、もう一度、画像に戻してみる、それを一連にやってくれるアプリです。
出来上がる写真はこんな感じです。レナさんです。
所謂、現実フィルタというものをやってみたつもりです。
実際、もう少しはっきりした写真で、周りの音に反応してフィルタが、、、という展開を期待したのですが。
(追記 「マイクやスピーカーで20Hz以下の周波数がカットされるから周波数が高い部分しか写真に表示されないんだ」と。なるほど。そうなるとあれか、、、。)
ただまあAndroidを振ったりすると歪みが出たりはしますが。
動機
アラブの春の時にエジプト政府がインターネットを遮断したことがありました。するとtwitterとgoogleが電話を使ったサービスを開始します。電話口から写真を音に変えて送れるなと思ったわけです。あれ、そうだったかな。ただただ平安を祈るばかりです。このアプリは44100Hzなんで電話では使えないけど。
改善
そもそもカメラアプリの作り方がわかっていないので修業が必要。
ナイキスト周波数の関係で半分RGBAの要素が反映されていないところです。(追記 ちょっとずるだが修正した)
音声を再生するAudioTrackのバッファの再生位置が思ったところにこないので画像の表示位置がずれてしまいます。色々試したのですがまだ方法がわかっていない。
今後
さて、Korgのmonotribeelectribeが音声を用いたアップデート方法を採用し話題になりました。
僕が思いつく方法としてはFSKというやつがあり、0と1のデータを作り変調して2つの周波数の音を作ります。
そして受信側がこのブログでも前に扱いましたがGoertzelアルゴリズムとかで周波数を検出してデータを復元する。
こういうこともやってみたいなと思いつつ、これだと変化を起こすのが大変そうではあります。
ダウンロード
物好きな方はアプリapkを置いておくので勝手に使ってください。
android2.2以上
https://dl.dropbox.com/u/78146942/android/apk/ORcamera.apk
android1.6以上(ちょっといい加減)
https://dl.dropbox.com/u/78146942/android/apk/CamSndCam.apk
手順
設定->アプリケーション->不明な提供元にチェック->apkをダウンロード->インストール
使い方
A.起動するとプレビュー画面が出るのでタッチする。(タッチダウンでフォーカスがあたり、タッチアップで撮影)
B.シャッター音が聞こえ少しするとノイズ音(びよよよ〜ん、ざざざざ〜)が出ます。
C.プレビューに戻ります。
D.sdカード以下にorcamera0000.jpgと保存されています。
ソース
package com.myapp.orcamera; import android.app.Activity; import android.os.Bundle; import android.view.Window; import android.view.WindowManager; import android.content.Context; import android.hardware.Camera; import android.os.Environment; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; import java.io.FileOutputStream; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Bitmap.CompressFormat; import android.graphics.Color; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioTrack; import android.media.AudioRecord; import android.media.MediaRecorder; public class ORcameraActivity extends Activity { Snd snd; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); requestWindowFeature(Window.FEATURE_NO_TITLE); snd = new Snd(); setContentView(new CameraView(this, snd)); } } class CameraView extends SurfaceView implements SurfaceHolder.Callback,Camera.PictureCallback,Camera.AutoFocusCallback { private SurfaceHolder holder; private Camera camera; public Snd snd; public CameraView(Context context, Snd s) { super(context); snd = s; holder=getHolder(); holder.addCallback(this); holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); } public void surfaceCreated(SurfaceHolder holder) { camera=Camera.open(); try { camera.setPreviewDisplay(holder); } catch (Exception e) {} } public void surfaceChanged(SurfaceHolder holder,int format,int w,int h) { camera.startPreview(); } public void surfaceDestroyed(SurfaceHolder holder) { camera.setPreviewCallback(null); camera.stopPreview(); camera.release(); camera=null; } @Override public boolean onTouchEvent(MotionEvent event) { if (event.getAction()==MotionEvent.ACTION_DOWN) { camera.autoFocus(this); } if (event.getAction()==MotionEvent.ACTION_UP) { camera.takePicture(null,null,this); } return true; } public void onAutoFocus(boolean success, Camera camera){} public void onPictureTaken(byte[] data,Camera camera) { Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 8; bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options); Bitmap b = bitmap.copy(Bitmap.Config.ARGB_8888, true); int width = b.getWidth(); int height = b.getHeight(); snd.setSize(bitmap, b, width, height); try { String path=Environment.getExternalStorageDirectory()+"/orcamera0000.jpg"; data2file(b,path); } catch (Exception e) {} camera.startPreview(); bitmap.recycle(); bitmap = null; b.recycle(); b = null; } private void data2file(Bitmap b,String fileName) throws Exception { FileOutputStream out=null; try { out=new FileOutputStream(fileName); b.compress (CompressFormat.JPEG, 100, out); out.close(); } catch (Exception e) { if (out!=null) out.close(); throw e; } } } class Snd{ public Snd(){} public void setSize(Bitmap bitmap, Bitmap b,int width, int height){ int buffer_size = width*height*4; byte[] outbuf = new byte[buffer_size]; byte[] inbuf = new byte[buffer_size]; AudioRecord record = new AudioRecord( MediaRecorder.AudioSource.MIC, 44100, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, buffer_size); AudioTrack track = new AudioTrack( AudioManager.STREAM_MUSIC, 44100, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, buffer_size, AudioTrack.MODE_STREAM); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int color = bitmap.getPixel(x, y); int rr = Color.red(color); int gg = Color.green(color); int bb = Color.blue(color); int aa = Color.alpha(color); outbuf[4*(x+y*width)] = (byte)(127-rr/2); outbuf[4*(x+y*width)+1] = (byte)(127-gg/2); outbuf[4*(x+y*width)+2] = (byte)(127-bb/2); outbuf[4*(x+y*width)+3] = (byte)(127-aa/2); } } track.setPlaybackHeadPosition(0); track.reloadStaticData(); track.play(); track.write(outbuf, 0, buffer_size); record.startRecording(); record.read(inbuf, 0, buffer_size); track.stop(); record.stop(); track.flush(); track.release(); record.release(); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int rr = (int)inbuf[4*(x+y*width)] + 128; int gg = (int)inbuf[4*(x+y*width)+1] + 128; int bb = (int)inbuf[4*(x+y*width)+2] + 128; int aa = (int)inbuf[4*(x+y*width)+3] + 128; b.setPixel(x, y, Color.argb(aa, rr, gg, bb)); } } } }
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.myapp.orcamera" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.RECORD_AUDIO"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity android:name=".ORcameraActivity" android:screenOrientation="landscape" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>