Android:如何在已显示相机预览的SurfaceView上绘制?

3
我正在编写一个应用程序,需要在手机屏幕上显示前置摄像头拍摄的内容[该应用程序不会将任何内容保存到手机内存中]。另外,如果拍摄到人脸(并且检测到),则必须出现一个矩形框围绕着它。
为此,我使用了:
1. Surfaceview来显示前置摄像头拍摄的内容。 2. FaceDetectionListener来检测相机输入中的人脸。
到目前为止,该应用程序可以正确地显示前置摄像头拍摄的内容。它还可以正确地检测人脸。但是,我无法在检测到的人脸周围绘制边界矩形。
以下是一些片段,展示了我如何解决这个任务。
活动:
@SuppressLint("InflateParams")
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)

public class FaceDetectorTutorial extends Activity {

   MySurface mMySurface;
   private SurfaceView surfaceView;

   public void onCreate(Bundle savedInstanceState) {

      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_face_detector_tutorial);
      setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

      surfaceView = (SurfaceView)findViewById(R.id.camPreview);

      mMySurface = new MySurface(this, surfaceView);

  }     

}    

MySurface 类:

class MySurface extends SurfaceView implements SurfaceHolder.Callback {  

  Paint paint = new Paint();

  public Camera camera;

  String Tag = "Log: ";


  private SurfaceHolder surfaceHolder;
  boolean preview = false;

 ////////// CLASS CONSTRUCTOR   //////////
 MySurface(Context context, SurfaceView msurfaceView) {
      super(context);

      paint.setColor(Color.RED);
      paint.setStrokeWidth(3);

      surfaceHolder = msurfaceView.getHolder();
      surfaceHolder.addCallback(this);  
      surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

 }


 /////////  FACE DETECTION LISTENER /////////
 @SuppressLint("NewApi")
   FaceDetectionListener faceDetectionListener = new FaceDetectionListener(){

        @Override
        public void onFaceDetection(Face[] faces, Camera camera) {

            if (faces.length > 0){

                //Just for the first one detected
                Rect Boundary = faces[0].rect;

                System.out.println(Boundary);

                tryDrawing(Boundary);

            }


 }};//End of FaceDetectionListener

 ////////// SURFACE METHODS ////////
 @Override
 public void surfaceChanged(SurfaceHolder holder, int format, int width,int height) {
         // TODO Auto-generated method stub
        if(preview){
               camera.stopFaceDetection();
               camera.stopPreview();
               preview = false;
         }

       if (camera != null){
               try {                
               camera.setPreviewDisplay(surfaceHolder);
               camera.startPreview();            
               camera.startFaceDetection();
               preview = true;
               } catch (IOException e) {
                   // TODO Auto-generated catch block
                  e.printStackTrace();
               }
           }

  }

  @Override
  public void surfaceCreated(SurfaceHolder holder) {
           // TODO Auto-generated method stub

        int cameraId = -1;      
        int numberOfCameras = camera.getNumberOfCameras();

        for (int i = 0; i < numberOfCameras; i++) {
            CameraInfo info = new CameraInfo();
            Camera.getCameraInfo(i, info);

            if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
                    cameraId = i;
                    break;
            }
        }

        camera = Camera.open(cameraId);
        camera.setFaceDetectionListener(faceDetectionListener);

  }

  @Override
  public void surfaceDestroyed(SurfaceHolder holder) {
           // TODO Auto-generated method stub
           camera.stopFaceDetection();
           camera.stopPreview();
           camera.release();
           camera = null;
           preview = false;
 }

 private void tryDrawing(Rect Boundary) {
      Log.i(Tag, "Trying to draw...");

      Canvas canvas = surfaceHolder.lockCanvas();

      if (canvas == null) {

          Log.e(Tag, "Cannot draw onto the canvas as it's null");

      } else {

          drawMyStuff(canvas,Boundary);
          surfaceHolder.unlockCanvasAndPost(canvas);
      }
  }

  private void drawMyStuff(final Canvas canvas, Rect Boundary) {

      canvas.drawRect(Boundary.left, Boundary.top, Boundary.right, Boundary.bottom, paint);

      Log.i(Tag, "Drawing...");

  }

}  

布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <SurfaceView
        android:id="@+id/camPreview"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

从logcat中我了解到错误是锁定表面。但我不明白为什么会出现这种情况。

