世界的日曜WEBプログラマー日記 -24ページ目

世界的日曜WEBプログラマー日記

一年後に世界的なWEBサービスを運営するにはどうすればいいのか書いていく(予定)


画像処理ではRGB形式の他にYUV形式がある。
YUV形式のフォーマットは一般にPlanar, Semi-Planar, Interleavedの3つある。
YUV planar semi-planar inter


尚ここでCr == V、Cv == U と同じ意味であることに注意。

planarはそれぞれのYUVのチャンネル毎にまとめてデータが置かれている。
semi-planarはYだけデータが置かれており、UVは交互に並んでいる。
interleavedはYUVが交互に並んで置かれている。

444であれば、それぞれのチャンネルはフルでサイズ分もっており、
422であればYだけフル、U, Vは1/2ずつ、
420(別名411)はYだけフル、U, Vは1/4ずつ持つ。

422, 420にする理由はデータ量圧縮のためである。

Androidにおいてカメラデバイスから来るフォーマットはAndroidは420 semiplanar( 420SP )で有ることがほとんどのようだ。
自身の端末のフォーマットについては確かめたほうが良く、以下のメソッドで確かめることができる。
 camera.getParameters().getPreviewFormat();


cameraはカメラオブジェクトである。(詳細は前回の記事参照のこと)
getPreviewFormatの返り値はintegerである。
0x11,(NV21)はYVU420SPを意味する。
0x16,(NV16)はYUV422SPを意味する。
ちなみ、NV21はYVUで、NV16はYUVであり、UとVの並び順が違うので注意すること!!

カメラからのプレビューの画像データを取得するには
onPreviewFrame(byte[] data, Camera camera) 

のメソッドを定義すればよい。プレビュー画像の配列はbyte型でくる。

カメラデバイスからY, U, Vに関して,最小値と最大値は0x00-0xffの範囲で返却される。
注意として、JAVAでは強制的にカメラデバイス側の符号を無しのデータを、signed型として扱ってしまう。
つまり、byte型配列を扱おうとすると、128から上の数は符号がマイナスになってしまう。
(ex. 255->-1, 254->-2 .. 2の補数。127までは問題が無い)
0-255を正しく扱うためには0xffでANDをとってやる必要がある。具体的には以下のようにしてアクセスする。
 int val = (data[ i ] & 0xff);// <== 強制的に最上位ビットの符号を消してやる。

これで-128~127の値が0~255として変換される。

尚、Native(JNI、C言語)で呼び出す場合はこうした処理をしなくともunsignedでキャストしてやれば事が足りる。

JNIについて
JNIを触る際、一番参考にするのはNDKをインストールした際についてくるサンプルコードである。コレが一番わかり易い。
これからNDKを勉強した人は必ず一回サンプルコードを実行すること!!!!変に文献やWEBは逆に遠回りである。

作成したC言語のファイルをコンパイルするには以下を呼ぶだけで良い。(makeコマンド相当)
$ ndk-build

Android.mkがMakefileとなっている。これがないとコンパイルされない。

JNIを使うときはプロンプトでndk-buildを叩いて、イクリプス上で実行して実機に流すという、ややたるいステップとなっている。
またC言語をで変更してndk-buildしても、JAVA上のソースコードに変更(更新)が無いとCライブラリの転送が行われないので注意。

C言語側のソースで作成する関数名にはルールがある。
例えばJava側のパッケージ名を
 package com.ekispresso.camera2;

としていた場合、
JNIを呼び出すクラス名がCameraView、
関数名がaddであれば、C言語側の関数名は
Java_com_ekispresso_camera2_CameraView_add
となる。(_区切り)

以下はその具体例である.

例)
--Java側
int a = 10;
int b = 20;
int c = add( a, b ); //<== JNIに記載する関数名
public native int add( int val1, int val2 );
// クラス内に記述する。Cの関数を使うという宣言

--C言語側
jint
Java_com_ekispresso_camera2_CameraView_add( JNIEnv* env, jobject thiz, jint a, jint b ) {
int c = a + b;
return c;
}

