今回は、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();
List supportedSizes = 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 件のコメント:
コメントを投稿