说明:权限等代码已经包含在页面中,注意别忘了申请权限
预览效果-->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;
}
}
}