注意:JNIEnvとjobjectの二つは必ず引数として入れなければならない。Java側で引数が二つある場合は4つとなる。
jintの他にも jfloatなどがあり、これはint, float と等価であるため、c=a+bのようなことは問題ない。

配列を引数として扱いたい場合はちょっと注意が必要である。
byteやIntである場合、

Java_com_ekispresso_camera2_CameraView_render( JNIEnv* env, jobject thiz,
jintArray _rgb,
jint width, jint height,
jbyteArray _data,
jint camera_width, jint camera_height)
{
jint* rgb_bitmap=(*env)->GetIntArrayElements( env, _rgb, &b);
jbyte* data = (*env)->GetByteArrayElements( env, _data, &b);
// .... // <== なんかやって
(*env)->ReleaseIntArrayElements(env, _rgb, rgb_bitmap, 0);
(*env)->ReleaseByteArrayElements(env, _data, data, 0);
}

のようにして使用する。
上記はJava側に明示的に上記の配列を使用してますよ!GCしないでね!と伝えている。
使い終わったあとはReleaseなどを忘れない様に。

C言語ソース側ではmallocなども問題なく使える。

Android.mkの書き方

インクルードパスをLOCAL_C_INCLUDESにガンガン足していく
 
LOCAL_C_INCLUDES += ./src/interface ./src/spec ./src/
とする。

コンパイルしたいC言語のファイルをガンガン足していく。具体的には
LOCAL_SRC_FILES :=rendering.c ./src/core/ekispresso_core_malloc.c ./src/core/ekispresso_heap.c ./src/core/ekispresso_crc32.c ./src/core/ekispresso_environment.c ./src/core/ekispresso_core_sincos.c ./src/core/ekispresso_prng_xorshift.c
とひたすら書く。

モジュール名を定義する。コレはなんでもいい。
LOCAL_MODULE := rendering

これで終わり。
正しくインクルードパスなど設定で来ていればndk-buildすれば通るはず。
Androidのデフォルトのサンプルを参考にすると良い。

株式会社OctOpt
コンピューターサイエンス会社OctOptの技術公式ブログ
等々力 康弘
@rocky_house

Androidのカメラプレビューを作成した時のメモ公開

全体的なプレビューカメラアプリの流れについて
0. カメラ起動( カメラへ撮影する画素サイズを指定 640x480 )

(以下はループ)
1. カメラから画像の取得(Yuv420SP --> 必ず関数を使って確認のこと)
2. 画像をyuv420sp->rgbに変更(RGBはInt型)
3. キャンバスのロック
4. ビットマップクラスからキャンバスに描画
5. キャンバス解除

6. カメラ終了

以上のような流れとなり、1-5を繰り返すことでカメラがプレビューされる。

素朴な疑問:なぜYuvSP(セミプラナー)からなんでRGBに変換しなければいけないのか?
答え:BitmapクラスがYuv420SPをサポートしていないため。BitmapクラスはRGBしか対応しておらず、変換関数を書くことになる。カメラがRGBでそのまま吐き出してくれれば楽だが、現在その機能はない。(2011年6月)チョーめんどい仕様であるうえ、YUV->RGB返還で遅くなる。

キャンバスサイズについて
カメラが処理できる画像サイズと実際に描くキャンバス(実際のスマートフォンの画面サイズ)が異なるので注意。
カメラサイズの取得には、明示的に
  Camera.Parameters parameters = camera.getParameters();
  parameters.setPreviewSize( camera_width,  camera_height);
  camera.setParameters(parameters);
として設定する。
camera_width, camera_heightは次の関数を呼び出すことで知ることができる。
 List<size> s= parameters.getSupportedPreviewSizes();
上記リストをぶん回せば、カメラがサポートしているプレビューデバイスのサイズが帰ってくる。
今回は640、480と決め打ちにした。

用語説明
YUV…
YUV空間という。Yは画面の輝度。UVは色を設定するもんだと考えて欲しい。
RGBは慣れ親しんでいるとおもうけど、画像処理界ではYUVが基本。

セミプラナー、プラナー、インターリーブド…
semi-planar, planar, interleaved を説明しているページが意外と少ない!
YUVの画像の格納方法を次回のページに記載予定。参考にして欲しい。

