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