Bootstrap

Android Camera2获取图像的最简编程

通过Android官方的Camera2Basic工程源码,总结出的Android摄像头获取图像的最简编程实现:

步骤1: 申请权限
因摄像头是一个隐私性非常强的设备,所以需要弹框向用户申请权限。
在工程配置文件AndroidManifest.xml中标明使用权限:

    <uses-permission android:name="android.permission.CAMERA"/>

在代码中弹框申请权限:

        String [] permissions = {
                Manifest.permission.CAMERA,
        };
        ActivityCompat.requestPermissions(this, permissions, 0);

步骤2: 准备子线程
因摄像头相关的操作是非常消耗资源的耗时工作,因此最好在一个子线程里执行。

        HbgThread = new HandlerThread("CameraThread");//创建HandlerThread对象,指定线程名
        bgThread.start();//子线程启动,注意它的run方法是没有指定,需要通过Handler把run方法代码交过来执行
        bgHandler = new Handler(bgThread.getLooper());//创建用于与bgThread通信的Handler对象,通过它的post方法把任务发到子线程执行
        // bgHandler.post(new Runnable() {  ... } );
        //摄像头操作相关的方法函数只需要提供bgHandler对象就会自动post相关的操作代码在bgThread里执行

步骤3. 打开摄像头
创建Android摄像头打开操作的回调方法对象,用于指定摄像头成功打开后、关闭后和打开失败后作什么操作。

    private CameraDevice.StateCallback cameraCallback = new CameraDevice.StateCallback() {
        @Override //摄像头成功打开后的操作
        public void onOpened(@NonNull CameraDevice cameraDevice) {
            Log.e("myinfo", "摄像头成功打开");
        }

        @Override //摄像头关闭后的操作
        public void onDisconnected(@NonNull CameraDevice cameraDevice) {

        }

        @Override //摄像头出错后的操作
        public void onError(@NonNull CameraDevice cameraDevice, int i) {

        }
    };

获取当前Android系统的摄像头管理器,并打开指定的摄像头。

        //获取当前系统的摄像头管理器
        CameraManager cameraManager = (CameraManager) getSystemService(CAMERA_SERVICE);
        String [] ids;
        //获取当前系统里所有摄像头设备的ID
        ids = cameraManager.getCameraIdList();
        //输出所有摄像头设备的ID, 一般"0"表示前摄像头, "1"表示后摄像头
        for (int i = 0; i < ids.length; i++)
            Log.e("myinfo", "id = " + ids[i]);

        //打开摄像头,指定摄像头打开操作的回调方法对象,并传入可下发任务到子线程执行的Handler对象
        cameraManager.openCamera(ids[0], cameraCallback, bgHandler);

步骤4. 创建摄像头设备的捕捉会话对象
摄像头设备的捕捉会话对象用于向摄像头申请捕捉图像、打开闪光灯等操作的命令通道。
首先创建摄像头设备会话对象的回调方法对象,用于指定会话通道配置成功或失败后作什么操作。

    private CameraCaptureSession.StateCallback captureCallback = new CameraCaptureSession.StateCallback() {
        @Override //会话对象创建配置成功后的操作
        public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
            Log.e("myinfo", "摄像头会话创建成功!");
        }

        @Override //会话对象创建配置失败后的操作
        public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
        }
    };

在摄像头成功打开后触发的回调方法onOpened中创建一个接收图像数据的ImageReader对象,
并创建一个摄像头会话对象,指定使用会话的回调方法对象,并传入可下发任务到子线程执行的Handler对象:

    private CameraDevice.StateCallback cameraCallback = new CameraDevice.StateCallback() {
        @Override //摄像头成功打开后的操作
        public void onOpened(@NonNull CameraDevice cameraDevice) {
            Log.e("myinfo", "摄像头成功打开");
            //创建接收摄像头图像的ImageReader对象,指定图像分辨率、编码格式、图像队列中最多存多少张图像
            imageReader = ImageReader.newInstance(800, 600, ImageFormat.JPEG, 3);
            camDevice = cameraDevice;//记录已成功打开的摄像头设备对象
            //创建会话对象
            camDevice.createCaptureSession(Arrays.asList(imageReader.getSurface()), captureCallback
                  , bgHandler);
        }
        ...
 }       

步骤5. 发出捕捉图像请求
为ImageReader对象创建一个监听器对象,指定当接收到摄像头图像后作什么操作。

    private ImageReader.OnImageAvailableListener imgListener = new ImageReader.OnImageAvailableListener() {
        @Override
        public void onImageAvailable(ImageReader imageReader) { //由bgThread子线程执行的代码
        
        }
    };

在会话对象创建并配置成功后触发的onConfigured方法中发出捕捉图像请求, 并设置ImageReader对象使用imgListener监听器对象。

    private CameraCaptureSession.StateCallback captureCallback = new CameraCaptureSession.StateCallback() {
        @Override //会话对象创建配置成功后的操作
        public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
            Log.e("myinfo", "摄像头会话创建成功!");
             //创建捕捉请求对象,指定请求摄像的预览图像
             CaptureRequest.Builder builder = camDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
             builder.addTarget(imageReader.getSurface());//指定捕捉到图像数据由imageReader对象处理
             //当ImageReader对象接收到图像后触发OnImageAvailableListener方法,并由bgThread子线程执行方法里的代码
             imageReader.setOnImageAvailableListener(imgListener, bgHandler);
             //通过会话对象提交请求,并且设置是循环的请求
             cameraCaptureSession.setRepeatingRequest(builder.build(), null, bgHandler);
        }
        ...
 }