コールバック…
コールバックとは、あるプロセスが走っているときに、そのプロセスが作業の途中で呼び出す
メソッドのことである。
「なんか、イベントを受け取ったときに呼び出す関数」
というイメージ。

サーフェイスホルダーとは(surfaceHolder)?
サーフェイスビューっていうのがあって、画面に描画するようなもの。(Viewと同じだが、ビューより描画が早い)
サーフェイスホルダーは,このサーフェイスビュークラスを管理する。たとえ一つしかsurfaceView生成しなくても、サーフェイスホルダークラスを作る必要がある。
つまり、画像に対してなんかをゴニョゴニョ(線を書く…絵を書く…など)したいときはサーフェイスホルダーをいちいち定義する必要がある、とうい事のようだ。
surfaceViewに対して生成、破棄などのことをやってくれる管理クラスってこと。

ところで、SurfaceHolderをImplemetsした場合は、以下の三つを実装する必要がある。
surfaceCreated, surfaceChanged, surfaceDestroyed
カメラプレビューアプリを作成するときは、
surfaceCreated --> カメラの初期化(カメラの起動、プレビューコールバックのセット)
surfaceChanged --> サーフェイスが更新されたときに呼び出される。つまり、サーフェイスのフォーマットはや、画面、幅などが変わったとき。(通常のカメラプレビューアプリの使い方をすればだが…)、一回しかこれも呼ばれることはない。
SurfaceDestroyed --> 破棄。
カメラアプリは描画にサーフェイスビューを使用するようだ。


--
package com.ekispresso.camera2;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.hardware.Camera;
import android.view.SurfaceView;
import android.view.SurfaceHolder;
import android.util.AttributeSet;
import android.util.Log;
 
public class CameraView extends SurfaceView implements SurfaceHolder.Callback {
 private SurfaceHolder holder;
 private Camera camera;
 private Bitmap bitmap;
 private int[] rgb_bitmap;
 private int width, height;
private int camera_width, camera_height;
 
 /**
  * プレビューコールバック
  *   prepareSavePreviewImageコールバックで登録され、プレビュー画像を取得する
  */
private static int chop(int  v) {
    return (v < 0 )? 0:(v>255)? 255:v;
}
 private final Camera.PreviewCallback _previewCallback =
  new Camera.PreviewCallback() {
  public void onPreviewFrame(byte[] data, Camera camera) {
   //decodeYUV420SP(rgb, data, width, height);
      int total = camera_width * camera_height;
      double ratiox = camera_width / (double)width;
      double ratioy = camera_height / (double)height;
      int rx = (int)(ratiox * (1 << 20));
      int ry = (int)(ratioy * (1 << 20));
      int cnt = 0;
      for( int y = 0; y < height; y ++ ) {
          for( int x = 0; x < width; x ++ ) {
              int camerax = x * rx >> 20;
              int cameray = y * ry >> 20;
              int addr = cameray * camera_width + camerax;
              int posx = (camerax >> 1) << 1;
              int posy = (cameray >> 1);
              int addr2=posy * camera_width + posx;
              
              int yy = (data[ addr ] ) & 0xff;
              // カメラからは-128 to 127で帰ってくる。まず、&0xffで 128 -- 255, 1 --- 127 にしてやり、
              // 一律に128を引くことで 0 -- 127 -127 -1という事にしてやる。
              int vv = ((data[ total + addr2 ]) & 0xff) - 128;
              int uu = ((data[ total + addr2 + 1] ) & 0xff) - 128;
              // 高速化のため20ビット固定小数点化
              //int R = (int)chop(yy + 1.403 * vv);
              //int G = (int)chop(yy  -  0.344* uu  - 0.714 * vv);
              //int B = (int)chop(yy + 1.770 * uu);
              int R = chop(yy + (( + 1471152 * vv)>>20)) ;
              int G = chop(yy + ((  -  360710 * uu  - 748683 * vv) >> 20));
              int B = chop(yy + (( + 1855979 * uu )>>20) );
              rgb_bitmap[ cnt ] = 0xff000000 |(  (R << 16) + (G << 8) +  B);
              cnt ++;
          }
      }

   //bitmap.setPixels(rgb_bitmap, 0, width, 0, 0, width, height);
   // 描画
   Canvas canv = holder.lockCanvas();
   //canv.drawBitmap(bitmap, 0, 0, null);
   canv.drawBitmap(rgb_bitmap, 0, width, 0, 0, width, height, false, null);
   holder.unlockCanvasAndPost(canv);
  }
 };
 
