Android:如何在已经显示 Camara 预览的 Surfaceview 上绘图

Posted

技术标签:

【中文标题】Android:如何在已经显示 Camara 预览的 Surfaceview 上绘图【英文标题】:Android: How to draw on a Surfaceview which already is displaying a Camara Preview 【发布时间】:2016-02-25 20:54:41 【问题描述】:

我正在尝试编写一个应用程序,该应用程序必须在手机屏幕上显示前置摄像头正在拍摄的内容[该应用程序未在手机内存中记录/保存任何内容]。此外,在拍摄(并检测到)脸部的情况下,它必须出现一个矩形。

为此,我正在使用:

    Surfaceview,用于显示前置摄像头正在拍摄的内容。 FaceDetectionListener 用于检测相机输入中的人脸。

到目前为止,应用程序可以正确显示前置摄像头正在拍摄的内容。它还可以正确检测人脸。但我无法在检测到的人脸周围绘制边界矩形。

这里有一些 sn-ps 来展示我是如何尝试解决这个任务的。

活动:

@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_
    android:layout_
    android:orientation="vertical" >

    <SurfaceView
        android:id="@+id/camPreview"
        android:layout_
        android:layout_ />

</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 drawing on surfaceview and canvas

但我似乎用错了。谁能告诉我应该改变什么?

【问题讨论】:

【参考方案1】:

你不能。 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“多表面测试”Activity。如果您想了解更多关于Android的图形系统,请参阅architecture doc。

【讨论】:

感谢您的信息。一旦我能够对其进行编程,我将使用解决方案更新帖子。 “最方便使用的视图是 SurfaceView 的一部分”是什么意思?你能进一步解释一下吗? @fadden @Charlesjean:SurfaceViews 有两个部分,一个 Surface 和一个 View。它们可以独立渲染。 SurfaceView 的 View 部分通常是完全透明的。【参考方案2】:

如果您使用 SurfaceView 来显示相机预览,则无法对其进行绘制。如您所见,lockCanvas 将失败。

我建议您使用两个单独的视图。一个用于预览相机的 SurfaceView,以及一个在预览顶部绘制的第二个 View(SurfaceView 或只是一个自定义 View)。您可以在 FrameLayoutRelativeLayout 中同时拥有这两个视图。

【讨论】:

虽然这是推荐的方法,但似乎无法保证 SurfaceView 将在实际手机上正确呈现:***.com/questions/50511373/…

以上是关于Android:如何在已经显示 Camara 预览的 Surfaceview 上绘图的主要内容,如果未能解决你的问题,请参考以下文章

Android ViewPager - 在左右显示页面预览

opencv for android 如何实现后台启动摄像头,不显示预览界面

使用Xamarin Forms+XLabs 完成 Camara access

如何在 Android Studio 2019 中捕获图片而不进行预览

Android:如何通过回调显示相机预览?

与设备相比,我在 android 中的布局预览显示不同