步骤6. 处理捕捉到的摄像头图像
当ImageReader对象接收到图像后触发OnImageAvailableListener方法,在此方法处理图像数据,如进行网络传输、存储、显示等操作。

    private ImageReader.OnImageAvailableListener imgListener = new ImageReader.OnImageAvailableListener() {
        @Override
        public void onImageAvailable(ImageReader imageReader) { //由bgThread子线程执行的代码
            Image img = imageReader.acquireNextImage();//获取出图像
            ByteBuffer buffer = img.getPlanes()[0].getBuffer();//获取图像数据
            byte [] bytes = new byte[buffer.remaining()];
            buffer.get(bytes);//图像数据存入bytes数组里
            img.close();//关闭图像

            //把bytes数组图像数据转成Bitmap图像,并在ImageView组件上显示
            Bitmap map = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
            //让主线程设置显示图像
            imageView.post(new Runnable() {
                @Override
                public void run() {
                    imageView.setImageBitmap(map);
                }
            });
        }
    };

获取摄像头图像并在ImageView组件上显示的完整工程代码:

public class MainActivity extends AppCompatActivity {
    private ImageView imageView;
    private Button btnStart;
    private HandlerThread bgThread;//可通过Handler下发任务的子线程
    private Handler bgHandler;//用于与bgThread通信的Handler对象
    private ImageReader imageReader;//用于接收图像的对象名
    private CameraDevice camDevice; //记录已打开的摄像头设备对象
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        imageView = findViewById(R.id.imageView);
        btnStart = findViewById(R.id.btnStart);
        btnStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                initializeCamera(); //摄像头初始化
            }
        });
        requestPermissions();//弹框请求权限
    }
    
    private ImageReader.OnImageAvailableListener imgListener = new ImageReader.OnImageAvailableListener() {
        @Override
        public void onImageAvailable(ImageReader imageReader) { //由bgThread子线程执行的代码
            Image img = imageReader.acquireNextImage();//获取出图像
            ByteBuffer buffer = img.getPlanes()[0].getBuffer();//获取图像数据
            byte [] bytes = new byte[buffer.remaining()];
            buffer.get(bytes);//图像数据存入bytes数组里
            img.close();//关闭图像

            //把bytes数组图像数据转成Bitmap图像
            Bitmap map = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
            //让主线程设置显示图像
            imageView.post(new Runnable() {
                @Override
                public void run() {
                    imageView.setImageBitmap(map);
                }
            });
        }
    };

    private CameraCaptureSession.StateCallback captureCallback = new CameraCaptureSession.StateCallback() {
        @Override //会话对象创建配置成功后的操作
        public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
            Log.e("myinfo", "摄像头会话创建成功!");
            try {
                //3.创建捕捉请求对象,指定请求摄像的预览图像
                CaptureRequest.Builder builder = camDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
                builder.addTarget(imageReader.getSurface());//指定捕捉到图像数据由imageReader对象处理
                //当ImageReader对象接收到图像后触发OnImageAvailableListener方法,并由bgThread子线程执行方法里的代码
                imageReader.setOnImageAvailableListener(imgListener, bgHandler);
                //通过会话对象提交请求,并且设置是循环的请求
                cameraCaptureSession.setRepeatingRequest(builder.build(), null, bgHandler);
            }catch (Exception e) {
                e.printStackTrace();
            }
        }

        @Override //会话对象创建配置失败后的操作
        public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {

        }
    };

    private CameraDevice.StateCallback cameraCallback = new CameraDevice.StateCallback() {
        @Override //摄像头成功打开后的操作
        public void onOpened(@NonNull CameraDevice cameraDevice) {
            Log.e("myinfo", "摄像头成功打开");
            //创建接收摄像头图像的ImageReader对象,指定图像分辨率、编码格式、图像队列中最多存多少张图像
            imageReader = ImageReader.newInstance(800, 600, ImageFormat.JPEG, 3);
            camDevice = cameraDevice;//记录已成功打开的摄像头设备对象
           try {
               //2.创建会话对象
               camDevice.createCaptureSession(Arrays.asList(imageReader.getSurface()), captureCallback
                       , bgHandler);
           }catch (Exception e) {
               e.printStackTrace();
           }
        }

        @Override //摄像头关闭后的操作
        public void onDisconnected(@NonNull CameraDevice cameraDevice) {

        }

        @Override //摄像头出错后的操作
        public void onError(@NonNull CameraDevice cameraDevice, int i) {

        }
    };

    @SuppressLint("MissingPermission")
    public void initializeCamera() {  //初始化摄像头
        bgThread = new HandlerThread("CameraThread");//创建HandlerThread对象,指定线程名
        bgThread.start();//子线程启动,注意它的run方法是没有指定,需要通过Handler把run方法代码交过来执行
        bgHandler = new Handler(bgThread.getLooper());//创建用于与bgThread通信的Handler对象,通过它的post方法把任务发到子线程执行
        // bgHandler.post(new Runnable() {  ... } );
        //摄像头操作相关的方法函数只需要提供bgHandler对象就会自动post相关的操作代码在bgThread里执行

        //获取当前系统的摄像头管理器
        CameraManager cameraManager = (CameraManager) getSystemService(CAMERA_SERVICE);
        String [] ids;
        try {
            //获取当前系统里所有摄像头设备的ID
            ids = cameraManager.getCameraIdList();
            //输出所有摄像头设备的ID, 一般"0"表示前摄像头, "1"表示后摄像头
            for (int i = 0; i < ids.length; i++)
                Log.e("myinfo", "id = " + ids[i]);

            //1.打开摄像头
            cameraManager.openCamera(ids[0], cameraCallback, bgHandler);
        } catch (Exception e) {
           e.printStackTrace();
        }
    }

    public void requestPermissions() {
        String [] permissions = {
                Manifest.permission.CAMERA,
        };
        ActivityCompat.requestPermissions(this, permissions, 0);
    }
}
;