 // コンストラクタ
 public CameraView(Context context) {
  super(context);
  this.initialize();
 }
 
 // コンストラクタ
 public CameraView(Context context, AttributeSet attrs) {
  super(context, attrs);
  this.initialize();
 }
 
 // 初期化処理
 private void initialize() {
  // サーフェイスホルダーの生成
  holder=getHolder();
  holder.addCallback(this);
 
  // サーフェイスホルダーのタイプを設定
  holder.setType(SurfaceHolder.SURFACE_TYPE_NORMAL);
  camera_width = 640;
  camera_height = 480;
 }
 
 // サーフェイス生成イベントの処理
 public void surfaceCreated(SurfaceHolder holder) {
  // カメラの初期化
  try {
   camera=Camera.open();
   camera.setPreviewCallback(_previewCallback);   
   Camera.Parameters parameters = camera.getParameters();
   //List<size> s= parameters.getSupportedPreviewSizes();
   parameters.setPreviewSize( camera_width,  camera_height);
   camera.setParameters(parameters);
  } catch (Exception e) {
  }
 }
 // サーフェイス変更イベントの処理
 public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
     // 描画データの準備
     width = w;
     height = h;
     bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);  
     rgb_bitmap = new int[ w * h ];
     // カメラのプレビュー開始
     camera.stopPreview();
     Camera.Parameters parameters = camera.getParameters();
     parameters.setPreviewSize(camera_width, camera_height);
     camera.setParameters(parameters);
     camera.startPreview();
 }
 
 //サーフェイス解放イベントの処理
 public void surfaceDestroyed(SurfaceHolder holder) {
  // カメラのプレビュー停止
  camera.stopPreview();
  camera.setPreviewCallback(null);
  camera.release();
  camera = null;
 }
 
 // YUV420 to BMP
}

株式会社OctOpt
コンピューターサイエンス会社OctOptの技術公式ブログ
等々力 康弘
@rocky_house

友人の結婚式のためにWindowsでビデオ編集した。
その際に役立ったソフトウェアや知識を備忘録として書く事にした。
なお、以下は全部フリーソフトである。

Youtubeから動画をダウンロード
vid-dlというサイトが一番良い。ここからYoutubeの動画をダウンロードする。
http://www.vid-dl.net/
ダウンロード形式はmp4かflv。

mp4->mpeg変換ツール
当方ではAdove社のプレミアCS3を使っていたがMP4が読み込めないのでMpegにする必要があった。
mp4からmpegに変更するにはPazara MP4 to AVI converterが一番良い。
どうやらエンジンでfmpegを読んでいる模様。AVIにも変換できるらしい。
http://download.cnet.com/Pazera-Free-MP4-to-AVI-Converter/3000-2194_4-10784027.html
MP4はquicktimeでも出来るらしいが有料版でないとダメだというアホな仕様でハマった。
後述のFisoft free Video Converterもいいかもしれない。

DVDの作成
FlickDVDこれだ。これ一本で動画をすべてDVDに焼ける。
http://www.dvdflick.net/

Alchol52% Free edition
FlickDVDなどで出来たISOなどをマウントするソフト。
間違って体験版をインストールしない様に。
http://www.free-downloads.net/programs/Alcohol_52__Free_Edition
インストール後、再起動が必要。

Wisoft Free Video Converter
mp4の編集はこのソフト。他にも色々とあったが、Youtubeからの
ダウンロードMP4をエラーなしで編集できたのはこいつだけだった。
出力なフォーマットに対応している。
編集の方法も直感的で扱いやすい。
http://www.easy-video-converter.com/download.html


株式会社OctOpt
コンピューターサイエンス会社OctOptの技術公式ブログ
等々力 康弘
@rocky_house