Camera2 api 获取摄像头预览画面并设置取景框

说明:权限等代码已经包含在页面中,注意别忘了申请权限

预览效果-->apk文件在资源绑定中

一、下载依赖

/*项目主要依赖*/
    //camera2
    implementation 'androidx.camera:camera-camera2:1.2.0'
    implementation 'androidx.camera:camera-lifecycle:1.2.0'
    //ML-kit文字识别
    implementation 'com.google.mlkit:text-recognition:16.0.0'

二、页面布局

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextureView
        android:id="@+id/texture_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


    <FrameLayout
        android:id="@+id/frame_layout"
        android:layout_width="280dp"
        android:layout_height="80dp"
        android:background="@drawable/rectangle_frame"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.497"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.213"></FrameLayout>

    <ImageView
        android:id="@+id/captured_image"
        android:layout_width="280dp"
        android:layout_height="80dp"
        android:elevation="10dp"
        android:scaleType="fitXY"
        android:src="@drawable/four"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.496"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:visibility="gone"
        app:layout_constraintVertical_bias="0.499" />

    <TextView
        android:id="@+id/tv_result"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="TTT666666"
        android:textColor="@color/colorRed"
        android:textSize="30sp"
        android:textStyle="bold"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.665" />

    <Button
        android:id="@+id/start_stop_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="开始识别"
        android:onClick="restartIdentify"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

三、页面代码

package com.example.tirenumberrecognition;

import android.Manifest;
import android.annotation.SuppressLint;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import android.os.Looper;
import android.widget.VideoView;

import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.mlkit.vision.common.InputImage;
import com.google.mlkit.vision.text.Text;
import com.google.mlkit.vision.text.TextRecognition;
import com.google.mlkit.vision.text.TextRecognizer;
import com.google.mlkit.vision.text.latin.TextRecognizerOptions;

public class MainActivity extends AppCompatActivity {
    private final static int PERMISSION_REQUEST_CODE = 666666;//权限请求code--请求标识
    /*权限请求字符集*/
    private String[] permissions = {
            android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
            android.Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.CAMERA
    };
    private TextureView textureView;
    private FrameLayout frameLayout;
    private ImageView mImageView;
    private TextView mTextView;
    private Handler handler = new Handler(Looper.getMainLooper());
    private Runnable captureRunnable = new Runnable() {
        @Override
        public void run() {
            if(identifyFlag) {
                captureBitmapFromTextureView();
            }
            handler.postDelayed(this, 1500); // 每隔1秒重新执行
        }
    };

    private CameraDevice cameraDevice;
    private CameraCaptureSession cameraCaptureSessions;
    private CaptureRequest.Builder captureRequestBuilder;
    private Size imageDimension;
    private Size previewSize; // 新增的尺寸变量
    private ImageReader imageReader;
    private static final String CAMERA_ID = "0";
    private Handler mBackgroundHandler;
    private HandlerThread mBackgroundThread;

