Android Camera previewアプリの作成( プレビュー画面を直接いぢる方法) | 世界的日曜WEBプログラマー日記

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

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


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