官方使用的是Kotlin代码,这里用Java。
导入依赖
在app的build.gradle中加入
// Use the most recent version of CameraX, currently that is alpha04
def camerax_version = "1.0.0-alpha05"
//noinspection GradleDependency
implementation "androidx.camera:camera-core:${camerax_version}"
//noinspection GradleDependency
implementation "androidx.camera:camera-camera2:${camerax_version}"
布局文件
布局一般是加一个TextureView来预览,因为用了不同的布局(ConstraintLayout),要到build.gradle中再添加implementation 'androidx.constraintlayout:constraintlayout:1.1.3'依赖。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center|center_horizontal|center_vertical"
android:orientation="vertical"
xmlns:app="http://schemas.android.com/apk/res-auto"
>
<TextureView
android:id="@+id/view_finder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
权限
为了后续方便,加入了照相机,存储读、写权限,录音权限是防止之后使用录像功能忘记打开声音而出错。
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
启动照相
在本个activity中的onCreat中加入:
// viewFinder 在前面定义
viewFinder = findViewById(R.id.view_finder);
viewFinder.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View view, int i, int i1, int i2, int i3, int i4, int i5, int i6, int i7) {
updateTransform();
}
});
viewFinder.post(new Runnable() {
@Override
public void run() {
startCamera();
}
});
其中两个方法分别是updateTransform、startCamera:
private void updateTransform() {
Matrix matrix = new Matrix();
// Compute the center of the view finder
float centerX = viewFinder.getWidth() / 2f;
float centerY = viewFinder.getHeight() / 2f;
float[] rotations = {0,90,180,270};
// Correct preview output to account for display rotation
float rotationDegrees = rotations[viewFinder.getDisplay().getRotation()];
matrix.postRotate(-rotationDegrees, centerX, centerY);
// Finally, apply transformations to our TextureView
viewFinder.setTransform(matrix);
}
更新相机预览:主要是给TextureView设置一个旋转的矩阵变化,防止预览方向不对
private void startCamera() {
CameraX.unbindAll();
// 1. preview
PreviewConfig previewConfig = new PreviewConfig.Builder()
.setLensFacing(CameraX.LensFacing.BACK)
// .setTargetAspectRatio(new Rational(1, 1))
// .setTargetResolution(new Size(640, 640))
.build();
Preview preview = new Preview(previewConfig);
preview.setOnPreviewOutputUpdateListener(new Preview.OnPreviewOutputUpdateListener() {
@Override
public void onUpdated(Preview.PreviewOutput output) {
ViewGroup parent = (ViewGroup) viewFinder.getParent();
parent.removeView(viewFinder);
parent.addView(viewFinder, 0);
viewFinder.setSurfaceTexture(output.getSurfaceTexture());
updateTransform();
}
});
CameraX.bindToLifecycle(this, preview);
}
启动相机:创建PreviewConfig和Preview这两个对象,可以设置预览图像的尺寸和比例,在OnPreviewOutputUpdateListener回调中用setSurfaceTexture方法,将相机图像输出到TextureView。最后用CameraX.bindToLifecycle方法将相机与当前页面的生命周期绑定。
bug记录:
在CameraX.bindToLifecycle(this, preview);出现错误
解决:activity继承类改为AppCompatActivity,而且在AndroidManifest.xml增加android:theme="@style/Theme.AppCompat.Light.NoActionBar"
拍照
拍照创建ImageCaptureConfig和ImageCapture这两个对象,放储存路径。
// 2. capture
ImageCaptureConfig imageCaptureConfig = new ImageCaptureConfig.Builder()
.setTargetAspectRatio(new Rational(1,1))
.setCaptureMode(ImageCapture.CaptureMode.MIN_LATENCY)
.build();
final ImageCapture imageCapture = new ImageCapture(imageCaptureConfig);
viewFinder.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
File externalFilesDir = getExternalFilesDir(Environment.DIRECTORY_DCIM);
String storePath = externalFilesDir + "/" + System.currentTimeMillis() + ".jpg";
File photo = new File(storePath);
// /storage/emulated/0/Android/data/com.example.android_ncnn_yolov4_tiny/cache/1623981023712.jpg
imageCapture.takePicture(photo, new ImageCapture.OnImageSavedListener() {
@Override
public void onImageSaved(@NonNull File file) {
// showToast("saved " + file.getAbsolutePath());
System.out.println(file.getAbsolutePath());
}
@Override
public void onError(@NonNull ImageCapture.ImageCaptureError imageCaptureError, @NonNull String message, @Nullable Throwable cause) {
// showToast("error " + message);
cause.printStackTrace();
}
});
return true;
}
});
CameraX.bindToLifecycle(this, preview, imageCapture);
public void showToast(String msg) {
Toast.makeText(LocationActivity.this, msg, Toast.LENGTH_SHORT).show();
}
bug记录:
安卓存储空间分内部(手机)和外部(卡),很难找到路径,在测试时候使用的手机没有安卡,存放位置有一点点问题。
处理
实时获取相机的图像数据,做想要的分析处理。
// 3. analyze
HandlerThread handlerThread = new HandlerThread("Analyze-thread");
handlerThread.start();
ImageAnalysisConfig imageAnalysisConfig = new ImageAnalysisConfig.Builder()
.setCallbackHandler(new Handler(handlerThread.getLooper()))
.setImageReaderMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE)
.setTargetAspectRatio(new Rational(2, 3))
// .setTargetResolution(new Size(600, 600))
.build();
ImageAnalysis imageAnalysis = new ImageAnalysis(imageAnalysisConfig);
imageAnalysis.setAnalyzer(new MyAnalyzer());
CameraX.bindToLifecycle(this, preview, imageCapture, imageAnalysis);
分析处理的内容是新建了一个MyAnalzer()的类
private class MyAnalyzer implements ImageAnalysis.Analyzer {
@Override
public void analyze(ImageProxy imageProxy, int rotationDegrees) {
final Image image = imageProxy.getImage();
if(image != null) {
Log.d("尺寸", image.getWidth() + "," + image.getHeight());
// 这里写自己的处理
}
}
}