今回は、SurfaceViewを使った、カメラの利用について、書いてみます。
結論から言うと、出てきた課題をクリアーできませんでしたので、その前提で呼んでくださいorz
---
SurfaceViewを使うと、自分で定義したActivityで、カメラプレビュー画面を使えます。
カメラで写している画像を、Viewとして表示できるようになるわけです。
サンプルコードは、ググれば・・・というより、ApiDemoにあります。
ApiDemos->Graphics->CameraPreviewを、たどってください。
このAPIDemoは、撮影には対応してませんが、撮影のコードも、ググれば出てきます。
(あとでサンプルコードを・・・って、ほとんど、ネット上のコードのコピペだけど)
ここまでできると、プレビュー>撮影ができるのですが・・・ここでも機種依存(だけじゃない)罠が。
1.カメラプレビューのアスペクト比がおかしい。もしくは、プレビューと、撮影サイズが一致しない。
2.Galaxy Sの場合・・・なんかヘン。
またGalaxyですが。(おかしいのは自分のコードかもしれない)
まず、1。
カメラプレビューで表示できる、プレビューサイズというのは、機種ごとに決まっています。
機種ごとにも、幾つか種類があって、そのサイズ一覧は
Camera.Parameters::getSupportedPreviewSizes()で取得できます。(戻り値はListです)
ApiDemoのサンプルコードの場合は、この中から、SurfaceViewのサイズとアスペクト比が
近しいものを選ぶようなアルゴリズムが書いてあります。getOptimalPreviewSize()メソッドです。
これをやらないで、適当に選ぶと、SurfaceViewのアスペクト比に、カメラプレビューがあわされてしまい、
例えば人をプレビューすると、太ったり痩せちゃったりします。
この処理があることで、適切なプレビューが選ばれるのですが、困ったことに、いざ撮影!してみると
またサイズが違っちゃったりします。プレビューに出てない両端が表示されたりとか、また痩せたり細ったりとか。
これは、実際に撮影し、保存された画像と、これまたアスペクト比が異なっちゃっているのが問題みたいです。
(この辺から推測ですが、)結局、「画面のアスペクト比」「プレビューのアスペクト比」「撮影され、保存された画像のアスペクト比」を
考えて、サイズを選ばないといけないっぽい。
アルゴリズム的にはこんな感じ(未検証だけど)
1.撮影後に保存される画像のアスペクト比の設定値を取得する。Camera.Parameters::getPictureSize()←たぶん
2.1と近しいアスペクト比のプレビュー画像サイズを取得するCamera.Parameters::getSupportedPreviewSizes()
3. 2 のアスペクト比をもとに、SurfaceViewのサイズを決める。
おそらく、3がポイント。1は、カメラスペックに依存してしまうので、どうにも変えられない。
最終的に保存される画像(HW依存)が全てなので、それに合わせていくために、2,3を決めていくしかない。
3は、SurfaceViewのマージンやSurfaceView以外の画面パーツ(View)を駆使して調整するしかないかなと。
たぶんこれでうまく行きそうな気がするんですが、実は機種依存にはまってしまって、SurfaceViewを使うのを諦めた(※)ので、検証用のコード書いてません。
(※)諦めたというより、労力に見合わなかった、、、
で、その機種依存ですが。以下の3つ(たぶんもっとある)を乗り越える必要があります。
1.CameraParameterは、フォーカス、ホワイトバランスなど、設定項目が多すぎ。機種ごとに使えるものも違う。
2.ボタンを押す、タッチするなどの、撮影トリガーは、自分のアプリで作ってあげる必要がある。
これはつまり、カメラアプリの動作と異なってきてしまう。
3.撮影後の動作が・・・
1.設定パラメータがたくさんある、つまり、自分で設定しなけりゃいけない。
オートフォーカス、ホワイトバランスとか、端末のカメラアプリなら勝手に設定してくれるものを、
チューニング含めて、自分でやる必要あります。
私がやりたかったのは、カメラで撮った画像から顔を取得する機能なんですが、
SurfaceViewを使った方法だと、(Intentでカメラを起動する方法に比べて)顔認識率が下がってしまいました。
これは、デフォルトのカメラアプリは、パラメータを、センサとかいろんなモノ使って自動で補正しているから。
2.カメラ系のUIは、やはり各メーカ、こだわりがあります。
撮影するのも、画面タッチで撮影するものと、ボタンで撮影するもの・・・などなど。
「自分のアプリはこうだ!」といって、撮影方法決めても、
ユーザから見たら同じカメラ、「なんで操作が違うの?」となります。
つまり、使いづらいわけです。
いっつもカメラボタン押して撮影してるのに、このアプリではカメラボタン効かない、とか。イライラします。
3.大抵の機種は、撮影後、プレビュー表示してくれます。
が、Galaxy Sだけ、なぜか、プレビューが出ず、ベタ塗りの画面に・・・
しかも、何回か起動すると落ちたりとか。端末の向きの検出してくれなかったりとか。
まぁ、これは自分の設定値に問題があるような気もしますが。
ということで、カメラ撮影した画像がほしいという場合、特別な理由がなければ、
IntentでCameraアプリを呼んだほうが、使い勝手とか、労力とかの点で、かなりベターだと思います。
それにしても、Cameraの機種依存は凄まじい。
メーカが力を注力する部分だし、仕方ない部分だとは思うけど、
少なくとも標準動作は決めて欲しいな>Google様。
最後に、SurfaceViewを使って、プレビュー、撮影するコードを晒しておきます。
一応、Orientationにも対応しているつもり。
もちろん、今回記述した問題には対処してないですwww
package test.android.Camera; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.Iterator; import java.util.List; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.hardware.Camera; import android.hardware.Camera.Size; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.Log; import android.view.MotionEvent; import android.view.OrientationEventListener; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.Window; import android.view.WindowManager; public class CameraEntry extends Activity { private CameraView camView; private static final String TAG = "CameraEntry"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); getWindow().clearFlags( WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); camView = new CameraView(this); setContentView(camView); } public class CameraView extends SurfaceView implements SurfaceHolder.Callback { private SurfaceHolder mHolder; // ホルダー private Camera mCamera; // カメラ private Size mSize; private static final String TAG = "CameraEntry"; private OrientationEventListener orientationListener = null; public CameraView(Context context) { super(context); // サーフェイスホルダーの生成 mHolder = getHolder(); mHolder.addCallback(this); // プッシュバッッファの指定 mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); orientationListener = new OrientationEventListener(CameraEntry.this) { @Override public void onOrientationChanged(int orientation) { CameraView.this.onOrientationChanged(orientation); } }; } private int prevOrientation = 0; public void onOrientationChanged(int orientation) { if (mCamera == null) { Log.d(TAG, "mCamera == null"); return; } if (mInProgress == true) { return; } if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) return; orientation = (orientation + 45) / 90 * 90; int rotation = 0; rotation = (orientation + 90) % 360; if (prevOrientation == rotation) return; Log.d(TAG, "Orientation " + prevOrientation + " to " + rotation); prevOrientation = rotation; mCamera.stopPreview(); Camera.Parameters params = mCamera.getParameters(); params.setRotation(rotation); mCamera.setParameters(params); mCamera.startPreview(); } public void surfaceCreated(SurfaceHolder holder) { mCamera = Camera.open(); Camera.Parameters params = mCamera.getParameters(); ListsupportedSizes = params.getSupportedPreviewSizes(); if (supportedSizes != null && supportedSizes.size() > 0) { mSize = supportedSizes.get(0); params.setPreviewSize(mSize.width, mSize.height); params.setRotation(0); mCamera.setParameters(params); } for (Iterator i = supportedSizes.iterator();i.hasNext();) { Size s = i.next(); Log.d(TAG, "Preview/w:" + s.width + "h:" + s.height); } supportedSizes = params.getSupportedPictureSizes(); for (Iterator i = supportedSizes.iterator();i.hasNext();) { Size s = i.next(); Log.d(TAG, "Picture/w:" + s.width + "h:" + s.height); } Size s = params.getPictureSize(); Log.d(TAG, "PictureSetting/w:" + s.width + "h:" + s.height); try { mCamera.setPreviewDisplay(holder); } catch (IOException exception) { mCamera.release(); mCamera = null; // TODO: add more exception handling logic here } } public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { Log.d(TAG, "Start preview!"); // カメラの初期化 try { Log.d(TAG, "Start create!"); Camera.Parameters parameters = mCamera.getParameters(); List sizes = parameters.getSupportedPreviewSizes(); Size pictureSize = parameters.getPictureSize(); Size optimalSize = getOptimalPreviewSize(sizes, pictureSize.width, pictureSize.height); parameters .setPreviewSize(optimalSize.width, optimalSize.height); // parameters.setPreviewSize(mSize.width, mSize.height); parameters.setPictureSize(1600, 1200); mCamera.setParameters(parameters); orientationListener.enable(); mCamera.startPreview(); } catch (Exception e) { e.printStackTrace(); } } private Size getOptimalPreviewSize(List sizes, int w, int h) { final double ASPECT_TOLERANCE = 0.05; double targetRatio = (double) w / h; if (sizes == null) return null; Size optimalSize = null; double minDiff = Double.MAX_VALUE; int targetHeight = h; // Try to find an size match aspect ratio and size Log.d(TAG, "Surface size w:" + w + "h:" + h); for (Size size : sizes) { Log.d(TAG, "Optimal charenge w:" + size.width + "h:" + size.height); double ratio = (double) size.width / size.height; if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue; //if (size.width > w || size.height > h) continue; //if (Math.abs(size.height - targetHeight) < minDiff) { if (Math.abs(ratio - targetRatio) < minDiff){ optimalSize = size; minDiff = Math.abs(ratio - targetRatio); } } // Cannot find the one match the aspect ratio, ignore the requirement if (optimalSize == null) { minDiff = Double.MAX_VALUE; for (Size size : sizes) { //if (size.width > w || size.height > h) continue; if (Math.abs(size.height - targetHeight) < minDiff) { optimalSize = size; minDiff = Math.abs(size.height - targetHeight); } } } Log.d(TAG, "OptimalSize w:" + optimalSize.width + "h:" + optimalSize.height); return optimalSize; } public void surfaceDestroyed(SurfaceHolder holder) { // カメラのプレビュー停止 orientationListener.disable(); mCamera.setPreviewCallback(null); mCamera.stopPreview(); mCamera.release(); mCamera = null; } @Override public boolean onTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { if (mCamera != null) { mCamera.autoFocus(mAutoFocusListener); } } return true; } private Camera.AutoFocusCallback mAutoFocusListener = new Camera.AutoFocusCallback() { public void onAutoFocus(boolean success, Camera camera) { camera.autoFocus(null); try { } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } camera.takePicture(null, null, mPictureListener); mInProgress = true; } }; private Camera.PictureCallback mPictureListener = new Camera.PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera camera) { Log.i(TAG, "Picture taken"); if (data != null) { Log.i(TAG, "JPEG Picture Taken"); FileOutputStream fo; try { fo = new FileOutputStream("/sdcard/test.jpg"); fo.write(data); fo.close(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } BitmapFactory.Options options = new BitmapFactory.Options(); // options.inSampleSize = IN_SAMPLE_SIZE; options.inPreferredConfig = Bitmap.Config.RGB_565; Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options); // TODO: Got the picture, add your own code. } } }; private boolean mInProgress = false; }; }
0 件のコメント:
コメントを投稿