当前位置: 首页 > news >正文

Android Camera2使用

一 简介

1.1 Camera API:

这是旧版本的相机API,也称为Camera1 API。它提供了较简单的使用方式,适用于旧版Android设备。但它存在一些限制,如性能不佳、操作复杂等

1.2 Camera2 API:

这是新版本的相机API,引入自Android 5.0(Lollipop)以后的版本。它提供了更强大和灵活的控制能力,并改善了性能

1.3 Camer2对比camera1优势:

  • 灵活度:Camera2 API提供了更多的手动设置选项,例如曝光时间、ISO感光度、焦距等。
  • 性能优化:Camera2 API支持并行拍摄和预览,使得在同时进行多个操作时表现更好。
  • 特殊特性支持:Camera2 API支持RAW图像捕获和高速连拍模式等新功能。
  • 能力检测:通过CameraCharacteristics类,可以检查设备相机的各种特性和功能。

1.4 Camera2 API中主要涉及以下几个关键类:

  • CameraManager:摄像头管理器,用于打开和关闭系统摄像头
  • CameraCharacteristics:描述摄像头的各种特性,我们可以通过CameraManager的getCameraCharacteristics(@NonNull String cameraId)方法来获取。
  • CameraDevice:描述系统摄像头,类似于早期的Camera。
  • CameraCaptureSession:Session类,当需要拍照、预览等功能时,需要先创建该类的实例,然后通过该实例里的方法进行控制(例如:拍照 capture())。
  • CaptureRequest:描述了一次操作请求,拍照、预览等操作都需要先传入CaptureRequest参数,具体的参数控制也是通过CameraRequest的成员变量来设置。
  • CaptureResult:描述拍照完成后的结果。

二 camera2实现步骤

2.1 定义TextureView作为预览界面:

(TextureView) findViewById(R.id.textureView).setSurfaceTextureListener(textureListener);

当SurfaceTexture准备好会回调onSurfaceTextureAvailable()方法:

TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {@Overridepublic void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {//当SurefaceTexture可用的时候,设置相机参数并打开相机setupCamera(width, height);openCamera();}
};

2.2 设置相机参数

private void setupCamera(int width, int height) {//获取摄像头的管理者CameraManagerCameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);try {//遍历所有摄像头for (String cameraId: manager.getCameraIdList()) {CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);//默认打开后置摄像头if (characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT)continue;//获取StreamConfigurationMap,它是管理摄像头支持的所有输出格式和尺寸StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);//根据TextureView的尺寸设置预览尺寸mPreviewSize = getOptimalSize(map.getOutputSizes(SurfaceTexture.class), width, height);mCameraId = cameraId;break;}} catch (CameraAccessException e) {e.printStackTrace();}
}

2.3 开启相机

private void openCamera() {//获取摄像头的管理者CameraManagerCameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);//检查权限try {if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {return;}//打开相机,第一个参数指示打开哪个摄像头,第二个参数stateCallback为相机的状态回调接口,第三个参数用来确定Callback在哪个线程执行,为null的话就在当前线程执行manager.openCamera(mCameraId, stateCallback, null);} catch (CameraAccessException e) {e.printStackTrace();}
}

实现StateCallback 接口,当相机打开后会回调onOpened方法,在这个方法里面开启预览

private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {@Overridepublic void onOpened(CameraDevice camera) {mCameraDevice = camera;//开启预览startPreview();}
}

 2.4 开启预览

private void startPreview() {SurfaceTexture mSurfaceTexture = mTextureView.getSurfaceTexture();//设置TextureView的缓冲区大小mSurfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());//获取Surface显示预览数据Surface mSurface = new Surface(mSurfaceTexture);try {//创建CaptureRequestBuilder,TEMPLATE_PREVIEW比表示预览请求mCaptureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);//设置Surface作为预览数据的显示界面mCaptureRequestBuilder.addTarget(mSurface);//创建相机捕获会话,第一个参数是捕获数据的输出Surface列表,第二个参数是CameraCaptureSession的状态回调接口,当它创建好后会回调onConfigured方法,第三个参数用来确定Callback在哪个线程执行,为null的话就在当前线程执行mCameraDevice.createCaptureSession(Arrays.asList(mSurface), new CameraCaptureSession.StateCallback() {@Overridepublic void onConfigured(CameraCaptureSession session) {try {//创建捕获请求mCaptureRequest = mCaptureRequestBuilder.build();mPreviewSession = session;//设置反复捕获数据的请求,这样预览界面就会一直有数据显示mPreviewSession.setRepeatingRequest(mCaptureRequest, mSessionCaptureCallback, null);} catch (CameraAccessException e) {e.printStackTrace();}}@Overridepublic void onConfigureFailed(CameraCaptureSession session) {}}, null);} catch (CameraAccessException e) {e.printStackTrace();}
}

2.5 预览回调

private void setupImageReader() {//前三个参数分别是需要的尺寸和格式,最后一个参数代表每次最多获取几帧数据,本例的2代表ImageReader中最多可以获取两帧图像流mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(),ImageFormat.JPEG, 2);//监听ImageReader的事件,当有图像流数据可用时会回调onImageAvailable方法,它的参数就是预览帧数据,可以对这帧数据进行处理mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {@Overridepublic void onImageAvailable(ImageReader reader) {Image image = reader.acquireLatestImage();//我们可以将这帧数据转成字节数组,类似于Camera1的PreviewCallback回调的预览帧数据ByteBuffer buffer = image.getPlanes()[0].getBuffer();byte[] data = new byte[buffer.remaining()];buffer.get(data);image.close();}}, null);
}

三,上面就是基本的使用流程,下面是拍照流程

3.1 Camera2拍照也是通过ImageReader来实现的

第一步,设置拍照参数,如方向、尺寸等

private static final SparseIntArray ORIENTATION = new SparseIntArray();static {//将不同的Surface旋转常量与相应的角度值进行映射ORIENTATION.append(Surface.ROTATION_0, 90); //无旋转,设备屏幕处于纵向(竖直)方向。ORIENTATION.append(Surface.ROTATION_90, 0); //顺时针旋转90度,设备屏幕处于横向(水平)方向,宽度大于高度。ORIENTATION.append(Surface.ROTATION_180, 270); //顺时针旋转180度,设备屏幕处于纵向(竖直)方向。ORIENTATION.append(Surface.ROTATION_270, 180); //顺时针旋转270度,设备屏幕处于横向(水平)方向,宽度大于高度。}

3.2 第二步,设置拍照尺寸,可以跟预览尺寸一起设置,然后ImageReader初始化使用此尺寸

mCaptureSize = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), new Comparator<Size>() {@Overridepublic int compare(Size lhs, Size rhs) {return Long.signum(lhs.getWidth() * lhs.getHeight() - rhs.getHeight() * rhs.getWidth());}
});

 3.3 第三步,创建保存图片的线程

public static class imageSaver implements Runnable {private Image mImage;public imageSaver(Image image) {mImage = image;}@Overridepublic void run() {ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();byte[] data = new byte[buffer.remaining()];buffer.get(data);mImageFile = new File(Environment.getExternalStorageDirectory() + "/DCIM/myPicture.jpg");FileOutputStream fos = null;try {fos = new FileOutputStream(mImageFile);fos.write(data, 0 ,data.length);} catch (IOException e) {e.printStackTrace();} finally {mImageFile = null;if (fos != null) {try {fos.close();fos = null;} catch (IOException e) {e.printStackTrace();}}}}}

3.4 第四步,然后当ImageReader有数据时,通过此线程保存图片

//使用前面获取的拍照尺寸
mImageReader = ImageReader.newInstance(mCaptureSize.getWidth(), mCaptureSize.getHeight(),ImageFormat.JPEG, 2);
mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {@Overridepublic void onImageAvailable(ImageReader reader) {//执行图像保存子线程mCameraHandler.post(new imageSaver(reader.acquireNextImage()));}}, mCameraHandler);

3.5 第五步, 然后开启预览创建CaptureSession时把ImageReader添加进去

mCameraDevice.createCaptureSession(Arrays.asList(previewSurface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() { 
}

3.6 第六步,响应点击拍照事件,我们设置点击拍照按钮调用capture()方法,capture()方法即实现拍照

private void capture() {try {//首先我们创建请求拍照的CaptureRequestfinal CaptureRequest.Builder mCaptureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);//获取屏幕方向int rotation = getWindowManager().getDefaultDisplay().getRotation();//设置CaptureRequest输出到mImageReadermCaptureBuilder.addTarget(mImageReader.getSurface());//设置拍照方向,ORIENTATION.get(rotation)根据设备屏幕的旋转状态获取相应的角度值。mCaptureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(rotation));//这个回调接口用于拍照结束时重启预览,因为拍照会导致预览停止CameraCaptureSession.CaptureCallback mImageSavedCallback = new CameraCaptureSession.CaptureCallback() {@Overridepublic void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {Toast.makeText(getApplicationContext(), "Image Saved!", Toast.LENGTH_SHORT).show();//重启预览restartPreview();}};//停止预览mCameraCaptureSession.stopRepeating();//开始拍照,然后回调上面的接口重启预览,因为mCaptureBuilder设置ImageReader作为target,所以会自动回调ImageReader的onImageAvailable()方法保存图片mCameraCaptureSession.capture(mCaptureBuilder.build(), mImageSavedCallback, null);} catch (CameraAccessException e) {e.printStackTrace();}}

 3.7 第七步,拍照后需要重启预览

 private void restartPreview() {try {//执行setRepeatingRequest方法就行了,注意mCaptureRequest是之前开启预览设置的请求mCameraCaptureSession.setRepeatingRequest(mCaptureRequest, null, mCameraHandler);} catch (CameraAccessException e) {e.printStackTrace();}}

四,完整示例,预览数据+人脸检测+人脸相框

4.1  创建activity_face_camera3.xml,两个TextureView,一个预览,一个人脸框

<?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"xmlns:app="http://schemas.android.com/apk/res-auto"><!-- TODO: Update blank fragment layout --><TextureViewandroid:id="@+id/textureView"android:layout_width="match_parent"android:layout_height="0dp"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintTop_toTopOf="parent"app:layout_constraintDimensionRatio="9:16"/><TextureViewandroid:id="@+id/facetextureView"android:layout_width="match_parent"android:layout_height="0dp"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintTop_toTopOf="parent"app:layout_constraintDimensionRatio="9:16"/>
</androidx.constraintlayout.widget.ConstraintLayout>

4.2 创建FaceCamera3Activity.java

package com.xixia.aiimageupload.opcv;public class FaceCamera3Activity extends Activity {private TextureView textureView;private TextureView faceTextureView;//用于标注人脸private String[] permissions = {Manifest.permission.CAMERA};private List<String> permissionList = new ArrayList();@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_face_camera3);textureView = (TextureView) findViewById(R.id.textureView);faceTextureView = (TextureView) findViewById(R.id.facetextureView);//初始化Camera2Utils.getInstance().init(getWindowManager(), this, textureView, faceTextureView);Camera2Utils.getInstance().setOnPreviewFrameListener(new Camera2Utils.OnPreviewFrameListener() {@Overridepublic void previewFrame(byte[] data, int width, int height) {Log.e("FFF", "previewFrame: " + width);decodeSynQrCode(data, width, height);}});//动态授权getPermission();}private void getPermission() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {for (String permission : permissions) {if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {permissionList.add(permission);}}if (!permissionList.isEmpty()) {//进行授权ActivityCompat.requestPermissions(this, permissionList.toArray(new String[permissionList.size()]), 1);} else {textureView.setSurfaceTextureListener(textureListener);}}}//只能写在Activity中,下次把授权写到activity中,减少麻烦@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);if (requestCode == 1) {if (grantResults.length != 0) {//表示有权限没有授权getPermission();} else {//表示都授权textureView.setSurfaceTextureListener(textureListener);}}}/*SurfaceView状态回调*/TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {@Overridepublic void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {Camera2Utils.getInstance().startPreview();}@Overridepublic void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {}@Overridepublic boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {return false;}@Overridepublic void onSurfaceTextureUpdated(SurfaceTexture surface) {}};@Overrideprotected void onDestroy() {super.onDestroy();Camera2Utils.getInstance().closeCamera();}/*** 预览数据回调*/private void decodeSynQrCode(byte[] data, int width, int height) {//获取预览数据//...............//可以保存bitmap或者进行二维码识别}}

 4.3 Camera2工具类

package com.xixia.aiimageupload;import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.Rect;
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.CameraMetadata;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.Face;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.util.Size;
import android.util.SparseIntArray;
import android.view.Surface;
import android.view.TextureView;
import android.view.WindowManager;import androidx.annotation.RequiresApi;import com.luck.picture.lib.tools.ToastUtils;import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;public class Camera2Utils {private static final String TAG = "Camera2Utils";private static Camera2Utils mCameraUtils;private CameraManager cManager;private Size cPixelSize;//相机成像尺寸private int cOrientation;private Size captureSize;private int[] faceDetectModes;private TextureView cView;//用于相机预览private TextureView faceTextView;//用于相机预览private Surface previewSurface;//预览Surfaceprivate ImageReader cImageReader;private Surface captureSurface;//拍照Surfaceprivate HandlerThread cHandlerThread;//相机处理线程private Handler cHandler;//相机处理private CameraDevice cDevice;private CameraCaptureSession cSession;private CameraDevice.StateCallback cDeviceOpenCallback = null;//相机开启回调private CaptureRequest.Builder previewRequestBuilder;//预览请求构建private CaptureRequest previewRequest;//预览请求private CameraCaptureSession.CaptureCallback previewCallback;//预览回调private CaptureRequest captureRequest;private CameraCaptureSession.CaptureCallback captureCallback;private Context mContext;private WindowManager mWindowManager;private boolean isFront = false;//为了使照片竖直显示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);}public static Camera2Utils getInstance() {if (mCameraUtils == null) {synchronized (Camera2Utils.class) {if (mCameraUtils == null) {mCameraUtils = new Camera2Utils();}}}return mCameraUtils;}public void init(WindowManager windowManager, Context context, TextureView textureView, TextureView faceTextView) {this.mWindowManager = windowManager;this.mContext = context;this.cView = textureView;this.faceTextView = faceTextView;}@SuppressLint("MissingPermission")public void startPreview() {//前置摄像头String cId;if (isFront) {cId = CameraCharacteristics.LENS_FACING_BACK + "";} else {cId = CameraCharacteristics.LENS_FACING_FRONT + "";}cManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);//根据摄像头ID,开启摄像头try {//获取开启相机的相关参数CameraCharacteristics characteristics = cManager.getCameraCharacteristics(cId);StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);Size[] previewSizes = map.getOutputSizes(SurfaceTexture.class);//获取预览尺寸Size[] captureSizes = map.getOutputSizes(ImageFormat.JPEG);//获取拍照尺寸cOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);//获取相机角度cPixelSize = characteristics.get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE);//获取成像区域尺寸,同上//可用于判断是否支持人脸检测,以及支持到哪种程度,支持的人脸检测模式faceDetectModes = characteristics.get(CameraCharacteristics.STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES);//支持的最大检测人脸数量int maxFaceCount = characteristics.get(CameraCharacteristics.STATISTICS_INFO_MAX_FACE_COUNT);int mFaceDetectMode = CaptureRequest.STATISTICS_FACE_DETECT_MODE_OFF;for (int i = 0; i < faceDetectModes.length; i++) {int face = faceDetectModes[i];if (face == CaptureRequest.STATISTICS_FACE_DETECT_MODE_FULL || face == CaptureRequest.STATISTICS_FACE_DETECT_MODE_SIMPLE) {mFaceDetectMode = CaptureRequest.STATISTICS_FACE_DETECT_MODE_FULL;break;}}if (mFaceDetectMode == CaptureRequest.STATISTICS_FACE_DETECT_MODE_OFF) {//Log.i(TAG, "相机硬件不支持人脸检测");ToastUtils.s(mContext, "相机硬件不支持人脸检测");return;}//此处写死640*480,实际从预览尺寸列表选择Size previewSize = new Size(1920, 1080);//设置预览尺寸(避免控件尺寸与预览画面尺寸不一致时画面变形)//transformImage(previewSizes, cView.getWidth(), cView.getHeight());cView.getSurfaceTexture().setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());cManager.openCamera(cId, getCDeviceOpenCallback(), getCHandler());} catch (CameraAccessException e) {e.printStackTrace();}}//设置预览尺寸(避免控件尺寸与预览画面尺寸不一致时画面变形)private void transformImage(Size[] previewSizes, int width, int height) {Size mPreviewSize = getOptimalSize(previewSizes, width, height);if (mPreviewSize == null || cView == null) {return;}Matrix matrix = new Matrix();int rotation = mWindowManager.getDefaultDisplay().getRotation();RectF textureRectF = new RectF(0, 0, width, height);RectF previewRectF = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());float centerX = textureRectF.centerX();float centery = textureRectF.centerY();if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {previewRectF.offset(centerX - previewRectF.centerX(), centery - previewRectF.centerY());matrix.setRectToRect(textureRectF, previewRectF, Matrix.ScaleToFit.FILL);float scale = Math.max((float) width / mPreviewSize.getWidth(), (float) height / mPreviewSize.getHeight());matrix.postScale(scale, scale, centerX, centery);matrix.postRotate(90 * (rotation - 2), centerX, centery);cView.setTransform(matrix);}}/*** 获取最佳尺寸,解决预览变形问题** @param sizeMap* @param width* @param height* @return*///选择sizeMap中大于并且最接近width和height的sizeprivate Size getOptimalSize(Size[] sizeMap, int width, int height) {List<Size> sizeList = new ArrayList<>();for (Size option : sizeMap) {if (width > height) {if (option.getWidth() > width && option.getHeight() > height) {sizeList.add(option);}} else {if (option.getWidth() > height && option.getHeight() > width) {sizeList.add(option);}}}if (sizeList.size() > 0) {return Collections.min(sizeList, new Comparator<Size>() {@Overridepublic int compare(Size lhs, Size rhs) {return Long.signum(lhs.getWidth() * lhs.getHeight() - rhs.getWidth() * rhs.getHeight());}});}return sizeMap[0];}private Size getOptimalPreviewSize(Size[] sizes, int w, int h) {final double ASPECT_TOLERANCE = 0.1;double targetRatio = (double) w / h;if (sizes == null) return null;Size optimalSize = null;double minDiff = Double.MAX_VALUE;int targetHeight = h;// Try to find an size match aspect ratio and sizeSize size = null;for (int i = 0; i < sizes.length; i++) {size = sizes[i];double ratio = (double) size.getWidth() / size.getHeight();if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;if (Math.abs(size.getHeight() - targetHeight) < minDiff) {optimalSize = size;minDiff = Math.abs(size.getHeight() - targetHeight);}}// Cannot find the one match the aspect ratio, ignore the requirementif (optimalSize == null) {minDiff = Double.MAX_VALUE;for (int i = 0; i < sizes.length; i++) {size = sizes[i];if (Math.abs(size.getHeight() - targetHeight) < minDiff) {optimalSize = size;minDiff = Math.abs(size.getHeight() - targetHeight);}}}return optimalSize;}private void configureTransform(int viewWidth, int viewHeight) {int rotation = 1;Matrix matrix = new Matrix();RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);float centerX = viewRect.centerX();float centerY = viewRect.centerY();if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {matrix.postRotate(90 * (rotation - 2), centerX, centerY);} else if (Surface.ROTATION_180 == rotation) {matrix.postRotate(180, centerX, centerY);}cView.setTransform(matrix);}/*** 初始化并获取相机开启回调对象。当准备就绪后,发起预览请求*/@SuppressLint("NewApi")private CameraDevice.StateCallback getCDeviceOpenCallback() {if (cDeviceOpenCallback == null) {cDeviceOpenCallback = new CameraDevice.StateCallback() {@Overridepublic void onOpened(CameraDevice camera) {//打开摄像头cDevice = camera;try {//创建Session,需先完成画面呈现目标(此处为预览和拍照Surface)的初始化camera.createCaptureSession(Arrays.asList(getPreviewSurface(), getCaptureSurface()), newCameraCaptureSession.StateCallback() {@Overridepublic void onConfigured(CameraCaptureSession session) {cSession = session;//构建预览请求,并发起请求Log.i(TAG, "[发出预览请求]");try {session.setRepeatingRequest(getPreviewRequest(), getPreviewCallback(),getCHandler());} catch (CameraAccessException e) {Log.i(TAG, "--" + e.getMessage());}}@Overridepublic void onConfigureFailed(CameraCaptureSession session) {session.close();}}, getCHandler());} catch (CameraAccessException e) {Log.i(TAG, "--" + e.getMessage());}}@Overridepublic void onDisconnected(CameraDevice camera) {//关闭摄像头camera.close();}@Overridepublic void onError(CameraDevice camera, int error) {//发生错误camera.close();}};}return cDeviceOpenCallback;}/*** 初始化并获取相机线程处理** @return*/private Handler getCHandler() {if (cHandler == null) {//单独开一个线程给相机使用cHandlerThread = new HandlerThread("cHandlerThread");cHandlerThread.start();cHandler = new Handler(cHandlerThread.getLooper());}return cHandler;}/*** 获取支持的最高人脸检测级别** @return*/private int getFaceDetectMode() {if (faceDetectModes == null) {Log.i(TAG, "getFaceDetectMode: ----");return CaptureRequest.STATISTICS_FACE_DETECT_MODE_FULL;} else {Log.i(TAG, "getFaceDetectMode: --2--" + faceDetectModes[faceDetectModes.length - 1]);return faceDetectModes[faceDetectModes.length - 1];}}/*** 初始化并获取预览回调对象** @return*/@SuppressLint("NewApi")private CameraCaptureSession.CaptureCallback getPreviewCallback() {if (previewCallback == null) {previewCallback = new CameraCaptureSession.CaptureCallback() {@Overridepublic void onCaptureCompleted(CameraCaptureSession session, CaptureRequestrequest, TotalCaptureResult result) {onCameraImagePreviewed(result);}};}return previewCallback;}/*** 生成并获取预览请求** @return*/@SuppressLint("NewApi")private CaptureRequest getPreviewRequest() {previewRequest = getPreviewRequestBuilder().build();return previewRequest;}/*** 初始化并获取预览请求构建对象,进行通用配置,并每次获取时进行人脸检测级别配置** @return*/@SuppressLint("NewApi")private CaptureRequest.Builder getPreviewRequestBuilder() {if (previewRequestBuilder == null) {try {previewRequestBuilder = cSession.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);previewRequestBuilder.addTarget(getPreviewSurface());previewRequestBuilder.addTarget(getCaptureSurface());previewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);//自动曝光、白平衡、对焦} catch (CameraAccessException e) {Log.i(TAG, "--" + e.getMessage());}}
//        previewRequestBuilder.set(CaptureRequest.STATISTICS_FACE_DETECT_MODE, getFaceDetectMode());//设置人脸检测级别previewRequestBuilder.set(CaptureRequest.STATISTICS_FACE_DETECT_MODE, CameraCharacteristics.STATISTICS_FACE_DETECT_MODE_SIMPLE);//设置人脸检测级别previewRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, 0);return previewRequestBuilder;}/*** 获取预览Surface** @return*/private Surface getPreviewSurface() {if (previewSurface == null) {previewSurface = new Surface(cView.getSurfaceTexture());}return previewSurface;}/*** 处理相机画面处理完成事件,获取检测到的人脸坐标,换算并绘制方框** @param result*/@SuppressLint({"NewApi", "LocalSuppress"})private void onCameraImagePreviewed(CaptureResult result) {Face[] faces = result.get(CaptureResult.STATISTICS_FACES);if (faces.length > 0) {Log.i(TAG, "检测到有人脸,-----------------------------------");Log.i(TAG, "检测到有人脸,进行拍照操作:faceLength=" + faces.length);//检测到有人脸,控制相机进行拍照操作//executeCapture();}if (faceTextView != null) {drawFace(faces);}}/*** 绘制人脸框*/private Paint facePaint;private void drawFace(Face[] faces) {if (facePaint == null) {facePaint = new Paint();facePaint.setColor(Color.BLUE);facePaint.setStrokeWidth(10);facePaint.setStyle(Paint.Style.STROKE);//使绘制的矩形中空//隐藏背景色,以免标注人脸时挡住预览画面faceTextView.setAlpha(0.9f);}Canvas canvas = faceTextView.lockCanvas();if (canvas != null) {canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);//旧画面清理覆盖if (faces.length > 0) {for (int i = 0; i < faces.length; i++) {Rect fRect = faces[i].getBounds();Log.e(TAG, "[R" + i + "]:[left:" + fRect.left + ",top:" + fRect.top + ",right:" + fRect.right + ",bottom:" + fRect.bottom + "]");//人脸检测坐标基于相机成像画面尺寸以及坐标原点。此处进行比例换算//成像画面与方框绘制画布长宽比比例(同画面角度情况下的长宽比例(此处前后摄像头成像画面相对预览画面倒置(±90°),计算比例时长宽互换))float scaleWidth = canvas.getHeight() * 1.0f / cPixelSize.getWidth();float scaleHeight = canvas.getWidth() * 1.0f / cPixelSize.getHeight();//坐标缩放int l = (int) (fRect.left * scaleWidth);int t = (int) (fRect.top * scaleHeight);int r = (int) (fRect.right * scaleWidth);int b = (int) (fRect.bottom * scaleHeight);Log.e(TAG, "[T" + i + "]:[left:" + l + ",top:" + t + ",right:" + r + ",bottom:" + b + "]");//人脸检测坐标基于相机成像画面尺寸以及坐标原点。此处进行坐标转换以及原点(0,0)换算//人脸检测:坐标原点为相机成像画面的左上角,left、top、bottom、right以成像画面左上下右为基准//画面旋转后:原点位置不一样,根据相机成像画面的旋转角度需要换算到画布的左上角,left、top、bottom、right基准也与原先不一样,//如相对预览画面相机成像画面角度为90°那么成像画面坐标的top,在预览画面就为left。如果再翻转,那成像画面的top就为预览画面的right,且坐标起点为右,需要换算到左边if (isFront) {//此处前置摄像头成像画面相对于预览画面顺时针90°+翻转。left、top、bottom、right变为bottom、right、top、left,并且由于坐标原点由左上角变为右下角,X,Y方向都要进行坐标换算canvas.drawRect(canvas.getWidth() - b, canvas.getHeight() - r, canvas.getWidth() - t, canvas.getHeight() - l, facePaint);} else {//此处后置摄像头成像画面相对于预览画面顺时针270°,left、top、bottom、right变为bottom、left、top、right,并且由于坐标原点由左上角变为左下角,Y方向需要进行坐标换算canvas.drawRect(canvas.getWidth() - b, l, canvas.getWidth() - t, r, facePaint);}}}faceTextView.unlockCanvasAndPost(canvas);}}/*** 初始化拍照相关*/@SuppressLint("NewApi")private Surface getCaptureSurface() {if (cImageReader == null) {cImageReader = ImageReader.newInstance(getCaptureSize().getWidth(), getCaptureSize().getHeight(),ImageFormat.YUV_420_888, 2);cImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {@Overridepublic void onImageAvailable(ImageReader reader) {//拍照最终回调onCaptureFinished(reader);}}, getCHandler());captureSurface = cImageReader.getSurface();}return captureSurface;}/*** 获取拍照尺寸** @return*/@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)private Size getCaptureSize() {if (captureSize != null) {return captureSize;} else {return new Size(cView.getWidth(), cView.getHeight());}}@SuppressLint("NewApi")private void onCaptureFinished(ImageReader reader) {if (reader != null) {Image image = reader.acquireLatestImage();if (image != null && image.getPlanes() != null && image.getPlanes().length > 0) {int width = image.getWidth();int height = image.getHeight();ByteBuffer buffer = image.getPlanes()[0].getBuffer();byte[] data = new byte[buffer.remaining()];buffer.get(data);image.close();buffer.clear();if (onPreviewFrameListener != null) {onPreviewFrameListener.previewFrame(data,width, height);}//onPreviewFrameToBitmap(data);}}}/*** 预览数据转为bitmap*/Bitmap takeBitmap = null;Bitmap takeBitmap2 = null;private void onPreviewFrameToBitmap(byte[] data) {takeBitmap = BitmapFactory.decodeByteArray(data, 0, data.length);/*** 为了解决预览和拍照左右颠倒问题*/Matrix m = new Matrix();m.postScale(-1, 1); // 镜像水平翻转takeBitmap2 = Bitmap.createBitmap(takeBitmap, 0, 0, takeBitmap.getWidth(), takeBitmap.getHeight(), m, true);if (ioShowBitmapListener != null) {ioShowBitmapListener.showBitmap(takeBitmap2);}}@SuppressLint("NewApi")public void closeCamera() {if (cSession != null) {try {cSession.stopRepeating();} catch (CameraAccessException e) {e.printStackTrace();}cSession.close();cSession = null;}if (cDevice != null) {cDevice.close();cDevice = null;}if (cImageReader != null) {cImageReader.close();cImageReader = null;}if (cHandlerThread != null) {cHandlerThread.quitSafely();try {cHandlerThread.join();cHandlerThread = null;cHandler = null;} catch (InterruptedException e) {Log.i(TAG, "--" + e.getMessage());}}//        if (captureRequestBuilder != null) {
//            captureRequestBuilder.removeTarget(captureSurface);
//            captureRequestBuilder = null;
//        }if (captureSurface != null) {captureSurface.release();captureSurface = null;}if (previewRequestBuilder != null) {previewRequestBuilder.removeTarget(previewSurface);previewRequestBuilder = null;}if (previewSurface != null) {previewSurface.release();previewSurface = null;}if (takeBitmap != null) {takeBitmap.recycle();takeBitmap = null;}}/*** 帧数据回调*/private OnPreviewFrameListener onPreviewFrameListener;public void setOnPreviewFrameListener(OnPreviewFrameListener onPreviewFrameListener) {this.onPreviewFrameListener = onPreviewFrameListener;}public interface OnPreviewFrameListener {void previewFrame(byte[] data, int width, int height);}/*** 帧数据bitmap回调*/private IOShowBitmapListener ioShowBitmapListener;public void setIoShowBitmapListener(IOShowBitmapListener ioShowBitmapListener) {this.ioShowBitmapListener = ioShowBitmapListener;}public interface IOShowBitmapListener {void showBitmap(Bitmap bitmap);}//    /**
//     * 执行拍照
//     */
//    private CaptureRequest.Builder captureRequestBuilder;
//    @SuppressLint("NewApi")
//    private void executeCapture() {
//        try {
//            Log.i(TAG, "发出请求");
//            cSession.capture(getCaptureRequest(), getCaptureCallback(), getCHandler());
//        } catch (CameraAccessException e) {
//            Log.i(TAG, "--" + e.getMessage());
//        }
//    }
//
//    @SuppressLint("NewApi")
//    private CaptureRequest getCaptureRequest() {
//        captureRequest = getCaptureRequestBuilder().build();
//        return captureRequest;
//    }
//
//    @SuppressLint("NewApi")
//    private CaptureRequest.Builder getCaptureRequestBuilder() {
//        if (captureRequestBuilder == null) {
//            try {
//                captureRequestBuilder = cDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
//                captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
//                //设置拍照回调接口
//                captureRequestBuilder.addTarget(getCaptureSurface());
//                //TODO 1 照片旋转int rotation =getWindowManager().getDefaultDisplay().getRotation();
//                int rotation = 0;
//                int rotationTo = getOrientation(rotation);
//
//                captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, rotationTo);
//            } catch (CameraAccessException e) {
//                Log.i(TAG, "--" + e.getMessage());
//            }
//        }
//        return captureRequestBuilder;
//    }//    @SuppressLint("NewApi")
//    private CameraCaptureSession.CaptureCallback getCaptureCallback() {
//        if (captureCallback == null) {
//            captureCallback = new CameraCaptureSession.CaptureCallback() {
//                @Override
//                public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest
//                        request, TotalCaptureResult result) {
//                }
//            };
//        }
//        return captureCallback;
//    }//    /**
//     * Retrieves the JPEG orientation from the specified screen rotation.
//     *
//     * @param rotation The screen rotation.
//     * @return The JPEG orientation (one of 0, 90, 270, and 360)
//     */
//    private int getOrientation(int rotation) {
//        return (ORIENTATIONS.get(rotation) + cOrientation + 270) % 360;
//    }}

相关文章:

Android Camera2使用

一 简介 1.1 Camera API&#xff1a; 这是旧版本的相机API&#xff0c;也称为Camera1 API。它提供了较简单的使用方式&#xff0c;适用于旧版Android设备。但它存在一些限制&#xff0c;如性能不佳、操作复杂等 1.2 Camera2 API&#xff1a; 这是新版本的相机API&#xff0…...

IOS/安卓+charles实现抓包(主要解决证书网站无法打开问题)

安装 官网下载 https://www.charlesproxy.com/latest-release/download.do 安装charles文档 流程 上述链接解决下图问题 使用介绍 Charles介绍 上述链接看一至三即可&#xff0c;了解首页各个按钮的作用 charles全面使用教程及常见功能详解&#xff08;较详细&#xff09…...

七、Lua字符串

文章目录 一、字符串&#xff08;一&#xff09;单引号间的一串字符&#xff08;二&#xff09;local str "Hello, "&#xff08;三&#xff09;[[ 与 ]] 间的一串字符&#xff08;四&#xff09;例子 二、字符串长度计算&#xff08;一&#xff09;string.len&…...

0基础学java-day13

一、包装类 1. 包装类的分类 1) 针对八种基本数据类型相应的引用类型【对象】—包装类 2) 有了类的特点&#xff0c;就可以调用类中的方法。 3) 如图: 2 包装类和基本数据的转换 3 案例演示 Integer01.java package com.hspedu.wrapper;/*** author 林然* version 1.0*/ p…...

好题记录:

好题记录&#xff1a; 1:2:3&#xff1a;三级目录 1: 下面代码的结果是&#xff1a;&#xff08; &#xff09; 下面代码的结果是&#xff1a;&#xff08; &#xff09;#include <stdio.h> int main() {int arr[] {1,2,3,4,5};short *p (short*)arr;int i …...

web前端之JavaScrip中的闭包

MENU 闭包--笔试-11defineReactive函数&#xff0c;利用闭包封装Object.defineProperty()闭包--节流函数--笔试-10闭包的定义JavaScript闭包的9大经典使用场景 闭包–笔试-11 function fun() { var n 9; // js 中强行给一个未声明的变量赋值&#xff0c;// 程序不会报错// 并…...

Windows下命令行启动与关闭WebLogic的相关服务

WebLogic 的服务器类型 WebLogic提供了三种类型的服务器&#xff1a; 管理服务器节点服务器托管服务器 示例和关系如下图&#xff1a; 对应三类服务器&#xff0c; 就有三种启动和关闭的方式。本篇介绍使用命令行脚本的方式启动和关闭这三种类型的服务器。 关于WebLogic 的…...

LeetCode Hot100 169.多数元素

题目&#xff1a; 给定一个大小为 n 的数组 nums &#xff0c;返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。 你可以假设数组是非空的&#xff0c;并且给定的数组总是存在多数元素。 方法一&#xff1a;哈希表 ​ class Solution {public int…...

数据结构:堆的实现思路

我们之前写过堆的实现代码&#xff1a;数据结构&#xff1a;堆的实现-CSDN博客 这篇文章我们了解一下堆到底是如何实现的 1.堆向下调整算法 现在我们给出一个数组&#xff0c;逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆 向下调…...

结合 DBSCAN 示例代码介绍 DBSCAN

前文为JoyT的科研之旅第一周——科研工具学习及论文阅读收获-CSDN博客 DBSCAN 介绍 DBSCAN&#xff08;Density-Based Spatial Clustering of Applications with Noise&#xff09;是一种基于密度的聚类算法&#xff0c;它能够识别出任意形状的簇&#xff0c;并有效地处理噪声…...

vscode 调试jlink

文章目录 软件使用说明1、启动GDB Server2、下载gdb3、vscode配置4、调试 软件 vscodejlink - (JLinkGDBServer.exe)gcc-arm-none-eabi-10-2020-q4-major (arm-none-eabi-gdb.exe) 使用说明 vscode通过TCP端口调用JLinkGDBServer通过jlink连接和操作设备&#xff0c;vscode不…...

微前端实战:打造高效、灵活的前端应用架构

文章目录 一、微前端简介二、微前端的优势1. 高度模块化2. 独立部署3. 易于扩展4. 技术栈无关5. 独立升级 三、微前端的原理四、微前端案例思路《微前端实战》编辑推荐内容简介作者简介目录前言/序言 随着互联网行业的快速发展&#xff0c;前端应用的规模和复杂度也在不断增加。…...

csv文件EXCEL默认打开乱码问题

这里讨论的问题是&#xff0c;当用记事本打开带有中文字符的csv正常时&#xff0c;用excel打开却是乱码。 简单概括就是&#xff1a;编码问题&#xff0c;windows的 excel打开csv文本文件时&#xff0c;默认使用的是系统内的ANSI&#xff0c;在中文环境下就是GB2312。如果写文件…...

C语言之实现贪吃蛇小游戏篇(2)

目录 &#x1f387;测试游戏test.c &#x1f387;游戏头文件包含&函数声明snake.h &#x1f387;游戏实现snake.c &#x1f387;测试游戏test.c #define _CRT_SECURE_NO_WARNINGS 1 #include "snake.h" void test() {int ch 0;do{Snake snake { 0 };//创建…...

Comparator接口

Comparator接口 Comparator 是 Java 中用于比较对象的接口。它允许开发者实现自定义的比较逻辑&#xff0c;以用于对对象进行排序或者确定它们的顺序。 主要方法&#xff1a; Comparator 接口中包含一个抽象方法&#xff1a; int compare(T o1, T o2)&#xff1a;用于比较两…...

SELinux refpolicy详解(9)

接前一篇文章&#xff1a;SELinux refpolicy详解&#xff08;8&#xff09; 三、refpolicy内容详解 上一回讲解了refpolicy源码根目录下的build.conf文件的前一部分内容。本回继续讲解其后一部分。 2. build.conf 文件路径&#xff1a;refpolicy源码根目录/build.conf。 文…...

零基础上手,秒识别检测,IDEA研究院发布全新T-Rex模型

目标检测作为当前计算机视觉落地的热点技术之一&#xff0c;已被广泛应用于自动驾驶、智慧园区、工业检测和卫星遥感等场景。开发者在研究相关目标检测技术时&#xff0c;通常需熟练掌握图像目标检测框架&#xff0c;如通用目标检测框架 YOLO 系列&#xff0c;旋转目标检测框架…...

python每日一题——20旋转图像

题目 给定一个 n n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。 你必须在 原地 旋转图像&#xff0c;这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,2,3],[4,5,6],[7,8,9]]…...

云计算生成式 -给你不一样的音乐推荐新体验

目录 摘要&#xff1a; 正文&#xff1a; 一、亚马逊云与生成式 AI 结合的展望/总结 二、我用亚马逊云科技生成式 AI 产品打造了什么&#xff0c;解决了什么问题 三、未来云端技术发展趋势的见解 四、云端技术未来需要解决的问题 1、如何保护数据安全和隐私&#xff1f; …...

Zabbix 6.0部署+自定义监控项+自动发现与自动注册+部署zabbix代理服务器

Zabbix 6.0 Zabbix 6.0一、关于zabbix1、什么是zabbix2、zabbix工作原理3、zabbix 6.0 特性4、zabbix 6.0 功能组件 二、Zabbix 6.0 部署1、 部署 zabbix 服务端(1) 部署 Nginx PHP 环境并测试(2) 部署数据库(3) 编译安装 zabbix server 服务端(4) 部署 Web 前端&#xff0c;进…...

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇&#xff0c;在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下&#xff1a; 【Note】&#xff1a;如果你已经完成安装等操作&#xff0c;可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作&#xff0c;重…...

微信小程序之bind和catch

这两个呢&#xff0c;都是绑定事件用的&#xff0c;具体使用有些小区别。 官方文档&#xff1a; 事件冒泡处理不同 bind&#xff1a;绑定的事件会向上冒泡&#xff0c;即触发当前组件的事件后&#xff0c;还会继续触发父组件的相同事件。例如&#xff0c;有一个子视图绑定了b…...

OkHttp 中实现断点续传 demo

在 OkHttp 中实现断点续传主要通过以下步骤完成&#xff0c;核心是利用 HTTP 协议的 Range 请求头指定下载范围&#xff1a; 实现原理 Range 请求头&#xff1a;向服务器请求文件的特定字节范围&#xff08;如 Range: bytes1024-&#xff09; 本地文件记录&#xff1a;保存已…...

Module Federation 和 Native Federation 的比较

前言 Module Federation 是 Webpack 5 引入的微前端架构方案&#xff0c;允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...

令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍

文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结&#xff1a; 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析&#xff1a; 实际业务去理解体会统一注…...

ArcGIS Pro制作水平横向图例+多级标注

今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作&#xff1a;ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等&#xff08;ArcGIS出图图例8大技巧&#xff09;&#xff0c;那这次我们看看ArcGIS Pro如何更加快捷的操作。…...

Kubernetes 网络模型深度解析:Pod IP 与 Service 的负载均衡机制,Service到底是什么?

Pod IP 的本质与特性 Pod IP 的定位 纯端点地址&#xff1a;Pod IP 是分配给 Pod 网络命名空间的真实 IP 地址&#xff08;如 10.244.1.2&#xff09;无特殊名称&#xff1a;在 Kubernetes 中&#xff0c;它通常被称为 “Pod IP” 或 “容器 IP”生命周期&#xff1a;与 Pod …...

【堆垛策略】设计方法

堆垛策略的设计是积木堆叠系统的核心&#xff0c;直接影响堆叠的稳定性、效率和容错能力。以下是分层次的堆垛策略设计方法&#xff0c;涵盖基础规则、优化算法和容错机制&#xff1a; 1. 基础堆垛规则 (1) 物理稳定性优先 重心原则&#xff1a; 大尺寸/重量积木在下&#xf…...

高考志愿填报管理系统---开发介绍

高考志愿填报管理系统是一款专为教育机构、学校和教师设计的学生信息管理和志愿填报辅助平台。系统基于Django框架开发&#xff0c;采用现代化的Web技术&#xff0c;为教育工作者提供高效、安全、便捷的学生管理解决方案。 ## &#x1f4cb; 系统概述 ### &#x1f3af; 系统定…...

41道Django高频题整理(附答案背诵版)

解释一下 Django 和 Tornado 的关系&#xff1f; Django和Tornado都是Python的web框架&#xff0c;但它们的设计哲学和应用场景有所不同。 Django是一个高级的Python Web框架&#xff0c;鼓励快速开发和干净、实用的设计。它遵循MVC设计&#xff0c;并强调代码复用。Django有…...