Android 绘制相机预览
Posted
技术标签:
【中文标题】Android 绘制相机预览【英文标题】:Android draw on camera preview 【发布时间】:2012-08-28 11:11:51 【问题描述】:我正在制作一个虚拟现实应用程序,在该应用程序中,相机应该检测人脸、定位他们并在相机预览中显示他们的位置。
我知道 3 种方法可以做到这一点,我想尽可能快地使用 GLSurfaceView (according to this post),但目前我正在尝试使用 same SurfaceView相机用于预览的位置。我的回调是onFaceDetection
,就像这样:
public class MyActivity extends Activity implements SurfaceHolder.Callback, Camera.FaceDetectionListener
Camera camera;
SurfaceView svPreview;
SurfaceHolder previewHolder;
TextView tvInfo;
Paint red;
@Override
public void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
svPreview = (SurfaceView) findViewById(R.id.svPreview);
tvInfo = (TextView) findViewById(R.id.tvInfo);
red = new Paint();
red.setStyle(Paint.Style.STROKE);
red.setStrokeWidth(3);
previewHolder = svPreview.getHolder();
previewHolder.addCallback(this);
previewHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
public void surfaceCreated(SurfaceHolder arg0)
camera = Camera.open();
try
camera.setDisplayOrientation(90);
camera.setFaceDetectionListener(this);
camera.setPreviewDisplay(previewHolder);
catch (IOException e)
e.printStackTrace();
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
// . . .
camera.startPreview();
camera.autoFocus(null);
camera.startFaceDetection();
public void surfaceDestroyed(SurfaceHolder arg0)
camera.stopFaceDetection();
camera.cancelAutoFocus();
camera.stopPreview();
camera.release();
camera = null;
public void onFaceDetection(Face[] faces, Camera camera)
tvInfo.setText("Faces: " + String.valueOf(faces.length));
Canvas canvas = previewHolder.lockCanvas();
for(int i=0; i < faces.length; i++)
Point leftEye = faces[i].leftEye;
Point rightEye = faces[i].rightEye;
// this is not working
canvas.drawPoint(leftEye.x, leftEye.y, red);
previewHolder.unlockCanvasAndPost(canvas);
使用此代码,我不断收到此错误:
09-03 19:35:42.743: E/SurfaceHolder(19394): Exception locking surface
09-03 19:35:42.743: E/SurfaceHolder(19394): java.lang.IllegalArgumentException
09-03 19:35:42.743: E/SurfaceHolder(19394): at android.view.Surface.lockCanvasNative(Native Method)
09-03 19:35:42.743: E/SurfaceHolder(19394): at android.view.Surface.lockCanvas(Surface.java:76)
09-03 19:35:42.743: E/SurfaceHolder(19394): at android.view.SurfaceView$4.internalLockCanvas(SurfaceView.java:744)
09-03 19:35:42.743: E/SurfaceHolder(19394): at android.view.SurfaceView$4.lockCanvas(SurfaceView.java:720)
09-03 19:35:42.743: E/SurfaceHolder(19394): at com.bluetooth.activities.MyActivity.onFaceDetection(MyActivity.java:90)
09-03 19:35:42.743: E/SurfaceHolder(19394): at android.hardware.Camera$EventHandler.handleMessage(Camera.java:729)
09-03 19:35:42.743: E/SurfaceHolder(19394): at android.os.Handler.dispatchMessage(Handler.java:99)
09-03 19:35:42.743: E/SurfaceHolder(19394): at android.os.Looper.loop(Looper.java:137)
09-03 19:35:42.743: E/SurfaceHolder(19394): at android.app.ActivityThread.main(ActivityThread.java:4424)
09-03 19:35:42.743: E/SurfaceHolder(19394): at java.lang.reflect.Method.invokeNative(Native Method)
09-03 19:35:42.743: E/SurfaceHolder(19394): at java.lang.reflect.Method.invoke(Method.java:511)
09-03 19:35:42.743: E/SurfaceHolder(19394): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
09-03 19:35:42.743: E/SurfaceHolder(19394): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
09-03 19:35:42.743: E/SurfaceHolder(19394): at dalvik.system.NativeStart.main(Native Method)
09-03 19:35:42.743: W/dalvikvm(19394): threadid=1: thread exiting with uncaught exception (group=0x40a561f8)
09-03 19:35:42.766: E/AndroidRuntime(19394): FATAL EXCEPTION: main
09-03 19:35:42.766: E/AndroidRuntime(19394): java.lang.IllegalArgumentException
09-03 19:35:42.766: E/AndroidRuntime(19394): at android.view.Surface.unlockCanvasAndPost(Native Method)
09-03 19:35:42.766: E/AndroidRuntime(19394): at android.view.SurfaceView$4.unlockCanvasAndPost(SurfaceView.java:775)
09-03 19:35:42.766: E/AndroidRuntime(19394): at com.bluetooth.activities.MyActivity.onFaceDetection(MyActivity.java:99)
09-03 19:35:42.766: E/AndroidRuntime(19394): at android.hardware.Camera$EventHandler.handleMessage(Camera.java:729)
09-03 19:35:42.766: E/AndroidRuntime(19394): at android.os.Handler.dispatchMessage(Handler.java:99)
09-03 19:35:42.766: E/AndroidRuntime(19394): at android.os.Looper.loop(Looper.java:137)
09-03 19:35:42.766: E/AndroidRuntime(19394): at android.app.ActivityThread.main(ActivityThread.java:4424)
09-03 19:35:42.766: E/AndroidRuntime(19394): at java.lang.reflect.Method.invokeNative(Native Method)
09-03 19:35:42.766: E/AndroidRuntime(19394): at java.lang.reflect.Method.invoke(Method.java:511)
09-03 19:35:42.766: E/AndroidRuntime(19394): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
09-03 19:35:42.766: E/AndroidRuntime(19394): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
09-03 19:35:42.766: E/AndroidRuntime(19394): at dalvik.system.NativeStart.main(Native Method)
相机尝试在同一个SurfaceView上绘制预览的人脸检测回调有问题吗?在不分层 SurfaceView 的情况下如何做到这一点?
【问题讨论】:
我为此添加了赏金,因为通过挖掘 Android 源代码,预览看起来运行在与应用程序的其余部分不同的线程上,我认为这意味着您的应用程序无法获得Canvas 对象,或者锁定 Surface,因为它们是不同的线程。也就是说,我希望处理过这个问题的人会回应 在所有这些具有透明背景的视图之上使用 FrameLayout 怎么样! 【参考方案1】:您无法锁定和绘制具有Type.PUSH_BUFFERS
的SurfaceView
(您要向其显示帧的那个)。您必须在 Z 方向上在原始视图上方创建另一个视图,并在该 View
中绘制 SurfaceView
。
因此,在您的 main.xml
中,在 FrameLayout
中的原始视图下方创建一个自定义视图。
在您的活动视图中创建和处理SurfaceView
。将此视图添加到相机预览显示。通过SurfaceHolder
启动您的自定义视图。在此视图中,您可以锁定并在画布上绘图。
【讨论】:
【参考方案2】:正如 James 指出的,您需要创建扩展 SurfaceView 的自定义表面(我通常也实现 SurfaceHolder.Callback):
public class CameraSurfacePreview extends SurfaceView implements SurfaceHolder.Callback
构造函数类似于:
public CameraSurfacePreview(Context context)
super(context);
...
mHolder = getHolder();
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
...
您需要在相机打开调用后将相机与您的表面绑定(如果您实现实现 SurfaceHolder.Callback 将其放在覆盖的表面创建中的某个位置):
mCamera = Camera.open();
mCamera.setPreviewDisplay(mHolder);
最后,您需要在活动内容视图中的某处添加自定义表面的实例:
CameraSurfacePreview cameraSurfacePreview = new CameraSurfacePreview(this);
//camera surface preview is first child!
((ViewGroup)findViewById(R.id.cameraLayout)).addView(cameraSurfacePreview, 0);
在我的活动布局示例中(我在主框架布局中显示相机预览):
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_
android:layout_
android:layout_gravity="top|left"
android:id="@+id/cameraLayout">
【讨论】:
【参考方案3】:我可以说,
这与线程无关。
也不是因为这一行出错。
canvas.drawPoint(leftEye.x, leftEye.y, red);
因为画布还在使用,无法锁定
如果你仔细检查,你会看到你得到的这个画布是 == null
canvas = canvasHolder.lockCanvas();
if (canvas == null) Log.i("Error", "Canvas == null!");
您可能会有疑问,那么它已经在哪里使用了?
它已经用于 display 来显示你正在发生的事情!那是
camera.setPreviewDisplay(previewHolder);
所以,我建议,如果你想在你的眼睛上画点,你可能需要在你的 SurfaceView 上有另一个 SurfaceView / SurfaceHolder 用于预览相机:]
【讨论】:
【参考方案4】:lockcanvas/unlockcanvasandpost 方法在使用 openGL 时不合适,因为 openGL 代码正在控制和锁定表面。如果您想使用标准的 2d API,请不要使用 OpenGL。
【讨论】:
以上是关于Android 绘制相机预览的主要内容,如果未能解决你的问题,请参考以下文章