我正在使用以下代码使用自定义Camera2 API捕获图像。我是初学者,我不知道如何在相机预览上显示矩形叠加。我确实尝试了这里提供的解决方案,但我无法弄清楚如何在我的代码中使用它。
我需要达到这样的效果来捕捉A4纸张。
只有框中的图像部分必须被捕获并显示在下一个活动中。
我的知识
package com.example.customcamera;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.ImageFormat;
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.CameraMetadata;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.util.Size;
import android.util.SparseIntArray;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
public class MainActivity extends AppCompatActivity {
private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
static
{
ORIENTATIONS.append(Surface.ROTATION_0, 90);
ORIENTATIONS.append(Surface.ROTATION_90, 0);
ORIENTATIONS.append(Surface.ROTATION_180, 270);
ORIENTATIONS.append(Surface.ROTATION_270, 180);
}
TextureView textureView;
private CameraDevice cameraDevice;
private String cameraId;
private Size imageDimensions;
private CameraCaptureSession cameraCaptureSession;
private CaptureRequest.Builder captureRequestBuilder;
private ImageReader imageReader;
private boolean flashSupported;
private HandlerThread backgroundThread;
private Handler backgroundHandler;
CameraDevice.StateCallback stateCallback;
File file;
final int MY_PERMISSIONS_REQUEST_CAMERA=102;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
stateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice camera) {
cameraDevice = camera;
createCameraPreview();
}
@Override
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
MainActivity.this.cameraDevice.close();
}
@Override
public void onError(@NonNull CameraDevice cameraDevice, int i) {
MainActivity.this.cameraDevice.close();
MainActivity.this.cameraDevice = null; // Change to global
}
};
textureView = findViewById(R.id.textureView);
textureView.setSurfaceTextureListener(textureListener);
Button btn_camera = findViewById(R.id.btn_camera);
btn_camera.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
takePicture();
}
});
}
public void takePicture()
{
if(cameraDevice == null)
return;
CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try
{
CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraDevice.getId());
Size[] jpegSizes = null;
if(cameraCharacteristics == null)
jpegSizes = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).getOutputSizes(ImageFormat.JPEG);
// Capture image with custom size
int width = 640;
int height = 480;
if(jpegSizes != null && jpegSizes.length > 0)
{
width = jpegSizes[0].getWidth();
height = jpegSizes[0].getHeight();
}
imageReader = ImageReader.newInstance(width, height, ImageFormat.JPEG, 1);
final List<Surface> outputSurface = new ArrayList<>(2);
outputSurface.add(imageReader.getSurface());
outputSurface.add(new Surface(textureView.getSurfaceTexture()));
final CaptureRequest.Builder captureBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
captureBuilder.addTarget(imageReader.getSurface());
captureBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
// Check orientation base on device
int rotation = getWindowManager().getDefaultDisplay().getRotation();
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation));
ImageReader.OnImageAvailableListener readerListener = new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader imageReader) {
file = new File(Environment.getExternalStorageDirectory() + "/" + UUID.randomUUID().toString() + ".jpg");
Image image = null;
try
{
image = imageReader.acquireLatestImage();
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
buffer.rewind();
byte[] bytes = new byte[buffer.capacity()];
buffer.get(bytes);
save(bytes);
startActivity(new Intent(MainActivity.this, ImageDisplay.class).putExtra("FILE", file.getPath()));
}
finally {
if(image != null)
image.close();
}
}
public void save(byte[] bytes)
{
OutputStream outputStream = null;
try
{
outputStream = new FileOutputStream(file);
outputStream.write(bytes);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(outputStream == null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
};
imageReader.setOnImageAvailableListener(readerListener, backgroundHandler);
final CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
super.onCaptureCompleted(session, request, result);
Toast.makeText(MainActivity.this, "Saved", Toast.LENGTH_SHORT).show();
createCameraPreview();
}
};
cameraDevice.createCaptureSession(outputSurface, new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
try {
cameraCaptureSession.capture(captureBuilder.build(), captureCallback, backgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
}
}, backgroundHandler);
}
catch (CameraAccessException e) {
e.printStackTrace();
}
}
private void createCameraPreview() {
SurfaceTexture texture = textureView.getSurfaceTexture();
assert texture != null;
texture.setDefaultBufferSize(imageDimensions.getWidth(), imageDimensions.getHeight());
Surface surface = new Surface(texture);
try {
captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
captureRequestBuilder.addTarget(surface);
cameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
if(cameraDevice == null)
return;
cameraCaptureSession = session;
updatePreview();
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
Toast.makeText(MainActivity.this, "Changed", Toast.LENGTH_SHORT).show();
}
}, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private void updatePreview() {
if(cameraDevice == null)
Toast.makeText(MainActivity.this, "Error!", Toast.LENGTH_SHORT).show();
captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
try {
cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, backgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private void openCamera()
{
CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try
{
cameraId = cameraManager.getCameraIdList()[0];
CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId);
StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
assert map != null;
imageDimensions = map.getOutputSizes(SurfaceTexture.class)[0];
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
// Permission to Camera is not granted, request for permission
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE}, MY_PERMISSIONS_REQUEST_CAMERA);
return;
}
cameraManager.openCamera(cameraId, stateCallback, null);
}
catch (CameraAccessException e) {
e.printStackTrace();
}
}
TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
openCamera();
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) {
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
Log.e("Surfacetexturedestroyed", "called");
if(cameraDevice != null)
{
Log.e("Camera not null", "make null");
cameraDevice.close();
cameraDevice = null;
}
return false;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
}
};
@Override
protected void onResume() {
super.onResume();
startBackgroundThread();
if(textureView.isAvailable())
openCamera();
else
textureView.setSurfaceTextureListener(textureListener);
}
@Override
protected void onPause() {
stopBackgroundThread();
super.onPause();
}
private void stopBackgroundThread() {
backgroundThread.quitSafely();
try {
backgroundThread.join();
backgroundThread = null;
backgroundHandler = null;
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
private void startBackgroundThread() {
backgroundThread = new HandlerThread("Camera Background");
backgroundThread.start();
backgroundHandler = new Handler(backgroundThread.getLooper());
}
/**
* Handler for handling the user event after requesting permission.
*/
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults)
{
switch (requestCode)
{
case 102: //MY_PERMISSIONS_REQUEST_CAMERA=102 Use Camera
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)
{
// permission was granted, yay!
openCamera();
}
else
{
// permission denied, boo!
Toast.makeText(this, "Permission to use device Camera denied! Cannot proceed ahead!", Toast.LENGTH_SHORT).show();
finish();
}
break;
default: Toast.makeText(this, "Failed to handle permissions response!", Toast.LENGTH_SHORT).show();
}
}
}
activity_main.xml中
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
tools:context=".MainActivity">
<TextureView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:id="@+id/textureView"
android:layout_alignParentTop="true"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Take Picture"
android:textSize="30sp"
android:id="@+id/btn_camera"
android:layout_alignParentBottom="true"
android:layout_margin="8dp"/>
</RelativeLayout>
ImageDisplay活动
package com.example.customcamera;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Path;
import android.media.Image;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ImageView;
import java.io.File;
public class ImageDisplay extends AppCompatActivity {
ImageView imageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.image_display);
imageView = findViewById(R.id.imageView);
String imgFile = getIntent().getStringExtra("FILE");
Bitmap bitmap = BitmapFactory.decodeFile(imgFile);
imageView.setImageBitmap(bitmap);
}
}
image_display.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
tools:context=".ImageDisplay">
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
在onImageAvailableListener对象中创建一个BitmapRegionDecoder对象。此对象采用矩形对象。创建矩形,使其仅保存ImageView边界。不要仅仅为了在TextureView上显示ImageView而启动新的Activity。只需将image-view添加到activity_main.xml即可。确保将图像视图高度和宽度设置为以dp为单位的实际数字。
要创建仅包含图像视图边界的矩形,请创建显示度量标准对象,并使用度量标准对象查询设备的密度。因此,假设您的图像视图尺寸为300dp =宽度和200dp =高度,并且从metrics对象返回的设备密度= 2.0 ...然后,乘以300 * 2.0和200 * 2.0以获得基于图像视图边界框区域在您的设备密度。由于您使用的是camera2 API,因此必须根据SCALER配置请求了解预览区域的内容。使用这个已知区域和图像视图区域,您可以通过定位中心点来创建矩形(确保纹理视图的中心与您的图像视图中心重合。在xml中设置layout_gravity = center)然后使用一些基本数学,您可以减去并添加图像视图的一半宽度和一半高度,以创建BitmapRegionDecoder对象所需的矩形对象。只需为BitmapRegionDecoder对象中所需的options参数传递null。您应该从解码器对象中获取一些字节,然后您可以将其转换为您选择的位图。
我刚刚在Xamarin iOS App上做过这个。如果你觉得它有用,你可以看一下它。