    @SuppressLint("MissingInflatedId")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
                && checkSelfPermission(permissions[0]) != PackageManager.PERMISSION_GRANTED
                && checkSelfPermission(permissions[1]) != PackageManager.PERMISSION_GRANTED
                && checkSelfPermission(permissions[2]) != PackageManager.PERMISSION_GRANTED)
        {
            requestPermissions(permissions, PERMISSION_REQUEST_CODE);
        }

        textureView = findViewById(R.id.texture_view);
        textureView.setSurfaceTextureListener(textureListener);
        mImageView = findViewById(R.id.captured_image);
        mTextView = findViewById(R.id.tv_result);
        frameLayout = findViewById(R.id.frame_layout);
    }

    //监听textureView的生命周期
    private final TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {
            openCamera();

        }
        @Override
        public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {
            configureTransform(width, height);
        }

        @Override
        public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
            return true;
        }

        @Override
        public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {
//            captureBitmapFromTextureView();
        }
    };
    private boolean identifyFlag = false;
    public void restartIdentify(View view) {
        handler.removeCallbacks(captureRunnable);
        handler.postDelayed(captureRunnable, 1500);
        identifyFlag = true;
    }
    private void captureBitmapFromTextureView() {
        // 获取取景框的位置
        Bitmap bitmap = textureView.getBitmap();
        int frameWidth = frameLayout.getWidth();
        int frameHeight = frameLayout.getHeight();
        float frameX = frameLayout.getX();
        float frameY = frameLayout.getY();

        frameX += 0; // 根据实际情况调整值
        frameY -= 20;
        // 裁剪Bitmap
        Bitmap croppedBitmap = Bitmap.createBitmap(
                bitmap,
                (int) frameX,
                (int) frameY,
                frameWidth,
                frameHeight
        );
        recognizeTextFromBitmap(croppedBitmap);

        // 显示Bitmap到ImageView
/*        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mImageView.setImageBitmap(croppedBitmap);
            }
        });*/

    }
    private void recognizeTextFromBitmap(Bitmap bitmap) {
        InputImage image = InputImage.fromBitmap(bitmap, 0);
        TextRecognizer myRecognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS);
        myRecognizer.process(image).addOnSuccessListener(new OnSuccessListener<Text>() {
            @Override
            public void onSuccess(com.google.mlkit.vision.text.Text text) {
                String text1 = text.getText();
                if(text1.length() > 7 && text1.length() < 14) {
                    identifyFlag = false;
                    handler.removeCallbacks(captureRunnable);
                    mTextView.setText(text1);
                    myRecognizer.close();
                    ShowDialog(text1);
                }
            }
        }).addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                // 处理错误
                e.printStackTrace();
                // 释放资源
                myRecognizer.close();
            }
        });
    }
    private void ShowDialog(String s) {
        AlertDialog alertDialog2 = new AlertDialog.Builder(this)
                .setTitle("识别结果")
                .setMessage(s)
                .setIcon(R.mipmap.ic_launcher)
                .setPositiveButton("确定", new DialogInterface.OnClickListener() {//添加"Yes"按钮
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        handler.removeCallbacks(captureRunnable);
                        dialogInterface.dismiss();
                    }
                })

                .setNegativeButton("重新识别", new DialogInterface.OnClickListener() {//添加取消
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        dialogInterface.dismiss();
                        handler.removeCallbacks(captureRunnable);
                        handler.postDelayed(captureRunnable, 1500);
                        identifyFlag = true;
                    }
                })
                .create();
        alertDialog2.show();
    }
    private CameraManager manager;
    private void openCamera() {
        manager = (CameraManager) getSystemService(CAMERA_SERVICE);
        try {
            CameraCharacteristics characteristics = manager.getCameraCharacteristics(CAMERA_ID);
            StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            if (map != null) {
                // 选择预览尺寸
                previewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), textureView.getWidth(), textureView.getHeight());

                // Add permission for camera and let user grant the permission
                if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                    ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CAMERA}, 100);
                    return;
                }
                manager.openCamera(CAMERA_ID, stateCallback, null);
            } else {
                Log.e("CameraActivity", "StreamConfigurationMap is null");
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    //此方法根据TextureView的宽度和高度选择一个合适的预览尺寸,以确保预览画面填充整个视图
    @SuppressLint("RestrictedApi")
    private Size chooseOptimalSize(Size[] choices, int textureViewWidth, int textureViewHeight) {
        List<Size> bigEnough = new ArrayList<>();
        int w = textureViewWidth;
        int h = textureViewHeight;
        if (choices != null) {
            for (Size option : choices) {
                if (option.getHeight() == option.getWidth() * h / w && option.getWidth() >= w && option.getHeight() >= h) {
                    bigEnough.add(option);
                }
            }
        }

        if (!bigEnough.isEmpty()) {
            return Collections.min(bigEnough, new CompareSizesByArea());
        } else {
            // If no suitable size was found, return the first one or handle it as needed.
            return choices != null && choices.length > 0 ? choices[0] : null;
        }
    }
    //当摄像头设备的状态发生变化时,例如打开成功或失败,这个回调会被调用
    CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            // This is called when the camera is opened
            cameraDevice = camera;
            createCameraPreview();
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {
            cameraDevice.close();
        }

        @Override
        public void onError(@NonNull CameraDevice camera, int error) {
            cameraDevice.close();
            cameraDevice = null;
        }
    };

    //这是一个比较器,用于根据尺寸的面积大小进行排序,以帮助选择最佳尺寸
    private class CompareSizesByArea implements Comparator<Size> {
        @Override
        public int compare(Size lhs, Size rhs) {
            // We cast here to ensure the multiplications won't overflow
            return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
                    (long) rhs.getWidth() * rhs.getHeight());
        }
    }

    //这个方法创建了一个CameraCaptureSession,并将预览画面配置为TextureView的输出
    private void createCameraPreview() {
        try {
            SurfaceTexture texture = textureView.getSurfaceTexture();
            if (texture != null && previewSize != null) {
                // We configure the size of default buffer to be the size of camera preview we want.
                texture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());

                // This is the output Surface we need to start preview.
                Surface surface = new Surface(texture);

                // We set up a CaptureRequest.Builder with the output Surface.
                captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
                captureRequestBuilder.addTarget(surface);
                //设置自动对焦
                captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                //获取并设置ISO
                /*
                CameraCharacteristics characteristics = manager.getCameraCharacteristics(CAMERA_ID);
                // 获取最小和最大 ISO 值
                int minIso = characteristics.get(CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE).getLower();
                int maxIso = characteristics.get(CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE).getUpper();
                Log.i("TAG", "createCameraPreview-maxIso: " + maxIso);
                * */

                /*
                // 设置ISO
                captureRequestBuilder.set(CaptureRequest.SENSOR_SENSITIVITY, 1600);
                * */

                // Now that we have set our Surface textures, we can open the camera preview and start taking pictures.
                cameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() {
                    @Override
                    public void onConfigured(@NonNull CameraCaptureSession session) {
                        // The camera is already closed
                        if (null == cameraDevice) {
                            return;
                        }

                        // When the session is ready, we start displaying the preview.
                        cameraCaptureSessions = session;
                        updatePreview();
                    }

                    @Override
                    public void onConfigureFailed(@NonNull CameraCaptureSession session) {
                        // Called when the camera could not be configured
                    }
                }, null);
            } else {
                Log.e("CameraActivity", "Texture or previewSize is null");
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
    //这个方法设置了一个重复的捕获请求,以保持预览画面的更新
    private void updatePreview() {
        if (null == cameraDevice) {
            Log.e("CameraActivity", "updatePreview error, return");
        }
//        captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.Control.CONTROL_MODE_AUTO_EXPOSURE);
        try {
            cameraCaptureSessions.setRepeatingRequest(captureRequestBuilder.build(), null, mBackgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    //此方法负责配置TextureView的变换矩阵,以确保预览画面正确显示
    private void configureTransform(int viewWidth, int viewHeight) {
        if (null == textureView || null == previewSize) {
            return;
        }
        int rotation = getWindowManager().getDefaultDisplay().getRotation();
        Matrix matrix = new Matrix();
        RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
        RectF bufferRect = new RectF(0, 0, previewSize.getHeight(), previewSize.getWidth());
        float centerX = viewRect.centerX();
        float centerY = viewRect.centerY();
        if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
            bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
            matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
            float scale = Math.max(
                    (float) viewHeight / previewSize.getHeight(),
                    (float) viewWidth / previewSize.getWidth());
            matrix.postScale(scale, scale, centerX, centerY);
            matrix.postRotate(90 * (rotation - 2), centerX, centerY);
        } else if (Surface.ROTATION_180 == rotation) {
            matrix.postRotate(180, centerX, centerY);
        }
        textureView.setTransform(matrix);
    }

    @Override
    protected void onResume() {
        super.onResume();
        startBackgroundThread();

        // When the activity is in foreground, start camera and view preview
        if (textureView != null) {
            textureView.setSurfaceTextureListener(textureListener);
        }
    }

    @Override
    protected void onPause() {
        stopBackgroundThread();

        // When the activity is in background, stop the preview
        if (null != textureView) {
            textureView.setSurfaceTextureListener(null);
        }
        super.onPause();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        handler.removeCallbacks(captureRunnable);
    }

    void startBackgroundThread() {
        mBackgroundThread = new HandlerThread("Camera Background");
        mBackgroundThread.start();
        mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
    }

    void stopBackgroundThread() {
        mBackgroundThread.quitSafely();
        try {
            mBackgroundThread.join();
            mBackgroundThread = null;
            mBackgroundHandler = null;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /*请求权限结果反馈*/
    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case PERMISSION_REQUEST_CODE:
                /*PackageManager.PERMISSION_GRANTED  权限被许可*/
                /*PackageManager.PERMISSION_DENIED  没有权限;拒绝访问*/
                if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(this, "无法写入内存", Toast.LENGTH_SHORT).show();
                } else if (grantResults[1] != PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(this, "无法读取内存", Toast.LENGTH_SHORT).show();
                } else if (grantResults[2] != PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(this, "无法使用相机", Toast.LENGTH_SHORT).show();
                }
                break;
            default:
                Toast.makeText(this, "权限获取失败", Toast.LENGTH_SHORT).show();
                break;
        }
    }
}

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值