11-23 17:13:51.791: I/System.out(12515): Rect(-187, -495 - 328, 196)
11-23 17:13:51.791: I/Log:(12515): Trying to draw...
11-23 17:13:51.791: E/SurfaceHolder(12515): Exception locking surface
11-23 17:13:51.791: E/SurfaceHolder(12515): java.lang.IllegalArgumentException
11-23 17:13:51.791: E/SurfaceHolder(12515):     at android.view.Surface.nativeLockCanvas(Native Method)
11-23 17:13:51.791: E/SurfaceHolder(12515):     at android.view.Surface.lockCanvas(Surface.java:452)
11-23 17:13:51.791: E/SurfaceHolder(12515):     at android.view.SurfaceView$4.internalLockCanvas(SurfaceView.java:781)
11-23 17:13:51.791: E/SurfaceHolder(12515):     at android.view.SurfaceView$4.lockCanvas(SurfaceView.java:757)
11-23 17:13:51.791: E/SurfaceHolder(12515):     at com.example.facedetectiontutorial.MySurface.tryDrawing(FaceDetectorTutorial.java:175)
11-23 17:13:51.791: E/SurfaceHolder(12515):     at com.example.facedetectiontutorial.MySurface.access$0(FaceDetectorTutorial.java:172)
11-23 17:13:51.791: E/SurfaceHolder(12515):     at com.example.facedetectiontutorial.MySurface$1.onFaceDetection(FaceDetectorTutorial.java:109)
11-23 17:13:51.791: E/SurfaceHolder(12515):     at android.hardware.Camera$EventHandler.handleMessage(Camera.java:815)
11-23 17:13:51.791: E/SurfaceHolder(12515):     at android.os.Handler.dispatchMessage(Handler.java:99)
11-23 17:13:51.791: E/SurfaceHolder(12515):     at android.os.Looper.loop(Looper.java:137)
11-23 17:13:51.791: E/SurfaceHolder(12515):     at android.app.ActivityThread.main(ActivityThread.java:5041)
11-23 17:13:51.791: E/SurfaceHolder(12515):     at java.lang.reflect.Method.invokeNative(Native Method)
11-23 17:13:51.791: E/SurfaceHolder(12515):     at java.lang.reflect.Method.invoke(Method.java:511)
11-23 17:13:51.791: E/SurfaceHolder(12515):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
11-23 17:13:51.791: E/SurfaceHolder(12515):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
11-23 17:13:51.791: E/SurfaceHolder(12515):     at dalvik.system.NativeStart.main(Native Method)
11-23 17:13:51.893: E/Log:(12515): Cannot draw onto the canvas as it's null

我基于以下问题的被接受的答案来解决我的问题:

Android绘制surfaceview和画布

但是似乎我应用错误了。有人可以告诉我应该改变什么吗?

2个回答

4
你做不到。Surface 是生产者-消费者对中的生产者端,每次只能有一个生产者。你可以提供相机帧,在 Canvas 中进行软件渲染或使用 OpenGL 进行硬件渲染,但不能在单个 Surface 上混合使用它们。
你有几个选择。其中一种方法是使用第二个 SurfaceView,它与第一个重叠(通过 FrameLayout)。为了使其工作,必须为第二个 Surface 指定 Z 轴顺序--如果不这样做,系统将看到你有两个 Surface 尝试占用同一空间,只有一个会获胜。使用 setZOrderMediaOverlay() 方法将新 Surface 放置在相机预览前面但 View UI 后面。你还需要将 Surface 的颜色格式从默认值 RGB565 更改为支持透明度的格式;setColorFormat(TRANSLUCENT)RGB8888 都可以。确保使用适当的颜色转换模式将 Surface 擦除为透明黑色。
另一种方法是使用自定义视图custom View。这更有效率,因为它不涉及创建单独的Surface(您正在绘制View UI层),并且Canvas渲染可以进行硬件加速。最方便的View是SurfaceView的一部分 - 不需要更改布局。如果您的代码是子类化SurfaceView,则已经完成了一半。
有关多个重叠SurfaceView的示例,请参见Grafika's“multi-surface test” Activity。如果您想了解更多关于Android图形系统的信息,请参见architecture doc

感谢提供的信息。一旦我能够编写出解决方案,我将更新帖子。 - Nacho_AlLarre
“最方便使用的视图是SurfaceView中的一部分”,这句话是什么意思?你能进一步解释一下吗?@fadden - Charlesjean
1
@Charlesjean:SurfaceView 由 Surface 和 View 两部分组成,它们可以独立地进行渲染。SurfaceView 的 View 部分通常是完全透明的。 - fadden

1
如果您使用SurfaceView来显示相机预览,则无法绘制到它上面。如您所见,“lockCanvas”将失败。
我建议您使用两个单独的视图。一个SurfaceView用于预览相机,另一个视图(可以是SurfaceView或自定义视图)在预览之上绘制。您可以将这两个视图都放在FrameLayout或RelativeLayout中。

尽管这是推荐的方法,但似乎无法保证SurfaceView在实际手机上正确渲染:https://dev59.com/U6vka4cB1Zd3GeqPwaRi - glenatron

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接